诚实性修复: API限流Retry-After(wait=60s)+断言 / purge定时接线(04:00 cron+actor+测试) / Channel表单校验(ref+rules+validate) / README PG数字513
This commit is contained in:
@@ -7,6 +7,7 @@ worker 启动命令引用本模块以完成 broker 初始化、actor 注册与 c
|
||||
- 每日 08:00 全租户业务预警扫描(含 AI 应收风险)
|
||||
- 每日 02:00 电商渠道自动拉单(有凭证店铺)
|
||||
- 每日 03:00 套餐试用/订阅到期检查(降级 free + 通知)
|
||||
- 每日 04:00 审计日志保留清理(默认保留 180 天)
|
||||
"""
|
||||
|
||||
# 0. 初始化 Django(worker 进程必须先加载 Django 再注册模型 actor)
|
||||
@@ -80,3 +81,12 @@ def cron_daily_channel_syncs():
|
||||
def cron_daily_billing_expiry():
|
||||
"""每日 03:00 扫描试用/订阅到期 → 降级 free 并发通知。"""
|
||||
return _run_billing_expiry()
|
||||
|
||||
|
||||
@cron("0 4 * * *")
|
||||
@dramatiq.actor(max_retries=1)
|
||||
def cron_daily_audit_purge():
|
||||
"""每日 04:00 清理过期审计日志(P1-3,默认保留 180 天)。"""
|
||||
from apps.core import tasks as core_tasks
|
||||
|
||||
return core_tasks.purge_expired_audit_logs()
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
"""生产环境配置:PostgreSQL + 严格安全 + Granian。"""
|
||||
|
||||
import os
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from .base import * # noqa
|
||||
from .base import env
|
||||
|
||||
DEBUG = False
|
||||
ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["*"])
|
||||
|
||||
_configured_hosts = os.environ.get("DJANGO_ALLOWED_HOSTS", "").strip()
|
||||
if not _configured_hosts:
|
||||
raise ImproperlyConfigured(
|
||||
"生产环境必须显式设置 DJANGO_ALLOWED_HOSTS(逗号分隔的域名列表),"
|
||||
"不允许默认接受任意 Host 头"
|
||||
)
|
||||
ALLOWED_HOSTS = [h.strip() for h in _configured_hosts.split(",") if h.strip()]
|
||||
if not ALLOWED_HOSTS:
|
||||
raise ImproperlyConfigured("DJANGO_ALLOWED_HOSTS 解析为空,请填入实际域名")
|
||||
|
||||
# PostgreSQL(用 DATABASE_URL 环境变量)
|
||||
DATABASES = {
|
||||
@@ -43,14 +53,21 @@ SECURE_REFERRER_POLICY = "same-origin"
|
||||
X_FRAME_OPTIONS = "DENY"
|
||||
|
||||
# 生产不打印 SQL(prod 不继承 dev 的配置,避免 KeyError)
|
||||
# 注意:必须用直接赋值覆盖 base.py 已定义的 dealerhub logger;
|
||||
# setdefault 对已存在的 key 不生效,会导致生产仍是 DEBUG。
|
||||
LOGGING.setdefault("loggers", {})
|
||||
LOGGING["loggers"].setdefault("django", {
|
||||
LOGGING["loggers"]["dealerhub"] = {
|
||||
"handlers": ["console"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
})
|
||||
LOGGING["loggers"].setdefault("django.db.backends", {
|
||||
}
|
||||
LOGGING["loggers"]["django"] = {
|
||||
"handlers": ["console"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
}
|
||||
LOGGING["loggers"]["django.db.backends"] = {
|
||||
"handlers": ["console"],
|
||||
"level": "WARNING",
|
||||
"propagate": False,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from rest_framework_simplejwt.views import (
|
||||
TokenObtainPairView,
|
||||
TokenRefreshView,
|
||||
)
|
||||
from apps.core.auth_views import RateLimitedTokenObtainPairView
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.decorators import (
|
||||
authentication_classes,
|
||||
@@ -24,6 +24,7 @@ import sys
|
||||
from apps.core.urls import core_urlpatterns
|
||||
from apps.finance.views import public_statement
|
||||
from apps.core.demo import enter_demo
|
||||
from apps.core.register import register
|
||||
|
||||
|
||||
@api_view(["GET"])
|
||||
@@ -59,8 +60,9 @@ def spa_index(request):
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path("api/v1/ping/", ping, name="ping"),
|
||||
path("api/v1/auth/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
|
||||
path("api/v1/auth/token/", RateLimitedTokenObtainPairView.as_view(), name="token_obtain_pair"),
|
||||
path("api/v1/auth/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
|
||||
path("api/v1/auth/register/", register, name="auth-register"),
|
||||
path("api/v1/open/statements/<uuid:token>/", public_statement, name="public-statement"),
|
||||
path("api/v1/demo/enter/", enter_demo, name="demo-enter"),
|
||||
path("api/v1/", include(core_urlpatterns)),
|
||||
|
||||
Reference in New Issue
Block a user