refactor: P2 core+common 建设(应用工厂/扩展/工具层迁入 src/)
【结构】 - src/core/:database/response/jwt/password/security/redis_client/mail/scheduler - src/common/:base_model/base_db/forms(base_form+fields)/constants/utils - src/models_registry.py:模型聚合器(Flask-Migrate 全量表识别) 【绞杀者 shim】extensions/、utils/、application/constants|forms|models 打兼容转发 【兼容修复】应用工厂抽取至 application/factory.py(P2 前置,P1 已完成) 【基线修复】application/models(25) + middleware(7) 原被嵌套 .gitignore(*) 误排除出版本库,已补入(base_model/base_db 现为 shim,原版在 src/common/) 【登记既有缺陷】 1. flask db CLI 因 .env FLASK_VERSION 与 --version 冲突报错 → Makefile 已加 FLASK_SKIP_DOTENV=1 2. 本机 Redis 未设密码但 REDIS_AUTH=True,/captcha 报 AUTH 错误(环境问题) 【验收】22 模型表全量识别;src/ flake8 零告警;pytest 5 passed;应用启动; /login 表单校验、/user/page 鉴权拦截冒烟正常 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
92130ef070
commit
658ee6d596
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
core 基础设施层
|
||||
=============================================================
|
||||
|
||||
提供与应用业务无关的基础设施:
|
||||
- database.py SQLAlchemy 实例
|
||||
- response.py 统一响应 R
|
||||
- jwt.py / password.py / security.py 认证与安全
|
||||
- redis_client.py Redis 客户端封装
|
||||
- mail.py / scheduler.py 扩展实例
|
||||
- exceptions.py / middleware.py / logger.py / context.py 后续阶段建设
|
||||
|
||||
依赖约束:core 层不得 import modules 层。
|
||||
"""
|
||||
@@ -0,0 +1,87 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 一米阳光团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
"""
|
||||
SQLAlchemy 数据库扩展模块
|
||||
创建数据库实例,供整个应用使用
|
||||
"""
|
||||
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
# ============================================================
|
||||
# 创建 SQLAlchemy 数据库实例
|
||||
# ============================================================
|
||||
# 该实例需要在应用工厂中通过 db.init_app(app) 完成初始化
|
||||
# 使用延迟绑定模式,支持循环导入
|
||||
# ============================================================
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
def commit_independently(obj):
|
||||
"""
|
||||
使用独立session提交对象,不受请求事务影响
|
||||
|
||||
日志等审计数据无论业务成功或失败都应持久化,
|
||||
因此需要绕过 AOP 事务拦截器的 commit/rollback 机制。
|
||||
|
||||
实现方式:基于现有引擎创建独立 session,将对象添加并提交,
|
||||
完成后关闭 session,不影响请求主 session。
|
||||
|
||||
Args:
|
||||
obj: SQLAlchemy 模型实例(未添加到任何 session)
|
||||
|
||||
Raises:
|
||||
Exception: 数据库操作异常
|
||||
"""
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
# 复用 Flask-SQLAlchemy 已创建的引擎,避免额外连接池
|
||||
independent_session = sessionmaker(bind=db.engine)()
|
||||
|
||||
try:
|
||||
independent_session.add(obj)
|
||||
independent_session.commit()
|
||||
except Exception:
|
||||
independent_session.rollback()
|
||||
raise
|
||||
finally:
|
||||
independent_session.close()
|
||||
+634
@@ -0,0 +1,634 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 珊瑚云团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 珊瑚云团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
"""
|
||||
JWT令牌处理模块
|
||||
=============================================================
|
||||
|
||||
该模块提供基于JWT(JSON Web Token)的令牌生成和验证功能。
|
||||
主要用于用户登录认证,生成访问令牌,并在后续请求中验证令牌的有效性。
|
||||
支持令牌过期机制,确保系统的安全性。
|
||||
|
||||
主要功能:
|
||||
1. JWT令牌生成(create_token)- 用于用户登录成功后生成访问令牌
|
||||
2. JWT令牌解析验证(parse_payload)- 验证请求中的令牌有效性
|
||||
3. 统一的异常处理,返回标准格式的验证结果
|
||||
4. 可配置的令牌过期时间
|
||||
|
||||
所属包: utils
|
||||
所在文件: utils/jwts.py
|
||||
依赖模块: jwt - JWT处理第三方库
|
||||
datetime - 时间处理模块
|
||||
os - 环境变量读取模块
|
||||
secrets - 安全随机数生成模块
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
import jwt
|
||||
from flask import request
|
||||
from jwt import exceptions
|
||||
|
||||
from config import JWT_SALT, JWT_EXPIRE_MINUTES
|
||||
|
||||
# 配置日志记录器
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# =============================================================
|
||||
# JWT密钥配置
|
||||
# =============================================================
|
||||
# 从 config.auth 统一读取,密钥配置在 .env 文件中管理
|
||||
|
||||
def _validate_key_strength(key: str) -> None:
|
||||
"""
|
||||
启动时校验 JWT 密钥强度
|
||||
|
||||
校验规则:
|
||||
1. 密钥不能为空
|
||||
2. 密钥长度至少 32 字节(256 位)
|
||||
|
||||
校验失败直接抛出 RuntimeError,拒绝启动应用。
|
||||
"""
|
||||
if not key:
|
||||
raise RuntimeError(
|
||||
"JWT_SALT 未配置!请在 .env 中设置至少32字节的强随机密钥。\n"
|
||||
"生成方式:python -c \"import secrets; print(secrets.token_urlsafe(48))\""
|
||||
)
|
||||
|
||||
key_bytes = len(key.encode('utf-8'))
|
||||
if key_bytes < 32:
|
||||
raise RuntimeError(
|
||||
f"JWT_SALT 长度不足:{key_bytes} 字节,至少需要 32 字节(256位)。\n"
|
||||
"生成方式:python -c \"import secrets; print(secrets.token_urlsafe(48))\""
|
||||
)
|
||||
|
||||
logger.info(f"JWT密钥校验通过,长度:{key_bytes}字节")
|
||||
|
||||
|
||||
# 启动时校验密钥强度
|
||||
_validate_key_strength(JWT_SALT)
|
||||
|
||||
# JWT算法配置
|
||||
JWT_ALGORITHM = 'HS256'
|
||||
|
||||
# 默认令牌过期时间(分钟),从配置读取
|
||||
DEFAULT_TIMEOUT_MINUTES = JWT_EXPIRE_MINUTES
|
||||
|
||||
|
||||
# =============================================================
|
||||
# JWT核心功能函数
|
||||
# =============================================================
|
||||
|
||||
def create_token(payload, timeout=None):
|
||||
"""
|
||||
生成JWT令牌
|
||||
=============================================================
|
||||
|
||||
根据传入的载荷数据生成JWT访问令牌,自动添加过期时间。
|
||||
使用强密钥确保令牌安全性,符合RFC 7518安全规范。
|
||||
|
||||
参数:
|
||||
payload (dict): JWT载荷数据,通常包含用户ID、用户名等信息
|
||||
timeout (int): 令牌过期时间(分钟),默认使用DEFAULT_TIMEOUT_MINUTES
|
||||
|
||||
返回:
|
||||
str: 生成的JWT令牌字符串
|
||||
|
||||
使用示例:
|
||||
>>> payload = {'userId': 1001, 'username': 'admin'}
|
||||
>>> token = create_token(payload, timeout=30)
|
||||
>>> print(token)
|
||||
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
|
||||
|
||||
注意事项:
|
||||
1. 密钥长度必须至少32字节,否则会发出警告
|
||||
2. 生产环境建议通过环境变量设置JWT_SALT
|
||||
3. 令牌过期后需要重新登录获取新令牌
|
||||
"""
|
||||
try:
|
||||
# 设置过期时间
|
||||
# 说明:在载荷中添加exp字段,指定令牌的过期时间点
|
||||
# 计算方式:当前UTC时间 + 指定的分钟数
|
||||
if timeout is None:
|
||||
timeout = DEFAULT_TIMEOUT_MINUTES
|
||||
|
||||
# 创建payload副本,避免修改原始数据
|
||||
payload_copy = payload.copy()
|
||||
|
||||
# 添加过期时间(exp)和签发时间(iat)
|
||||
# exp: 过期时间,超过此时间令牌无效
|
||||
# iat: 签发时间,用于令牌生命周期计算
|
||||
current_time = datetime.datetime.now(tz=datetime.timezone.utc)
|
||||
payload_copy['exp'] = current_time + datetime.timedelta(minutes=timeout)
|
||||
payload_copy['iat'] = current_time
|
||||
|
||||
# 声明类型和加密算法
|
||||
# 说明:定义JWT头部信息,包含令牌类型和加密算法
|
||||
headers = {
|
||||
"typ": "JWT", # 令牌类型
|
||||
"alg": JWT_ALGORITHM # 加密算法
|
||||
}
|
||||
|
||||
# 生成JWT令牌
|
||||
# 函数:jwt.encode()
|
||||
# 参数说明:
|
||||
# payload: JWT载荷数据
|
||||
# key: 加密密钥(盐)
|
||||
# algorithm: 加密算法(HS256)
|
||||
# headers: JWT头部信息
|
||||
token = jwt.encode(
|
||||
payload=payload_copy,
|
||||
key=JWT_SALT,
|
||||
algorithm=JWT_ALGORITHM,
|
||||
headers=headers
|
||||
)
|
||||
|
||||
# 记录日志(可选,生产环境建议使用DEBUG级别)
|
||||
logger.debug(f"生成JWT令牌成功,过期时间:{timeout}分钟")
|
||||
|
||||
return token
|
||||
|
||||
except Exception as e:
|
||||
# 捕获并记录生成令牌时的异常
|
||||
logger.error(f"生成JWT令牌失败: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
def parse_payload(token):
|
||||
"""
|
||||
解析验证JWT令牌
|
||||
=============================================================
|
||||
|
||||
验证传入的JWT令牌,检查签名有效性和过期状态。
|
||||
统一处理各种异常情况,返回标准格式的结果对象。
|
||||
|
||||
参数:
|
||||
token (str): 需要验证的JWT令牌字符串
|
||||
|
||||
返回:
|
||||
dict: 验证结果,包含以下字段:
|
||||
code (int): 状态码,0-成功,-1-失败
|
||||
data (dict/None): 验证成功时返回载荷数据,失败时为None
|
||||
msg (str): 提示信息
|
||||
|
||||
使用示例:
|
||||
>>> result = parse_payload(token)
|
||||
>>> if result['code'] == 0:
|
||||
... user_id = result['data']['userId']
|
||||
... username = result['data']['username']
|
||||
... else:
|
||||
... error_msg = result['msg']
|
||||
... # 处理令牌失效,跳转登录页
|
||||
|
||||
异常处理说明:
|
||||
- ExpiredSignatureError: 令牌已过期,需要重新登录
|
||||
- DecodeError: 解码失败,令牌格式错误或签名无效
|
||||
- InvalidTokenError: 其他JWT相关异常
|
||||
"""
|
||||
# 初始化返回结果
|
||||
# 说明:定义统一返回格式,默认成功状态
|
||||
result = {
|
||||
"code": 0,
|
||||
"data": None,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
|
||||
# 参数校验
|
||||
if not token:
|
||||
result['code'] = -1
|
||||
result['msg'] = "token不能为空"
|
||||
return result
|
||||
|
||||
try:
|
||||
# 解密验证JWT令牌
|
||||
# 函数:jwt.decode()
|
||||
# 参数说明:
|
||||
# jwt: 令牌字符串
|
||||
# key: 解密密钥(必须与加密时使用的密钥一致)
|
||||
# algorithms: 允许的算法列表
|
||||
#
|
||||
# 说明:jwt.decode会自动完成以下验证:
|
||||
# 1. 签名验证 - 确保令牌未被篡改
|
||||
# 2. 过期时间验证 - 检查exp字段是否大于当前时间
|
||||
# 3. 算法验证 - 确保使用正确的加密算法
|
||||
verified_payload = jwt.decode(
|
||||
token,
|
||||
JWT_SALT,
|
||||
algorithms=[JWT_ALGORITHM]
|
||||
)
|
||||
|
||||
# 验证成功,将载荷数据存入返回结果
|
||||
# 注意:返回的数据中不包含敏感信息(如密码等)
|
||||
result['data'] = verified_payload
|
||||
|
||||
# 可选:记录验证成功的日志(生产环境建议使用DEBUG级别)
|
||||
logger.debug(f"JWT令牌验证成功,用户: {verified_payload.get('username', 'unknown')}")
|
||||
|
||||
except exceptions.ExpiredSignatureError as e:
|
||||
# 令牌已过期异常处理
|
||||
# 说明:令牌超过设置的exp时间,需要重新登录获取新令牌
|
||||
result['code'] = -1
|
||||
result['msg'] = "token已失效,请重新登录"
|
||||
logger.info(f"JWT令牌已过期: {str(e)}")
|
||||
|
||||
except exceptions.DecodeError as e:
|
||||
# 令牌解码失败异常处理
|
||||
# 说明:令牌格式错误或签名验证失败,可能被篡改
|
||||
result['code'] = -1
|
||||
result['msg'] = "token认证失败,无效的令牌格式"
|
||||
logger.warning(f"JWT令牌解码失败: {str(e)}")
|
||||
|
||||
except exceptions.InvalidTokenError as e:
|
||||
# 无效令牌异常处理
|
||||
# 说明:捕获其他所有JWT相关异常,如算法不匹配等
|
||||
result['code'] = -1
|
||||
result['msg'] = "非法的token,请检查令牌有效性"
|
||||
logger.warning(f"无效的JWT令牌: {str(e)}")
|
||||
|
||||
except Exception as e:
|
||||
# 其他未预期的异常处理
|
||||
result['code'] = -1
|
||||
result['msg'] = "token验证失败,系统异常"
|
||||
logger.error(f"JWT验证系统异常: {str(e)}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def refresh_token(token, extend_minutes=None):
|
||||
"""
|
||||
刷新JWT令牌
|
||||
=============================================================
|
||||
|
||||
验证现有令牌有效性,如果有效则生成新的令牌,延长有效期。
|
||||
|
||||
参数:
|
||||
token (str): 需要刷新的JWT令牌字符串
|
||||
extend_minutes (int): 延长的分钟数,默认使用DEFAULT_TIMEOUT_MINUTES
|
||||
|
||||
返回:
|
||||
dict: 刷新结果,包含以下字段:
|
||||
code (int): 状态码,0-成功,-1-失败
|
||||
data (dict/None): 成功时包含新令牌,失败时为None
|
||||
msg (str): 提示信息
|
||||
|
||||
使用示例:
|
||||
>>> result = refresh_token(old_token, extend_minutes=30)
|
||||
>>> if result['code'] == 0:
|
||||
... new_token = result['data']['access_token']
|
||||
... else:
|
||||
... # 令牌无效,需要重新登录
|
||||
... error_msg = result['msg']
|
||||
|
||||
注意事项:
|
||||
1. 令牌只有在有效期内才能刷新
|
||||
2. 刷新后会生成全新的令牌,旧令牌立即失效
|
||||
3. 建议在令牌剩余有效期少于一半时进行刷新
|
||||
"""
|
||||
if extend_minutes is None:
|
||||
extend_minutes = DEFAULT_TIMEOUT_MINUTES
|
||||
|
||||
# 先验证原令牌
|
||||
result = parse_payload(token)
|
||||
|
||||
# 如果验证失败,返回错误信息
|
||||
if result['code'] != 0:
|
||||
return {
|
||||
"code": -1,
|
||||
"data": None,
|
||||
"msg": f"令牌无效,无法刷新: {result['msg']}"
|
||||
}
|
||||
|
||||
try:
|
||||
# 获取原载荷数据(不包含exp、iat等JWT标准字段)
|
||||
payload = result['data']
|
||||
|
||||
# 移除JWT标准字段,避免重复添加
|
||||
# 注意:保留原始业务数据,移除系统字段
|
||||
clean_payload = {k: v for k, v in payload.items()
|
||||
if k not in ['exp', 'iat', 'nbf']}
|
||||
|
||||
# 生成新令牌
|
||||
new_token = create_token(clean_payload, timeout=extend_minutes)
|
||||
|
||||
logger.info(f"JWT令牌刷新成功,延长{extend_minutes}分钟")
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"access_token": new_token,
|
||||
"expires_in": extend_minutes * 60 # 转换为秒
|
||||
},
|
||||
"msg": "令牌刷新成功"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"刷新JWT令牌失败: {str(e)}")
|
||||
return {
|
||||
"code": -1,
|
||||
"data": None,
|
||||
"msg": "令牌刷新失败,请重新登录"
|
||||
}
|
||||
|
||||
|
||||
def get_token_expiration(token):
|
||||
"""
|
||||
获取JWT令牌的过期时间
|
||||
|
||||
验证签名后读取过期时间,确保token未被篡改。
|
||||
不过验证过期状态(verify_exp=False),以便调用方
|
||||
读取已过期token的过期时间用于剩余时间计算等场景。
|
||||
|
||||
Args:
|
||||
token (str): JWT令牌字符串
|
||||
|
||||
Returns:
|
||||
datetime/None: 令牌过期时间(UTC),解析失败返回None
|
||||
"""
|
||||
try:
|
||||
# 验证签名确保token未被篡改,但不验证过期状态
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
JWT_SALT,
|
||||
algorithms=[JWT_ALGORITHM],
|
||||
options={"verify_exp": False}
|
||||
)
|
||||
|
||||
exp_timestamp = payload.get('exp')
|
||||
if exp_timestamp:
|
||||
return datetime.datetime.fromtimestamp(exp_timestamp, tz=datetime.timezone.utc)
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"获取JWT过期时间失败: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
def get_access_token():
|
||||
"""
|
||||
从请求头中提取 access_token
|
||||
=============================================================
|
||||
|
||||
从 HTTP 请求的 Authorization 头中提取 JWT token,返回纯净的 token 字符串。
|
||||
统一处理 token 提取逻辑,便于在其他模块中复用。
|
||||
|
||||
参数:
|
||||
request: Django 请求对象
|
||||
|
||||
返回:
|
||||
tuple: (success, token, message)
|
||||
- success (bool): 是否成功获取 token
|
||||
- token (str/None): token 字符串,失败时为 None
|
||||
- message (str): 提示信息
|
||||
|
||||
使用示例:
|
||||
>>> success, token, msg = get_access_token()
|
||||
>>> if success:
|
||||
... # 使用 token 进行后续处理
|
||||
... parse_result = parse_payload(token)
|
||||
... else:
|
||||
... # 处理错误
|
||||
... return R.failed(msg)
|
||||
"""
|
||||
try:
|
||||
# 从请求头中获取 token 值
|
||||
# 说明:Authorization 头格式通常为 "Bearer <token>"
|
||||
auth_header = request.headers.get('Authorization', '')
|
||||
if not auth_header:
|
||||
return False, None, '缺少 Authorization 头'
|
||||
|
||||
# 处理 Bearer 前缀
|
||||
# 说明:移除 "Bearer " 前缀并去除首尾空格,获取纯净 token
|
||||
access_token = auth_header.replace('Bearer ', "").strip()
|
||||
if not access_token:
|
||||
return False, None, 'token 为空'
|
||||
|
||||
return True, access_token, 'success'
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取 access_token 异常: {str(e)}")
|
||||
return False, None, f'token 提取异常: {str(e)}'
|
||||
|
||||
|
||||
# token解析工具类
|
||||
def parse_token(request):
|
||||
"""
|
||||
解析请求头中的token,返回解析结果
|
||||
=============================================================
|
||||
|
||||
从HTTP请求的Authorization头中提取JWT token,进行解析验证,
|
||||
并返回包含用户信息的结构化数据。统一处理各种异常情况。
|
||||
|
||||
参数:
|
||||
request: Django请求对象
|
||||
|
||||
返回:
|
||||
dict: 包含解析结果和用户信息的字典
|
||||
{
|
||||
'code': 0, # 0-成功,-1-失败
|
||||
'msg': 'success', # 提示信息
|
||||
'data': {
|
||||
'userId': 0, # 用户ID
|
||||
'username': '', # 用户名
|
||||
'realname': '' # 真实姓名
|
||||
}
|
||||
}
|
||||
|
||||
使用示例:
|
||||
>>> result = parse_token(request)
|
||||
>>> if result['code'] == 0:
|
||||
... user_id = result['data']['userId']
|
||||
... username = result['data']['username']
|
||||
"""
|
||||
# 初始化返回结果
|
||||
# 说明:设置默认失败状态,确保返回结构完整性
|
||||
result = {
|
||||
'code': -1,
|
||||
'msg': 'token无效',
|
||||
'data': {
|
||||
'userId': 0,
|
||||
'userName': None
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
# 获取 access_token
|
||||
success, access_token, msg = get_access_token()
|
||||
if not success:
|
||||
result['msg'] = msg
|
||||
return result
|
||||
|
||||
# JWT解密
|
||||
# 函数:parse_payload(access_token)
|
||||
# 说明:调用JWT解析工具验证token有效性
|
||||
parse_result = parse_payload(access_token)
|
||||
|
||||
# 验证解析结果
|
||||
# 说明:确保解析结果格式正确
|
||||
if not isinstance(parse_result, dict):
|
||||
result['msg'] = '解析结果格式错误'
|
||||
return result
|
||||
|
||||
# 结果标识
|
||||
# 说明:code为0表示验证通过
|
||||
code = parse_result.get('code', -1)
|
||||
if code != 0:
|
||||
result['msg'] = parse_result.get('msg', 'token验证失败')
|
||||
return result
|
||||
|
||||
# 获取数据部分
|
||||
# 说明:提取token载荷中的用户数据
|
||||
data = parse_result.get('data', {})
|
||||
if not isinstance(data, dict):
|
||||
result['msg'] = '数据格式错误'
|
||||
return result
|
||||
|
||||
# 提取用户信息
|
||||
# 说明:从载荷中获取具体用户字段
|
||||
user_id = data.get('userId', 0)
|
||||
username = data.get('username', '')
|
||||
realname = data.get('realname', '')
|
||||
|
||||
# 更新返回结果
|
||||
# 说明:设置成功状态并填充用户信息
|
||||
result['code'] = 0
|
||||
result['msg'] = 'success'
|
||||
result['data']['userId'] = user_id
|
||||
result['data']['username'] = username
|
||||
result['data']['realname'] = realname
|
||||
|
||||
except Exception as e:
|
||||
# 异常捕获
|
||||
# 说明:防止未处理的异常导致系统崩溃
|
||||
result['msg'] = f'token解析异常: {str(e)}'
|
||||
# 可以在这里记录日志
|
||||
# logger.error(f"token解析异常: {str(e)}")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# =============================================================
|
||||
# Token黑名单功能
|
||||
# =============================================================
|
||||
|
||||
# 黑名单Key前缀,用于Redis中存储被注销的Token
|
||||
_TOKEN_BLACKLIST_PREFIX = 'token:blacklist:'
|
||||
|
||||
|
||||
def _token_fingerprint(token: str) -> str:
|
||||
"""
|
||||
生成Token指纹(SHA256摘要)
|
||||
|
||||
避免将完整Token存入Redis,仅存储摘要值,减少泄露风险。
|
||||
|
||||
Args:
|
||||
token: JWT令牌字符串
|
||||
|
||||
Returns:
|
||||
str: SHA256十六进制摘要
|
||||
"""
|
||||
return hashlib.sha256(token.encode('utf-8')).hexdigest()
|
||||
|
||||
|
||||
def add_token_to_blacklist(token: str) -> bool:
|
||||
"""
|
||||
将Token加入黑名单
|
||||
|
||||
退出登录时调用,使Token立即失效。黑名单条目的TTL与Token剩余有效期一致,
|
||||
Token自然过期后黑名单条目自动清除,避免Redis内存无限增长。
|
||||
|
||||
Args:
|
||||
token: JWT令牌字符串
|
||||
|
||||
Returns:
|
||||
bool: 是否成功加入黑名单
|
||||
"""
|
||||
try:
|
||||
from src.core.redis_client import redis
|
||||
|
||||
# 获取Token剩余有效期
|
||||
expiration = get_token_expiration(token)
|
||||
if expiration is None:
|
||||
# 无法解析过期时间,使用默认过期时间作为TTL
|
||||
ttl_seconds = DEFAULT_TIMEOUT_MINUTES * 60
|
||||
else:
|
||||
ttl_seconds = int((expiration - datetime.datetime.now(tz=datetime.timezone.utc)).total_seconds())
|
||||
if ttl_seconds <= 0:
|
||||
# Token已自然过期,无需加入黑名单
|
||||
return True
|
||||
|
||||
fingerprint = _token_fingerprint(token)
|
||||
key = f'{_TOKEN_BLACKLIST_PREFIX}{fingerprint}'
|
||||
redis.set(key, '1', expire=ttl_seconds)
|
||||
|
||||
logger.info(f"Token已加入黑名单,TTL={ttl_seconds}秒")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Token加入黑名单失败: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def is_token_blacklisted(token: str) -> bool:
|
||||
"""
|
||||
检查Token是否在黑名单中
|
||||
|
||||
在登录验证中间件中调用,拒绝已被注销的Token。
|
||||
|
||||
Args:
|
||||
token: JWT令牌字符串
|
||||
|
||||
Returns:
|
||||
bool: True-已被注销(在黑名单中),False-有效
|
||||
"""
|
||||
try:
|
||||
from src.core.redis_client import redis
|
||||
|
||||
fingerprint = _token_fingerprint(token)
|
||||
key = f'{_TOKEN_BLACKLIST_PREFIX}{fingerprint}'
|
||||
return redis.exists(key) > 0
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Token黑名单检查异常: {str(e)}")
|
||||
# Redis不可用时放行,避免阻断所有请求
|
||||
return False
|
||||
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
Flask-Mail 扩展初始化模块
|
||||
=============================================================
|
||||
|
||||
仅负责 Flask-Mail 扩展实例的创建与初始化。
|
||||
|
||||
"""
|
||||
|
||||
from flask import Flask
|
||||
from flask_mail import Mail
|
||||
|
||||
# Flask-Mail 扩展实例
|
||||
# 在应用工厂中通过 mail.init_app(app) 完成绑定
|
||||
mail = Mail()
|
||||
|
||||
|
||||
def init_mail(app: Flask):
|
||||
"""
|
||||
初始化 Flask-Mail 扩展,将 Mail 实例绑定到应用。
|
||||
|
||||
Args:
|
||||
app: Flask 应用实例
|
||||
|
||||
Example:
|
||||
# 在应用工厂函数中调用
|
||||
init_mail(app)
|
||||
"""
|
||||
from config import (
|
||||
MAIL_MAILER, MAIL_SERVER, MAIL_PORT, MAIL_USE_SSL,
|
||||
MAIL_USERNAME, MAIL_PASSWORD, MAIL_ENCRYPTION,
|
||||
MAIL_FROM_NAME, MAIL_FROM_ADDRESS,
|
||||
)
|
||||
|
||||
app.config['MAIL_MAILER'] = MAIL_MAILER
|
||||
app.config['MAIL_SERVER'] = MAIL_SERVER
|
||||
app.config['MAIL_PORT'] = MAIL_PORT
|
||||
app.config['MAIL_USE_SSL'] = MAIL_USE_SSL
|
||||
app.config['MAIL_USERNAME'] = MAIL_USERNAME
|
||||
app.config['MAIL_PASSWORD'] = MAIL_PASSWORD
|
||||
app.config['MAIL_ENCRYPTION'] = MAIL_ENCRYPTION
|
||||
app.config['MAIL_DEFAULT_SENDER'] = (
|
||||
f'{MAIL_FROM_NAME} <{MAIL_FROM_ADDRESS}>' if MAIL_FROM_NAME and MAIL_FROM_ADDRESS
|
||||
else MAIL_FROM_ADDRESS or None
|
||||
)
|
||||
|
||||
mail.init_app(app)
|
||||
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
密码加密工具模块
|
||||
=============================================================
|
||||
|
||||
该模块提供基于 bcrypt 的密码加密和验证功能。
|
||||
采用 bcrypt(password + salt) 方案,bcrypt 内置盐与外部 salt 双重加盐,
|
||||
确保相同密码产生不同哈希,有效抵御彩虹表攻击。
|
||||
|
||||
主要功能:
|
||||
1. 生成随机盐值(generate_salt)
|
||||
2. 密码加密(encrypt_password)- bcrypt(password + salt)
|
||||
3. 密码验证(verify_password)
|
||||
|
||||
所属包: utils
|
||||
所在文件: utils/password.py
|
||||
依赖模块: bcrypt - 密码哈希第三方库
|
||||
secrets - 安全随机数生成模块
|
||||
"""
|
||||
|
||||
import secrets
|
||||
|
||||
import bcrypt
|
||||
|
||||
# bcrypt 工作因子,值越大计算越慢越安全,默认12(约250ms)
|
||||
BCRYPT_ROUNDS = 12
|
||||
|
||||
|
||||
def generate_salt(length: int = 10) -> str:
|
||||
"""
|
||||
生成随机盐值
|
||||
|
||||
Args:
|
||||
length: 盐值长度,默认10位
|
||||
|
||||
Returns:
|
||||
str: 随机盐值字符串
|
||||
"""
|
||||
return secrets.token_urlsafe(length)[:length]
|
||||
|
||||
|
||||
def encrypt_password(password: str, salt: str) -> str:
|
||||
"""
|
||||
使用 bcrypt 加密密码
|
||||
|
||||
将密码与外部 salt 拼接后进行 bcrypt 哈希,实现双重加盐。
|
||||
|
||||
Args:
|
||||
password: 明文密码
|
||||
salt: 外部盐值
|
||||
|
||||
Returns:
|
||||
str: bcrypt 哈希字符串(60字符)
|
||||
"""
|
||||
combined = (password + salt).encode('utf-8')
|
||||
hashed = bcrypt.hashpw(combined, bcrypt.gensalt(rounds=BCRYPT_ROUNDS))
|
||||
return hashed.decode('utf-8')
|
||||
|
||||
|
||||
def verify_password(password: str, salt: str, hashed: str) -> bool:
|
||||
"""
|
||||
验证密码是否正确
|
||||
|
||||
Args:
|
||||
password: 待验证的明文密码
|
||||
salt: 外部盐值
|
||||
hashed: 存储的 bcrypt 哈希值
|
||||
|
||||
Returns:
|
||||
bool: 密码是否匹配
|
||||
"""
|
||||
combined = (password + salt).encode('utf-8')
|
||||
return bcrypt.checkpw(combined, hashed.encode('utf-8'))
|
||||
@@ -0,0 +1,377 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 一米阳光团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
"""
|
||||
Redis 客户端封装模块
|
||||
=============================================================
|
||||
|
||||
整合来源:
|
||||
- extensions/redis.py:FlaskRedis 扩展实例与初始化
|
||||
- utils/redis_client.py:RedisClient 高级封装(序列化、类型转换)
|
||||
|
||||
提供 Redis 操作的统一封装,支持字符串、哈希、列表、集合、有序集合等
|
||||
数据类型。自动处理序列化/反序列化,业务代码无需关心底层存储格式。
|
||||
|
||||
使用方式:
|
||||
from src.core.redis_client import redis
|
||||
|
||||
redis.set('key', 'value', expire=3600)
|
||||
value = redis.get('key')
|
||||
"""
|
||||
|
||||
import json
|
||||
import pickle
|
||||
from typing import Any, Union, List, Dict, Tuple
|
||||
|
||||
from flask import Flask
|
||||
from flask_redis import FlaskRedis
|
||||
|
||||
# ============================================================
|
||||
# FlaskRedis 扩展实例
|
||||
# ============================================================
|
||||
# 在应用工厂中通过 init_redis(app) 完成绑定
|
||||
redis_client = FlaskRedis(decode_responses=True, protocol=2)
|
||||
|
||||
|
||||
def init_redis(app: Flask):
|
||||
"""
|
||||
初始化 Redis 扩展,将 FlaskRedis 实例绑定到应用。
|
||||
|
||||
Args:
|
||||
app: Flask 应用实例
|
||||
|
||||
Example:
|
||||
# 在应用工厂函数中调用
|
||||
init_redis(app)
|
||||
"""
|
||||
redis_client.init_app(app)
|
||||
|
||||
|
||||
class RedisClient:
|
||||
"""
|
||||
Redis客户端封装类
|
||||
|
||||
提供丰富的Redis操作方法,支持字符串、哈希、列表、集合、有序集合等数据类型
|
||||
"""
|
||||
|
||||
def __init__(self, client=None):
|
||||
"""
|
||||
初始化Redis客户端
|
||||
|
||||
Args:
|
||||
client: FlaskRedis实例,如果为None则使用全局redis_client
|
||||
"""
|
||||
self.client = client or redis_client
|
||||
|
||||
# ==================== 基础方法 ====================
|
||||
|
||||
def set(self, key: str, value: Any, expire: int = None, nx: bool = False, xx: bool = False) -> bool:
|
||||
"""设置键值对"""
|
||||
serialized_value = self._serialize(value)
|
||||
|
||||
if nx and xx:
|
||||
raise ValueError("nx和xx不能同时为True")
|
||||
|
||||
if nx:
|
||||
result = self.client.setnx(key, serialized_value)
|
||||
if expire and result:
|
||||
self.client.expire(key, expire)
|
||||
return result
|
||||
elif xx:
|
||||
result = self.client.set(key, serialized_value, xx=True)
|
||||
if expire and result:
|
||||
self.client.expire(key, expire)
|
||||
return result
|
||||
else:
|
||||
if expire:
|
||||
return self.client.setex(key, expire, serialized_value)
|
||||
else:
|
||||
return self.client.set(key, serialized_value)
|
||||
|
||||
def get(self, key: str, default: Any = None, deserialize: bool = True) -> Any:
|
||||
"""获取键值"""
|
||||
data = self.client.get(key)
|
||||
if data is None:
|
||||
return default
|
||||
|
||||
if deserialize:
|
||||
return self._deserialize(data)
|
||||
return data
|
||||
|
||||
def delete(self, *keys: str) -> int:
|
||||
"""删除一个或多个键"""
|
||||
if not keys:
|
||||
return 0
|
||||
return self.client.delete(*keys)
|
||||
|
||||
def exists(self, *keys: str) -> int:
|
||||
"""检查键是否存在"""
|
||||
if not keys:
|
||||
return 0
|
||||
return self.client.exists(*keys)
|
||||
|
||||
def expire(self, key: str, seconds: int) -> bool:
|
||||
"""设置键的过期时间"""
|
||||
return self.client.expire(key, seconds)
|
||||
|
||||
def expireat(self, key: str, timestamp: int) -> bool:
|
||||
"""设置键在指定时间戳过期"""
|
||||
return self.client.expireat(key, timestamp)
|
||||
|
||||
def ttl(self, key: str) -> int:
|
||||
"""获取键的剩余过期时间"""
|
||||
return self.client.ttl(key)
|
||||
|
||||
def keys(self, pattern: str = '*') -> List[str]:
|
||||
"""获取匹配模式的所有键(使用SCAN,避免KEYS阻塞)"""
|
||||
result = []
|
||||
cursor = 0
|
||||
while True:
|
||||
cursor, batch = self.client.scan(cursor=cursor, match=pattern, count=100)
|
||||
result.extend(batch)
|
||||
if cursor == 0:
|
||||
break
|
||||
return result
|
||||
|
||||
# ==================== 哈希表操作 ====================
|
||||
|
||||
def hset(self, name: str, key: str, value: Any) -> int:
|
||||
"""设置哈希表中的字段值"""
|
||||
serialized_value = self._serialize(value)
|
||||
return self.client.hset(name, key, serialized_value)
|
||||
|
||||
def hget(self, name: str, key: str, deserialize: bool = True) -> Any:
|
||||
"""获取哈希表中的字段值"""
|
||||
data = self.client.hget(name, key)
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
if deserialize:
|
||||
return self._deserialize(data)
|
||||
return data
|
||||
|
||||
def hgetall(self, name: str, deserialize: bool = True) -> Dict[str, Any]:
|
||||
"""获取哈希表中所有字段和值"""
|
||||
data = self.client.hgetall(name)
|
||||
if not data:
|
||||
return {}
|
||||
|
||||
if deserialize:
|
||||
return {k.decode(): self._deserialize(v) for k, v in data.items()}
|
||||
return {k.decode(): v for k, v in data.items()}
|
||||
|
||||
def hdel(self, name: str, *keys: str) -> int:
|
||||
"""删除哈希表中的字段"""
|
||||
if not keys:
|
||||
return 0
|
||||
return self.client.hdel(name, *keys)
|
||||
|
||||
def hincrby(self, name: str, key: str, amount: int = 1) -> int:
|
||||
"""为哈希表中的字段值增加指定数值"""
|
||||
return self.client.hincrby(name, key, amount)
|
||||
|
||||
# ==================== 列表操作 ====================
|
||||
|
||||
def lpush(self, name: str, *values: Any) -> int:
|
||||
"""从列表左侧插入元素"""
|
||||
serialized_values = [self._serialize(v) for v in values]
|
||||
return self.client.lpush(name, *serialized_values)
|
||||
|
||||
def rpush(self, name: str, *values: Any) -> int:
|
||||
"""从列表右侧插入元素"""
|
||||
serialized_values = [self._serialize(v) for v in values]
|
||||
return self.client.rpush(name, *serialized_values)
|
||||
|
||||
def lpop(self, name: str, deserialize: bool = True) -> Any:
|
||||
"""从列表左侧弹出元素"""
|
||||
data = self.client.lpop(name)
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
if deserialize:
|
||||
return self._deserialize(data)
|
||||
return data
|
||||
|
||||
def rpop(self, name: str, deserialize: bool = True) -> Any:
|
||||
"""从列表右侧弹出元素"""
|
||||
data = self.client.rpop(name)
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
if deserialize:
|
||||
return self._deserialize(data)
|
||||
return data
|
||||
|
||||
def lrange(self, name: str, start: int, end: int, deserialize: bool = True) -> List[Any]:
|
||||
"""获取列表指定范围的元素"""
|
||||
data = self.client.lrange(name, start, end)
|
||||
if not data:
|
||||
return []
|
||||
|
||||
if deserialize:
|
||||
return [self._deserialize(item) for item in data]
|
||||
return [item for item in data]
|
||||
|
||||
def llen(self, name: str) -> int:
|
||||
"""获取列表长度"""
|
||||
return self.client.llen(name)
|
||||
|
||||
# ==================== 集合操作 ====================
|
||||
|
||||
def sadd(self, name: str, *values: Any) -> int:
|
||||
"""向集合中添加元素"""
|
||||
serialized_values = [self._serialize(v) for v in values]
|
||||
return self.client.sadd(name, *serialized_values)
|
||||
|
||||
def srem(self, name: str, *values: Any) -> int:
|
||||
"""从集合中移除元素"""
|
||||
serialized_values = [self._serialize(v) for v in values]
|
||||
return self.client.srem(name, *serialized_values)
|
||||
|
||||
def smembers(self, name: str, deserialize: bool = True) -> set:
|
||||
"""获取集合中所有元素"""
|
||||
data = self.client.smembers(name)
|
||||
if not data:
|
||||
return set()
|
||||
|
||||
if deserialize:
|
||||
return {self._deserialize(item) for item in data}
|
||||
return {item for item in data}
|
||||
|
||||
def sismember(self, name: str, value: Any) -> bool:
|
||||
"""判断元素是否在集合中"""
|
||||
serialized_value = self._serialize(value)
|
||||
return self.client.sismember(name, serialized_value)
|
||||
|
||||
# ==================== 有序集合操作 ====================
|
||||
|
||||
def zadd(self, name: str, mapping: Dict[Any, float]) -> int:
|
||||
"""向有序集合中添加元素"""
|
||||
serialized_mapping = {self._serialize(k): v for k, v in mapping.items()}
|
||||
return self.client.zadd(name, serialized_mapping)
|
||||
|
||||
def zrange(self, name: str, start: int, end: int, withscores: bool = False,
|
||||
deserialize: bool = True) -> Union[List[Any], List[Tuple[Any, float]]]:
|
||||
"""获取有序集合指定范围的元素(按分数升序)"""
|
||||
data = self.client.zrange(name, start, end, withscores=withscores)
|
||||
if not data:
|
||||
return []
|
||||
|
||||
if withscores:
|
||||
if deserialize:
|
||||
return [(self._deserialize(item[0]), item[1]) for item in data]
|
||||
return [(item[0], item[1]) for item in data]
|
||||
else:
|
||||
if deserialize:
|
||||
return [self._deserialize(item) for item in data]
|
||||
return list(data)
|
||||
|
||||
def zrevrange(self, name: str, start: int, end: int, withscores: bool = False,
|
||||
deserialize: bool = True) -> Union[List[Any], List[Tuple[Any, float]]]:
|
||||
"""获取有序集合指定范围的元素(按分数降序)"""
|
||||
data = self.client.zrevrange(name, start, end, withscores=withscores)
|
||||
if not data:
|
||||
return []
|
||||
|
||||
if withscores:
|
||||
if deserialize:
|
||||
return [(self._deserialize(item[0]), item[1]) for item in data]
|
||||
return [(item[0], item[1]) for item in data]
|
||||
else:
|
||||
if deserialize:
|
||||
return [self._deserialize(item) for item in data]
|
||||
return list(data)
|
||||
|
||||
# ==================== 高级操作 ====================
|
||||
|
||||
def pipeline(self):
|
||||
"""获取管道对象,用于批量执行命令"""
|
||||
return self.client.pipeline()
|
||||
|
||||
def incr(self, key: str, amount: int = 1) -> int:
|
||||
"""增加键的值(用于计数器)"""
|
||||
return self.client.incrby(key, amount)
|
||||
|
||||
def decr(self, key: str, amount: int = 1) -> int:
|
||||
"""减少键的值"""
|
||||
return self.client.decrby(key, amount)
|
||||
|
||||
def lock(self, key: str, timeout: int = 10, blocking: bool = True,
|
||||
blocking_timeout: int = None) -> Any:
|
||||
"""获取分布式锁"""
|
||||
return self.client.lock(key, timeout=timeout, blocking=blocking,
|
||||
blocking_timeout=blocking_timeout)
|
||||
|
||||
# ==================== 私有方法 ====================
|
||||
|
||||
def _serialize(self, value: Any) -> Union[str, bytes]:
|
||||
"""序列化值"""
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
elif isinstance(value, (int, float, bool)):
|
||||
return str(value)
|
||||
elif isinstance(value, (dict, list, tuple)):
|
||||
return json.dumps(value, ensure_ascii=False)
|
||||
else:
|
||||
return pickle.dumps(value)
|
||||
|
||||
def _deserialize(self, value: Union[str, bytes]) -> Any:
|
||||
"""反序列化值"""
|
||||
if not value:
|
||||
return None
|
||||
|
||||
if isinstance(value, bytes):
|
||||
try:
|
||||
value = value.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
return pickle.loads(value)
|
||||
except Exception:
|
||||
return value
|
||||
|
||||
try:
|
||||
return json.loads(value)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return value
|
||||
|
||||
|
||||
# 创建全局Redis客户端实例
|
||||
redis = RedisClient()
|
||||
@@ -0,0 +1,222 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 珊瑚云团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 珊瑚云团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
"""
|
||||
统一响应结果模块
|
||||
=============================================================
|
||||
|
||||
该模块提供了系统统一的JSON响应格式,确保所有API接口返回格式一致。
|
||||
包含成功响应和失败响应两种基础方法,支持扩展参数。
|
||||
|
||||
响应格式规范:
|
||||
- code: 状态码,0表示成功,1表示失败(可扩展其他状态码)
|
||||
- data: 响应数据,成功时返回具体数据,失败时为None
|
||||
- msg: 提示信息,对操作结果的文字描述
|
||||
- ok: 布尔值,标识操作是否成功(兼容旧版前端)
|
||||
- 其他扩展参数:可通过kwargs动态添加
|
||||
|
||||
使用示例:
|
||||
# 成功响应
|
||||
return R.ok(data={"id": 1, "name": "张三"})
|
||||
return R.ok(msg="操作成功")
|
||||
|
||||
# 失败响应
|
||||
return R.failed(msg="用户名或密码错误")
|
||||
return R.failed(msg="服务器内部错误", code=500)
|
||||
|
||||
# 带扩展参数的响应
|
||||
return R.ok(data=user_info, total=100, pages=10)
|
||||
"""
|
||||
|
||||
from typing import Any, Optional, Dict
|
||||
|
||||
from flask import jsonify
|
||||
|
||||
from src.common.constants.common import MESSAGE_OK, MESSAGE_FAIL
|
||||
|
||||
|
||||
def ok(
|
||||
data: Optional[Any] = None,
|
||||
msg: str = MESSAGE_OK,
|
||||
code: int = 0,
|
||||
**kwargs: Any
|
||||
) -> jsonify:
|
||||
"""
|
||||
生成成功响应
|
||||
|
||||
构建一个标准的成功响应JSON对象,包含状态码、数据、提示信息等字段。
|
||||
支持通过kwargs添加额外的响应字段,便于扩展。
|
||||
|
||||
Args:
|
||||
data: 响应数据,可以是任意类型(dict, list, str, int等),默认为None
|
||||
msg: 成功提示信息,默认使用常量MESSAGE_OK
|
||||
code: 状态码,0表示成功,默认为0
|
||||
**kwargs: 额外的键值对参数,会合并到响应体中
|
||||
|
||||
Returns:
|
||||
jsonify: 格式化的JSON响应对象
|
||||
|
||||
Examples:
|
||||
>>> return R.ok()
|
||||
{"code": 0, "data": None, "msg": "操作成功", "ok": True}
|
||||
|
||||
>>> return R.ok(data={"id": 1, "name": "张三"})
|
||||
{"code": 0, "data": {"id": 1, "name": "张三"}, "msg": "操作成功", "ok": True}
|
||||
|
||||
>>> return R.ok(msg="创建成功", total=100)
|
||||
{"code": 0, "data": None, "msg": "创建成功", "ok": True, "total": 100}
|
||||
"""
|
||||
# 构建基础响应体
|
||||
response_body: Dict[str, Any] = {
|
||||
"code": code,
|
||||
"data": data,
|
||||
"msg": msg,
|
||||
"ok": True
|
||||
}
|
||||
|
||||
# 合并扩展参数
|
||||
if kwargs:
|
||||
response_body.update(kwargs)
|
||||
|
||||
# 返回JSON响应
|
||||
return jsonify(response_body)
|
||||
|
||||
|
||||
def failed(
|
||||
msg: str = MESSAGE_FAIL,
|
||||
code: int = 1,
|
||||
data: Optional[Any] = None,
|
||||
**kwargs: Any
|
||||
) -> jsonify:
|
||||
"""
|
||||
生成失败响应
|
||||
|
||||
构建一个标准的失败响应JSON对象,包含状态码、错误信息等字段。
|
||||
支持通过kwargs添加额外的响应字段,便于扩展。
|
||||
|
||||
Args:
|
||||
msg: 错误提示信息,默认使用常量MESSAGE_FAIL
|
||||
code: 状态码,1表示失败,默认为1(可根据业务需求定义其他错误码)
|
||||
data: 错误时的附加数据,通常为None,默认为None
|
||||
**kwargs: 额外的键值对参数,会合并到响应体中
|
||||
|
||||
Returns:
|
||||
jsonify: 格式化的JSON响应对象
|
||||
|
||||
Examples:
|
||||
>>> return R.failed()
|
||||
{"code": 1, "data": None, "msg": "操作失败", "ok": False}
|
||||
|
||||
>>> return R.failed(msg="用户名或密码错误")
|
||||
{"code": 1, "data": None, "msg": "用户名或密码错误", "ok": False}
|
||||
|
||||
>>> return R.failed(msg="未授权访问", code=401)
|
||||
{"code": 401, "data": None, "msg": "未授权访问", "ok": False}
|
||||
|
||||
>>> return R.failed(msg="参数错误", errors={"name": "该字段不能为空"})
|
||||
{"code": 1, "data": None, "msg": "参数错误", "ok": False, "errors": {...}}
|
||||
"""
|
||||
# 构建基础响应体
|
||||
response_body: Dict[str, Any] = {
|
||||
"code": code,
|
||||
"data": data,
|
||||
"msg": msg,
|
||||
"ok": False
|
||||
}
|
||||
|
||||
# 合并扩展参数
|
||||
if kwargs:
|
||||
response_body.update(kwargs)
|
||||
|
||||
# 返回JSON响应
|
||||
return jsonify(response_body)
|
||||
|
||||
|
||||
def response(
|
||||
data: Optional[Any] = None,
|
||||
msg: str = MESSAGE_OK,
|
||||
code: int = 0,
|
||||
success: bool = True,
|
||||
**kwargs: Any
|
||||
) -> jsonify:
|
||||
"""
|
||||
通用响应生成器(高级用法)
|
||||
|
||||
提供更灵活的响应生成方式,允许自定义所有字段。
|
||||
通常推荐使用ok()和failed()两个专用方法,但在需要完全控制响应格式时可以使用此方法。
|
||||
|
||||
Args:
|
||||
data: 响应数据
|
||||
msg: 提示信息
|
||||
code: 状态码
|
||||
success: 操作是否成功标识
|
||||
**kwargs: 额外的键值对参数
|
||||
|
||||
Returns:
|
||||
jsonify: 格式化的JSON响应对象
|
||||
|
||||
Examples:
|
||||
>>> return R.response(data=[], msg="查询成功", code=200, success=True)
|
||||
{"code": 200, "data": [], "msg": "查询成功", "ok": True}
|
||||
"""
|
||||
response_body: Dict[str, Any] = {
|
||||
"code": code,
|
||||
"data": data,
|
||||
"msg": msg,
|
||||
"ok": success
|
||||
}
|
||||
|
||||
if kwargs:
|
||||
response_body.update(kwargs)
|
||||
|
||||
return jsonify(response_body)
|
||||
|
||||
|
||||
# 为方便使用,定义常用错误码常量(可选)
|
||||
class Codes:
|
||||
"""常用状态码定义"""
|
||||
SUCCESS = 0
|
||||
FAILED = 1
|
||||
UNAUTHORIZED = 401 # 未认证
|
||||
FORBIDDEN = 403 # 无权限
|
||||
NOT_FOUND = 404 # 资源不存在
|
||||
VALIDATE_ERROR = 422 # 验证错误
|
||||
SERVER_ERROR = 500 # 服务器错误
|
||||
@@ -0,0 +1,199 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 一米阳光团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 一米阳光团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
"""
|
||||
定时任务调度器扩展模块
|
||||
负责初始化和管理 APScheduler 定时任务
|
||||
"""
|
||||
|
||||
import logging
|
||||
from flask import Flask
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Scheduler:
|
||||
"""
|
||||
定时任务调度器管理类
|
||||
|
||||
负责调度器的初始化和生命周期管理
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化调度器实例"""
|
||||
self._initialized = False
|
||||
|
||||
def init_app(self, app: Flask):
|
||||
"""
|
||||
初始化定时任务调度器
|
||||
|
||||
Args:
|
||||
app: Flask 应用实例
|
||||
|
||||
使用示例:
|
||||
from extensions.scheduler import scheduler
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
# ... 其他配置
|
||||
scheduler.init_app(app)
|
||||
return app
|
||||
"""
|
||||
# 避免重复初始化
|
||||
if self._initialized:
|
||||
logger.warning("定时任务调度器已初始化,跳过重复初始化")
|
||||
return
|
||||
|
||||
# ============================================================
|
||||
# 在应用上下文中执行初始化
|
||||
# ============================================================
|
||||
with app.app_context():
|
||||
try:
|
||||
from application.services.scheduler import reload_scheduler
|
||||
|
||||
# 重新加载所有任务
|
||||
count = reload_scheduler()
|
||||
|
||||
self._initialized = True
|
||||
|
||||
if count > 0:
|
||||
logger.info(f"定时任务调度器初始化完成,已加载 {count} 个任务")
|
||||
else:
|
||||
logger.info("定时任务调度器初始化完成,当前无可用任务")
|
||||
|
||||
# 打印调度器状态(调试用)
|
||||
self._log_scheduler_status()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"定时任务调度器初始化失败: {str(e)}")
|
||||
raise
|
||||
|
||||
def _log_scheduler_status(self):
|
||||
"""打印调度器状态(用于调试)"""
|
||||
try:
|
||||
from application.services.scheduler import scheduler
|
||||
|
||||
if scheduler.running:
|
||||
jobs = scheduler.get_jobs()
|
||||
if jobs:
|
||||
logger.debug("当前调度器中的任务列表:")
|
||||
for job in jobs:
|
||||
logger.debug(f" - ID: {job.id}, Name: {job.name}, Next run: {job.next_run_time}")
|
||||
else:
|
||||
logger.debug("当前调度器中无任务")
|
||||
else:
|
||||
logger.warning("调度器未运行")
|
||||
except Exception as e:
|
||||
logger.error(f"获取调度器状态失败: {str(e)}")
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
关闭定时任务调度器
|
||||
|
||||
在应用退出时调用
|
||||
"""
|
||||
try:
|
||||
from application.services.scheduler import scheduler
|
||||
|
||||
if scheduler.running:
|
||||
scheduler.shutdown()
|
||||
logger.info("定时任务调度器已关闭")
|
||||
except Exception as e:
|
||||
logger.error(f"关闭调度器失败: {str(e)}")
|
||||
|
||||
def reload(self) -> int:
|
||||
"""
|
||||
重新加载所有定时任务
|
||||
|
||||
Returns:
|
||||
int: 成功加载的任务数量
|
||||
"""
|
||||
try:
|
||||
from application.services.scheduler import reload_scheduler
|
||||
count = reload_scheduler()
|
||||
logger.info(f"重新加载定时任务完成,共加载 {count} 个任务")
|
||||
return count
|
||||
except Exception as e:
|
||||
logger.error(f"重新加载定时任务失败: {str(e)}")
|
||||
return 0
|
||||
|
||||
def get_status(self) -> dict:
|
||||
"""
|
||||
获取调度器状态
|
||||
|
||||
Returns:
|
||||
dict: 包含调度器运行状态和任务列表
|
||||
"""
|
||||
try:
|
||||
from application.services.scheduler import scheduler
|
||||
|
||||
jobs = scheduler.get_jobs()
|
||||
job_list = []
|
||||
|
||||
for job in jobs:
|
||||
job_list.append({
|
||||
'id': job.id,
|
||||
'name': job.name,
|
||||
'next_run_time': str(job.next_run_time) if job.next_run_time else None,
|
||||
'trigger': str(job.trigger)
|
||||
})
|
||||
|
||||
return {
|
||||
'initialized': self._initialized,
|
||||
'running': scheduler.running,
|
||||
'job_count': len(job_list),
|
||||
'jobs': job_list
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"获取调度器状态失败: {str(e)}")
|
||||
return {
|
||||
'initialized': self._initialized,
|
||||
'running': False,
|
||||
'job_count': 0,
|
||||
'jobs': [],
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 创建全局调度器单例实例
|
||||
# ============================================================
|
||||
scheduler = Scheduler()
|
||||
@@ -0,0 +1,121 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 珊瑚云团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 珊瑚云团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
"""
|
||||
用户安全工具模块
|
||||
=============================================================
|
||||
|
||||
该模块提供了用户认证和安全相关的工具函数。
|
||||
用于快速获取当前登录用户的信息,简化业务代码中的用户信息获取操作。
|
||||
"""
|
||||
import logging
|
||||
|
||||
from flask import request
|
||||
|
||||
from src.core.jwt import parse_token
|
||||
|
||||
|
||||
# ============================= 用户信息获取工具 =============================
|
||||
|
||||
|
||||
def get_user_id():
|
||||
"""
|
||||
获取当前登录用户ID
|
||||
=============================================================
|
||||
|
||||
快速获取当前登录用户的ID,用于需要用户ID的业务场景。
|
||||
基于parse_token函数实现,简化调用方式。
|
||||
|
||||
使用场景:
|
||||
- 记录操作日志时需要记录操作人ID
|
||||
- 数据查询时进行用户权限过滤
|
||||
- 创建数据时自动关联当前用户
|
||||
|
||||
参数:
|
||||
request (HttpRequest): Django请求对象,包含请求头中的token信息
|
||||
|
||||
返回:
|
||||
int: 当前登录用户的ID,若token无效或用户不存在则返回0
|
||||
|
||||
异常:
|
||||
可能抛出parse_token函数产生的异常(如token过期、token无效等)
|
||||
建议调用方自行处理异常
|
||||
|
||||
示例:
|
||||
>>> user_id = get_user_id()
|
||||
>>> if user_id:
|
||||
... # 执行需要用户ID的操作
|
||||
... create_log(user_id, "操作内容")
|
||||
"""
|
||||
try:
|
||||
# 解析请求中的token,获取用户信息
|
||||
result = parse_token(request)
|
||||
|
||||
# 安全获取用户ID,避免KeyError
|
||||
if result and isinstance(result, dict):
|
||||
data = result.get('data', {})
|
||||
if isinstance(data, dict):
|
||||
return data.get('userId', 0)
|
||||
|
||||
return 0
|
||||
except Exception:
|
||||
# 记录异常日志(可根据需要添加日志记录)
|
||||
return 0
|
||||
|
||||
|
||||
def get_username():
|
||||
"""
|
||||
获取当前登录用户账号
|
||||
|
||||
Returns:
|
||||
str: 当前登录用户的账号(username),若token无效或用户不存在则返回空字符串
|
||||
"""
|
||||
try:
|
||||
result = parse_token(request)
|
||||
if result and isinstance(result, dict):
|
||||
data = result.get('data', {})
|
||||
if isinstance(data, dict):
|
||||
username = data.get('username')
|
||||
return username if username else ''
|
||||
return ''
|
||||
except Exception as e:
|
||||
logging.error(f"获取用户名失败: {str(e)}")
|
||||
return ''
|
||||
Reference in New Issue
Block a user