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:
DjangoAdmin Dev
2026-08-07 13:20:46 +08:00
co-authored by Claude
parent 92130ef070
commit 658ee6d596
96 changed files with 9926 additions and 4153 deletions
+4 -307
View File
@@ -1,311 +1,8 @@
"""
Redis客户端封装模块
兼容 shim(绞杀者迁移期间保留)
=============================================================
提供Redis操作的统一封装,支持字符串、哈希、列表、集合、有序集合等数据类型。
自动处理序列化/反序列化,业务代码无需关心底层存储格式。
使用方式:
from utils.redis_client import redis
redis.set('key', 'value', expire=3600)
value = redis.get('key')
Redis 客户端封装已迁移至 src/core/redis_client.py。
本文件仅作兼容转发,最终在 P7(切尾)阶段删除。
"""
import json
import pickle
from typing import Any, Union, List, Dict, Tuple
from extensions.redis import redis_client
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()
from src.core.redis_client import * # noqa: F401,F403