1. 登录装饰器
Flask
需要自定义 登录装饰器
def admin_log_req(f):
"""
登录装饰器
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if "admin" not in session:
return redirect(url_for("admin.login", next=request.url))
return f(*args, **kwargs)
return decorated_function
functools.wraps 的作用 让wrapper继承所有func(视图函数)传递的属性
from functools import wraps
from flask import g, make_response, session
from flask.json import jsonify
from flask_wtf import csrf
from werkzeug.routing import BaseConverter
def login_required(view):
"""
登录状态检查装饰器,并保存用户状态
:param view: 视图函数
:return 未登录: 返回session错误; 已登录: 返回被装饰的视图函数
"""
@wraps(view)
def view_wrapper(*args, **kwargs):
from ihome.models import User
from ihome.utils.response_code import RET
user_id = session.get('user_id')
if user_id is None:
return jsonify(errno=RET.SESSIONERR, errmsg='用户未登录')
try:
user = User.query.get(user_id)
except Exception as e:
return jsonify(errno=RET.DBERR, errmsg='读取用户信息异常')
else:
g.user_id = user.id
g.name = user.name
g.mobile = user.mobile
return view(*args, **kwargs)
return view_wrapper
Django
Django 中有自带的登录装饰器 login_required与LoginRequiredMixin
老版的Django使用装饰器login_required来限制用户登录
新版的Dajngo通过继承LoginRequiredMixin类来限制用户登录,必须是第一个继承,在继承列表最左侧位置
utils.mixin.py
from django.contrib.auth.decorators import login_required
# 只有在使用了内置的登录信息认证系统的时候,才能使用内置的装饰器函数login_required()
class LoginRequiredMixin(object):
@classmethod
def as_view(cls, **initkwargs):
# 调用父类的as_view,以下两种方法都可调用super的方法
# view = super().as_view(**initkwargs)
view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
return login_required(view)
from utils.mixin import LoginRequiredMixin # 自定义认证检查
class UserInfoView(LoginRequiredMixin, View):
def get(self, request):
2. 权限控制装饰器
def admin_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
admin = Admin.query.join(
Role
).filter(
Role.id == Admin.role_id,
Admin.id == session["admin_id"]
).first()
# print(admin.role)
if admin.role.name != '超级管理员':
auths = admin.role.auths # 登录用户的权限操作id
auths = list(map(lambda v: int(v), auths.split(",")))
auth_list = Auth.query.all() # 所有权限
urls = [v.url for v in auth_list for val in auths if val == v.id] # 筛选出所有路由
rule = request.url_rule
# print(rule)
if str(rule) not in urls:
abort(404)
return f(*args, **kwargs)
else:
return f(*args, **kwargs)
return decorated_function
花费时间装饰器
作用:程序花费时间
def cost(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__} cost {end-start}s ')
return res
return wrapper
修改文件名
def change_filename(filename):
"""
修改上传文件名称
"""
# print("原始文件name", filename)
fileinfo = os.path.splitext(filename)
# print("分割后:", fileinfo)
filename = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + str(uuid.uuid4().hex) + list(fileinfo)[-1]
# print("最终文件name", filename)
return filename
自定义re转换器
from werkzeug.routing import BaseConverter
class ReConverter(BaseConverter):
""" 自定义re转换器 """
def __init__(self, url_map, regex):
super().__init__(url_map)
# 保存正则表达式
self.regex = regex
@api.route('/sms_code/<re(r"1[345789]\d{9}"):mobile_num>')
def get_sms_code(mobile_num):
alilog
init.py
# -*- coding: utf8 -*-
from enum import Enum
class LogLevelEnum(Enum):
"""
log等级
"""
# 记录子系统初始化,核心请求正常执行,用于查询更高级别日志的上下文
INFO = 3
# 记录系统可能出现的问题,例如网络波动,硬盘空间不足等,不需要立即处理,但要及时维护
WARN = 4
# 影响个别用户访问的问题,需要立即处理
ERROR = 5
from .alilog_client import alilog
from .logger import ali_log_exception
__all__ = [LogLevelEnum, alilog, ali_log_exception]
/log/logger.py
# -*- coding: utf8 -*-
import traceback
from functools import wraps
from .alilog_client import alilog
def ali_log_exception(func):
"""
a decorator that wraps the passed in function and logs exceptions using ali log
:param func:
:return:
"""
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except:
err = f"""There was an exception in {func.__name__},
Traceback: {traceback.format_exc()}"""
alilog.log_warn(err)
return wrapper
/log/alilog_client.py
# -*- coding: utf8 -*-
import os
import time
import traceback
from functools import wraps
from aliyun.log.logitem import LogItem
from aliyun.log.logclient import LogClient
from aliyun.log.putlogsrequest import PutLogsRequest
from config import config
from . import LogLevelEnum
from ..utils import PassObject
def program_exception(func):
"""
a decorator that wraps the passed in function and send warning email to developers
in case that ali log doesn't work
:param func:
:return:
"""
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except:
# send email
subject = "阿里云日志宕机"
content = f"""{func.__name__} 出错
Traceback: {traceback.format_exc()}"""
recipients = ["hongfu.pan@stardust.ai", "xin.zhang@stardust.ai"]
# send_email(subject, content, recipients)
return wrapper
class AliLog:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, endpoint: str, access_key_id: str, access_key: str, project: str, log_store: str):
self._end_point = endpoint
self._access_key_id = access_key_id
self._access_key = access_key
self._project = project
self._log_store = log_store
# 构建client
self.client = LogClient(self._end_point, self._access_key_id, self._access_key)
@program_exception
def _send_log(self, level: str, error_msg: str = '', params: str = ''):
"""
发送一条log给阿里云log服务
:param level:
:param error_msg:
:param params:
:return:
"""
# 阿里日志的空间参数,每个 Logstore 根据 Topic 可以划分成一个或多个子空间,当进行查询时,指定 topic 可以限定查询范围,达到更快速度。
topic = ''
# 阿里日志的来源参数,空则默认为访问者IP
source = ''
log_item_list = []
env = os.getenv('APP_ENV', 'local').lower()
content = ', '.join([env, error_msg, params])
contents = [(str(level), content[:-1])]
log_item = LogItem()
log_item.set_time(int(time.time()))
log_item.set_contents(contents)
log_item_list.append(log_item)
req = PutLogsRequest(self._project, self._log_store, topic, source, log_item_list)
# 阿里不提供正确记录日志的返回状态,不报错则是成功,控制台在s级别能看到日志信息
self.client.put_logs(req)
def log_info(self, error_msg: str = '', params: str = ''):
"""
发送一条info级别的log
:param error_msg:
:param params:
:return:
"""
self._send_log(LogLevelEnum.INFO.name, error_msg, params)
def log_warn(self, error_msg: str = '', params: str = ''):
"""
发送一条warn级别的log
:param error_msg:
:param params:
:return:
"""
self._send_log(LogLevelEnum.WARN.name, error_msg, params)
def log_error(self, error_msg: str = '', params: str = ''):
"""
发送一条error级别的log
:param error_msg:
:param params:
:return:
"""
self._send_log(LogLevelEnum.ERROR.name, error_msg, params)
if config['ALILOG_ENABLE']:
alilog = AliLog(
config['ALILOG_ENDPOINT'],
config['ALILOG_ACCESS_KEY_ID'],
config['ALILOG_ACCESS_KEY'],
config['ALILOG_PROJECT'],
config['ALILOG_LOG_STORE']
)
else:
alilog = PassObject()
Celery
celery 任务为 celery_tasks.py,在package的 init.py 下
from package.celery_tasks import app as celery_app
__all__ = ('celery_app', )
直接可以运行celery 而不用指定位置
celery -A dataset worker -l INFO &
from celery import Celery
redis_config='redis://127.0.0.1:6379/0'
celery_app = Celery(__name__, backend=redis_config, broker=redis_config)
@celery_app.task(ignore_result=True)
#task_ignore_result 设置全局忽略任务结果
def async_add(x, y):
return x + y
工厂模式下Flask与Celery结合
from xx.core import create_app
from xx.factory import celery
from xx.statistics.data import save_statistics
app = create_app()
# 关键点,往celery推入flask信息,使得celery能使用flask上下文
app.app_context().push()
if __name__ == '__main__':
app.run()
from flask import Flask
from .celery import celery_app
def create_app():
app = Flask(__name__)
celery_app.conf.update({"broker_url": 'redis://127.0.0.1:6379/0',
"result_backend": 'redis://127.0.0.1:6379/0', })
from .api import bp
app.register_blueprint(bp)
return app
class CeleryClient(Celery):
def __init__(self, app=None):
super().__init__()
if app is not None:
self.init_app(app)
def init_app(self, app):
super().__init__(app.import_name,
broker=app.config['CELERY_BROKER'],
backend=app.config['CELERY_BACKEND'])
# celery
celery = CeleryClient()