签入版本

This commit is contained in:
2026-09-12 23:12:15 +08:00
commit 91998056ca
1841 changed files with 341325 additions and 0 deletions
@@ -0,0 +1,238 @@
# 第四章 架构设计 - 缓存架构
## 4.1 缓存架构概述
项目使用 Redis 作为缓存后端,通过 Django 的缓存框架实现统一的缓存管理。缓存主要用于以下场景:
```
+------------------+
| Django App |
+------------------+
|
v
+------------------+
| Django Cache | <-- Django缓存框架
| Framework |
+------------------+
|
v
+------------------+
| django-redis | <-- Redis缓存后端
+------------------+
|
v
+------------------+
| Redis | <-- Redis服务器
+------------------+
```
## 4.2 Redis 配置
### 缓存后端配置
在 `application/settings.py` 中配置 Redis 缓存:
```python
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": f"redis://{env.REDIS_HOST}:{env.REDIS_PORT}/{env.REDIS_DB}",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PASSWORD": env.REDIS_PASSWORD if env.REDIS_PASSWORD else None,
"SOCKET_CONNECT_TIMEOUT": 5, # 连接超时(秒)
"SOCKET_TIMEOUT": 5, # 读写超时(秒)
"CONNECTION_POOL_KWARGS": {"max_connections": 100}, # 连接池大小
"IGNORE_EXCEPTIONS": True, # Redis不可用时不抛异常,降级处理
},
"KEY_PREFIX": "djangoadmin", # Key前缀,避免多项目共用Redis时冲突
}
}
```
### Redis 连接参数
在 `config/env.py` 中定义 Redis 连接参数:
```python
REDIS_HOST = os.getenv('REDIS_HOST', '127.0.0.1')
REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', '123456')
REDIS_DB = int(os.getenv('REDIS_DB', 0))
```
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `REDIS_HOST` | `127.0.0.1` | Redis服务器地址 |
| `REDIS_PORT` | `6379` | Redis端口 |
| `REDIS_PASSWORD` | `123456` | Redis密码 |
| `REDIS_DB` | `0` | Redis数据库索引(0-15) |
## 4.3 缓存应用场景
### 4.3.1 Token 黑名单
退出登录时将 Token 加入黑名单,使 Token 立即失效。
**实现代码**(`utils/jwt.py`):
```python
_TOKEN_BLACKLIST_PREFIX = 'token:blacklist:'
def _token_fingerprint(token: str) -> str:
"""生成Token指纹(SHA256摘要)"""
return hashlib.sha256(token.encode('utf-8')).hexdigest()
def add_token_to_blacklist(token: str) -> bool:
"""将Token加入黑名单"""
from django_redis import get_redis_connection
redis = get_redis_connection("default")
# 获取Token剩余有效期
expiration = get_token_expiration(token)
if expiration is None:
ttl_seconds = DEFAULT_TIMEOUT_MINUTES * 60
else:
ttl_seconds = int(
(expiration - datetime.datetime.now(tz=datetime.timezone.utc)).total_seconds()
)
if ttl_seconds <= 0:
return True # Token已自然过期,无需加入黑名单
# 使用SHA256指纹作为Key,避免存储完整Token
fingerprint = _token_fingerprint(token)
key = f'{_TOKEN_BLACKLIST_PREFIX}{fingerprint}'
redis.set(key, '1', ex=ttl_seconds)
return True
def is_token_blacklisted(token: str) -> bool:
"""检查Token是否在黑名单中"""
from django_redis import get_redis_connection
redis = get_redis_connection("default")
fingerprint = _token_fingerprint(token)
key = f'{_TOKEN_BLACKLIST_PREFIX}{fingerprint}'
return redis.exists(key) > 0
```
**Key格式**:`djangoadmin:token:blacklist:<sha256_fingerprint>`
**TTL策略**:与 Token 剩余有效期一致,Token 自然过期后黑名单条目自动清除。
**降级策略**:Redis 不可用时采用 fail-open 策略放行请求,避免全部请求被拒。
### 4.3.2 IP 地理位置缓存
缓存 IP 地理位置查询结果,避免重复查询外部 API 或本地数据库。
**配置项**(`config/env.py`):
```python
# IP地理位置信息来源:'local' - 本地数据库, 'api' - 在线API
IP_LOCATION_SOURCE = os.environ.get('IP_LOCATION_SOURCE', 'api')
# 是否启用IP地理位置缓存
IP_CACHE_ENABLED = os.environ.get('IP_CACHE_ENABLED', 'True') == 'True'
# IP缓存时间(秒),默认1天
IP_CACHE_TIMEOUT = int(os.environ.get('IP_CACHE_TIMEOUT', 86400))
```
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `IP_LOCATION_SOURCE` | `api` | IP查询来源:本地数据库或在线API |
| `IP_CACHE_ENABLED` | `True` | 是否启用缓存 |
| `IP_CACHE_TIMEOUT` | `86400` | 缓存过期时间(秒),默认1天 |
### 4.3.3 验证码存储
图形验证码通过 Redis 存储,支持自动过期。
**配置项**(`application/settings.py`):
```python
CAPTCHA_IMAGE_SIZE = (200, 60) # 验证码图片尺寸
CAPTCHA_LENGTH = 6 # 验证码字符个数
CAPTCHA_TIMEOUT = 1 # 验证码超时时间(分钟)
CAPTCHA_FONT_SIZE = 40 # 验证码字体大小
CAPTCHA_FOREGROUND_COLOR = "#1d953f" # 验证码前景色
CAPTCHA_BACKGROUND_COLOR = "#ffffff" # 验证码背景色
```
## 4.4 缓存 Key 命名规范
所有缓存 Key 统一使用 `djangoadmin:` 前缀(通过 `KEY_PREFIX` 配置),避免多项目共用 Redis 时冲突。
| 场景 | Key格式 | TTL | 说明 |
|------|---------|-----|------|
| Token黑名单 | `djangoadmin:token:blacklist:<fingerprint>` | Token剩余有效期 | SHA256指纹 |
| IP位置缓存 | `djangoadmin:ip:location:<ip>` | 86400秒 | IP查询结果 |
| 验证码 | `djangoadmin:captcha:<key>` | 60秒 | 图形验证码 |
## 4.5 缓存降级策略
### Redis 不可用时的处理
系统配置了 `IGNORE_EXCEPTIONS: True`,当 Redis 不可用时:
1. **Token黑名单检查**:采用 fail-open 策略,放行请求(已注销的Token可能被误放行)
2. **IP位置缓存**:跳过缓存,直接查询数据源
3. **验证码**:降级为内存存储或其他方案
```python
# Token黑名单检查的降级处理
def is_token_blacklisted(token: str) -> bool:
try:
from django_redis import get_redis_connection
redis = get_redis_connection("default")
fingerprint = _token_fingerprint(token)
key = f'{_TOKEN_BLACKLIST_PREFIX}{fingerprint}'
return redis.exists(key) > 0
except Exception as e:
# fail-open: Redis不可用时放行
_log_blacklist_check_alert(e)
return False
```
### 告警节流
Redis 故障期间,采用节流告警避免日志刷屏:
```python
_BLACKLIST_ALERT_INTERVAL = 60.0 # 告警间隔(秒)
_blacklist_check_last_alert = 0.0
def _log_blacklist_check_alert(error: Exception) -> None:
"""节流记录黑名单检查失败告警"""
global _blacklist_check_last_alert
now = time.monotonic()
if now - _blacklist_check_last_alert >= _BLACKLIST_ALERT_INTERVAL:
_blacklist_check_last_alert = now
logger.error("Token黑名单检查异常,已按 fail-open 放行: %s", error)
else:
logger.debug("Token黑名单检查异常(节流中,跳过 ERROR): %s", error)
```
## 4.6 缓存最佳实践
### Key 设计原则
1. **使用前缀**:所有 Key 使用统一前缀,便于管理和清理
2. **避免冲突**:不同业务使用不同的 Key 格式
3. **合理TTL**:根据业务场景设置合适的过期时间
4. **使用指纹**:敏感数据(如Token)使用摘要值作为Key
### 性能优化
1. **连接池**:配置 `max_connections: 100`,复用连接
2. **超时控制**:连接和读写超时各5秒,避免长时间阻塞
3. **批量操作**:尽量使用 `pipeline` 减少网络往返
4. **降级处理**:Redis 不可用时不影响核心业务
### 安全考虑
1. **密码保护**:Redis 连接使用密码认证
2. **网络隔离**:生产环境 Redis 应限制内网访问
3. **Key加密**:敏感数据的 Key 使用 SHA256 摘要
4. **定期清理**:依赖 TTL 自动过期,避免内存溢出