From 5826529218e1224ae7426b9edde7a392e7adf2bd Mon Sep 17 00:00:00 2001 From: pedro Date: Sat, 16 Feb 2019 13:52:55 +0800 Subject: [PATCH 01/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=BD=AF?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=B8=A6=E6=9D=A5=E7=9A=84=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E4=B8=AA=E6=95=B0=E4=B8=8E=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E4=B8=8D=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/cms/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/cms/admin.py b/app/api/cms/admin.py index 5d039eb..31a9774 100644 --- a/app/api/cms/admin.py +++ b/app/api/cms/admin.py @@ -40,7 +40,7 @@ def get_admin_users(): condition = {'super': UserSuper.COMMON.value, 'group_id': group_id} if group_id else { 'super': UserSuper.COMMON.value} users = db.session.query(manager.user_model, manager.group_model.name) \ - .filter_by(**condition) \ + .filter_by(soft=True, **condition) \ .join(manager.group_model, manager.user_model.group_id == manager.group_model.id) \ .offset(start).limit(count).all() user_and_group = [] From 58b43dcaa6661dfe5018b014df66a2d100ae1cd5 Mon Sep 17 00:00:00 2001 From: pedro Date: Mon, 18 Feb 2019 16:42:11 +0800 Subject: [PATCH 02/21] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=BD=AF?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=B8=A6=E6=9D=A5=E7=9A=84=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/cms/notify.py | 4 ++-- app/api/v1/book.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/api/cms/notify.py b/app/api/cms/notify.py index a289fa0..b8229a9 100644 --- a/app/api/cms/notify.py +++ b/app/api/cms/notify.py @@ -9,7 +9,7 @@ from lin import db from lin.core import route_meta, Event -from lin.exception import NotFound, Success +from lin.exception import NotFound, Success, Forbidden from lin.jwt import group_required, admin_required from lin.redprint import Redprint from lin.notify import MESSAGE_EVENTS @@ -51,7 +51,7 @@ def create_events(): form = EventsForm().validate_for_api() event = Event.query.filter_by(group_id=form.group_id.data, soft=False).first() if event: - raise NotFound(msg='当前权限组已存在推送项') + raise Forbidden(msg='当前权限组已存在推送项') with db.auto_commit(): ev = Event() ev.group_id = form.group_id.data diff --git a/app/api/v1/book.py b/app/api/v1/book.py index 1a9d939..667db14 100644 --- a/app/api/v1/book.py +++ b/app/api/v1/book.py @@ -23,7 +23,7 @@ @login_required @Notify(template='{user.nickname}查询了一本图书', event='queryBook') def get_book(id): - book = Book.query.filter_by(id=id).first() # 通过Book模型在数据库中查询id=`id`的书籍 + book = Book.query.filter_by(soft=True, id=id).first() # 通过Book模型在数据库中查询id=`id`的书籍 if book is None: raise NotFound(msg='没有找到相关书籍') # 如果书籍不存在,返回一个异常给前端 return jsonify(book) # 如果存在,返回该数据的信息 From b4f840b1e7dc5c98ac079ccb09306c69fdfe5bbf Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Sat, 23 Feb 2019 23:15:19 +0800 Subject: [PATCH 03/21] =?UTF-8?q?refactor:=20=E6=B7=BB=E5=8A=A0DAO?= =?UTF-8?q?=E5=B1=82=EF=BC=8C=E5=88=86=E7=A6=BB=E8=A7=86=E5=9B=BE=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E4=B8=AD=E6=95=B0=E6=8D=AE=E5=BA=93=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=E4=B8=9A=E5=8A=A1=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/cms/admin.py | 204 +++++++++++-------------------------------- app/api/cms/log.py | 45 ++-------- app/api/cms/user.py | 53 +++-------- app/api/v1/book.py | 54 +++--------- app/dao/auth.py | 65 ++++++++++++++ app/dao/group.py | 78 +++++++++++++++++ app/dao/log.py | 50 +++++++++++ app/dao/user.py | 145 ++++++++++++++++++++++++++++++ app/models/book.py | 59 ++++++++++++- fake.py | 65 +++++++++----- 10 files changed, 524 insertions(+), 294 deletions(-) create mode 100644 app/dao/auth.py create mode 100644 app/dao/group.py create mode 100644 app/dao/log.py create mode 100644 app/dao/user.py diff --git a/app/api/cms/admin.py b/app/api/cms/admin.py index 31a9774..45a1f7b 100644 --- a/app/api/cms/admin.py +++ b/app/api/cms/admin.py @@ -7,17 +7,16 @@ from itertools import groupby from operator import itemgetter -from flask import jsonify, request +from flask import jsonify +from lin.core import get_ep_infos, route_meta +from lin.exception import Success +from lin.jwt import admin_required from lin.log import Logger - from lin.redprint import Redprint -from lin.core import get_ep_infos, manager, find_auth_module, route_meta, find_user -from lin.jwt import admin_required -from lin.util import paginate -from lin.db import db, get_total_nums -from lin.enums import UserSuper, UserActive -from lin.exception import NotFound, Forbidden, Success, ParameterException +from app.dao.auth import AuthDAO +from app.dao.group import GroupDAO +from app.dao.user import UserDAO from app.validators.forms import NewGroup, DispatchAuth, DispatchAuths, RemoveAuths, UpdateGroup, ResetPasswordForm, \ UpdateUserInfoForm @@ -35,100 +34,53 @@ def authority(): @route_meta(auth='查询所有用户', module='管理员', mount=False) @admin_required def get_admin_users(): - start, count = paginate() - group_id = request.args.get('group_id') - condition = {'super': UserSuper.COMMON.value, 'group_id': group_id} if group_id else { - 'super': UserSuper.COMMON.value} - users = db.session.query(manager.user_model, manager.group_model.name) \ - .filter_by(soft=True, **condition) \ - .join(manager.group_model, manager.user_model.group_id == manager.group_model.id) \ - .offset(start).limit(count).all() - user_and_group = [] - for user, group_name in users: - setattr(user, 'group_name', group_name) - user._fields.append('group_name') - user.hide('update_time', 'delete_time') - user_and_group.append(user) - # 有分组的时候就加入分组条件 - # total_nums = get_total_nums(manager.user_model, is_soft=True, super=UserSuper.COMMON.value) - total_nums = get_total_nums(manager.user_model, is_soft=True, **condition) + user_and_group, total_nums = UserDAO().get_all() return jsonify({ "collection": user_and_group, - # 超级管理员不算入总数 'total_nums': total_nums }) -@admin_api.route('/password/', methods=['PUT']) +@admin_api.route('/password/', methods=['PUT']) @route_meta(auth='修改用户密码', module='管理员', mount=False) @admin_required -def change_user_password(id): +def change_user_password(uid): form = ResetPasswordForm().validate_for_api() - user = find_user(id=id) - if user is None: - raise NotFound(msg='用户不存在') - with db.auto_commit(): - user.reset_password(form.new_password.data) + UserDAO().reset_user_password(uid, form.new_password.data) return Success(msg='密码修改成功') -@admin_api.route('/', methods=['DELETE']) +@admin_api.route('/', methods=['DELETE']) @route_meta(auth='删除用户', module='管理员', mount=False) @Logger(template='管理员删除了一个用户') # 记录日志 @admin_required -def delete_user(id): - user = manager.user_model.get(id=id) - if user is None: - raise NotFound(msg='用户不存在') - # user.delete(commit=True) - # 此处我们使用硬删除,一般情况下,推荐使用软删除即,上一行注释的代码 - user.hard_delete(commit=True) +def delete_user(uid): + UserDAO().remove_user(uid) return Success(msg='操作成功') -@admin_api.route('/', methods=['PUT']) +@admin_api.route('/', methods=['PUT']) @route_meta(auth='管理员更新用户信息', module='管理员', mount=False) @admin_required -def update_user(id): +def update_user(uid): form = UpdateUserInfoForm().validate_for_api() - user = manager.user_model.get(id=id) - if user is None: - raise NotFound(msg='用户不存在') - if user.email != form.email.data: - exit = manager.user_model.get(email=form.email.data) - if exit: - raise ParameterException(msg='邮箱已被注册,请重新输入邮箱') - with db.auto_commit(): - user.email = form.email.data - user.group_id = form.group_id.data + UserDAO().update(uid, form) return Success(msg='操作成功') -@admin_api.route('/disable/', methods=['PUT']) +@admin_api.route('/disable/', methods=['PUT']) @route_meta(auth='禁用用户', module='管理员', mount=False) @admin_required -def trans2disable(id): - user = manager.user_model.get(id=id) - if user is None: - raise NotFound(msg='用户不存在') - if not user.is_active: - raise Forbidden(msg='当前用户已处于禁止状态') - with db.auto_commit(): - user.active = UserActive.NOT_ACTIVE.value +def trans2disable(uid): + UserDAO().change_status(uid, 'active') return Success(msg='操作成功') -@admin_api.route('/active/', methods=['PUT']) +@admin_api.route('/active/', methods=['PUT']) @route_meta(auth='激活用户', module='管理员', mount=False) @admin_required -def trans2active(id): - user = manager.user_model.get(id=id) - if user is None: - raise NotFound(msg='用户不存在') - if user.is_active: - raise Forbidden(msg='当前用户已处于激活状态') - with db.auto_commit(): - user.active = UserActive.ACTIVE.value +def trans2active(uid): + UserDAO().change_status(uid, 'disable') return Success(msg='操作成功') @@ -136,56 +88,27 @@ def trans2active(id): @route_meta(auth='查询所有权限组及其权限', module='管理员', mount=False) @admin_required def get_admin_groups(): - start, count = paginate() - groups = manager.group_model.query.filter().offset(start).limit(count).all() - if groups is None: - raise NotFound(msg='不存在任何权限组') - for group in groups: - auths = db.session.query(manager.auth_model.auth, manager.auth_model.module) \ - .filter_by(soft=False, group_id=group.id).all() - auths = [{'auth': auth[0], 'module': auth[1]} for auth in auths] - res = _split_modules(auths) - setattr(group, 'auths', res) - group._fields.append('auths') - total_nums = get_total_nums(manager.group_model) + groups_info, total_nums = GroupDAO().get_groups_info() + return jsonify({ - "collection": groups, + "collection": groups_info, 'total_nums': total_nums }) -def _split_modules(auths): - auths.sort(key=itemgetter('module')) - tmps = groupby(auths, itemgetter('module')) - res = [] - for key, group in tmps: - res.append({key: list(group)}) - return res - - @admin_api.route('/group/all', methods=['GET']) @route_meta(auth='查询所有权限组', module='管理员', mount=False) @admin_required def get_all_group(): - groups = manager.group_model.get(one=False) - if groups is None: - raise NotFound(msg='不存在任何权限组') + groups = GroupDAO().get_all() return jsonify(groups) -@admin_api.route('/group/', methods=['GET']) +@admin_api.route('/group/', methods=['GET']) @route_meta(auth='查询一个权限组及其权限', module='管理员', mount=False) @admin_required -def get_group(id): - group = manager.group_model.get(id=id, one=True, soft=False) - if group is None: - raise NotFound(msg='分组不存在') - auths = db.session.query(manager.auth_model.auth, manager.auth_model.module) \ - .filter_by(soft=False, group_id=group.id).all() - auths = [{'auth': auth[0], 'module': auth[1]} for auth in auths] - res = _split_modules(auths) - setattr(group, 'auths', res) - group._fields.append('auths') +def get_group(gid): + group = GroupDAO().get_single_info(gid) return jsonify(group) @@ -195,44 +118,25 @@ def get_group(id): @admin_required def create_group(): form = NewGroup().validate_for_api() - exist = manager.group_model.get(name=form.name.data) - if exist: - raise Forbidden(msg='分组已存在,不可创建同名分组') - with db.auto_commit(): - group = manager.group_model.create(name=form.name.data, info=form.info.data) - db.session.flush() - for auth in form.auths.data: - meta = find_auth_module(auth) - if meta: - manager.auth_model.create(auth=meta.auth, module=meta.module, group_id=group.id) + GroupDAO().new_group(form) return Success(msg='新建分组成功') -@admin_api.route('/group/', methods=['PUT']) +@admin_api.route('/group/', methods=['PUT']) @route_meta(auth='更新一个权限组', module='管理员', mount=False) @admin_required -def update_group(id): +def update_group(gid): form = UpdateGroup().validate_for_api() - exist = manager.group_model.get(id=id) - if not exist: - raise NotFound(msg='分组不存在,更新失败') - exist.update(name=form.name.data, info=form.info.data, commit=True) + GroupDAO().update_group(gid, form) return Success(msg='更新分组成功') -@admin_api.route('/group/', methods=['DELETE']) +@admin_api.route('/group/', methods=['DELETE']) @route_meta(auth='删除一个权限组', module='管理员', mount=False) @Logger(template='管理员删除一个权限组') # 记录日志 @admin_required -def delete_group(id): - exist = manager.group_model.get(id=id) - if not exist: - raise NotFound(msg='分组不存在,删除失败') - if manager.user_model.get(group_id=id): - raise Forbidden(msg='分组下存在用户,不可删除') - # 删除group拥有的权限 - db.session.query(manager.auth_model).filter(manager.auth_model.group_id == id).delete() - exist.delete(commit=True) +def delete_group(gid): + GroupDAO().remove_group(gid) return Success(msg='删除分组成功') @@ -240,13 +144,8 @@ def delete_group(id): @route_meta(auth='分配单个权限', module='管理员', mount=False) @admin_required def dispatch_auth(): - form = DispatchAuth() - form.validate_for_api() - one = manager.auth_model.get(group_id=form.group_id.data, auth=form.auth.data) - if one: - raise Forbidden(msg='已有权限,不可重复添加') - meta = find_auth_module(form.auth.data) - manager.auth_model.create(group_id=form.group_id.data, auth=meta.auth, module=meta.module, commit=True) + form = DispatchAuth().validate_for_api() + AuthDAO().patch_one(form) return Success(msg='添加权限成功') @@ -254,14 +153,8 @@ def dispatch_auth(): @route_meta(auth='分配多个权限', module='管理员', mount=False) @admin_required def dispatch_auths(): - form = DispatchAuths() - form.validate_for_api() - with db.auto_commit(): - for auth in form.auths.data: - one = manager.auth_model.get(group_id=form.group_id.data, auth=auth) - if not one: - meta = find_auth_module(auth) - manager.auth_model.create(group_id=form.group_id.data, auth=meta.auth, module=meta.module) + form = DispatchAuths().validate_for_api() + AuthDAO().patch_all(form) return Success(msg='添加权限成功') @@ -270,13 +163,18 @@ def dispatch_auths(): @admin_required def remove_auths(): form = RemoveAuths().validate_for_api() - with db.auto_commit(): - db.session.query(manager.auth_model) \ - .filter(manager.auth_model.auth.in_(form.auths.data), - manager.auth_model.group_id == form.group_id.data) \ - .delete(synchronize_session=False) + AuthDAO().remove_auths(form) return Success(msg='删除权限成功') + +def _split_modules(auths): + auths.sort(key=itemgetter('module')) + tmps = groupby(auths, itemgetter('module')) + res = [] + for key, group in tmps: + res.append({key: list(group)}) + return res + # -------------------------------------------------- # --------------------Abandon----------------------- # -------------------------------------------------- diff --git a/app/api/cms/log.py b/app/api/cms/log.py index 8128582..a594379 100644 --- a/app/api/cms/log.py +++ b/app/api/cms/log.py @@ -3,14 +3,12 @@ :license: MIT, see LICENSE for more details. """ -from flask import request, jsonify -from sqlalchemy import text -from lin.redprint import Redprint +from flask import jsonify +from lin.core import route_meta from lin.jwt import group_required -from lin.exception import NotFound, ParameterException -from lin.db import db -from lin.util import paginate -from lin.core import Log, route_meta +from lin.redprint import Redprint + +from app.dao.log import LogDAO from app.validators.forms import LogFindForm log_api = Redprint('log') @@ -22,16 +20,7 @@ @group_required def get_logs(): form = LogFindForm().validate_for_api() - start, count = paginate() - logs = db.session.query(Log).filter() - if form.name.data: - logs = logs.filter(Log.user_name == form.name.data) - if form.start.data and form.end.data: - logs = logs.filter(Log.time.between(form.start.data, form.end.data)) - total_nums = logs.count() - logs = logs.order_by(text('time desc')).offset(start).limit(count).all() - if logs is None or len(logs) < 1: - raise NotFound(msg='没有找到相关日志') + logs, total_nums = LogDAO().get_by_paginate(form) return jsonify({ "total_nums": total_nums, "collection": logs @@ -43,20 +32,8 @@ def get_logs(): @route_meta(auth='搜索日志', module='日志') @group_required def get_user_logs(): - keyword = request.args.get('keyword', default=None, type=str) - if keyword is None or '': - raise ParameterException(msg='搜索关键字不可为空') - start, count = paginate() form = LogFindForm().validate_for_api() - logs = db.session.query(Log).filter(Log.message.like(f'%{keyword}%')) - if form.name.data: - logs = logs.filter(Log.user_name == form.name.data) - if form.start.data and form.end.data: - logs = logs.filter(Log._time.between(form.start.data, form.end.data)) - total_nums = logs.count() - logs = logs.order_by(text('time desc')).offset(start).limit(count).all() - if logs is None or len(logs) < 1: - raise NotFound(msg='没有找到相关日志') + logs, total_nums = LogDAO().search_by_keyword(form) return jsonify({ "total_nums": total_nums, "collection": logs @@ -67,9 +44,5 @@ def get_user_logs(): @route_meta(auth='查询日志记录的用户', module='日志') @group_required def get_users(): - start, count = paginate() - user_names = db.session.query(Log.user_name).filter_by(soft=False) \ - .group_by(text('user_name')).having(text('count(user_name) > 0')).offset(start) \ - .limit(count).all() - res = [user_name[0] for user_name in user_names] - return jsonify(res) + users = LogDAO().get_users_by_paginate() + return jsonify(users) diff --git a/app/api/cms/user.py b/app/api/cms/user.py index 7e6c9b8..972fb84 100644 --- a/app/api/cms/user.py +++ b/app/api/cms/user.py @@ -9,12 +9,13 @@ from flask_jwt_extended import create_access_token, jwt_refresh_token_required, get_jwt_identity, get_current_user from lin.core import manager, route_meta, Log from lin.db import db -from lin.exception import NotFound, RepeatException, Success, Failed, ParameterException +from lin.exception import NotFound, Success, Failed, ParameterException from lin.jwt import login_required, admin_required, get_tokens from lin.log import Logger from lin.redprint import Redprint -from sqlalchemy import and_ +from app.dao.auth import AuthDAO +from app.dao.user import UserDAO from app.validators.forms import LoginForm, RegisterForm, ChangePasswordForm, UpdateInfoForm user_api = Redprint('user') @@ -26,15 +27,7 @@ @admin_required def register(): form = RegisterForm().validate_for_api() - user = manager.find_user(nickname=form.nickname.data) - if user: - raise RepeatException(msg='用户名重复,请重新输入') - if form.email.data and form.email.data.strip() != "": - user = manager.user_model.query.filter(and_(manager.user_model.email.isnot(None), - manager.user_model.email == form.email.data)).first() - if user: - raise RepeatException(msg='注册邮箱重复,请重新输入') - _register_user(form) + UserDAO().register(form) return Success(msg='用户创建成功') @@ -44,9 +37,12 @@ def login(): form = LoginForm().validate_for_api() user = manager.user_model.verify(form.nickname.data, form.password.data) # 此处不能用装饰器记录日志 - Log.create_log(message=f'{user.nickname}登陆成功获取了令牌', user_id=user.id, user_name=user.nickname, - status_code=200, method='post', - path='/cms/user/login', authority='无', commit=True) + Log.create_log( + message=f'{user.nickname}登陆成功获取了令牌', + user_id=user.id, user_name=user.nickname, + status_code=200, method='post',path='/cms/user/login', + authority='无', commit=True + ) access_token, refresh_token = get_tokens(user) return jsonify({ 'access_token': access_token, @@ -59,13 +55,7 @@ def login(): @login_required def update(): form = UpdateInfoForm().validate_for_api() - user = get_current_user() - if user.email != form.email.data: - exit = manager.user_model.get(email=form.email.data) - if exit: - raise ParameterException(msg='邮箱已被注册,请重新输入邮箱') - with db.auto_commit(): - user.email = form.email.data + UserDAO().update_info(form) return Success(msg='操作成功') @@ -109,24 +99,5 @@ def refresh(): @route_meta(auth='查询自己拥有的权限', module='用户', mount=False) @login_required def get_allowed_apis(): - user = get_current_user() - auths = db.session.query(manager.auth_model.auth, manager.auth_model.module) \ - .filter_by(soft=False, group_id=user.group_id).all() - auths = [{'auth': auth[0], 'module': auth[1]} for auth in auths] - from .admin import _split_modules - res = _split_modules(auths) - setattr(user, 'auths', res) - user._fields.append('auths') + user = UserDAO.get_allowed_apis() return jsonify(user) - - -def _register_user(form: RegisterForm): - with db.auto_commit(): - # 注意:此处使用挂载到manager上的user_model,不可使用默认的User - user = manager.user_model() - user.nickname = form.nickname.data - if form.email.data and form.email.data.strip() != "": - user.email = form.email.data - user.password = form.password.data - user.group_id = form.group_id.data - db.session.add(user) diff --git a/app/api/v1/book.py b/app/api/v1/book.py index 667db14..15b6d19 100644 --- a/app/api/v1/book.py +++ b/app/api/v1/book.py @@ -4,13 +4,12 @@ :copyright: © 2019 by the Lin team. :license: MIT, see LICENSE for more details. """ -from lin import db, route_meta, group_required, login_required +from flask import jsonify +from lin import route_meta, group_required, login_required +from lin.exception import Success from lin.notify import Notify from lin.redprint import Redprint -from flask import jsonify -from lin.exception import NotFound, ParameterException, Success -from app.libs.error_code import BookNotFound from app.models.book import Book from app.validators.forms import BookSearchForm, CreateOrUpdateBookForm @@ -23,61 +22,36 @@ @login_required @Notify(template='{user.nickname}查询了一本图书', event='queryBook') def get_book(id): - book = Book.query.filter_by(soft=True, id=id).first() # 通过Book模型在数据库中查询id=`id`的书籍 - if book is None: - raise NotFound(msg='没有找到相关书籍') # 如果书籍不存在,返回一个异常给前端 - return jsonify(book) # 如果存在,返回该数据的信息 + book = Book().get_detail(id) + return jsonify(book) @book_api.route('/', methods=['GET']) @login_required @Notify(template='{user.nickname}查询了所有图书', event='queryBooks') def get_books(): - books = Book.query.filter_by(soft=True).all() - if books is None or len(books) < 1: - raise NotFound(msg='没有找到相关书籍') + books = Book().get_all() return jsonify(books) @book_api.route('/search', methods=['GET']) def search(): form = BookSearchForm().validate_for_api() - q = '%' + form.q.data + '%' - books = Book.query.filter(Book.title.like(q)).all() - if books is None or len(books) < 1: - raise BookNotFound() + books = Book().search_by_keywords(form.q.data) return jsonify(books) @book_api.route('/', methods=['POST']) def create_book(): - form = CreateOrUpdateBookForm().validate_for_api() # 校验参数 - book = Book.query.filter_by(title=form.title.data).filter(Book.delete_time == None).first() # 避免同名图书 - if book is not None: - raise ParameterException(msg='图书已存在') - # 新增图书 - with db.auto_commit(): - new_book = Book() - new_book.title = form.title.data - new_book.author = form.author.data - new_book.summary = form.summary.data - new_book.image = form.image.data - db.session.add(new_book) + form = CreateOrUpdateBookForm().validate_for_api() + Book().new_book(form) return Success(msg='新建图书成功') @book_api.route('/', methods=['PUT']) def update_book(id): - form = CreateOrUpdateBookForm().validate_for_api() # 校验参数 - book = Book.query.filter_by(id=id).first() # 通过Book模型在数据库中查询id=`id`的书籍 - if book is None: - raise NotFound(msg='没有找到相关书籍') # 如果书籍不存在,返回一个异常给前端 - # 更新图书 - with db.auto_commit(): - book.title = form.title.data - book.author = form.author.data - book.summary = form.summary.data - book.image = form.image.data + form = CreateOrUpdateBookForm().validate_for_api() + Book().edit_book(id, form) return Success(msg='更新图书成功') @@ -85,9 +59,5 @@ def update_book(id): @route_meta(auth='删除图书', module='图书') @group_required def delete_book(id): - book = Book.query.filter_by(id=id).first() # 通过Book模型在数据库中查询id=`id`的书籍 - if book is None: - raise NotFound(msg='没有找到相关书籍') # 如果书籍不存在,返回一个异常给前端 - # 删除图书,软删除 - book.delete(commit=True) + Book().remove_book(id) return Success(msg='删除图书成功') diff --git a/app/dao/auth.py b/app/dao/auth.py new file mode 100644 index 0000000..b614669 --- /dev/null +++ b/app/dao/auth.py @@ -0,0 +1,65 @@ +# -*- coding: utf8 -*- +from lin import db +from lin.core import Auth, manager, find_auth_module +from lin.exception import Forbidden + +__author__ = 'Colorful' +__date__ = '2019/2/23 7:22 PM' + + +class AuthDAO(Auth): + def __init__(self): + super(AuthDAO, self).__init__() + + def get_by_group_id(self, group): + auths = db.session.query( + manager.auth_model.auth, manager.auth_model.module + ).filter_by(soft=False, group_id=group.id).all() + + auths = [{'auth': auth[0], 'module': auth[1]} for auth in auths] + from .group import GroupDAO + res = GroupDAO.split_modules(auths) + setattr(group, 'auths', res) + group._fields.append('auths') + + return auths + + def create_auths(self, auths, group_id): + for auth in auths: + meta = find_auth_module(auth) + if meta: + self.create(auth=meta.auth, module=meta.module, group_id=group_id) + + def delete_auths_by_gid(self, group_id): + self.query.filter(manager.auth_model.group_id == group_id).delete() + + def remove_auths(self, form): + with db.auto_commit(): + self.query(manager.auth_model).filter( + manager.auth_model.auth.in_(form.auths.data), + manager.auth_model.group_id == form.group_id.data + ).delete(synchronize_session=False) + + def patch_one(self, form): + one = manager.auth_model.get(group_id=form.group_id.data, auth=form.auth.data) + if one: + raise Forbidden(msg='已有权限,不可重复添加') + meta = find_auth_module(form.auth.data) + self.create( + group_id=form.group_id.data, + auth=meta.auth, + module=meta.module, + commit=True + ) + + def patch_all(self, form): + with db.auto_commit(): + for auth in form.auths.data: + one = self.get(group_id=form.group_id.data, auth=auth) + if not one: + meta = find_auth_module(auth) + self.create( + group_id=form.group_id.data, + auth=meta.auth, + module=meta.module + ) diff --git a/app/dao/group.py b/app/dao/group.py new file mode 100644 index 0000000..6e93712 --- /dev/null +++ b/app/dao/group.py @@ -0,0 +1,78 @@ +# -*- coding: utf8 -*- +from itertools import groupby +from operator import itemgetter + +from lin.core import Group, manager +from lin.db import get_total_nums, db +from lin.exception import NotFound, Forbidden + +from app.dao.auth import AuthDAO +from app.libs.utils import paginate + +__author__ = 'Colorful' +__date__ = '2019/2/23 7:20 PM' + + +class GroupDAO(Group): + def __init__(self): + super(GroupDAO, self).__init__() + + def get_groups_info(self): + start, count = paginate() + groups = self.query.filter().offset( + start).limit(count).all() + if groups is None: + raise NotFound(msg='不存在任何权限组') + + for group in groups: + AuthDAO().get_by_group_id(group) + + total_nums = get_total_nums(manager.group_model) + return groups, total_nums + + def get_single_info(self, gid): + group = self.get(id=gid, one=True, soft=False) + if group is None: + raise NotFound(msg='分组不存在') + AuthDAO().get_by_group_id(group) + return group + + def get_all(self): + groups = self.get(one=False) + if groups is None: + raise NotFound(msg='不存在任何权限组') + return groups + + def new_group(self, form): + exists = self.get(name=form.name.data) + if exists: + raise Forbidden(msg='分组已存在,不可创建同名分组') + with db.auto_commit(): + group = self.create(name=form.name.data, info=form.info.data) + db.session.flush() + AuthDAO().create_auths(form.auths.data, group.id) + + def update_group(self, gid, form): + exists = self.get(id=gid) + if not exists: + raise NotFound(msg='分组不存在,更新失败') + exists.update(name=form.name.data, info=form.info.data, commit=True) + + def remove_group(self, gid): + exist = self.get(id=gid) + if not exist: + raise NotFound(msg='分组不存在,删除失败') + if manager.user_model.get(group_id=gid): + raise Forbidden(msg='分组下存在用户,不可删除') + # 删除group拥有的权限 + AuthDAO().delete_auths_by_gid(gid) + exist.delete(commit=True) + + @staticmethod + def split_modules(auths): + auths.sort(key=itemgetter('module')) + tmps = groupby(auths, itemgetter('module')) + res = [] + for key, group in tmps: + res.append({key: list(group)}) + return res diff --git a/app/dao/log.py b/app/dao/log.py new file mode 100644 index 0000000..2852601 --- /dev/null +++ b/app/dao/log.py @@ -0,0 +1,50 @@ +# -*- coding: utf8 -*- +from flask import request +from lin import db +from lin.core import Log +from lin.exception import NotFound, ParameterException +from lin.util import paginate +from sqlalchemy import text + +__author__ = 'Colorful' +__date__ = '2019/2/23 10:24 PM' + + +class LogDAO(Log): + + def get_by_paginate(self, form): + start, count = paginate() + logs = self.query.filter() + if form.name.data: + logs = logs.filter(Log.user_name == form.name.data) + if form.start.data and form.end.data: + logs = logs.filter(Log.time.between(form.start.data, form.end.data)) + total_nums = logs.count() + logs = logs.order_by(text('time desc')).offset(start).limit(count).all() + if not logs: + raise NotFound(msg='没有找到相关日志') + return logs, total_nums + + def search_by_keyword(self, form): + keyword = request.args.get('keyword', default=None, type=str) + if keyword is None or '': + raise ParameterException(msg='搜索关键字不可为空') + start, count = paginate() + logs = self.query.filter(Log.message.like(f'%{keyword}%')) + if form.name.data: + logs = logs.filter(Log.user_name == form.name.data) + if form.start.data and form.end.data: + logs = logs.filter(Log._time.between(form.start.data, form.end.data)) + total_nums = logs.count() + logs = logs.order_by(text('time desc')).offset(start).limit(count).all() + if not logs: + raise NotFound(msg='没有找到相关日志') + return logs, total_nums + + def get_users_by_paginate(self): + start, count = paginate() + user_names = db.session.query(Log.user_name).filter_by( + soft=False).group_by(text('user_name')).having( + text('count(user_name) > 0')).offset(start).limit(count).all() + res = [user_name[0] for user_name in user_names] + return res diff --git a/app/dao/user.py b/app/dao/user.py new file mode 100644 index 0000000..7cbb4fd --- /dev/null +++ b/app/dao/user.py @@ -0,0 +1,145 @@ +# -*- coding: utf8 -*- +from flask import request +from flask_jwt_extended import get_current_user +from lin import db +from lin.core import find_user, manager, User as LinUser +from lin.db import get_total_nums +from lin.enums import UserSuper, UserActive +from lin.exception import NotFound, ParameterException, Forbidden, RepeatException +from sqlalchemy import and_ + +from app.libs.utils import paginate +from app.validators.forms import RegisterForm + +__author__ = 'Colorful' +__date__ = '2019/2/23 5:10 PM' + + +class UserDAO(LinUser): + + def get_all(self): + start, count = paginate() + group_id = request.args.get('group_id') + condition = { + 'super': UserSuper.COMMON.value, + 'group_id': group_id + } if group_id else { + 'super': UserSuper.COMMON.value + } + + users = db.session.query( + manager.user_model, manager.group_model.name + ).filter_by(soft=True, **condition).join( + manager.group_model, + manager.user_model.group_id == manager.group_model.id + ).offset(start).limit(count).all() + + user_and_group = [] + for user, group_name in users: + setattr(user, 'group_name', group_name) + user._fields.append('group_name') + user.hide('update_time', 'delete_time') + user_and_group.append(user) + # 有分组的时候就加入分组条件 + # total_nums = get_total_nums(manager.user_model, is_soft=True, super=UserSuper.COMMON.value) + total_nums = get_total_nums(manager.user_model, is_soft=True, **condition) + + return user_and_group, total_nums + + def reset_user_password(self, uid, new_password): + user = find_user(id=uid) + if user is None: + raise NotFound(msg='用户不存在') + with db.auto_commit(): + user.reset_password(new_password) + return self + + def remove_user(self, uid): + user = self.get(id=uid) + if user is None: + raise NotFound(msg='用户不存在') + # user.delete(commit=True) + # 此处我们使用硬删除,一般情况下,推荐使用软删除即,上一行注释的代码 + user.hard_delete(commit=True) + return self + + def update(self, uid, form): + user = self.get(id=uid) + if user is None: + raise NotFound(msg='用户不存在') + if user.email != form.email.data: + exists = self.get(email=form.email.data) + if exists: + raise ParameterException(msg='邮箱已被注册,请重新输入邮箱') + with db.auto_commit(): + user.email = form.email.data + user.group_id = form.group_id.data + return self + + def change_status(self, uid, active_or_disable='active'): + user = self.get(id=uid) + if user is None: + raise NotFound(msg='用户不存在') + + active_or_not = UserActive.NOT_ACTIVE.value \ + if active_or_disable == 'active'\ + else UserActive.ACTIVE.value + + if active_or_disable == 'active': + if not user.is_active: + raise Forbidden(msg='当前用户已处于禁止状态') + + elif active_or_disable == 'disable': + if user.is_active: + raise Forbidden(msg='当前用户已处于激活状态') + + with db.auto_commit(): + user.active = active_or_not + + return self + + def register(self, form): + user = manager.find_user(nickname=form.nickname.data) + if user: + raise RepeatException(msg='用户名重复,请重新输入') + if form.email.data and form.email.data.strip() != "": + user = self.query.filter(and_( + manager.user_model.email.isnot(None), + manager.user_model.email == form.email.data) + ).first() + if user: + raise RepeatException(msg='注册邮箱重复,请重新输入') + self._register_user(form) + + def update_info(self, form): + user = get_current_user() + if user.email != form.email.data: + exists = self.get(email=form.email.data) + if exists: + raise ParameterException(msg='邮箱已被注册,请重新输入邮箱') + with db.auto_commit(): + user.email = form.email.data + + @staticmethod + def get_allowed_apis(): + user = get_current_user() + auths = db.session.query( + manager.auth_model.auth, manager.auth_model.module + ).filter_by(soft=False, group_id=user.group_id).all() + auths = [{'auth': auth[0], 'module': auth[1]} for auth in auths] + from .group import GroupDAO + res = GroupDAO.split_modules(auths) + setattr(user, 'auths', res) + user._fields.append('auths') + return user + + def _register_user(self, form: RegisterForm): + with db.auto_commit(): + # 注意:此处使用挂载到manager上的user_model,不可使用默认的User + user = self + user.nickname = form.nickname.data + if form.email.data and form.email.data.strip() != "": + user.email = form.email.data + user.password = form.password.data + user.group_id = form.group_id.data + db.session.add(user) diff --git a/app/models/book.py b/app/models/book.py index 44b5f59..e220d6b 100644 --- a/app/models/book.py +++ b/app/models/book.py @@ -2,9 +2,11 @@ :copyright: © 2019 by the Lin team. :license: MIT, see LICENSE for more details. """ +from lin.exception import NotFound, ParameterException +from lin.interface import InfoCrud as Base from sqlalchemy import Column, String, Integer -from lin.interface import InfoCrud as Base +from app.libs.error_code import BookNotFound class Book(Base): @@ -13,3 +15,58 @@ class Book(Base): author = Column(String(30), default='未名') summary = Column(String(1000)) image = Column(String(50)) + + def get_detail(self, bid): + book = self.query.filter_by(id=bid, delete_time=None).first() + if book is None: + raise NotFound(msg='没有找到相关书籍') + return book + + def get_all(self): + books = self.query.filter_by(delete_time=None).all() + if not books: + raise NotFound(msg='没有找到相关书籍') + return books + + def search_by_keywords(self, q): + books = self.query.filter(Book.title.like('%' + q + '%'), Book.delete_time == None).all() + if not books: + raise BookNotFound() + return books + + def new_book(self, form): + book = Book.query.filter_by(title=form.title.data, delete_time=None).first() + if book is not None: + raise ParameterException(msg='图书已存在') + + self.create( + title=form.title.data, + author=form.author.data, + summary=form.summary.data, + image=form.image.data, + commit=True + ) + return self + + def edit_book(self, bid, form): + book = Book.query.filter_by(id=bid, delete_time=None).first() + if book is None: + raise NotFound(msg='没有找到相关书籍') + + self.update( + id=bid, + title=form.title.data, + author=form.author.data, + summary=form.summary.data, + image=form.image.data, + commit=True + ) + return self + + def remove_book(self, bid): + book = self.query.filter_by(id=bid, delete_time=None).first() + if book is None: + raise NotFound(msg='没有找到相关书籍') + # 删除图书,软删除 + book.delete(commit=True) + return self diff --git a/fake.py b/fake.py index 71a937d..6400f54 100644 --- a/fake.py +++ b/fake.py @@ -1,8 +1,3 @@ -""" - :copyright: © 2019 by the Lin team. - :license: MIT, see LICENSE for more details. -""" - from app.app import create_app from app.plugins.poem.app.model import Poem from lin.db import db @@ -12,29 +7,57 @@ with db.auto_commit(): # 添加诗歌 poem1 = Poem() - poem1.title = '夜宿山寺' - poem1.author = '李白' - poem1.dynasty = '唐代' - poem1.content = '危楼高百尺,手可摘星辰。不敢高声语,恐惊天上人。' + poem1.title = '生查子·元夕' + poem1.author = '欧阳修' + poem1.dynasty = '宋代' + poem1._content = """去年元夜时/花市灯如昼/月上柳梢头/人约黄昏后|今年元夜时/月与灯依旧/不见去年人/泪湿春衫袖""" db.session.add(poem1) poem2 = Poem() - poem2.title = '视刀环歌' - poem2.author = '刘禹锡' - poem2.dynasty = '唐代' - poem2.content = '常恨言语浅,不如人意深。今朝两相视,脉脉万重心。' + poem2.title = '临江仙·送钱穆父' + poem2.author = '苏轼' + poem2.dynasty = '宋代' + poem2._content = """一别都门三改火/天涯踏尽红尘/依然一笑作春温/无波真古井/有节是秋筠|惆怅孤帆连夜发/送行淡月微云/尊前不用翠眉颦/人生如逆旅/我亦是行人""" db.session.add(poem2) poem3 = Poem() - poem3.title = '己亥杂诗 · 其五' - poem3.author = '龚自珍' - poem3.dynasty = '清代' - poem3.content = '浩荡离愁白日斜,吟鞭东指即天涯。落红不是无情物,化作春泥更护花。' + poem3.title = '春望词四首' + poem3.author = '薛涛' + poem3.dynasty = '唐代' + poem3._content = """花开不同赏/花落不同悲/欲问相思处/花开花落时/揽草结同心/将以遗知音/春愁正断绝/春鸟复哀吟/风花日将老/佳期犹渺渺/不结同心人/空结同心草/那堪花满枝/翻作两相思/玉箸垂朝镜/春风知不知""" db.session.add(poem3) poem4 = Poem() - poem4.title = '杨柳枝' - poem4.author = '温庭筠' - poem4.dynasty = '唐代' - poem4.content = '井底点灯深烛伊,共郎长行莫围棋。玲珑骰子安红豆,入骨相思知不知。' + poem4.title = '长相思' + poem4.author = '纳兰性德' + poem4.dynasty = '清代' + poem4._content = """山一程/水一程/身向榆关那畔行/夜深千帐灯|风一更/雪一更/聒碎乡心梦不成/故园无此声""" db.session.add(poem4) + + poem5 = Poem() + poem5.title = '离思五首·其四' + poem5.author = '元稹' + poem5.dynasty = '唐代' + poem5._content = """曾经沧海难为水/除却巫山不是云/取次花丛懒回顾/半缘修道半缘君""" + db.session.add(poem5) + + poem6 = Poem() + poem6.title = '浣溪沙·一曲新词酒一杯' + poem6.author = '晏殊' + poem6.dynasty = '宋代' + poem6._content = """一曲新词酒一杯/去年天气旧亭台/夕阳西下几时回|无可奈何花落去/似曾相识燕归来/小园香径独徘徊""" + db.session.add(poem6) + + poem7 = Poem() + poem7.title = '浣溪沙·残雪凝辉冷画屏' + poem7.author = '纳兰性德' + poem7.dynasty = '清代' + poem7._content = """残雪凝辉冷画屏/落梅横笛已三更/更无人处月胧明|我是人间惆怅客/知君何事泪纵横/断肠声里忆平生""" + db.session.add(poem7) + + poem8 = Poem() + poem8.title = '蝶恋花·春景' + poem8.author = '苏轼' + poem8.dynasty = '宋代' + poem8._content = """花褪残红青杏小/燕子飞时/绿水人家绕/枝上柳绵吹又少/天涯何处无芳草|墙里秋千墙外道/墙外行人/墙里佳人笑/笑渐不闻声渐悄/多情却被无情恼""" + db.session.add(poem8) \ No newline at end of file From 8f9f65c04b6c309ded32cbc6324d9a1d65e7d7eb Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Sat, 23 Feb 2019 23:33:26 +0800 Subject: [PATCH 04/21] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E6=B3=A8=E9=87=8A=E4=B8=BA=E5=9B=A2=E9=98=9F=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/dao/auth.py | 9 ++--- app/dao/group.py | 9 ++--- app/dao/log.py | 9 ++--- app/dao/user.py | 9 ++--- fake.py | 93 +++++++++++++++++++----------------------------- 5 files changed, 56 insertions(+), 73 deletions(-) diff --git a/app/dao/auth.py b/app/dao/auth.py index b614669..73b65a2 100644 --- a/app/dao/auth.py +++ b/app/dao/auth.py @@ -1,11 +1,12 @@ -# -*- coding: utf8 -*- +""" + :copyright: © 2019 by the Lin team. + :license: MIT, see LICENSE for more details. +""" + from lin import db from lin.core import Auth, manager, find_auth_module from lin.exception import Forbidden -__author__ = 'Colorful' -__date__ = '2019/2/23 7:22 PM' - class AuthDAO(Auth): def __init__(self): diff --git a/app/dao/group.py b/app/dao/group.py index 6e93712..708a134 100644 --- a/app/dao/group.py +++ b/app/dao/group.py @@ -1,4 +1,8 @@ -# -*- coding: utf8 -*- +""" + :copyright: © 2019 by the Lin team. + :license: MIT, see LICENSE for more details. +""" + from itertools import groupby from operator import itemgetter @@ -9,9 +13,6 @@ from app.dao.auth import AuthDAO from app.libs.utils import paginate -__author__ = 'Colorful' -__date__ = '2019/2/23 7:20 PM' - class GroupDAO(Group): def __init__(self): diff --git a/app/dao/log.py b/app/dao/log.py index 2852601..86fbbd2 100644 --- a/app/dao/log.py +++ b/app/dao/log.py @@ -1,4 +1,8 @@ -# -*- coding: utf8 -*- +""" + :copyright: © 2019 by the Lin team. + :license: MIT, see LICENSE for more details. +""" + from flask import request from lin import db from lin.core import Log @@ -6,9 +10,6 @@ from lin.util import paginate from sqlalchemy import text -__author__ = 'Colorful' -__date__ = '2019/2/23 10:24 PM' - class LogDAO(Log): diff --git a/app/dao/user.py b/app/dao/user.py index 7cbb4fd..7ca036a 100644 --- a/app/dao/user.py +++ b/app/dao/user.py @@ -1,4 +1,8 @@ -# -*- coding: utf8 -*- +""" + :copyright: © 2019 by the Lin team. + :license: MIT, see LICENSE for more details. +""" + from flask import request from flask_jwt_extended import get_current_user from lin import db @@ -11,9 +15,6 @@ from app.libs.utils import paginate from app.validators.forms import RegisterForm -__author__ = 'Colorful' -__date__ = '2019/2/23 5:10 PM' - class UserDAO(LinUser): diff --git a/fake.py b/fake.py index 6400f54..43c3a13 100644 --- a/fake.py +++ b/fake.py @@ -1,63 +1,42 @@ +""" + :copyright: © 2019 by the Lin team. + :license: MIT, see LICENSE for more details. +""" + from app.app import create_app -from app.plugins.poem.app.model import Poem +from app.models.book import Book from lin.db import db app = create_app() with app.app_context(): with db.auto_commit(): - # 添加诗歌 - poem1 = Poem() - poem1.title = '生查子·元夕' - poem1.author = '欧阳修' - poem1.dynasty = '宋代' - poem1._content = """去年元夜时/花市灯如昼/月上柳梢头/人约黄昏后|今年元夜时/月与灯依旧/不见去年人/泪湿春衫袖""" - db.session.add(poem1) - - poem2 = Poem() - poem2.title = '临江仙·送钱穆父' - poem2.author = '苏轼' - poem2.dynasty = '宋代' - poem2._content = """一别都门三改火/天涯踏尽红尘/依然一笑作春温/无波真古井/有节是秋筠|惆怅孤帆连夜发/送行淡月微云/尊前不用翠眉颦/人生如逆旅/我亦是行人""" - db.session.add(poem2) - - poem3 = Poem() - poem3.title = '春望词四首' - poem3.author = '薛涛' - poem3.dynasty = '唐代' - poem3._content = """花开不同赏/花落不同悲/欲问相思处/花开花落时/揽草结同心/将以遗知音/春愁正断绝/春鸟复哀吟/风花日将老/佳期犹渺渺/不结同心人/空结同心草/那堪花满枝/翻作两相思/玉箸垂朝镜/春风知不知""" - db.session.add(poem3) - - poem4 = Poem() - poem4.title = '长相思' - poem4.author = '纳兰性德' - poem4.dynasty = '清代' - poem4._content = """山一程/水一程/身向榆关那畔行/夜深千帐灯|风一更/雪一更/聒碎乡心梦不成/故园无此声""" - db.session.add(poem4) - - poem5 = Poem() - poem5.title = '离思五首·其四' - poem5.author = '元稹' - poem5.dynasty = '唐代' - poem5._content = """曾经沧海难为水/除却巫山不是云/取次花丛懒回顾/半缘修道半缘君""" - db.session.add(poem5) - - poem6 = Poem() - poem6.title = '浣溪沙·一曲新词酒一杯' - poem6.author = '晏殊' - poem6.dynasty = '宋代' - poem6._content = """一曲新词酒一杯/去年天气旧亭台/夕阳西下几时回|无可奈何花落去/似曾相识燕归来/小园香径独徘徊""" - db.session.add(poem6) - - poem7 = Poem() - poem7.title = '浣溪沙·残雪凝辉冷画屏' - poem7.author = '纳兰性德' - poem7.dynasty = '清代' - poem7._content = """残雪凝辉冷画屏/落梅横笛已三更/更无人处月胧明|我是人间惆怅客/知君何事泪纵横/断肠声里忆平生""" - db.session.add(poem7) - - poem8 = Poem() - poem8.title = '蝶恋花·春景' - poem8.author = '苏轼' - poem8.dynasty = '宋代' - poem8._content = """花褪残红青杏小/燕子飞时/绿水人家绕/枝上柳绵吹又少/天涯何处无芳草|墙里秋千墙外道/墙外行人/墙里佳人笑/笑渐不闻声渐悄/多情却被无情恼""" - db.session.add(poem8) \ No newline at end of file + # 添加书籍 + book1 = Book() + book1.title = '深入理解计算机系统' + book1.author = 'Randal E.Bryant' + book1.summary = ''' + 从程序员的视角,看计算机系统!\n + 本书适用于那些想要写出更快、更可靠程序的程序员。 + 通过掌握程序是如何映射到系统上,以及程序是如何执行的,读者能够更好的理解程序的行为为什么是这样的,以及效率低下是如何造成的。 + 粗略来看,计算机系统包括处理器和存储器硬件、编译器、操作系统和网络互连环境。 + 而通过程序员的视角,读者可以清晰地明白学习计算机系统的内部工作原理会对他们今后作为计算机科学研究者和工程师的工作有进一步的帮助。 + 它还有助于为进一步学习计算机体系结构、操作系统、编译器和网络互连做好准备。\n + 本书的主要论题包括:数据表示、C程序的机器级表示、处理器结构,程序优化、存储器层次结构、链接、异常控制流、虚拟存储器和存储器管理、系统级I/O、网络编程和并发编程。书中所覆盖的内容主要是这些方面是如何影响应用和系统程序员的。 + ''' + book1.image = 'https://img3.doubanio.com/lpic/s1470003.jpg' + db.session.add(book1) + + book2 = Book() + book2.title = 'C程序设计语言' + book2.author = '(美)Brian W. Kernighan' + book2.summary = ''' + 在计算机发展的历史上,没有哪一种程序设计语言像C语言这样应用广泛。 + 本书原著即为C语言的设计者之一Dennis M.Ritchie和著名计算机科学家Brian W.Kernighan合著的一本介绍C语言的权威经典著作。 + 我们现在见到的大量论述C语言程序设计的教材和专著均以此书为蓝本。 + 原著第1版中介绍的C语言成为后来广泛使用的C语言版本——标准C的基础。 + 人们熟知的“hello,World"程序就是由本书首次引入的,现在,这一程序已经成为众多程序设计语言入门的第一课。\n + 原著第2版根据1987年制定的ANSIC标准做了适当的修订.引入了最新的语言形式,并增加了新的示例,通过简洁的描述、典型的示例,作者全面、系统、准确地讲述了C语言的各个特性以及程序设计的基本方法。 + 对于计算机从业人员来说,《C程序设计语言》是一本必读的程序设计语 言方面的参考书。 + ''' + book2.image = 'https://img3.doubanio.com/lpic/s1106934.jpg' + db.session.add(book2) \ No newline at end of file From 3d894d50b32e78fed4998e853e23308aa08180e8 Mon Sep 17 00:00:00 2001 From: pedro Date: Tue, 26 Feb 2019 14:53:28 +0800 Subject: [PATCH 05/21] =?UTF-8?q?feat:=E6=94=AF=E6=8C=81=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pipfile | 2 +- app/api/cms/notify.py | 4 +- app/plugins/oss/README.md | 5 +++ app/plugins/oss/requirements.txt | 0 requirements.txt | 2 +- vendor/plugin_generator.py | 71 ++++++++++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 app/plugins/oss/README.md create mode 100644 app/plugins/oss/requirements.txt create mode 100644 vendor/plugin_generator.py diff --git a/Pipfile b/Pipfile index 1bcf1c2..0ca6c99 100644 --- a/Pipfile +++ b/Pipfile @@ -11,7 +11,7 @@ cymysql = "==0.9.1" flask-cors = "==2.1.0" requests = "==2.18.4" pipfile = "*" -lin-cms = "==0.1.1a2" +lin-cms = "==0.1.1a3" "oss2" = "*" [dev-packages] diff --git a/app/api/cms/notify.py b/app/api/cms/notify.py index b8229a9..73e6a46 100644 --- a/app/api/cms/notify.py +++ b/app/api/cms/notify.py @@ -78,5 +78,5 @@ def event_stream(): yield sser.pop() else: yield sser.heartbeat() - # 每个5秒发送一次心跳 - time.sleep(5) + # 每个3秒发送一次心跳 + time.sleep(3) diff --git a/app/plugins/oss/README.md b/app/plugins/oss/README.md new file mode 100644 index 0000000..8d3a782 --- /dev/null +++ b/app/plugins/oss/README.md @@ -0,0 +1,5 @@ +# oss 插件 + +## 插件模板自动生成 + +## 添加requirements.txt \ No newline at end of file diff --git a/app/plugins/oss/requirements.txt b/app/plugins/oss/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt index 230b3e6..24d87eb 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.1a1 +Lin-CMS==0.1.1a3 MarkupSafe==1.1.0 pipfile==0.0.2 PyJWT==1.7.1 diff --git a/vendor/plugin_generator.py b/vendor/plugin_generator.py new file mode 100644 index 0000000..54736dc --- /dev/null +++ b/vendor/plugin_generator.py @@ -0,0 +1,71 @@ +""" + :copyright: © 2019 by the Lin team. + :license: MIT, see LICENSE for more details. +""" +import argparse +import os + +banner = """ +\""" + :copyright: © 2019 by the Lin team. + :license: MIT, see LICENSE for more details. +\""" +""" + +controller = """ +from lin.redprint import Redprint + +{0}_api = Redprint("{0}") + + +@{0}_api.route("/", methods=["GET"]) +def test(): + return "hi, guy!" +""" + +init = """ +from .controller import {0}_api +""" + +info = """ +__name__ = '{0}' +__version__ = '0.1.0' +__author__ = 'Team Lin' +""" + +readme = """# {0}""" + + +def create_plugin(name: str): + cmd = os.getcwd() + plugins_path = os.path.join(cmd, "app/plugins") + plugindir = os.path.join(plugins_path, name) + os.mkdir(plugindir) + + open(os.path.join(plugindir, "config.py"), mode="x", encoding="utf-8") + open(os.path.join(plugindir, "requirements.txt"), mode="x", encoding="utf-8") + + with open(os.path.join(plugindir, "info.py"), mode="x", encoding="utf-8") as f: + f.write(banner + info.format(name)) + + with open(os.path.join(plugindir, "README.md"), mode="x", encoding="utf-8") as f: + f.write(readme.format(name)) + + appdir = os.path.join(plugindir, "app") + os.mkdir(appdir) + + with open(os.path.join(appdir, "__init__.py"), mode="x", encoding="utf-8") as f: + f.write(banner + init.format(name)) + + with open(os.path.join(appdir, "controller.py"), mode="x", encoding="utf-8") as f: + f.write(banner + controller.format(name)) + + open(os.path.join(appdir, "model.py"), mode="x", encoding="utf-8") + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(usage="it's usage tip.", description="help info.") + parser.add_argument("-n", "--name", default="tpl", help="the name of plugin", dest="name") + args = parser.parse_args() + name = args.name + create_plugin(name) From a4e275fef777fd54e4d3acffb2f0379255d423dc Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Wed, 27 Feb 2019 08:51:45 +0800 Subject: [PATCH 06/21] =?UTF-8?q?refactor:=20=E4=B8=BA=E4=BA=86=E6=96=B9?= =?UTF-8?q?=E4=BE=BF=E5=AD=A6=E4=B9=A0=E6=BA=90=E7=A0=81=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E6=8E=89DAO=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/cms/admin.py | 183 +++++++++++++++++++++++++++++++++++++------ app/api/cms/log.py | 40 ++++++++-- app/api/cms/user.py | 47 +++++++++-- app/api/v1/book.py | 18 ++--- app/dao/auth.py | 66 ---------------- app/dao/group.py | 79 ------------------- app/dao/log.py | 51 ------------ app/dao/user.py | 146 ---------------------------------- app/models/book.py | 2 +- 9 files changed, 246 insertions(+), 386 deletions(-) delete mode 100644 app/dao/auth.py delete mode 100644 app/dao/group.py delete mode 100644 app/dao/log.py delete mode 100644 app/dao/user.py diff --git a/app/api/cms/admin.py b/app/api/cms/admin.py index 45a1f7b..bd15a63 100644 --- a/app/api/cms/admin.py +++ b/app/api/cms/admin.py @@ -7,16 +7,17 @@ from itertools import groupby from operator import itemgetter -from flask import jsonify -from lin.core import get_ep_infos, route_meta -from lin.exception import Success +from flask import jsonify, request +from lin import db +from lin.core import get_ep_infos, route_meta, manager, find_user, find_auth_module +from lin.db import get_total_nums +from lin.enums import UserSuper, UserActive +from lin.exception import Success, NotFound, ParameterException, Forbidden from lin.jwt import admin_required from lin.log import Logger from lin.redprint import Redprint -from app.dao.auth import AuthDAO -from app.dao.group import GroupDAO -from app.dao.user import UserDAO +from app.libs.utils import paginate from app.validators.forms import NewGroup, DispatchAuth, DispatchAuths, RemoveAuths, UpdateGroup, ResetPasswordForm, \ UpdateUserInfoForm @@ -34,7 +35,31 @@ def authority(): @route_meta(auth='查询所有用户', module='管理员', mount=False) @admin_required def get_admin_users(): - user_and_group, total_nums = UserDAO().get_all() + start, count = paginate() + group_id = request.args.get('group_id') + condition = { + 'super': UserSuper.COMMON.value, + 'group_id': group_id + } if group_id else { + 'super': UserSuper.COMMON.value + } + + users = db.session.query( + manager.user_model, manager.group_model.name + ).filter_by(soft=True, **condition).join( + manager.group_model, + manager.user_model.group_id == manager.group_model.id + ).offset(start).limit(count).all() + + user_and_group = [] + for user, group_name in users: + setattr(user, 'group_name', group_name) + user._fields.append('group_name') + user.hide('update_time', 'delete_time') + user_and_group.append(user) + # 有分组的时候就加入分组条件 + # total_nums = get_total_nums(manager.user_model, is_soft=True, super=UserSuper.COMMON.value) + total_nums = get_total_nums(manager.user_model, is_soft=True, **condition) return jsonify({ "collection": user_and_group, 'total_nums': total_nums @@ -46,7 +71,13 @@ def get_admin_users(): @admin_required def change_user_password(uid): form = ResetPasswordForm().validate_for_api() - UserDAO().reset_user_password(uid, form.new_password.data) + + user = find_user(id=uid) + if user is None: + raise NotFound(msg='用户不存在') + with db.auto_commit(): + user.reset_password(form.new_password.data) + return Success(msg='密码修改成功') @@ -55,7 +86,12 @@ def change_user_password(uid): @Logger(template='管理员删除了一个用户') # 记录日志 @admin_required def delete_user(uid): - UserDAO().remove_user(uid) + user = manager.user_model.get(id=uid) + if user is None: + raise NotFound(msg='用户不存在') + # user.delete(commit=True) + # 此处我们使用硬删除,一般情况下,推荐使用软删除即,上一行注释的代码 + user.hard_delete(commit=True) return Success(msg='操作成功') @@ -64,7 +100,17 @@ def delete_user(uid): @admin_required def update_user(uid): form = UpdateUserInfoForm().validate_for_api() - UserDAO().update(uid, form) + + user = manager.user_model.get(id=uid) + if user is None: + raise NotFound(msg='用户不存在') + if user.email != form.email.data: + exists = manager.user_model.get(email=form.email.data) + if exists: + raise ParameterException(msg='邮箱已被注册,请重新输入邮箱') + with db.auto_commit(): + user.email = form.email.data + user.group_id = form.group_id.data return Success(msg='操作成功') @@ -72,7 +118,7 @@ def update_user(uid): @route_meta(auth='禁用用户', module='管理员', mount=False) @admin_required def trans2disable(uid): - UserDAO().change_status(uid, 'active') + _change_status(uid, 'active') return Success(msg='操作成功') @@ -80,7 +126,7 @@ def trans2disable(uid): @route_meta(auth='激活用户', module='管理员', mount=False) @admin_required def trans2active(uid): - UserDAO().change_status(uid, 'disable') + _change_status(uid, 'disable') return Success(msg='操作成功') @@ -88,10 +134,26 @@ def trans2active(uid): @route_meta(auth='查询所有权限组及其权限', module='管理员', mount=False) @admin_required def get_admin_groups(): - groups_info, total_nums = GroupDAO().get_groups_info() + start, count = paginate() + groups = manager.group_model.query.filter().offset( + start).limit(count).all() + if groups is None: + raise NotFound(msg='不存在任何权限组') + + for group in groups: + auths = db.session.query( + manager.auth_model.auth, manager.auth_model.module + ).filter_by(soft=False, group_id=group.id).all() + + auths = [{'auth': auth[0], 'module': auth[1]} for auth in auths] + res = _split_modules(auths) + setattr(group, 'auths', res) + group._fields.append('auths') + + total_nums = get_total_nums(manager.group_model) return jsonify({ - "collection": groups_info, + "collection": groups, 'total_nums': total_nums }) @@ -100,7 +162,9 @@ def get_admin_groups(): @route_meta(auth='查询所有权限组', module='管理员', mount=False) @admin_required def get_all_group(): - groups = GroupDAO().get_all() + groups = manager.group_model.get(one=False) + if groups is None: + raise NotFound(msg='不存在任何权限组') return jsonify(groups) @@ -108,7 +172,16 @@ def get_all_group(): @route_meta(auth='查询一个权限组及其权限', module='管理员', mount=False) @admin_required def get_group(gid): - group = GroupDAO().get_single_info(gid) + group = manager.group_model.get(id=gid, one=True, soft=False) + if group is None: + raise NotFound(msg='分组不存在') + auths = db.session.query( + manager.auth_model.auth, manager.auth_model.module + ).filter_by(soft=False, group_id=group.id).all() + auths = [{'auth': auth[0], 'module': auth[1]} for auth in auths] + res = _split_modules(auths) + setattr(group, 'auths', res) + group._fields.append('auths') return jsonify(group) @@ -118,7 +191,18 @@ def get_group(gid): @admin_required def create_group(): form = NewGroup().validate_for_api() - GroupDAO().new_group(form) + exists = manager.group_model.get(name=form.name.data) + if exists: + raise Forbidden(msg='分组已存在,不可创建同名分组') + with db.auto_commit(): + group = manager.group_model.create(name=form.name.data, info=form.info.data) + db.session.flush() + + for auth in form.auths.data: + meta = find_auth_module(auth) + if meta: + manager.auth_model.create(auth=meta.auth, module=meta.module, group_id=group.id) + return Success(msg='新建分组成功') @@ -127,7 +211,10 @@ def create_group(): @admin_required def update_group(gid): form = UpdateGroup().validate_for_api() - GroupDAO().update_group(gid, form) + exists = manager.group_model.get(id=gid) + if not exists: + raise NotFound(msg='分组不存在,更新失败') + exists.update(name=form.name.data, info=form.info.data, commit=True) return Success(msg='更新分组成功') @@ -136,7 +223,14 @@ def update_group(gid): @Logger(template='管理员删除一个权限组') # 记录日志 @admin_required def delete_group(gid): - GroupDAO().remove_group(gid) + exist = manager.group_model.get(id=gid) + if not exist: + raise NotFound(msg='分组不存在,删除失败') + if manager.user_model.get(group_id=gid): + raise Forbidden(msg='分组下存在用户,不可删除') + # 删除group拥有的权限 + manager.auth_model.query.filter(manager.auth_model.group_id == gid).delete() + exist.delete(commit=True) return Success(msg='删除分组成功') @@ -145,7 +239,16 @@ def delete_group(gid): @admin_required def dispatch_auth(): form = DispatchAuth().validate_for_api() - AuthDAO().patch_one(form) + one = manager.auth_model.get(group_id=form.group_id.data, auth=form.auth.data) + if one: + raise Forbidden(msg='已有权限,不可重复添加') + meta = find_auth_module(form.auth.data) + manager.auth_model.create( + group_id=form.group_id.data, + auth=meta.auth, + module=meta.module, + commit=True + ) return Success(msg='添加权限成功') @@ -154,7 +257,16 @@ def dispatch_auth(): @admin_required def dispatch_auths(): form = DispatchAuths().validate_for_api() - AuthDAO().patch_all(form) + with db.auto_commit(): + for auth in form.auths.data: + one = manager.auth_model.get(group_id=form.group_id.data, auth=auth) + if not one: + meta = find_auth_module(auth) + manager.auth_model.create( + group_id=form.group_id.data, + auth=meta.auth, + module=meta.module + ) return Success(msg='添加权限成功') @@ -163,7 +275,13 @@ def dispatch_auths(): @admin_required def remove_auths(): form = RemoveAuths().validate_for_api() - AuthDAO().remove_auths(form) + + with db.auto_commit(): + db.session.query(manager.auth_model).filter( + manager.auth_model.auth.in_(form.auths.data), + manager.auth_model.group_id == form.group_id.data + ).delete(synchronize_session=False) + return Success(msg='删除权限成功') @@ -175,6 +293,27 @@ def _split_modules(auths): res.append({key: list(group)}) return res + +def _change_status(uid, active_or_disable='active'): + user = manager.user_model.get(id=uid) + if user is None: + raise NotFound(msg='用户不存在') + + active_or_not = UserActive.NOT_ACTIVE.value \ + if active_or_disable == 'active' \ + else UserActive.ACTIVE.value + + if active_or_disable == 'active': + if not user.is_active: + raise Forbidden(msg='当前用户已处于禁止状态') + + elif active_or_disable == 'disable': + if user.is_active: + raise Forbidden(msg='当前用户已处于激活状态') + + with db.auto_commit(): + user.active = active_or_not + # -------------------------------------------------- # --------------------Abandon----------------------- # -------------------------------------------------- diff --git a/app/api/cms/log.py b/app/api/cms/log.py index a594379..e9ccb4d 100644 --- a/app/api/cms/log.py +++ b/app/api/cms/log.py @@ -3,12 +3,15 @@ :license: MIT, see LICENSE for more details. """ -from flask import jsonify -from lin.core import route_meta +from flask import jsonify, request +from lin import db +from lin.core import route_meta, Log +from lin.exception import NotFound, ParameterException from lin.jwt import group_required from lin.redprint import Redprint +from lin.util import paginate +from sqlalchemy import text -from app.dao.log import LogDAO from app.validators.forms import LogFindForm log_api = Redprint('log') @@ -20,7 +23,16 @@ @group_required def get_logs(): form = LogFindForm().validate_for_api() - logs, total_nums = LogDAO().get_by_paginate(form) + start, count = paginate() + logs = db.session.query(Log).filter() + if form.name.data: + logs = logs.filter(Log.user_name == form.name.data) + if form.start.data and form.end.data: + logs = logs.filter(Log.time.between(form.start.data, form.end.data)) + total_nums = logs.count() + logs = logs.order_by(text('time desc')).offset(start).limit(count).all() + if not logs: + raise NotFound(msg='没有找到相关日志') return jsonify({ "total_nums": total_nums, "collection": logs @@ -33,7 +45,19 @@ def get_logs(): @group_required def get_user_logs(): form = LogFindForm().validate_for_api() - logs, total_nums = LogDAO().search_by_keyword(form) + keyword = request.args.get('keyword', default=None, type=str) + if keyword is None or '': + raise ParameterException(msg='搜索关键字不可为空') + start, count = paginate() + logs = Log.query.filter(Log.message.like(f'%{keyword}%')) + if form.name.data: + logs = logs.filter(Log.user_name == form.name.data) + if form.start.data and form.end.data: + logs = logs.filter(Log._time.between(form.start.data, form.end.data)) + total_nums = logs.count() + logs = logs.order_by(text('time desc')).offset(start).limit(count).all() + if not logs: + raise NotFound(msg='没有找到相关日志') return jsonify({ "total_nums": total_nums, "collection": logs @@ -44,5 +68,9 @@ def get_user_logs(): @route_meta(auth='查询日志记录的用户', module='日志') @group_required def get_users(): - users = LogDAO().get_users_by_paginate() + start, count = paginate() + user_names = db.session.query(Log.user_name).filter_by( + soft=False).group_by(text('user_name')).having( + text('count(user_name) > 0')).offset(start).limit(count).all() + users = [user_name[0] for user_name in user_names] return jsonify(users) diff --git a/app/api/cms/user.py b/app/api/cms/user.py index 972fb84..985606a 100644 --- a/app/api/cms/user.py +++ b/app/api/cms/user.py @@ -4,18 +4,17 @@ :copyright: © 2019 by the Lin team. :license: MIT, see LICENSE for more details. """ +from operator import and_ from flask import jsonify from flask_jwt_extended import create_access_token, jwt_refresh_token_required, get_jwt_identity, get_current_user from lin.core import manager, route_meta, Log from lin.db import db -from lin.exception import NotFound, Success, Failed, ParameterException +from lin.exception import NotFound, Success, Failed, RepeatException, ParameterException from lin.jwt import login_required, admin_required, get_tokens from lin.log import Logger from lin.redprint import Redprint -from app.dao.auth import AuthDAO -from app.dao.user import UserDAO from app.validators.forms import LoginForm, RegisterForm, ChangePasswordForm, UpdateInfoForm user_api = Redprint('user') @@ -27,7 +26,17 @@ @admin_required def register(): form = RegisterForm().validate_for_api() - UserDAO().register(form) + user = manager.find_user(nickname=form.nickname.data) + if user: + raise RepeatException(msg='用户名重复,请重新输入') + if form.email.data and form.email.data.strip() != "": + user = manager.user_model.query.filter(and_( + manager.user_model.email.isnot(None), + manager.user_model.email == form.email.data + )).first() + if user: + raise RepeatException(msg='注册邮箱重复,请重新输入') + _register_user(form) return Success(msg='用户创建成功') @@ -55,7 +64,13 @@ def login(): @login_required def update(): form = UpdateInfoForm().validate_for_api() - UserDAO().update_info(form) + user = get_current_user() + if user.email != form.email.data: + exists = manager.user_model.get(email=form.email.data) + if exists: + raise ParameterException(msg='邮箱已被注册,请重新输入邮箱') + with db.auto_commit(): + user.email = form.email.data return Success(msg='操作成功') @@ -99,5 +114,25 @@ def refresh(): @route_meta(auth='查询自己拥有的权限', module='用户', mount=False) @login_required def get_allowed_apis(): - user = UserDAO.get_allowed_apis() + user = get_current_user() + auths = db.session.query( + manager.auth_model.auth, manager.auth_model.module + ).filter_by(soft=False, group_id=user.group_id).all() + auths = [{'auth': auth[0], 'module': auth[1]} for auth in auths] + from .admin import _split_modules + res = _split_modules(auths) + setattr(user, 'auths', res) + user._fields.append('auths') return jsonify(user) + + +def _register_user(form: RegisterForm): + with db.auto_commit(): + # 注意:此处使用挂载到manager上的user_model,不可使用默认的User + user = manager.user_model() + user.nickname = form.nickname.data + if form.email.data and form.email.data.strip() != "": + user.email = form.email.data + user.password = form.password.data + user.group_id = form.group_id.data + db.session.add(user) diff --git a/app/api/v1/book.py b/app/api/v1/book.py index 15b6d19..439a1bb 100644 --- a/app/api/v1/book.py +++ b/app/api/v1/book.py @@ -18,11 +18,11 @@ # 注意:如果Notify添加的视图函数,没有添加任何视图函数,那么不可识别用户身份 # 这与真实的情况是一致的,因为一般的情况下,重要的接口需要被保护,重要的消息才需要推送 -@book_api.route('/', methods=['GET']) +@book_api.route('/', methods=['GET']) @login_required @Notify(template='{user.nickname}查询了一本图书', event='queryBook') -def get_book(id): - book = Book().get_detail(id) +def get_book(bid): + book = Book().get_detail(bid) return jsonify(book) @@ -48,16 +48,16 @@ def create_book(): return Success(msg='新建图书成功') -@book_api.route('/', methods=['PUT']) -def update_book(id): +@book_api.route('/', methods=['PUT']) +def update_book(bid): form = CreateOrUpdateBookForm().validate_for_api() - Book().edit_book(id, form) + Book().edit_book(bid, form) return Success(msg='更新图书成功') -@book_api.route('/', methods=['DELETE']) +@book_api.route('/', methods=['DELETE']) @route_meta(auth='删除图书', module='图书') @group_required -def delete_book(id): - Book().remove_book(id) +def delete_book(bid): + Book().remove_book(bid) return Success(msg='删除图书成功') diff --git a/app/dao/auth.py b/app/dao/auth.py deleted file mode 100644 index 73b65a2..0000000 --- a/app/dao/auth.py +++ /dev/null @@ -1,66 +0,0 @@ -""" - :copyright: © 2019 by the Lin team. - :license: MIT, see LICENSE for more details. -""" - -from lin import db -from lin.core import Auth, manager, find_auth_module -from lin.exception import Forbidden - - -class AuthDAO(Auth): - def __init__(self): - super(AuthDAO, self).__init__() - - def get_by_group_id(self, group): - auths = db.session.query( - manager.auth_model.auth, manager.auth_model.module - ).filter_by(soft=False, group_id=group.id).all() - - auths = [{'auth': auth[0], 'module': auth[1]} for auth in auths] - from .group import GroupDAO - res = GroupDAO.split_modules(auths) - setattr(group, 'auths', res) - group._fields.append('auths') - - return auths - - def create_auths(self, auths, group_id): - for auth in auths: - meta = find_auth_module(auth) - if meta: - self.create(auth=meta.auth, module=meta.module, group_id=group_id) - - def delete_auths_by_gid(self, group_id): - self.query.filter(manager.auth_model.group_id == group_id).delete() - - def remove_auths(self, form): - with db.auto_commit(): - self.query(manager.auth_model).filter( - manager.auth_model.auth.in_(form.auths.data), - manager.auth_model.group_id == form.group_id.data - ).delete(synchronize_session=False) - - def patch_one(self, form): - one = manager.auth_model.get(group_id=form.group_id.data, auth=form.auth.data) - if one: - raise Forbidden(msg='已有权限,不可重复添加') - meta = find_auth_module(form.auth.data) - self.create( - group_id=form.group_id.data, - auth=meta.auth, - module=meta.module, - commit=True - ) - - def patch_all(self, form): - with db.auto_commit(): - for auth in form.auths.data: - one = self.get(group_id=form.group_id.data, auth=auth) - if not one: - meta = find_auth_module(auth) - self.create( - group_id=form.group_id.data, - auth=meta.auth, - module=meta.module - ) diff --git a/app/dao/group.py b/app/dao/group.py deleted file mode 100644 index 708a134..0000000 --- a/app/dao/group.py +++ /dev/null @@ -1,79 +0,0 @@ -""" - :copyright: © 2019 by the Lin team. - :license: MIT, see LICENSE for more details. -""" - -from itertools import groupby -from operator import itemgetter - -from lin.core import Group, manager -from lin.db import get_total_nums, db -from lin.exception import NotFound, Forbidden - -from app.dao.auth import AuthDAO -from app.libs.utils import paginate - - -class GroupDAO(Group): - def __init__(self): - super(GroupDAO, self).__init__() - - def get_groups_info(self): - start, count = paginate() - groups = self.query.filter().offset( - start).limit(count).all() - if groups is None: - raise NotFound(msg='不存在任何权限组') - - for group in groups: - AuthDAO().get_by_group_id(group) - - total_nums = get_total_nums(manager.group_model) - return groups, total_nums - - def get_single_info(self, gid): - group = self.get(id=gid, one=True, soft=False) - if group is None: - raise NotFound(msg='分组不存在') - AuthDAO().get_by_group_id(group) - return group - - def get_all(self): - groups = self.get(one=False) - if groups is None: - raise NotFound(msg='不存在任何权限组') - return groups - - def new_group(self, form): - exists = self.get(name=form.name.data) - if exists: - raise Forbidden(msg='分组已存在,不可创建同名分组') - with db.auto_commit(): - group = self.create(name=form.name.data, info=form.info.data) - db.session.flush() - AuthDAO().create_auths(form.auths.data, group.id) - - def update_group(self, gid, form): - exists = self.get(id=gid) - if not exists: - raise NotFound(msg='分组不存在,更新失败') - exists.update(name=form.name.data, info=form.info.data, commit=True) - - def remove_group(self, gid): - exist = self.get(id=gid) - if not exist: - raise NotFound(msg='分组不存在,删除失败') - if manager.user_model.get(group_id=gid): - raise Forbidden(msg='分组下存在用户,不可删除') - # 删除group拥有的权限 - AuthDAO().delete_auths_by_gid(gid) - exist.delete(commit=True) - - @staticmethod - def split_modules(auths): - auths.sort(key=itemgetter('module')) - tmps = groupby(auths, itemgetter('module')) - res = [] - for key, group in tmps: - res.append({key: list(group)}) - return res diff --git a/app/dao/log.py b/app/dao/log.py deleted file mode 100644 index 86fbbd2..0000000 --- a/app/dao/log.py +++ /dev/null @@ -1,51 +0,0 @@ -""" - :copyright: © 2019 by the Lin team. - :license: MIT, see LICENSE for more details. -""" - -from flask import request -from lin import db -from lin.core import Log -from lin.exception import NotFound, ParameterException -from lin.util import paginate -from sqlalchemy import text - - -class LogDAO(Log): - - def get_by_paginate(self, form): - start, count = paginate() - logs = self.query.filter() - if form.name.data: - logs = logs.filter(Log.user_name == form.name.data) - if form.start.data and form.end.data: - logs = logs.filter(Log.time.between(form.start.data, form.end.data)) - total_nums = logs.count() - logs = logs.order_by(text('time desc')).offset(start).limit(count).all() - if not logs: - raise NotFound(msg='没有找到相关日志') - return logs, total_nums - - def search_by_keyword(self, form): - keyword = request.args.get('keyword', default=None, type=str) - if keyword is None or '': - raise ParameterException(msg='搜索关键字不可为空') - start, count = paginate() - logs = self.query.filter(Log.message.like(f'%{keyword}%')) - if form.name.data: - logs = logs.filter(Log.user_name == form.name.data) - if form.start.data and form.end.data: - logs = logs.filter(Log._time.between(form.start.data, form.end.data)) - total_nums = logs.count() - logs = logs.order_by(text('time desc')).offset(start).limit(count).all() - if not logs: - raise NotFound(msg='没有找到相关日志') - return logs, total_nums - - def get_users_by_paginate(self): - start, count = paginate() - user_names = db.session.query(Log.user_name).filter_by( - soft=False).group_by(text('user_name')).having( - text('count(user_name) > 0')).offset(start).limit(count).all() - res = [user_name[0] for user_name in user_names] - return res diff --git a/app/dao/user.py b/app/dao/user.py deleted file mode 100644 index 7ca036a..0000000 --- a/app/dao/user.py +++ /dev/null @@ -1,146 +0,0 @@ -""" - :copyright: © 2019 by the Lin team. - :license: MIT, see LICENSE for more details. -""" - -from flask import request -from flask_jwt_extended import get_current_user -from lin import db -from lin.core import find_user, manager, User as LinUser -from lin.db import get_total_nums -from lin.enums import UserSuper, UserActive -from lin.exception import NotFound, ParameterException, Forbidden, RepeatException -from sqlalchemy import and_ - -from app.libs.utils import paginate -from app.validators.forms import RegisterForm - - -class UserDAO(LinUser): - - def get_all(self): - start, count = paginate() - group_id = request.args.get('group_id') - condition = { - 'super': UserSuper.COMMON.value, - 'group_id': group_id - } if group_id else { - 'super': UserSuper.COMMON.value - } - - users = db.session.query( - manager.user_model, manager.group_model.name - ).filter_by(soft=True, **condition).join( - manager.group_model, - manager.user_model.group_id == manager.group_model.id - ).offset(start).limit(count).all() - - user_and_group = [] - for user, group_name in users: - setattr(user, 'group_name', group_name) - user._fields.append('group_name') - user.hide('update_time', 'delete_time') - user_and_group.append(user) - # 有分组的时候就加入分组条件 - # total_nums = get_total_nums(manager.user_model, is_soft=True, super=UserSuper.COMMON.value) - total_nums = get_total_nums(manager.user_model, is_soft=True, **condition) - - return user_and_group, total_nums - - def reset_user_password(self, uid, new_password): - user = find_user(id=uid) - if user is None: - raise NotFound(msg='用户不存在') - with db.auto_commit(): - user.reset_password(new_password) - return self - - def remove_user(self, uid): - user = self.get(id=uid) - if user is None: - raise NotFound(msg='用户不存在') - # user.delete(commit=True) - # 此处我们使用硬删除,一般情况下,推荐使用软删除即,上一行注释的代码 - user.hard_delete(commit=True) - return self - - def update(self, uid, form): - user = self.get(id=uid) - if user is None: - raise NotFound(msg='用户不存在') - if user.email != form.email.data: - exists = self.get(email=form.email.data) - if exists: - raise ParameterException(msg='邮箱已被注册,请重新输入邮箱') - with db.auto_commit(): - user.email = form.email.data - user.group_id = form.group_id.data - return self - - def change_status(self, uid, active_or_disable='active'): - user = self.get(id=uid) - if user is None: - raise NotFound(msg='用户不存在') - - active_or_not = UserActive.NOT_ACTIVE.value \ - if active_or_disable == 'active'\ - else UserActive.ACTIVE.value - - if active_or_disable == 'active': - if not user.is_active: - raise Forbidden(msg='当前用户已处于禁止状态') - - elif active_or_disable == 'disable': - if user.is_active: - raise Forbidden(msg='当前用户已处于激活状态') - - with db.auto_commit(): - user.active = active_or_not - - return self - - def register(self, form): - user = manager.find_user(nickname=form.nickname.data) - if user: - raise RepeatException(msg='用户名重复,请重新输入') - if form.email.data and form.email.data.strip() != "": - user = self.query.filter(and_( - manager.user_model.email.isnot(None), - manager.user_model.email == form.email.data) - ).first() - if user: - raise RepeatException(msg='注册邮箱重复,请重新输入') - self._register_user(form) - - def update_info(self, form): - user = get_current_user() - if user.email != form.email.data: - exists = self.get(email=form.email.data) - if exists: - raise ParameterException(msg='邮箱已被注册,请重新输入邮箱') - with db.auto_commit(): - user.email = form.email.data - - @staticmethod - def get_allowed_apis(): - user = get_current_user() - auths = db.session.query( - manager.auth_model.auth, manager.auth_model.module - ).filter_by(soft=False, group_id=user.group_id).all() - auths = [{'auth': auth[0], 'module': auth[1]} for auth in auths] - from .group import GroupDAO - res = GroupDAO.split_modules(auths) - setattr(user, 'auths', res) - user._fields.append('auths') - return user - - def _register_user(self, form: RegisterForm): - with db.auto_commit(): - # 注意:此处使用挂载到manager上的user_model,不可使用默认的User - user = self - user.nickname = form.nickname.data - if form.email.data and form.email.data.strip() != "": - user.email = form.email.data - user.password = form.password.data - user.group_id = form.group_id.data - db.session.add(user) diff --git a/app/models/book.py b/app/models/book.py index e220d6b..7b4ce51 100644 --- a/app/models/book.py +++ b/app/models/book.py @@ -53,7 +53,7 @@ def edit_book(self, bid, form): if book is None: raise NotFound(msg='没有找到相关书籍') - self.update( + book.update( id=bid, title=form.title.data, author=form.author.data, From f215b0be389831df5a3cd2a30f4e1439750547f0 Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Thu, 28 Feb 2019 13:33:25 +0800 Subject: [PATCH 07/21] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=B1=82=E7=9A=84=E5=AE=9E=E4=BE=8B=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E4=B8=BA=E7=B1=BB=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/v1/book.py | 12 ++++++------ app/models/book.py | 34 ++++++++++++++++++++-------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/app/api/v1/book.py b/app/api/v1/book.py index 439a1bb..d43a517 100644 --- a/app/api/v1/book.py +++ b/app/api/v1/book.py @@ -22,7 +22,7 @@ @login_required @Notify(template='{user.nickname}查询了一本图书', event='queryBook') def get_book(bid): - book = Book().get_detail(bid) + book = Book.get_detail(bid) return jsonify(book) @@ -30,28 +30,28 @@ def get_book(bid): @login_required @Notify(template='{user.nickname}查询了所有图书', event='queryBooks') def get_books(): - books = Book().get_all() + books = Book.get_all() return jsonify(books) @book_api.route('/search', methods=['GET']) def search(): form = BookSearchForm().validate_for_api() - books = Book().search_by_keywords(form.q.data) + books = Book.search_by_keywords(form.q.data) return jsonify(books) @book_api.route('/', methods=['POST']) def create_book(): form = CreateOrUpdateBookForm().validate_for_api() - Book().new_book(form) + Book.new_book(form) return Success(msg='新建图书成功') @book_api.route('/', methods=['PUT']) def update_book(bid): form = CreateOrUpdateBookForm().validate_for_api() - Book().edit_book(bid, form) + Book.edit_book(bid, form) return Success(msg='更新图书成功') @@ -59,5 +59,5 @@ def update_book(bid): @route_meta(auth='删除图书', module='图书') @group_required def delete_book(bid): - Book().remove_book(bid) + Book.remove_book(bid) return Success(msg='删除图书成功') diff --git a/app/models/book.py b/app/models/book.py index 7b4ce51..f7c532a 100644 --- a/app/models/book.py +++ b/app/models/book.py @@ -16,39 +16,44 @@ class Book(Base): summary = Column(String(1000)) image = Column(String(50)) - def get_detail(self, bid): - book = self.query.filter_by(id=bid, delete_time=None).first() + @classmethod + def get_detail(cls, bid): + book = cls.query.filter_by(id=bid, delete_time=None).first() if book is None: raise NotFound(msg='没有找到相关书籍') return book - def get_all(self): - books = self.query.filter_by(delete_time=None).all() + @classmethod + def get_all(cls): + books = cls.query.filter_by(delete_time=None).all() if not books: raise NotFound(msg='没有找到相关书籍') return books - def search_by_keywords(self, q): - books = self.query.filter(Book.title.like('%' + q + '%'), Book.delete_time == None).all() + @classmethod + def search_by_keywords(cls, q): + books = cls.query.filter(Book.title.like('%' + q + '%'), Book.delete_time == None).all() if not books: raise BookNotFound() return books - def new_book(self, form): + @classmethod + def new_book(cls, form): book = Book.query.filter_by(title=form.title.data, delete_time=None).first() if book is not None: raise ParameterException(msg='图书已存在') - self.create( + Book.create( title=form.title.data, author=form.author.data, summary=form.summary.data, image=form.image.data, commit=True ) - return self + return True - def edit_book(self, bid, form): + @classmethod + def edit_book(cls, bid, form): book = Book.query.filter_by(id=bid, delete_time=None).first() if book is None: raise NotFound(msg='没有找到相关书籍') @@ -61,12 +66,13 @@ def edit_book(self, bid, form): image=form.image.data, commit=True ) - return self + return True - def remove_book(self, bid): - book = self.query.filter_by(id=bid, delete_time=None).first() + @classmethod + def remove_book(cls, bid): + book = cls.query.filter_by(id=bid, delete_time=None).first() if book is None: raise NotFound(msg='没有找到相关书籍') # 删除图书,软删除 book.delete(commit=True) - return self + return True From 44e7bcbe79beaf8f678e5a1481ae81f29e7ff753 Mon Sep 17 00:00:00 2001 From: pedro Date: Mon, 4 Mar 2019 13:58:32 +0800 Subject: [PATCH 08/21] =?UTF-8?q?doc:=20=E6=9B=B4=E6=96=B0README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/config/secure.py | 2 ++ app/plugins/oss/README.md | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/config/secure.py b/app/config/secure.py index a44a355..d4e59f0 100644 --- a/app/config/secure.py +++ b/app/config/secure.py @@ -6,4 +6,6 @@ # 安全性配置 SQLALCHEMY_DATABASE_URI = 'mysql+cymysql://root:123456@localhost:3306/lin-cms' +SQLALCHEMY_ECHO = False + SECRET_KEY = '\x88W\xf09\x91\x07\x98\x89\x87\x96\xa0A\xc68\xf9\xecJJU\x17\xc5V\xbe\x8b\xef\xd7\xd8\xd3\xe6\x95*4' diff --git a/app/plugins/oss/README.md b/app/plugins/oss/README.md index 8d3a782..9836f58 100644 --- a/app/plugins/oss/README.md +++ b/app/plugins/oss/README.md @@ -2,4 +2,14 @@ ## 插件模板自动生成 -## 添加requirements.txt \ No newline at end of file +请在项目的根目录下运行如下命令: + +```bash +python vendor/plugin_generator.py -n oss +``` + +即可快速生成一个名为oss的插件,`-n`表示指定插件名 + +## 添加requirements.txt + +如果你是在插件中使用了一些第三方库,这些库在主应用中是没有的,那么请你将它添加到**requirements.txt**中。 \ No newline at end of file From 7c9323fed3ecf56b40eb328a6ea89dc6808c083c Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Sat, 9 Mar 2019 12:39:03 +0800 Subject: [PATCH 09/21] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E5=88=9D=E5=A7=8B=E5=8C=96=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.py | 13 +- app/config/setting.py | 4 +- app/plugins/poem/app/__init__.py | 76 +++++++++++ app/plugins/poem/app/model.py | 1 + app/plugins/poem/info.py | 3 +- app/plugins/poem/requirements.txt | 0 vendor/plugin_init.py | 211 ++++++++++++++++++++++++++++++ 7 files changed, 299 insertions(+), 9 deletions(-) create mode 100644 app/plugins/poem/requirements.txt create mode 100644 vendor/plugin_init.py diff --git a/app/app.py b/app/app.py index 4ebe4aa..56ccbd1 100644 --- a/app/app.py +++ b/app/app.py @@ -25,13 +25,14 @@ def create_tables(app): db.create_all() -def create_app(): +def create_app(register_all=True): app = Flask(__name__) app.config.from_object('app.config.setting') app.config.from_object('app.config.secure') - register_blueprints(app) - Lin(app) - apply_cors(app) - # 创建所有表格 - create_tables(app) + if register_all: + register_blueprints(app) + Lin(app) + apply_cors(app) + # 创建所有表格 + create_tables(app) return app diff --git a/app/config/setting.py b/app/config/setting.py index 5640d7d..acc8227 100644 --- a/app/config/setting.py +++ b/app/config/setting.py @@ -15,6 +15,6 @@ # 插件模块暂时没有开启,以下配置可忽略 # plugin config写在字典里面 PLUGIN_PATH = { - 'oss': {'path': 'app.plugins.oss', 'enable': True, 'upload_folder': 'app/static'}, - 'poem': {'path': 'app.plugins.poem', 'enable': True, 'limit': 5}, + 'poem': {'path': 'app.plugins.poem', 'enable': True, 'version': '0.0.1', 'limit': 20}, + 'oss': {'path': 'app.plugins.oss', 'enable': True, 'version': '0.0.1', 'access_key_id': 'not complete', 'access_key_secret': 'not complete', 'endpoint': 'http://oss-cn-shenzhen.aliyuncs.com', 'bucket_name': 'not complete', 'upload_folder': 'app', 'allowed_extensions': ['jpg', 'gif', 'png', 'bmp']} } diff --git a/app/plugins/poem/app/__init__.py b/app/plugins/poem/app/__init__.py index c722e18..59aabd6 100644 --- a/app/plugins/poem/app/__init__.py +++ b/app/plugins/poem/app/__init__.py @@ -4,3 +4,79 @@ """ from .controller import api from .model import Poem + + +def initial_data(): + from app.app import create_app + from lin.db import db + + app = create_app() + with app.app_context(): + with db.auto_commit(): + # 添加诗歌 + img_url = 'http://yanlan.oss-cn-shenzhen.aliyuncs.com/gqmgbmu06yO2zHD.png' + poem1 = Poem() + poem1.title = '生查子·元夕' + poem1.author = '欧阳修' + poem1.dynasty = '宋代' + poem1._content = """去年元夜时/花市灯如昼/月上柳梢头/人约黄昏后|今年元夜时/月与灯依旧/不见去年人/泪湿春衫袖""" + poem1.image = img_url + db.session.add(poem1) + + poem2 = Poem() + poem2.title = '临江仙·送钱穆父' + poem2.author = '苏轼' + poem2.dynasty = '宋代' + poem2._content = """一别都门三改火/天涯踏尽红尘/依然一笑作春温/无波真古井/有节是秋筠|惆怅孤帆连夜发/送行淡月微云/尊前不用翠眉颦/人生如逆旅/我亦是行人""" + poem2.image = img_url + db.session.add(poem2) + + poem3 = Poem() + poem3.title = '春望词四首' + poem3.author = '薛涛' + poem3.dynasty = '唐代' + poem3._content = """花开不同赏/花落不同悲/欲问相思处/花开花落时/揽草结同心/将以遗知音/春愁正断绝/春鸟复哀吟/风花日将老/佳期犹渺渺/不结同心人/空结同心草/那堪花满枝/翻作两相思/玉箸垂朝镜/春风知不知""" + poem3.image = img_url + db.session.add(poem3) + + poem4 = Poem() + poem4.title = '长相思' + poem4.author = '纳兰性德' + poem4.dynasty = '清代' + poem4._content = """山一程/水一程/身向榆关那畔行/夜深千帐灯|风一更/雪一更/聒碎乡心梦不成/故园无此声""" + poem4.image = img_url + db.session.add(poem4) + + poem5 = Poem() + poem5.title = '离思五首·其四' + poem5.author = '元稹' + poem5.dynasty = '唐代' + poem5._content = """曾经沧海难为水/除却巫山不是云/取次花丛懒回顾/半缘修道半缘君""" + poem5.image = img_url + db.session.add(poem5) + + poem6 = Poem() + poem6.title = '浣溪沙' + poem6.author = '晏殊' + poem6.dynasty = '宋代' + poem6._content = """一曲新词酒一杯/去年天气旧亭台/夕阳西下几时回|无可奈何花落去/似曾相识燕归来/小园香径独徘徊""" + poem6.image = img_url + db.session.add(poem6) + + poem7 = Poem() + poem7.title = '浣溪沙' + poem7.author = '纳兰性德' + poem7.dynasty = '清代' + poem7._content = """残雪凝辉冷画屏/落梅横笛已三更/更无人处月胧明|我是人间惆怅客/知君何事泪纵横/断肠声里忆平生""" + poem7.image = img_url + db.session.add(poem7) + + poem8 = Poem() + poem8.title = '蝶恋花·春景' + poem8.author = '苏轼' + poem8.dynasty = '宋代' + poem8._content = """花褪残红青杏小/燕子飞时/绿水人家绕/枝上柳绵吹又少/天涯何处无芳草|墙里秋千墙外道/墙外行人/墙里佳人笑/笑渐不闻声渐悄/多情却被无情恼""" + poem8.image = img_url + db.session.add(poem8) + + return app diff --git a/app/plugins/poem/app/model.py b/app/plugins/poem/app/model.py index ae17cd8..a455f0f 100644 --- a/app/plugins/poem/app/model.py +++ b/app/plugins/poem/app/model.py @@ -6,6 +6,7 @@ class Poem(Base): + __tablename__ = 'lin_poem' id = Column(Integer, primary_key=True, autoincrement=True) title = Column(String(50), nullable=False, comment='标题') author = Column(String(50), default='未名', comment='作者') diff --git a/app/plugins/poem/info.py b/app/plugins/poem/info.py index a97d15e..bd25ac8 100644 --- a/app/plugins/poem/info.py +++ b/app/plugins/poem/info.py @@ -1,2 +1,3 @@ -__version__ = '0.1.0' +__name__ = 'poem' +__version__ = '0.0.1' __author__ = 'Lin team' diff --git a/app/plugins/poem/requirements.txt b/app/plugins/poem/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/plugin_init.py b/vendor/plugin_init.py new file mode 100644 index 0000000..42ad55f --- /dev/null +++ b/vendor/plugin_init.py @@ -0,0 +1,211 @@ +""" + :copyright: © 2019 by the Lin team. + :license: MIT, see LICENSE for more details. +""" +import re +from importlib import import_module + +import os + +from app.app import create_app + +""" +插件初始化流程: +1、输入要初始化的插件名称。(多个用空格隔开,*表示初始化所有) +2、python依赖的自动检测和安装 +2、将插件的配置写入到项目setting.py中 +3、将model中的模型插入到数据库中 +4、如果有需要,将初始数据插入到数据表中 +""" + + +class PluginInit: + # 插件位置默认前缀 + plugin_path = 'app.plugins' + + def __init__(self, name): + self.app = create_app(register_all=False) + self.name = name.strip() + # 插件相关的路径信息,包含plugin_path(插件路径),plugin_config_path(插件的配置文件路径),plugin_info_path(插件的基本信息路径) + self.path_info = dict() + # 根据name生成path,写入到path_info属性中 + self.generate_path() + # 安装依赖 + self.auto_install_rely() + # 将插件的配置自动写入setting + self.auto_write_setting() + # 创建数据表,并且向表中插入模型中的一些初始数据 + self.create_data() + + def generate_path(self): + if self.name == '*': + names = self.__get_all_plugins() + else: + names = self.name.split(" ") + for name in names: + exit('插件名称不能为空,请重试') if self.name == '' else print('正在初始化插件' + name + '...') + self.path_info[name] = { + 'plugin_path': self.plugin_path + '.' + name, + 'plugin_config_path': self.plugin_path + '.' + name + '.config', + 'plugin_info_path': self.plugin_path + '.' + name + '.info' + } + + def auto_install_rely(self): + from subprocess import CalledProcessError + for name in self.path_info: + filename = 'requirements.txt' + file_path = self.app.config.root_path + '/plugins/' + name + '/' + filename + success_msg = '安装' + name + '插件的依赖成功' + fail_msg = name + '插件的依赖安装失败,请[手动安装依赖]: http://doc.7yue.pro/' + if os.path.exists(file_path): + if (os.path.getsize(file_path)) == 0: + continue + print('正在安装' + name + '插件的依赖...') + + try: + # 使用try except来判断使用pip管理包还是pipenv管理包,首选pipenv + ret = self.__execute_cmd(cmd='pipenv install -r ' + file_path) + + if ret: + print(success_msg) + else: + exit(fail_msg) + + except CalledProcessError: + try: + ret = self.__execute_cmd(cmd='pip install -r ' + file_path) + + if ret: + print(success_msg) + else: + exit(fail_msg) + + except Exception as e: + exit((str(e)) + '\n' + fail_msg) + + except Exception as e: + exit((str(e)) + '\n' + fail_msg) + + def auto_write_setting(self): + print('正在自动写入配置文件...') + setting_text = dict() + for name, val in self.path_info.items(): + try: + info_mod = import_module(self.path_info[name]['plugin_info_path']) + except ModuleNotFoundError as e: + raise exit(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确') + + res = self._generate_setting(name, info_mod) + setting_text[name] = res + + # 正则匹配setting.py中的配置文件,将配置文件替换成新的setting_doc + self.__update_setting(new_setting=setting_text) + + def create_data(self): + print('正在创建基础数据...') + for name, val in self.path_info.items(): + # 调用插件__init__模块中的initial_data方法,创建初始的数据 + try: + plugin_module = import_module(self.path_info[name]['plugin_path'] + '.app.__init__') + dir_info = dir(plugin_module) + except ModuleNotFoundError as e: + raise exit(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确') + if 'initial_data' in dir_info: + # TODO 解决多次初始化数据重复添加的问题 + plugin_module.initial_data() + print('插件初始化成功') + + def _generate_setting(self, name, info_mod): + info_mod_dic = info_mod.__dict__ + ret = { + 'path': self.path_info[name]['plugin_path'], + 'enable': True, + 'version': info_mod_dic.pop("__version__", '0.0.1') # info_mod_dic.__version__ + } + # 向setting_doc中写入插件的配置项 + cfg_mod = import_module(self.path_info[name]['plugin_config_path']) + dic = cfg_mod.__dict__ + for key in dic.keys(): + if not key.startswith('__'): + ret[key] = dic[key] + return ret + + def __update_setting(self, new_setting): + # 得到现存的插件配置 + old_setting = self.app.config.get('PLUGIN_PATH') + final_setting = self.__cal_setting(new_setting, old_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: + content = f.read() + pattern = 'PLUGIN_PATH = \{([\s\S]*)\}+.*?' + result = re.sub(pattern, sub_str, content) + + with open(setting_path, 'w+') as f: + f.write(result) + + def __get_all_plugins(self): + # 返回所有插件的目录名称 + ret = [] + path = self.app.config.root_path + '/plugins' + for file in os.listdir(path=path): + file_path = os.path.join(path, file) + if os.path.isdir(file_path): + ret.append(file) + return ret + + @classmethod + def __execute_cmd(cls, cmd): + import subprocess + code = subprocess.check_call(cmd, shell=True, stdout=subprocess.PIPE) + if code == 0: + return True + elif code == 1: + return False + + @classmethod + def __format_setting(cls, setting): + # 格式化setting字符串 + setting_str = str(setting) + ret = setting_str.replace('},', '},\n ').replace('{', '{\n ', 1) + replace_reg = re.compile(r'\}$') + ret = replace_reg.sub('\n}', ret) + return ret + + @staticmethod + def __cal_setting(new_setting, old_setting): + # 将新旧的setting合并,返回一个字典 + # 1、对比old和new,并且将这两个配置合并 + # 2、如果新的存在,旧的不存在,就追加新的; + # 3、如果旧的存在,新的不存在,就保留旧的; + # 4、如果新旧都存在,那么在版本号相同的情况下,保留旧的配置项,否则新的配置覆盖旧的配置。 + + final_setting = dict() + all_keys = new_setting.keys() | old_setting.keys() # 得到新旧配置的并集 + + for key in all_keys: + if key not in old_setting.keys(): + # 不存在,追加新的 + final_setting[key] = new_setting[key] + else: + # 存在,对比版本号,看看是否需要更新 TODO 优化条件判断 + if key not in new_setting: + # 新的不存在 + final_setting[key] = old_setting[key] + else: + # 新的存在 + if new_setting[key]['version'] == old_setting[key]['version']: + # 版本号相同,使用旧的配置 + final_setting[key] = old_setting[key] + else: + # 版本号不同,更新配置为新的 + final_setting[key] = new_setting[key] + + return final_setting + + +if __name__ == '__main__': + plugin_name = input('请输入要初始化的插件名,如果多个插件请使用空格分隔插件名,输入*表示初始化所有插件:\n') + PluginInit(plugin_name) From 06b8d8cee3206217bb39277249ce33cc56db665a Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Sun, 10 Mar 2019 12:26:21 +0800 Subject: [PATCH 10/21] =?UTF-8?q?refactor:=20=E5=B0=86plugin=5Finit?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E7=A7=BB=E8=87=B3=E6=A0=B9=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/plugins/oss/info.py | 1 + app/plugins/poem/app/__init__.py | 3 +++ vendor/plugin_init.py => plugin_init.py | 0 3 files changed, 4 insertions(+) rename vendor/plugin_init.py => plugin_init.py (100%) diff --git a/app/plugins/oss/info.py b/app/plugins/oss/info.py index b517262..674f871 100644 --- a/app/plugins/oss/info.py +++ b/app/plugins/oss/info.py @@ -1,2 +1,3 @@ +__name__ = 'oss' __version__ = '0.0.1' __author__ = 'Lin team' diff --git a/app/plugins/poem/app/__init__.py b/app/plugins/poem/app/__init__.py index 59aabd6..3152d79 100644 --- a/app/plugins/poem/app/__init__.py +++ b/app/plugins/poem/app/__init__.py @@ -12,6 +12,9 @@ def initial_data(): app = create_app() with app.app_context(): + data = Poem.query.limit(1).all() + if data: + return with db.auto_commit(): # 添加诗歌 img_url = 'http://yanlan.oss-cn-shenzhen.aliyuncs.com/gqmgbmu06yO2zHD.png' diff --git a/vendor/plugin_init.py b/plugin_init.py similarity index 100% rename from vendor/plugin_init.py rename to plugin_init.py From 69124c992dd531d299f06e6f8cefc397472349e3 Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Wed, 13 Mar 2019 21:32:34 +0800 Subject: [PATCH 11/21] =?UTF-8?q?feat:=20=E6=8F=92=E4=BB=B6=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E6=94=AF=E6=8C=81=E8=87=AA=E5=8A=A8=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E4=BE=9D=E8=B5=96=E5=86=B2=E7=AA=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pipfile | 5 +- app/plugins/oss/requirements.txt | 1 + plugin_init.py | 152 ++++++++++++++++++++++++++++++- requirements.txt | 6 +- 4 files changed, 153 insertions(+), 11 deletions(-) diff --git a/Pipfile b/Pipfile index 0ca6c99..044e8b9 100644 --- a/Pipfile +++ b/Pipfile @@ -11,11 +11,10 @@ cymysql = "==0.9.1" flask-cors = "==2.1.0" requests = "==2.18.4" pipfile = "*" -lin-cms = "==0.1.1a3" -"oss2" = "*" +lin-cms = "==0.1.1a4" [dev-packages] pytest = "*" [requires] -python_version = "3.6" +python_version = "3.6" \ No newline at end of file diff --git a/app/plugins/oss/requirements.txt b/app/plugins/oss/requirements.txt index e69de29..b2156cd 100644 --- a/app/plugins/oss/requirements.txt +++ b/app/plugins/oss/requirements.txt @@ -0,0 +1 @@ +oss2=="*" \ No newline at end of file diff --git a/plugin_init.py b/plugin_init.py index 42ad55f..c7fdb78 100644 --- a/plugin_init.py +++ b/plugin_init.py @@ -2,9 +2,10 @@ :copyright: © 2019 by the Lin team. :license: MIT, see LICENSE for more details. """ +import json import re from importlib import import_module - +import subprocess import os from app.app import create_app @@ -51,6 +52,10 @@ def generate_path(self): } def auto_install_rely(self): + try: + DependenciesResolve(app, self.path_info) + except Exception as e: + raise Exception('安装插件依赖时发生错误!\nError:' + str(e)) from subprocess import CalledProcessError for name in self.path_info: filename = 'requirements.txt' @@ -93,7 +98,7 @@ def auto_write_setting(self): try: info_mod = import_module(self.path_info[name]['plugin_info_path']) except ModuleNotFoundError as e: - raise exit(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确') + raise Exception(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确') res = self._generate_setting(name, info_mod) setting_text[name] = res @@ -109,9 +114,8 @@ def create_data(self): plugin_module = import_module(self.path_info[name]['plugin_path'] + '.app.__init__') dir_info = dir(plugin_module) except ModuleNotFoundError as e: - raise exit(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确') + raise Exception(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确或插件中是否有未安装的依赖包') if 'initial_data' in dir_info: - # TODO 解决多次初始化数据重复添加的问题 plugin_module.initial_data() print('插件初始化成功') @@ -158,7 +162,6 @@ def __get_all_plugins(self): @classmethod def __execute_cmd(cls, cmd): - import subprocess code = subprocess.check_call(cmd, shell=True, stdout=subprocess.PIPE) if code == 0: return True @@ -206,6 +209,145 @@ def __cal_setting(new_setting, old_setting): return final_setting +class DependenciesResolve: + + def __init__(self, app_obj, path): + self.app = app_obj + self.path_info = path + # 主项目的依赖关系列表 + self.root_graph = [] + # 所有插件的依赖关系列表 + self.plugin_graph = [] + # 生成主项目和插件依赖关系列表 + self.generate_graph() + self.check_dependencies() + + def generate_graph(self): + try: + p = subprocess.Popen(["pipenv", "graph", "--json"], + stdout=subprocess.PIPE) + + r = p.communicate()[0].decode('utf-8') + self.root_graph = json.loads(r) + + except subprocess.CalledProcessError as e: + exit("pipenv指令未安装,请先使用pip安装命令\nError" + str(e)) + + except Exception as e: + exit("pipenv 错误,请检测你的pipenv配置!\nError:" + str(e)) + + self.__generate_plugin_graph() + + def check_dependencies(self): + for package in self.root_graph: + # 验证顶级包是否符合规范 + self.__check_top_dependencies(package['package']) + + # 验证子包是否符合规范 + self.__check_sub_dependencies(package['dependencies']) + + def __generate_plugin_graph(self): + for name, val in self.path_info.items(): + # 首先去校验插件依赖于住项目的依赖是否存在冲突 + plugin_path = self.path_info[name]['plugin_path'].replace( + '.', '/').replace('app', '') + requirements_path = self.app.config.root_path + \ + plugin_path + '/requirements.txt' + with open(requirements_path, 'r') as f: + while True: + + # 正则匹配requirements的每一行的信息 + line = f.readline() + if not line: + break + + pattern = '(.*?)(==|<=|>=|!=|>|<)(.*)' + search_obj = re.search(pattern, line) + if search_obj: + + package_name = search_obj.group(1) + condition = search_obj.group(2) + version = search_obj.group(3) + key = search_obj.group(1).lower() + + plugin_package = dict({'package': {}}) + plugin_package['package']['key'] = key + plugin_package['package']['package_name'] = package_name + plugin_package['package']['version'] = version + plugin_package['package']['condition'] = condition + plugin_package['package']['plugin_name'] = name + self.plugin_graph.append(plugin_package) + + def __check_top_dependencies(self, top_package): + for plugin_package in self.plugin_graph: + # top_version = top_package['installed_version'] + # plugin_version = plugin_package['version'] + if top_package['key'] == plugin_package['package']['key']: + err_msg = '由于项目主目录已经存在在包' \ + '' + top_package['package_name'] + ',但 ' + plugin_package['package']['plugin_name']\ + + ' 插件尝试重复安装不同版本,请尝试手动去掉该插件的requirements.txt中的包' + raise Exception(err_msg) + + def __check_sub_dependencies(self, dep_package): + # 判断插件中要安装的依赖,是否符合主项目已安装的依赖规定的范围 + for dependence in dep_package: + required_version = dependence['required_version'] + dep_name = dependence['key'] + + if required_version is not None: + version_infos = required_version.split(",") + for version_info in version_infos: + pattern = '(>=|<=|!=|==|<|>)(.*)' + search_obj = re.search(pattern, version_info) + condition = search_obj.group(1) + version = int(search_obj.group(2).replace('.', '')) + + for plugin_package in self.plugin_graph: + name = plugin_package['package']['key'] + if dep_name == name: + plugin_package_version = int(plugin_package['package']['version'].replace('.', '')) + err_msg = plugin_package['package']['plugin_name'] + '插件的依赖 ' + name +\ + ' 与主项目依赖版本发生冲突! 请自行手动解决' + if condition == '>=': + if plugin_package_version >= version: + pass + else: + raise Exception(err_msg) + + elif condition == '==': + if plugin_package_version == version: + pass + else: + raise Exception(err_msg) + + elif condition == '!=': + if plugin_package_version != version: + pass + else: + raise Exception(err_msg) + + elif condition == '<=': + if plugin_package_version <= version: + pass + else: + raise Exception(err_msg) + + elif condition == '<': + if plugin_package_version < version: + pass + else: + raise Exception(err_msg) + + elif condition == '>': + if plugin_package_version > version: + pass + else: + raise Exception(err_msg) + else: + pass + + if __name__ == '__main__': + app = create_app(register_all=False) plugin_name = input('请输入要初始化的插件名,如果多个插件请使用空格分隔插件名,输入*表示初始化所有插件:\n') PluginInit(plugin_name) diff --git a/requirements.txt b/requirements.txt index 24d87eb..0cea76a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -certifi==2018.11.29 +certifi==2019.3.9 chardet==3.0.4 Click==7.0 cymysql==0.9.1 @@ -10,8 +10,8 @@ Flask-WTF==0.14.2 idna==2.6 itsdangerous==1.1.0 Jinja2==2.10 -Lin-CMS==0.1.1a3 -MarkupSafe==1.1.0 +Lin-CMS==0.1.1a4 +MarkupSafe==1.1.1 pipfile==0.0.2 PyJWT==1.7.1 requests==2.18.4 From 084bc8a10377a39b77f5b1475c3bdf1d0aa50e5d Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Wed, 13 Mar 2019 21:44:04 +0800 Subject: [PATCH 12/21] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9oss=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E4=BE=9D=E8=B5=96=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pipfile | 13 +++++++------ app/plugins/oss/requirements.txt | 2 +- plugin_init.py | 10 ++++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Pipfile b/Pipfile index 044e8b9..eb61d8c 100644 --- a/Pipfile +++ b/Pipfile @@ -4,17 +4,18 @@ verify_ssl = true name = "pypi" [packages] -flask = "==1.0.2" -flask-sqlalchemy = "==2.3.2" -flask-wtf = "==0.14.2" cymysql = "==0.9.1" -flask-cors = "==2.1.0" requests = "==2.18.4" pipfile = "*" -lin-cms = "==0.1.1a4" +oss2 = "==2.6.1" +Flask = "==1.0.2" +Flask-SQLAlchemy = "==2.3.2" +Flask-WTF = "==0.14.2" +Flask-Cors = "==2.1.0" +Lin-CMS = "==0.1.1a4" [dev-packages] pytest = "*" [requires] -python_version = "3.6" \ No newline at end of file +python_version = "3.6" diff --git a/app/plugins/oss/requirements.txt b/app/plugins/oss/requirements.txt index b2156cd..279cc13 100644 --- a/app/plugins/oss/requirements.txt +++ b/app/plugins/oss/requirements.txt @@ -1 +1 @@ -oss2=="*" \ No newline at end of file +oss2==2.6.1 \ No newline at end of file diff --git a/plugin_init.py b/plugin_init.py index c7fdb78..c135439 100644 --- a/plugin_init.py +++ b/plugin_init.py @@ -44,7 +44,8 @@ def generate_path(self): else: names = self.name.split(" ") for name in names: - exit('插件名称不能为空,请重试') if self.name == '' else print('正在初始化插件' + name + '...') + if self.name == '': + exit('插件名称不能为空,请重试') self.path_info[name] = { 'plugin_path': self.plugin_path + '.' + name, 'plugin_config_path': self.plugin_path + '.' + name + '.config', @@ -58,6 +59,7 @@ def auto_install_rely(self): raise Exception('安装插件依赖时发生错误!\nError:' + str(e)) from subprocess import CalledProcessError for name in self.path_info: + print('正在初始化插件' + name + '...') filename = 'requirements.txt' file_path = self.app.config.root_path + '/plugins/' + name + '/' + filename success_msg = '安装' + name + '插件的依赖成功' @@ -280,9 +282,9 @@ def __generate_plugin_graph(self): def __check_top_dependencies(self, top_package): for plugin_package in self.plugin_graph: - # top_version = top_package['installed_version'] - # plugin_version = plugin_package['version'] - if top_package['key'] == plugin_package['package']['key']: + top_version = top_package['installed_version'] + plugin_version = plugin_package['package']['version'] + if top_package['key'] == plugin_package['package']['key'] and top_version != plugin_version: err_msg = '由于项目主目录已经存在在包' \ '' + top_package['package_name'] + ',但 ' + plugin_package['package']['plugin_name']\ + ' 插件尝试重复安装不同版本,请尝试手动去掉该插件的requirements.txt中的包' From 3cfde315ebbb136018d07a5b75ec662011e52e79 Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Wed, 27 Mar 2019 21:52:00 +0800 Subject: [PATCH 13/21] =?UTF-8?q?refactor:=20=E5=B0=86=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E6=89=80=E6=9C=89super=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=94=B9=E4=B8=BAadmin=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pipfile | 2 +- add_super.py | 4 ++-- app/api/cms/admin.py | 8 ++++---- app/api/cms/notify.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Pipfile b/Pipfile index eb61d8c..c9654eb 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.1a4" +Lin-CMS = "==0.1.1a6" [dev-packages] pytest = "*" diff --git a/add_super.py b/add_super.py index 64fa9de..c064f13 100644 --- a/add_super.py +++ b/add_super.py @@ -15,6 +15,6 @@ user.nickname = 'super' user.password = '123456' user.email = '1234995678@qq.com' - # super为 2 的时候为超级管理员,普通用户为 1 - user.super = 2 + # admin 2 的时候为超级管理员,普通用户为 1 + user.admin = 2 db.session.add(user) diff --git a/app/api/cms/admin.py b/app/api/cms/admin.py index bd15a63..4a6d140 100644 --- a/app/api/cms/admin.py +++ b/app/api/cms/admin.py @@ -11,7 +11,7 @@ from lin import db from lin.core import get_ep_infos, route_meta, manager, find_user, find_auth_module from lin.db import get_total_nums -from lin.enums import UserSuper, UserActive +from lin.enums import UserAdmin, UserActive from lin.exception import Success, NotFound, ParameterException, Forbidden from lin.jwt import admin_required from lin.log import Logger @@ -38,10 +38,10 @@ def get_admin_users(): start, count = paginate() group_id = request.args.get('group_id') condition = { - 'super': UserSuper.COMMON.value, + 'admin': UserAdmin.COMMON.value, 'group_id': group_id } if group_id else { - 'super': UserSuper.COMMON.value + 'admin': UserAdmin.COMMON.value } users = db.session.query( @@ -58,7 +58,7 @@ def get_admin_users(): user.hide('update_time', 'delete_time') user_and_group.append(user) # 有分组的时候就加入分组条件 - # total_nums = get_total_nums(manager.user_model, is_soft=True, super=UserSuper.COMMON.value) + # total_nums = get_total_nums(manager.user_model, is_soft=True, admin=UserAdmin.COMMON.value) total_nums = get_total_nums(manager.user_model, is_soft=True, **condition) return jsonify({ "collection": user_and_group, diff --git a/app/api/cms/notify.py b/app/api/cms/notify.py index 73e6a46..d036a69 100644 --- a/app/api/cms/notify.py +++ b/app/api/cms/notify.py @@ -35,7 +35,7 @@ def stream(): @group_required def get_events(): current_user = get_current_user() - if current_user.is_super: + if current_user.is_admin: return jsonify({'events': list(MESSAGE_EVENTS)}) event = Event.query.filter_by(group_id=current_user.group_id, soft=False).first() if event is None: From b5dcf42ee1b1eca8a319fb2ad1ff63221d2fcaec Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Wed, 27 Mar 2019 23:06:13 +0800 Subject: [PATCH 14/21] =?UTF-8?q?feat:=20refresh=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0refresh=5Ftoken=E7=9A=84=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/cms/user.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/api/cms/user.py b/app/api/cms/user.py index 985606a..a55785b 100644 --- a/app/api/cms/user.py +++ b/app/api/cms/user.py @@ -7,7 +7,8 @@ from operator import and_ from flask import jsonify -from flask_jwt_extended import create_access_token, jwt_refresh_token_required, get_jwt_identity, get_current_user +from flask_jwt_extended import create_access_token, jwt_refresh_token_required, get_jwt_identity, get_current_user, \ + create_refresh_token from lin.core import manager, route_meta, Log from lin.db import db from lin.exception import NotFound, Success, Failed, RepeatException, ParameterException @@ -104,8 +105,10 @@ def refresh(): identity = get_jwt_identity() if identity: access_token = create_access_token(identity=identity) + refresh_token = create_refresh_token(identity=identity) return jsonify({ - 'access_token': access_token + 'access_token': access_token, + 'refresh_token': refresh_token }) return NotFound(msg='refresh_token未被识别') From ef860338decf11f7e2a2798485ad5e68bbf5293b Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Fri, 5 Apr 2019 17:08:13 +0800 Subject: [PATCH 15/21] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9requirements.txt?= =?UTF-8?q?=20=E4=B8=ADlin-cms=20=E7=9A=84=E7=89=88=E6=9C=AC=E5=8F=B7?= =?UTF-8?q?=E4=B8=BA=E6=9C=80=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0cea76a..47893b1 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.1a4 +Lin-CMS==0.1.1a6 MarkupSafe==1.1.1 pipfile==0.0.2 PyJWT==1.7.1 @@ -21,3 +21,4 @@ toml==0.10.0 urllib3==1.22 Werkzeug==0.14.1 WTForms==2.2.1 +oss2==2.6.1 From 7a60654c7bdca8d4c3d0b942c7140d47c68fe040 Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Mon, 8 Apr 2019 00:25:17 +0800 Subject: [PATCH 16/21] =?UTF-8?q?fix:=20=E5=A2=9E=E5=8A=A0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9=E7=9B=AE=EF=BC=8C=E5=B1=8F=E8=94=BDsql=20alc?= =?UTF-8?q?hemy=E7=9A=84FSADeprecationWarning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/config/setting.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/config/setting.py b/app/config/setting.py index acc8227..22cf1fe 100644 --- a/app/config/setting.py +++ b/app/config/setting.py @@ -12,9 +12,12 @@ # 令牌配置 JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=1) +# 屏蔽 sql alchemy 的 FSADeprecationWarning +SQLALCHEMY_TRACK_MODIFICATIONS = False + # 插件模块暂时没有开启,以下配置可忽略 # plugin config写在字典里面 PLUGIN_PATH = { - 'poem': {'path': 'app.plugins.poem', 'enable': True, 'version': '0.0.1', 'limit': 20}, - 'oss': {'path': 'app.plugins.oss', 'enable': True, 'version': '0.0.1', 'access_key_id': 'not complete', 'access_key_secret': 'not complete', 'endpoint': 'http://oss-cn-shenzhen.aliyuncs.com', 'bucket_name': 'not complete', 'upload_folder': 'app', 'allowed_extensions': ['jpg', 'gif', 'png', 'bmp']} + 'oss': {'path': 'app.plugins.oss', 'enable': True, 'version': '0.0.1', 'access_key_id': 'not complete', 'access_key_secret': 'not complete', 'endpoint': 'http://oss-cn-shenzhen.aliyuncs.com', 'bucket_name': 'not complete', 'upload_folder': 'app', 'allowed_extensions': ['jpg', 'gif', 'png', 'bmp']}, + 'poem': {'path': 'app.plugins.poem', 'enable': True, 'version': '0.0.1', 'limit': 20} } From 26c630e1f940f02175a9ffd10c0c7c49043e02b4 Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Mon, 8 Apr 2019 09:18:37 +0800 Subject: [PATCH 17/21] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20add=5Fsuper?= =?UTF-8?q?=20=E8=84=9A=E6=9C=AC=E7=9A=84=E6=88=90=E5=8A=9F=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- add_super.py | 33 ++++++++++++++++++++++----------- app/config/setting.py | 4 ++-- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/add_super.py b/add_super.py index c064f13..0ee912d 100644 --- a/add_super.py +++ b/add_super.py @@ -2,19 +2,30 @@ :copyright: © 2019 by the Lin team. :license: MIT, see LICENSE for more details. """ +from sqlalchemy.exc import IntegrityError from app.app import create_app from lin.db import db from lin.core import User -app = create_app() -with app.app_context(): - with db.auto_commit(): - # 创建一个超级管理员 - user = User() - user.nickname = 'super' - user.password = '123456' - user.email = '1234995678@qq.com' - # admin 2 的时候为超级管理员,普通用户为 1 - user.admin = 2 - db.session.add(user) + +def main(): + app = create_app() + with app.app_context(): + with db.auto_commit(): + # 创建一个超级管理员 + user = User() + user.nickname = 'super' + user.password = '123456' + user.email = '1234995678@qq.com' + # admin 2 的时候为超级管理员,普通用户为 1 + user.admin = 2 + db.session.add(user) + + +if __name__ == '__main__': + try: + main() + print("新增超级管理员成功") + except Exception as e: + raise e diff --git a/app/config/setting.py b/app/config/setting.py index 22cf1fe..950ab57 100644 --- a/app/config/setting.py +++ b/app/config/setting.py @@ -18,6 +18,6 @@ # 插件模块暂时没有开启,以下配置可忽略 # plugin config写在字典里面 PLUGIN_PATH = { - 'oss': {'path': 'app.plugins.oss', 'enable': True, 'version': '0.0.1', 'access_key_id': 'not complete', 'access_key_secret': 'not complete', 'endpoint': 'http://oss-cn-shenzhen.aliyuncs.com', 'bucket_name': 'not complete', 'upload_folder': 'app', 'allowed_extensions': ['jpg', 'gif', 'png', 'bmp']}, - 'poem': {'path': 'app.plugins.poem', 'enable': True, 'version': '0.0.1', 'limit': 20} + 'poem': {'path': 'app.plugins.poem', 'enable': True, 'version': '0.0.1', 'limit': 20}, + 'oss': {'path': 'app.plugins.oss', 'enable': True, 'version': '0.0.1', 'access_key_id': 'not complete', 'access_key_secret': 'not complete', 'endpoint': 'http://oss-cn-shenzhen.aliyuncs.com', 'bucket_name': 'not complete', 'upload_folder': 'app', 'allowed_extensions': ['jpg', 'gif', 'png', 'bmp']} } From df25a77b853b062b5ba41b9701ce4975d176dcfa Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Thu, 18 Apr 2019 23:00:31 +0800 Subject: [PATCH 18/21] =?UTF-8?q?fix:=20=E4=B8=BA=E8=8E=B7=E5=8F=96refresh?= =?UTF-8?q?=20token=E6=8E=A5=E5=8F=A3=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=BC=82=E5=B8=B8=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E5=BE=AA=E7=8E=AF=E8=AF=B7=E6=B1=82=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/cms/user.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/api/cms/user.py b/app/api/cms/user.py index a55785b..27f2ac5 100644 --- a/app/api/cms/user.py +++ b/app/api/cms/user.py @@ -7,11 +7,11 @@ from operator import and_ from flask import jsonify -from flask_jwt_extended import create_access_token, jwt_refresh_token_required, get_jwt_identity, get_current_user, \ - create_refresh_token +from flask_jwt_extended import create_access_token, get_jwt_identity, get_current_user, \ + create_refresh_token, verify_jwt_refresh_token_in_request from lin.core import manager, route_meta, Log from lin.db import db -from lin.exception import NotFound, Success, Failed, RepeatException, ParameterException +from lin.exception import NotFound, Success, Failed, RepeatException, ParameterException, RefreshException from lin.jwt import login_required, admin_required, get_tokens from lin.log import Logger from lin.redprint import Redprint @@ -100,9 +100,15 @@ def get_information(): @user_api.route('/refresh', methods=['GET']) @route_meta(auth='刷新令牌', module='用户', mount=False) -@jwt_refresh_token_required def refresh(): + + try: + verify_jwt_refresh_token_in_request() + except Exception: + return RefreshException() + identity = get_jwt_identity() + if identity: access_token = create_access_token(identity=identity) refresh_token = create_refresh_token(identity=identity) @@ -110,6 +116,7 @@ def refresh(): 'access_token': access_token, 'refresh_token': refresh_token }) + return NotFound(msg='refresh_token未被识别') From 5b4dc1797b4cf2305d3837602b0f94a01185f972 Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Thu, 18 Apr 2019 23:13:35 +0800 Subject: [PATCH 19/21] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Eerror=5Fcode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code.md b/code.md index ca7b1d3..7b8bcd9 100644 --- a/code.md +++ b/code.md @@ -24,6 +24,8 @@ 10070 禁止操作 +10100 refresh token 获取失败 + 20000 werkzeug 中的HTTP EXCEPTION,error_code统一为1007,前端应读取msg ## 项目使用的状态码 From 6c93815e07de509bf5e282c182a86e82d15dbe59 Mon Sep 17 00:00:00 2001 From: fujiale33 Date: Fri, 19 Apr 2019 11:20:30 +0800 Subject: [PATCH 20/21] =?UTF-8?q?feat:=20=E5=9C=A8=E4=B8=BB=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E4=B8=AD=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=9A=84error=5Fc?= =?UTF-8?q?ode=20RefreshException?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/cms/user.py | 3 ++- app/libs/error_code.py | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/api/cms/user.py b/app/api/cms/user.py index 27f2ac5..4011b45 100644 --- a/app/api/cms/user.py +++ b/app/api/cms/user.py @@ -11,11 +11,12 @@ create_refresh_token, verify_jwt_refresh_token_in_request from lin.core import manager, route_meta, Log from lin.db import db -from lin.exception import NotFound, Success, Failed, RepeatException, ParameterException, RefreshException +from lin.exception import NotFound, Success, Failed, RepeatException, ParameterException from lin.jwt import login_required, admin_required, get_tokens from lin.log import Logger from lin.redprint import Redprint +from app.libs.error_code import RefreshException from app.validators.forms import LoginForm, RegisterForm, ChangePasswordForm, UpdateInfoForm user_api = Redprint('user') diff --git a/app/libs/error_code.py b/app/libs/error_code.py index 510abda..ef02a3f 100644 --- a/app/libs/error_code.py +++ b/app/libs/error_code.py @@ -10,3 +10,9 @@ class BookNotFound(APIException): code = 404 # http状态码 msg = '没有找到相关图书' # 异常信息 error_code = 80010 # 约定的异常码 + + +class RefreshException(APIException): + code = 401 + msg = "refresh token 获取失败" + error_code = 10100 From 218519fd5a2c67c013a8751a9af893dd536aadc9 Mon Sep 17 00:00:00 2001 From: Colorful Date: Fri, 26 Apr 2019 16:08:58 +0800 Subject: [PATCH 21/21] Fix/close notify (#42) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 在注册校验层,增加group_id字段的存在性判断 * fix: 暂时移除notify接口 --- app/api/cms/__init__.py | 2 - app/api/cms/notify.py | 82 ----------------------------------------- app/validators/forms.py | 7 +++- 3 files changed, 6 insertions(+), 85 deletions(-) delete mode 100644 app/api/cms/notify.py diff --git a/app/api/cms/__init__.py b/app/api/cms/__init__.py index 4dde61f..559d701 100644 --- a/app/api/cms/__init__.py +++ b/app/api/cms/__init__.py @@ -13,11 +13,9 @@ def create_cms(): from .admin import admin_api from .user import user_api from .log import log_api - from .notify import notify_api from .test import test_api admin_api.register(cms) user_api.register(cms) log_api.register(cms) - notify_api.register(cms) test_api.register(cms) return cms diff --git a/app/api/cms/notify.py b/app/api/cms/notify.py deleted file mode 100644 index d036a69..0000000 --- a/app/api/cms/notify.py +++ /dev/null @@ -1,82 +0,0 @@ -""" - :copyright: © 2019 by the Lin team. - :license: MIT, see LICENSE for more details. -""" -import time - -from flask import Response, jsonify -from flask_jwt_extended import get_current_user - -from lin import db -from lin.core import route_meta, Event -from lin.exception import NotFound, Success, Forbidden -from lin.jwt import group_required, admin_required -from lin.redprint import Redprint -from lin.notify import MESSAGE_EVENTS -from lin.sse import sser -from app.validators.forms import EventsForm - -notify_api = Redprint('notify') - - -@notify_api.route('/', methods=['GET'], strict_slashes=False) -@route_meta(auth='消息推送', module='推送', mount=False) -@group_required -def stream(): - return Response( - event_stream(), - mimetype="text/event-stream", - headers=[('Cache-Control', 'no-cache'), ('Connection', 'keep-alive')] - ) - - -@notify_api.route('/events', methods=['GET']) -@route_meta(auth='获得events', module='推送', mount=False) -@group_required -def get_events(): - current_user = get_current_user() - if current_user.is_admin: - return jsonify({'events': list(MESSAGE_EVENTS)}) - event = Event.query.filter_by(group_id=current_user.group_id, soft=False).first() - if event is None: - raise NotFound(msg='当前用户没有推送项') - events = event.message_events.split(',') - return jsonify({'events': events}) - - -@notify_api.route('/events', methods=['POST']) -@route_meta(auth='创建events', module='推送', mount=False) -@admin_required -def create_events(): - form = EventsForm().validate_for_api() - event = Event.query.filter_by(group_id=form.group_id.data, soft=False).first() - if event: - raise Forbidden(msg='当前权限组已存在推送项') - with db.auto_commit(): - ev = Event() - ev.group_id = form.group_id.data - ev.message_events = ','.join(form.events.data) - return Success(msg='创建成功') - - -@notify_api.route('/events', methods=['PUT']) -@route_meta(auth='更新events', module='推送', mount=False) -@admin_required -def put_events(): - form = EventsForm().validate_for_api() - event = Event.query.filter_by(group_id=form.group_id.data, soft=False).first() - if event is None: - raise NotFound(msg='当前权限组不存在推送项') - with db.auto_commit(): - event.message_events = ','.join(form.events.data) - return Success(msg='更新成功') - - -def event_stream(): - while True: - if sser.exit_message(): - yield sser.pop() - else: - yield sser.heartbeat() - # 每个3秒发送一次心跳 - time.sleep(3) diff --git a/app/validators/forms.py b/app/validators/forms.py index 7734c6a..2ef9359 100644 --- a/app/validators/forms.py +++ b/app/validators/forms.py @@ -2,7 +2,7 @@ :copyright: © 2019 by the Lin team. :license: MIT, see LICENSE for more details. """ - +from lin import manager from wtforms import DateTimeField, PasswordField, FieldList, IntegerField, StringField from wtforms.validators import DataRequired, Regexp, EqualTo, length, Optional, NumberRange import time @@ -27,6 +27,11 @@ class RegisterForm(Form): Optional() ]) + def validate_group_id(self, value): + exists = manager.group_model.get(id=value.data) + if not exists: + raise ValueError('分组不存在') + # 登陆校验 class LoginForm(Form):