diff --git a/applications/common/curd.py b/applications/common/curd.py index 9a4f16c0d4a4ed5a0c80579c914f1b4a0cb0c080..097a1dd93b3fe12211b5d59882d763c0e5a5008d 100644 --- a/applications/common/curd.py +++ b/applications/common/curd.py @@ -44,6 +44,8 @@ def auto_model_jsonify(data, model: db.Model): def model_to_dicts(schema: ma.Schema, data): """ + 将模型查询的数据对象转化为字典 + :param schema: schema类 :param model: sqlalchemy查询结果 :return: 返回单个查询结果 diff --git a/applications/common/utils/__init__.py b/applications/common/utils/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..589ddd8f17dd487a705e05a15aaf06a310df6c21 100644 --- a/applications/common/utils/__init__.py +++ b/applications/common/utils/__init__.py @@ -0,0 +1,15 @@ +from . import user +from . import role +from . import power +from . import department +from . import console +from . import gen_captcha +from . import http +from . import mail +from . import rights +from . import upload +from . import validate +from flask import Flask + +# 获取app应用实例,会被初始化插件时重新赋值 +app = None # type: Flask diff --git a/applications/dev/console.py b/applications/common/utils/console.py similarity index 100% rename from applications/dev/console.py rename to applications/common/utils/console.py diff --git a/applications/dev/department.py b/applications/common/utils/department.py similarity index 100% rename from applications/dev/department.py rename to applications/common/utils/department.py diff --git a/applications/common/utils/http.py b/applications/common/utils/http.py index 75f4cd84c440b9602912c4a0dba4f9864bed0db2..680df5fc336e3a1895816ef2d7f24a79a9cca291 100644 --- a/applications/common/utils/http.py +++ b/applications/common/utils/http.py @@ -2,7 +2,7 @@ from flask import jsonify def success_api(msg: str = "成功"): - """ 成功响应 默认值”成功“ """ + """ 成功响应 默认值“成功” """ return jsonify(success=True, msg=msg) diff --git a/applications/common/utils/mail.py b/applications/common/utils/mail.py index eddc666e914b784f8c173771bfa20bad9cbde094..ecb29bb079cd9e0515b1f80d96176af21025dc2c 100644 --- a/applications/common/utils/mail.py +++ b/applications/common/utils/mail.py @@ -1,13 +1,96 @@ +""" +集成了对 Pear Admin Flask 二次开发的的邮件操作,并给了相对应的示例。 +""" +from flask import current_app from flask_mail import Message +from applications.common.curd import model_to_dicts +from applications.common.helper import ModelFilter +from applications.extensions import db, flask_mail from applications.extensions.init_mail import mail +from applications.models import Mail +from applications.schemas import MailOutSchema -# send_mail(subject='title', recipients=['123@qq.com'], content='body') -def send_mail(subject, recipients, content): +def get_all(receiver=None, subject=None, content=None): + """ + 获取邮件 + + 返回的列表中的字典构造如下:: + + { + "content": "", # html内容 + "create_at": "2022-12-25T10:51:17", # 时间 + "id": 17, # 邮件ID + "realname": "超级管理", # 创建者 + "receiver": "", # 接收者 + "subject": "" # 主题 + } + + :param receiver: 发送者 + :param subject: 邮件标题 + :param content: 邮件内容 + :return: 列表 + """ + # 查询参数构造 + mf = ModelFilter() + if receiver: + mf.contains(field_name="receiver", value=receiver) + if subject: + mf.contains(field_name="subject", value=subject) + if content: + mf.exact(field_name="content", value=content) + # orm查询 + # 使用分页获取data需要.items + mail = Mail.query.filter(mf.get_filter(Mail)).layui_paginate() + return model_to_dicts(schema=MailOutSchema, data=mail.items) + + +def add(receiver, subject, content, user_id): + """ + 发送一封邮件,若发送成功立刻提交数据库。 + + :param receiver: 接收者 多个用英文分号隔开 + :param subject: 邮件主题 + :param content: 邮件 html + :param user_id: 发送用户ID(谁发送的?) 可以用 from flask_login import current_user ; current_user.id 来表示当前登录用户 + :return: 成功与否 + """ try: - message = Message(subject=subject, recipients=recipients, body=content) - mail.send(message) - except Exception as e: - print('邮箱发送出错了') - raise + msg = Message(subject=subject, recipients=receiver.split(";"), html=content) + flask_mail.send(msg) + except BaseException as e: + current_app.log_exception(e) + return False + + mail = Mail(receiver=receiver, subject=subject, content=content, user_id=user_id) + + db.session.add(mail) + db.session.commit() + return True + + +def delete(id): + """ + 删除邮件记录,立刻写入数据库。 + + :param id: 邮件ID + :return: 成功与否 + """ + res = Mail.query.filter_by(id=id).delete() + if not res: + return False + db.session.commit() + return True + +def send_mail(subject, recipients, content): + """原发送邮件函数,不会记录邮件发送记录 + + 失败报错,请注意使用 try 拦截。 + + :param subject: 主题 + :param recipients: 接收者 多个用英文分号隔开 + :param content: 邮件 html + """ + message = Message(subject=subject, recipients=recipients, html=content) + mail.send(message) diff --git a/applications/dev/power.py b/applications/common/utils/power.py similarity index 96% rename from applications/dev/power.py rename to applications/common/utils/power.py index 99d275723067a25f3da080a39a43877f36bde507..d6b5b0b70657661f6e9a84995bf071794d36bc34 100644 --- a/applications/dev/power.py +++ b/applications/common/utils/power.py @@ -60,7 +60,7 @@ def add(parentId, powerName, powerType, icon, sort: int, enable: bool, powerCode 参考代码:: - dev.power.add("0", "测试", "1", "layui-icon-time", 0, True, "testfor", "https://baidu.com", "_iframe") + power.add("0", "测试", "1", "layui-icon-time", 0, True, "testfor", "https://baidu.com", "_iframe") :param parentId: 父ID,0为顶级菜单ID :param powerName: 菜单名称 diff --git a/applications/common/utils/rights.py b/applications/common/utils/rights.py index 655593f0ce412514e96294522cd87e27954a7f29..5ae9e3135c4cf925c310139b2384094a374d1627 100644 --- a/applications/common/utils/rights.py +++ b/applications/common/utils/rights.py @@ -5,6 +5,13 @@ from applications.common.admin_log import admin_log def authorize(power: str, log: bool = False): + """用户权限判断,用于判断目前会话用户是否拥有访问权限 + + :param power: 权限标识 + :type power: str + :param log: 是否记录日志, defaults to False + :type log: bool, optional + """ def decorator(func): @login_required @wraps(func) diff --git a/applications/dev/role.py b/applications/common/utils/role.py similarity index 88% rename from applications/dev/role.py rename to applications/common/utils/role.py index a0cc28682c9eb818d05f7c1297b99e00b4d656be..8a12e3136216b50943e8c5f94c7e2b065fbdea2b 100644 --- a/applications/dev/role.py +++ b/applications/common/utils/role.py @@ -20,11 +20,11 @@ def filter_by(**kwargs): 参考调用如下:: - roleinfo = dev.role.filter_by(code='admin').first() # 第一个符合要求的角色信息 + roleinfo = role.filter_by(code='admin').first() # 第一个符合要求的角色信息 print(role.id, role.name) # 输出角色名称与角色标识 # 找出所有角色id - for role in dev.role.filter_by().all(): + for role in role.filter_by().all(): print(role.id, role.name) :param kwargs: 查询参数 @@ -78,7 +78,7 @@ def get_power(role_filter, detail=False, p=0): } - :param role_filter: dev.role.filter_by() 返回结果。 + :param role_filter: role.filter_by() 返回结果。 :param detail: 返回详细数据 :param p: 如果有多个结果被找到,p可以确定使用第几个结果。内部使用 role_filter.all()[p] :return: 用户拥有的权限列表。 @@ -105,7 +105,7 @@ def set_power(role_filter, powerIds, p=0): """ 保存角色权限。此函数会直接写入数据库。 - :param role_filter: dev.role.filter_by() 返回结果。 + :param role_filter: role.filter_by() 返回结果。 :param powerIds: 必须是一个包含权限ID的列表。如 [1, 2, 3] :param p: 如果有多个结果被找到,p可以确定使用第几个结果。内部使用 role_filter.all()[p] :return: None @@ -128,10 +128,10 @@ def update(role_filter, data): 参考调用如下:: - role_filter = dev.role.filter_by(id=0) # 获取指定角色ID的角色,注意不要使用会引起歧义的查询条件,否则会匹配到多个角色。 - dev.role.update(role_filter, {enable: 0}) # 禁用 + role_filter = role.filter_by(id=0) # 获取指定角色ID的角色,注意不要使用会引起歧义的查询条件,否则会匹配到多个角色。 + role.update(role_filter, {enable: 0}) # 禁用 - :param role_filter: dev.role.filter_by() 返回结果。 + :param role_filter: role.filter_by() 返回结果。 :param data: 要更新的数据,必须是字典。 :return: None """ @@ -143,7 +143,7 @@ def delete(role_filter): """ 删除角色。此功能将直接写入数据库。 - :param role_filter: dev.role.filter_by() 返回结果。 + :param role_filter: role.filter_by() 返回结果。 :return: 是否成功。 """ role = role_filter.first() diff --git a/applications/dev/user.py b/applications/common/utils/user.py similarity index 72% rename from applications/dev/user.py rename to applications/common/utils/user.py index 00688a3ccee296138f247cbc705798914f211dad..ef938d5ffc47b7efd937814a8f446898208ce24c 100644 --- a/applications/dev/user.py +++ b/applications/common/utils/user.py @@ -3,10 +3,10 @@ 调用示例:: - from applications import dev - dev.user.login_required # 用户是否登录 - dev.user.current_user # 当前登录用户 - dev.user.authorize("XXX", log=True) # 用户是否有此权限 + from applications.common.utils import * + user.login_required # 用户是否登录 + user.current_user # 当前登录用户 + user.authorize("XXX", log=True) # 用户是否有此权限 """ @@ -31,7 +31,7 @@ def filter_by(**kwargs): 参考调用如下:: - userinfo = dev.user.filter_by(username='zsq').first() # 查询符合要求的第一个用户 + userinfo = user.filter_by(username='admin').first() # 查询符合要求的第一个用户 print(userinfo.realname) # 获取用户真实名字 @@ -44,7 +44,7 @@ def filter_by(**kwargs): def update(user_filter, data): """ 更新用户数据,修改将直接保存到数据库中。 - 注意:更新用户角色(role)请使用 dev.user.update_role() 函数。 + 注意:更新用户角色(role)请使用 user.update_role() 函数。 可更新的字段如下:: @@ -54,12 +54,12 @@ def update(user_filter, data): 参考调用如下:: - user_filter = dev.user.filter_by(id=0) # 获取指定用户ID的用户,注意不要使用会引起歧义的查询条件,否则会匹配到多个用户。 - dev.user.update(user_filter, {username: 'zsq1314'}) # 更新其用户名 + user_filter = user.filter_by(id=0) # 获取指定用户ID的用户,注意不要使用会引起歧义的查询条件,否则会匹配到多个用户。 + user.update(user_filter, {username: 'admin'}) # 更新其用户名 - :param user_filter: dev.user.filter_by() 的结果。 + :param user_filter: user.filter_by() 的结果。 :param data: 要更新的数据,必须是字典。 :return: None """ @@ -73,14 +73,14 @@ def update_role(user_filter, roleIds): 参考调用如下:: - user_filter = dev.user.filter_by(username='zsq') # 获取符合要求的第一个用户 + user_filter = user.filter_by(username='test') # 获取符合要求的第一个用户 roleIds = [] - roleIds.append(dev.role.filter_by(code='admin').first().id) # 管理员角色ID - roleIds.append(dev.role.filter_by(code='common').first().id) # 普通用户角色ID - dev.user.update_role(user_filter, roleIds) + roleIds.append(role.filter_by(code='admin').first().id) # 管理员角色ID + roleIds.append(role.filter_by(code='common').first().id) # 普通用户角色ID + user.update_role(user_filter, roleIds) - :param user_filter: dev.user.filter_by() 的结果。 + :param user_filter: user.filter_by() 的结果。 :param roleIds: 要更新的角色ID,作为列表传入。 :return: None """ @@ -92,7 +92,7 @@ def get_role(user_filter): """ 获取用户的所有角色ID,将会返回一个整数列表。 - :param user_filter: dev.user.filter_by() 的结果。 + :param user_filter: user.filter_by() 的结果。 :return: 列表 (roleIds) """ checked_roles = [] @@ -107,10 +107,10 @@ def set_password(user_filter, password): 参考调用如下:: - user_filter = dev.user.filter_by(username='zsq') # 获取符合要求的用户 - dev.user.set_password(user_filter, 'zsq1314') # 设置密码 + user_filter = user.filter_by(username='admin') # 获取符合要求的用户 + user.set_password(user_filter, 'admin') # 设置密码 - :param user_filter: dev.user.filter_by() 的结果。 + :param user_filter: user.filter_by() 的结果。 :param password: 新密码。 :return: None """ @@ -147,7 +147,7 @@ def delete(user_filter): """ 删除一个用户。此函数立刻写入数据库。 - :param user_filter: dev.user.filter_by() 的结果。 + :param user_filter: user.filter_by() 的结果。 :return: 是否成功 """ user = user_filter.first() diff --git a/applications/common/utils/validate.py b/applications/common/utils/validate.py index 05b8011d85362e8a88913a3c62a4b1dcfc6d930e..73e46e4ca46c101a3451b46f41a1127f5edb3ecc 100644 --- a/applications/common/utils/validate.py +++ b/applications/common/utils/validate.py @@ -2,7 +2,15 @@ from flask import abort, make_response, jsonify, escape -def str_escape(s): +def str_escape(s: str) -> str: + """xss过滤,内部采用flask自带的过滤函数。 + 与原过滤函数不同的是此过滤函数将在 s 为 None 时返回 None。 + + :param s: 要过滤的字符串 + :type s: str + :return: s 为 None 时返回 None,否则过滤字符串后返回。 + :rtype: str + """ if not s: return None return str(escape(s)) diff --git a/applications/dev/__init__.py b/applications/dev/__init__.py deleted file mode 100644 index 48191296e75d3ad80d8356a22a9a6b89495b8ed3..0000000000000000000000000000000000000000 --- a/applications/dev/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from applications.dev import user -from applications.dev import role -from applications.dev import power -from applications.dev import department -from applications.dev import console -from flask import Flask - -# 获取app应用实例,会被初始化插件时重新赋值 -app = None # type: Flask diff --git a/applications/dev/mail.py b/applications/dev/mail.py deleted file mode 100644 index 8c734746021ca9d65d6dd03d2a21ebc77952dd05..0000000000000000000000000000000000000000 --- a/applications/dev/mail.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -集成了对 Pear Admin Flask 二次开发的的邮件操作,并给了相对应的示例。 -""" -from flask import current_app -from flask_mail import Message - -from applications.common.curd import model_to_dicts -from applications.common.helper import ModelFilter -from applications.extensions import db, flask_mail -from applications.models import Mail -from applications.schemas import MailOutSchema - - -def get_all(receiver=None, subject=None, content=None): - """ - 获取邮件 - - 返回的列表中的字典构造如下:: - - { - "content": "", # html内容 - "create_at": "2022-12-25T10:51:17", # 时间 - "id": 17, # 邮件ID - "realname": "超级管理", # 创建者 - "receiver": "", # 接收者 - "subject": "" # 主题 - } - - :param receiver: 发送者 - :param subject: 邮件标题 - :param content: 邮件内容 - :return: 列表 - """ - # 查询参数构造 - mf = ModelFilter() - if receiver: - mf.contains(field_name="receiver", value=receiver) - if subject: - mf.contains(field_name="subject", value=subject) - if content: - mf.exact(field_name="content", value=content) - # orm查询 - # 使用分页获取data需要.items - mail = Mail.query.filter(mf.get_filter(Mail)).layui_paginate() - return model_to_dicts(schema=MailOutSchema, data=mail.items) - - -def add(receiver, subject, content, user_id): - """ - 发送一封邮件,若发送成功立刻提交数据库。 - - :param receiver: 接收者 多个用英文逗号隔开 - :param subject: 邮件主题 - :param content: 邮件 html - :param user_id: 发送用户ID(谁发送的?) 可以用 from flask_login import current_user ; current_user.id 来表示当前登录用户 - :return: 成功与否 - """ - try: - msg = Message(subject=subject, recipients=receiver.split(";"), html=content) - flask_mail.send(msg) - except BaseException as e: - current_app.log_exception(e) - return False - - mail = Mail(receiver=receiver, subject=subject, content=content, user_id=user_id) - - db.session.add(mail) - db.session.commit() - return True - - -def delete(id): - """ - 删除邮件记录,立刻写入数据库。 - - :param id: 邮件ID - :return: 成功与否 - """ - res = Mail.query.filter_by(id=id).delete() - if not res: - return False - db.session.commit() - return True diff --git a/applications/view/plugin/__init__.py b/applications/view/plugin/__init__.py index 2aa2d479e6d706043ca046232f18f84ff65b3c01..cb10335425ecb812e455d10b1b861486435e58fc 100644 --- a/applications/view/plugin/__init__.py +++ b/applications/view/plugin/__init__.py @@ -8,7 +8,7 @@ import json import traceback import importlib -import applications.dev +import applications.common.utils from applications.common.utils.rights import authorize plugin_bp = Blueprint('plugin', __name__, url_prefix='/plugin') @@ -16,7 +16,7 @@ PLUGIN_ENABLE_FOLDERS = [] def register_plugin_views(app: Flask): global PLUGIN_ENABLE_FOLDERS - applications.dev.app = app # 对app重新赋值 便于插件简单调用 + applications.common.utils.app = app # 对app重新赋值 便于插件简单调用 app.register_blueprint(plugin_bp) # 载入插件过程 # plugin_folder 配置的是插件的文件夹名 @@ -71,10 +71,6 @@ def data(): "plugin_name": info["plugin_name"], "plugin_version": info["plugin_version"], "plugin_description": info["plugin_description"], - "developer_name": info["developer_name"], - "developer_website": info["developer_website"], - "developer_email": info["developer_email"], - "developer_phone": info["developer_phone"], "plugin_folder_name": filename, "enable": "1" if filename in PLUGIN_ENABLE_FOLDERS else "0" } diff --git a/plugins/helloworld/__init__.json b/plugins/helloworld/__init__.json index 110f9f70e2583f1faaf8e32a8e2c0ba8bd4631d7..1373e2dfe5eab92b38421ec199c4d63b954493fb 100644 --- a/plugins/helloworld/__init__.json +++ b/plugins/helloworld/__init__.json @@ -1,9 +1,5 @@ { "plugin_name": "Hello World", "plugin_version": "1.0.0.1", - "plugin_description": "一个测试的插件。", - "developer_name": "Yishang", - "developer_website": "https://lovepikachu.top", - "developer_email": "422880152@qq.com", - "developer_phone": "-" + "plugin_description": "一个测试的插件。" } \ No newline at end of file diff --git a/plugins/helloworld/__init__.py b/plugins/helloworld/__init__.py index b972aa9b9b85c538a7e6c22c1fc37a34a14b2fb3..efbe8aef6ebd3855f422ec6d0b3935131e3f3173 100644 --- a/plugins/helloworld/__init__.py +++ b/plugins/helloworld/__init__.py @@ -14,7 +14,6 @@ def event_enable(): """当此插件被启用时会调用此处""" print(f"启用插件,dir_path: {dir_path} ; folder_name: {folder_name}") - def event_disable(): """当此插件被禁用时会调用此处""" print(f"禁用插件,dir_path: {dir_path} ; folder_name: {folder_name}") diff --git a/plugins/helloworld/main.py b/plugins/helloworld/main.py index 0f8f56ed9103264ecdf67e449ead70fe2243fbe1..f5592104c1a49e9ce17dce9301eee56377253034 100644 --- a/plugins/helloworld/main.py +++ b/plugins/helloworld/main.py @@ -1,5 +1,3 @@ -from applications.dev import * - from flask import render_template, Blueprint # 创建蓝图 diff --git a/plugins/realip/__init__.json b/plugins/realip/__init__.json index 2f7e77fe9cafa02709ca0eb6fdc912109e9a072b..659ed4fff98ae1bd3d550da7f24a110f1c05d2e2 100644 --- a/plugins/realip/__init__.json +++ b/plugins/realip/__init__.json @@ -1,9 +1,5 @@ { "plugin_name": "真实IP插件", "plugin_version": "1.0.0.1", - "plugin_description": "在上游更改访客IP地址,并采用自定义日志输出。", - "developer_name": "Yishang", - "developer_website": "https://lovepikachu.top", - "developer_email": "422880152@qq.com", - "developer_phone": "-" + "plugin_description": "在上游更改访客IP地址,并采用自定义日志输出。" } \ No newline at end of file diff --git a/plugins/realip/__init__.py b/plugins/realip/__init__.py index f1aee919ab10be73d201f64a4b3bde8491caa5f3..dee1d5a4e61652dfe808cad8241d175a37515dbe 100644 --- a/plugins/realip/__init__.py +++ b/plugins/realip/__init__.py @@ -4,7 +4,7 @@ import os import logging from flask import Flask, request -from applications.dev import console +from applications.common.utils import console # 获取插件所在的目录(结尾没有分割符号) dir_path = os.path.dirname(__file__).replace("\\", "/") diff --git a/plugins/replacePage/__init__.json b/plugins/replacePage/__init__.json index d5558828bde4bb71c69017f0f4faf74f8b3c04fa..143dd0cafad57cf43173081ebcb5989bc330a609 100644 --- a/plugins/replacePage/__init__.json +++ b/plugins/replacePage/__init__.json @@ -1,9 +1,5 @@ { "plugin_name": "页面替换插件", "plugin_version": "1.0.0.1", - "plugin_description": "在不更改原有框架视图函数的情况下更改渲染输出页面。", - "developer_name": "Yishang", - "developer_website": "https://lovepikachu.top", - "developer_email": "422880152@qq.com", - "developer_phone": "-" + "plugin_description": "在不更改原有框架视图函数的情况下更改渲染输出页面。" } \ No newline at end of file diff --git a/plugins/replacePage/__init__.py b/plugins/replacePage/__init__.py index e973e203f22c57a4f43ac2d4307833105d2f6cf1..3756f0a66c66b50c9d6dfc0276bace98854bfdba 100644 --- a/plugins/replacePage/__init__.py +++ b/plugins/replacePage/__init__.py @@ -2,9 +2,7 @@ 初始化插件 """ import os -import logging from flask import Flask, render_template_string -from applications.dev import console # 获取插件所在的目录(结尾没有分割符号) dir_path = os.path.dirname(__file__).replace("\\", "/") diff --git a/templates/admin/plugin/main.html b/templates/admin/plugin/main.html index 4ba74657a52175b9a7644fa6fc1f1de027a11b4f..b0e4f181ff2731fb97f0a58851f571e121676162 100644 --- a/templates/admin/plugin/main.html +++ b/templates/admin/plugin/main.html @@ -133,10 +133,6 @@ {field: 'plugin_name', minWidth: 100, title: '插件名称'}, {field: 'plugin_version', title: '插件版本'}, {field: 'plugin_description', title: '插件描述'}, - {field: 'developer_name', title: '开发者名称'}, - {field: 'developer_website', title: '开发者网站'}, - {field: 'developer_email', title: '开发者邮箱'}, - {field: 'developer_phone', title: '开发者电话'}, {field: 'plugin_folder_name', hide: true}, {field: 'enable', title: '状态', templet: '#plugin-enable', width: 100, align: 'center'}, {title: '操作', templet: '#plugin-bar', width: 120, align: 'center'}