|
|
"""
|
|
|
Django settings for djangoblog project.
|
|
|
项目基本配置文件,包含Django运行所需的核心配置项
|
|
|
|
|
|
Generated by 'django-admin startproject' using Django 1.10.2.
|
|
|
基于Django 1.10.2版本创建
|
|
|
|
|
|
For more information on this file, see
|
|
|
https://docs.djangoproject.com/en/1.10/topics/settings/
|
|
|
|
|
|
For the full list of settings and their values, see
|
|
|
https://docs.djangoproject.com/en/1.10/ref/settings/
|
|
|
"""
|
|
|
#wr 导入必要模块
|
|
|
import os
|
|
|
import sys
|
|
|
from pathlib import Path # 用于路径处理的现代工具
|
|
|
|
|
|
from django.utils.translation import gettext_lazy as _ # 国际化翻译工具
|
|
|
|
|
|
|
|
|
def env_to_bool(env, default):
|
|
|
"""
|
|
|
将环境变量值转换为布尔值
|
|
|
:param env: 环境变量名称
|
|
|
:param default: 当环境变量不存在时的默认值
|
|
|
:return: 转换后的布尔值(环境变量为'True'时返回True,否则返回default)
|
|
|
"""
|
|
|
str_val = os.environ.get(env)
|
|
|
return default if str_val is None else str_val == 'True'
|
|
|
|
|
|
|
|
|
#wr 项目路径配置
|
|
|
# 构建项目内部路径,如:BASE_DIR / 'subdir'
|
|
|
BASE_DIR = Path(__file__).resolve().parent.parent # 项目根目录(当前文件的父级父级目录)
|
|
|
|
|
|
|
|
|
#wr 快速开发设置 - 不适用于生产环境
|
|
|
# 安全警告:生产环境中必须保护好密钥
|
|
|
SECRET_KEY = os.environ.get(
|
|
|
'DJANGO_SECRET_KEY') or 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6'
|
|
|
# 优先从环境变量获取密钥,不存在则使用默认(仅开发环境用)
|
|
|
|
|
|
# 安全警告:生产环境必须关闭调试模式
|
|
|
DEBUG = env_to_bool('DJANGO_DEBUG', True) # 调试模式开关,默认开启
|
|
|
# DEBUG = False # 生产环境关闭调试的示例
|
|
|
|
|
|
#wr 测试模式判断:当执行命令包含'test'时视为测试环境
|
|
|
TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test'
|
|
|
|
|
|
#wr 允许访问的主机列表(生产环境需指定具体域名,不可用'*')
|
|
|
# ALLOWED_HOSTS = [] # 默认空列表(仅允许本地访问)
|
|
|
ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com'] # 开发环境允许所有主机访问
|
|
|
|
|
|
#wr Django 4.0新增配置:信任的CSRF来源(跨域请求时需要)
|
|
|
CSRF_TRUSTED_ORIGINS = ['http://example.com']
|
|
|
|
|
|
|
|
|
#wr 应用定义(安装的所有Django应用)
|
|
|
INSTALLED_APPS = [
|
|
|
# 'django.contrib.admin', # 默认管理员界面(全功能版)
|
|
|
'django.contrib.admin.apps.SimpleAdminConfig', # 简化版管理员界面
|
|
|
'django.contrib.auth', # 身份认证系统
|
|
|
'django.contrib.contenttypes', # 内容类型框架(用于权限管理)
|
|
|
'django.contrib.sessions', # 会话管理
|
|
|
'django.contrib.messages', # 消息提示系统
|
|
|
'django.contrib.staticfiles', # 静态文件管理
|
|
|
'django.contrib.sites', # 多站点支持框架
|
|
|
'django.contrib.sitemaps', # 站点地图生成
|
|
|
'mdeditor', # Markdown编辑器(第三方应用)
|
|
|
'haystack', # 全文搜索框架(第三方应用)
|
|
|
'blog', # 自定义博客应用
|
|
|
'accounts', # 自定义用户账户应用
|
|
|
'comments', # 自定义评论应用
|
|
|
'oauth', # 第三方登录(OAuth)应用
|
|
|
'servermanager', # 服务器管理应用
|
|
|
'owntracks', # 位置追踪应用
|
|
|
'compressor', # 静态文件压缩工具(第三方应用)
|
|
|
'djangoblog' # 项目主应用
|
|
|
]
|
|
|
|
|
|
|
|
|
#wr 中间件配置(请求/响应处理的钩子函数)
|
|
|
MIDDLEWARE = [
|
|
|
'django.middleware.security.SecurityMiddleware', # 安全相关处理(如HTTPS重定向)
|
|
|
'django.contrib.sessions.middleware.SessionMiddleware', # 会话管理中间件
|
|
|
'django.middleware.locale.LocaleMiddleware', # 国际化语言处理
|
|
|
'django.middleware.gzip.GZipMiddleware', # 响应内容GZip压缩
|
|
|
# 'django.middleware.cache.UpdateCacheMiddleware', # 缓存更新中间件(按需启用)
|
|
|
'django.middleware.common.CommonMiddleware', # 通用中间件(如URL重写)
|
|
|
# 'django.middleware.cache.FetchFromCacheMiddleware', # 缓存读取中间件(按需启用)
|
|
|
'django.middleware.csrf.CsrfViewMiddleware', # CSRF防护中间件
|
|
|
'django.contrib.auth.middleware.AuthenticationMiddleware', # 用户认证中间件
|
|
|
'django.contrib.messages.middleware.MessageMiddleware', # 消息处理中间件
|
|
|
'django.middleware.clickjacking.XFrameOptionsMiddleware', # 点击劫持防护
|
|
|
'django.middleware.http.ConditionalGetMiddleware', # 处理条件请求(如304响应)
|
|
|
'blog.middleware.OnlineMiddleware' # 自定义在线状态中间件
|
|
|
]
|
|
|
|
|
|
|
|
|
#wr URL根配置
|
|
|
ROOT_URLCONF = 'djangoblog.urls' # 主URL配置模块路径
|
|
|
|
|
|
|
|
|
#wr 模板配置
|
|
|
TEMPLATES = [
|
|
|
{
|
|
|
'BACKEND': 'django.template.backends.django.DjangoTemplates', # 使用Django模板引擎
|
|
|
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 全局模板目录
|
|
|
'APP_DIRS': True, # 允许从应用内的templates目录加载模板
|
|
|
'OPTIONS': {
|
|
|
'context_processors': [ # 模板上下文处理器(全局变量)
|
|
|
'django.template.context_processors.debug', # 调试相关上下文
|
|
|
'django.template.context_processors.request', # 请求对象(request)
|
|
|
'django.contrib.auth.context_processors.auth', # 认证相关上下文
|
|
|
'django.contrib.messages.context_processors.messages', # 消息相关上下文
|
|
|
'blog.context_processors.seo_processor' # 自定义SEO相关上下文
|
|
|
],
|
|
|
},
|
|
|
},
|
|
|
]
|
|
|
|
|
|
|
|
|
#wr WSGI应用配置(部署用)
|
|
|
WSGI_APPLICATION = 'djangoblog.wsgi.application' # WSGI应用入口路径
|
|
|
|
|
|
|
|
|
wr# 数据库配置
|
|
|
DATABASES = {
|
|
|
'default': {
|
|
|
'ENGINE': 'django.db.backends.mysql', # 使用MySQL数据库引擎
|
|
|
'NAME': os.environ.get('DJANGO_MYSQL_DATABASE') or 'djangoblog', # 数据库名
|
|
|
'USER': os.environ.get('DJANGO_MYSQL_USER') or 'root', # 数据库用户名
|
|
|
'PASSWORD': os.environ.get('DJANGO_MYSQL_PASSWORD') or 'root', # 数据库密码
|
|
|
'HOST': os.environ.get('DJANGO_MYSQL_HOST') or '127.0.0.1', # 数据库主机地址
|
|
|
'PORT': int(
|
|
|
os.environ.get('DJANGO_MYSQL_PORT') or 3306), # 数据库端口(默认3306)
|
|
|
'OPTIONS': {
|
|
|
'charset': 'utf8mb4'}, # 字符集(支持emoji表情)
|
|
|
}}
|
|
|
|
|
|
|
|
|
#wr 密码验证配置
|
|
|
|
|
|
AUTH_PASSWORD_VALIDATORS = [
|
|
|
{
|
|
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
|
# 验证密码与用户属性(如用户名、邮箱)的相似度
|
|
|
},
|
|
|
{
|
|
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
|
# 验证密码最小长度(默认8位)
|
|
|
},
|
|
|
{
|
|
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
|
# 验证密码是否在常见密码列表中
|
|
|
},
|
|
|
{
|
|
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
|
# 验证密码是否仅包含数字
|
|
|
},
|
|
|
]
|
|
|
|
|
|
|
|
|
#wr 国际化配置
|
|
|
LANGUAGES = (
|
|
|
('en', _('English')), # 英语
|
|
|
('zh-hans', _('Simplified Chinese')), # 简体中文
|
|
|
('zh-hant', _('Traditional Chinese')), # 繁体中文
|
|
|
)
|
|
|
LOCALE_PATHS = (
|
|
|
os.path.join(BASE_DIR, 'locale'), # 翻译文件存储目录
|
|
|
)
|
|
|
|
|
|
LANGUAGE_CODE = 'zh-hans' # 默认语言(简体中文)
|
|
|
TIME_ZONE = 'Asia/Shanghai' # 时区(上海)
|
|
|
USE_I18N = True # 启用国际化
|
|
|
USE_L10N = True # 启用本地化
|
|
|
USE_TZ = False # 不使用UTC时区(使用本地时区)
|
|
|
|
|
|
|
|
|
#wr 静态文件配置(CSS、JavaScript、图片等)
|
|
|
|
|
|
#wr 全文搜索配置(基于Haystack框架)
|
|
|
HAYSTACK_CONNECTIONS = {
|
|
|
'default': {
|
|
|
'ENGINE': 'djangoblog.whoosh_cn_backend.WhooshEngine', # 使用Whoosh搜索引擎(支持中文)
|
|
|
'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'), # 索引文件存储路径
|
|
|
},
|
|
|
}
|
|
|
#wr 实时更新搜索索引(当数据变化时自动更新)
|
|
|
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
|
|
|
|
|
|
#wr 认证后端:允许使用用户名或邮箱登录
|
|
|
AUTHENTICATION_BACKENDS = [
|
|
|
'accounts.user_login_backend.EmailOrUsernameModelBackend']
|
|
|
|
|
|
|
|
|
#wr 静态文件收集目录(生产环境用,通过collectstatic命令收集)
|
|
|
STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic')
|
|
|
|
|
|
#wr 静态文件URL前缀
|
|
|
STATIC_URL = '/static/'
|
|
|
#wr 静态文件主目录
|
|
|
STATICFILES = os.path.join(BASE_DIR, 'static')
|
|
|
|
|
|
#wr 额外的静态文件目录(如插件静态文件)
|
|
|
STATICFILES_DIRS = [
|
|
|
os.path.join(BASE_DIR, 'plugins'), # 插件静态文件目录
|
|
|
]
|
|
|
|
|
|
|
|
|
#wr 自定义用户模型(替换Django默认用户模型)
|
|
|
AUTH_USER_MODEL = 'accounts.BlogUser'
|
|
|
# 登录页面URL(未登录时访问受保护页面会重定向到此处)
|
|
|
LOGIN_URL = '/login/'
|
|
|
|
|
|
|
|
|
#wr 时间格式配置(模板中使用)
|
|
|
TIME_FORMAT = '%Y-%m-%d %H:%M:%S' # 完整时间格式
|
|
|
DATE_TIME_FORMAT = '%Y-%m-%d' # 日期格式
|
|
|
|
|
|
|
|
|
#wr Bootstrap样式颜色类型(前端样式用)
|
|
|
BOOTSTRAP_COLOR_TYPES = [
|
|
|
'default', 'primary', 'success', 'info', 'warning', 'danger'
|
|
|
]
|
|
|
|
|
|
|
|
|
#wr 分页配置
|
|
|
PAGINATE_BY = 10 # 每页显示10条数据
|
|
|
|
|
|
#wr HTTP缓存超时时间(秒):2592000 = 30天
|
|
|
CACHE_CONTROL_MAX_AGE = 2592000
|
|
|
|
|
|
#wr 缓存配置
|
|
|
CACHES = {
|
|
|
'default': {
|
|
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', # 本地内存缓存
|
|
|
'TIMEOUT': 10800, # 缓存超时时间(秒):10800 = 3小时
|
|
|
'LOCATION': 'unique-snowflake', # 缓存位置标识(唯一即可)
|
|
|
}
|
|
|
}
|
|
|
#wr 若存在Redis环境变量,则使用Redis作为缓存
|
|
|
if os.environ.get("DJANGO_REDIS_URL"):
|
|
|
CACHES = {
|
|
|
'default': {
|
|
|
'BACKEND': 'django.core.cache.backends.redis.RedisCache', # Redis缓存引擎
|
|
|
'LOCATION': f'redis://{os.environ.get("DJANGO_REDIS_URL")}', # Redis连接地址
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
#wr 站点框架配置(用于多站点管理)
|
|
|
SITE_ID = 1
|
|
|
#wr 百度链接提交API地址(用于SEO)
|
|
|
BAIDU_NOTIFY_URL = os.environ.get('DJANGO_BAIDU_NOTIFY_URL') \
|
|
|
or 'http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn'
|
|
|
|
|
|
|
|
|
#wr 邮件配置(用于发送通知、验证码等)
|
|
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # SMTP邮件后端
|
|
|
EMAIL_USE_TLS = env_to_bool('DJANGO_EMAIL_TLS', False) # 是否使用TLS加密
|
|
|
EMAIL_USE_SSL = env_to_bool('DJANGO_EMAIL_SSL', True) # 是否使用SSL加密
|
|
|
EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST') or 'smtp.mxhichina.com' # 邮件服务器地址
|
|
|
EMAIL_PORT = int(os.environ.get('DJANGO_EMAIL_PORT') or 465) # 邮件服务器端口
|
|
|
EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER') # 邮箱用户名
|
|
|
EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD') # 邮箱密码/授权码
|
|
|
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER # 默认发件人
|
|
|
SERVER_EMAIL = EMAIL_HOST_USER # 服务器发件人(用于错误报告)
|
|
|
|
|
|
#wr 管理员邮箱(用于接收系统错误报告)
|
|
|
ADMINS = [('admin', os.environ.get('DJANGO_ADMIN_EMAIL') or 'admin@admin.com')]
|
|
|
|
|
|
#wr 微信管理员密码(二次MD5加密)
|
|
|
WXADMIN = os.environ.get(
|
|
|
'DJANGO_WXADMIN_PASSWORD') or '995F03AC401D6CABABAEF756FC4D43C7'
|
|
|
|
|
|
|
|
|
#wr 日志配置
|
|
|
LOG_PATH = os.path.join(BASE_DIR, 'logs') # 日志文件存储目录
|
|
|
#wr 若日志目录不存在则创建
|
|
|
if not os.path.exists(LOG_PATH):
|
|
|
os.makedirs(LOG_PATH, exist_ok=True)
|
|
|
|
|
|
LOGGING = {
|
|
|
'version': 1, # 日志配置版本
|
|
|
'disable_existing_loggers': False, # 不禁用已存在的日志器
|
|
|
'root': { # 根日志器
|
|
|
'level': 'INFO', # 日志级别(INFO及以上)
|
|
|
'handlers': ['console', 'log_file'], # 使用的处理器
|
|
|
},
|
|
|
'formatters': { # 日志格式
|
|
|
'verbose': { # 详细格式
|
|
|
'format': '[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d %(module)s] %(message)s',
|
|
|
}
|
|
|
},
|
|
|
'filters': { # 日志过滤器
|
|
|
'require_debug_false': { # 仅当DEBUG=False时生效
|
|
|
'()': 'django.utils.log.RequireDebugFalse',
|
|
|
},
|
|
|
'require_debug_true': { # 仅当DEBUG=True时生效
|
|
|
'()': 'django.utils.log.RequireDebugTrue',
|
|
|
},
|
|
|
},
|
|
|
'handlers': { # 日志处理器
|
|
|
'log_file': { # 文件处理器
|
|
|
'level': 'INFO', # 处理INFO及以上级别
|
|
|
'class': 'logging.handlers.TimedRotatingFileHandler', # 按时间轮转的文件处理器
|
|
|
'filename': os.path.join(LOG_PATH, 'djangoblog.log'), # 日志文件路径
|
|
|
'when': 'D', # 每天轮转一次
|
|
|
'formatter': 'verbose', # 使用详细格式
|
|
|
'interval': 1, # 轮转间隔(1天)
|
|
|
'delay': True, # 延迟创建文件
|
|
|
'backupCount': 5, # 保留5个备份
|
|
|
'encoding': 'utf-8' # 编码格式
|
|
|
},
|
|
|
'console': { # 控制台处理器
|
|
|
'level': 'DEBUG', # 处理DEBUG及以上级别
|
|
|
'filters': ['require_debug_true'], # 仅调试模式生效
|
|
|
'class': 'logging.StreamHandler', # 流处理器(输出到控制台)
|
|
|
'formatter': 'verbose' # 使用详细格式
|
|
|
},
|
|
|
'null': { # 空处理器(不处理日志)
|
|
|
'class': 'logging.NullHandler',
|
|
|
},
|
|
|
'mail_admins': { # 邮件通知处理器
|
|
|
'level': 'ERROR', # 仅处理ERROR及以上级别
|
|
|
'filters': ['require_debug_false'], # 仅生产环境生效
|
|
|
'class': 'django.utils.log.AdminEmailHandler' # 发送邮件给管理员
|
|
|
}
|
|
|
},
|
|
|
'loggers': { # 日志器
|
|
|
'djangoblog': { # 项目主日志器
|
|
|
'handlers': ['log_file', 'console'], # 使用文件和控制台处理器
|
|
|
'level': 'INFO', # 日志级别
|
|
|
'propagate': True, # 是否向上传播日志
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
#wr 静态文件压缩配置(使用django-compressor)
|
|
|
STATICFILES_FINDERS = (
|
|
|
'django.contrib.staticfiles.finders.FileSystemFinder', # 从文件系统查找静态文件
|
|
|
'django.contrib.staticfiles.finders.AppDirectoriesFinder', # 从应用目录查找静态文件
|
|
|
'compressor.finders.CompressorFinder', # 压缩器查找器
|
|
|
)
|
|
|
COMPRESS_ENABLED = True # 启用压缩
|
|
|
#wr 根据环境变量决定是否启用离线压缩(预压缩静态文件)
|
|
|
COMPRESS_OFFLINE = os.environ.get('COMPRESS_OFFLINE', 'False').lower() == 'true'
|
|
|
|
|
|
#wr 压缩文件输出目录
|
|
|
COMPRESS_OUTPUT_DIR = 'compressed'
|
|
|
|
|
|
#wr 压缩文件名包含哈希值(用于缓存失效)
|
|
|
COMPRESS_CSS_HASHING_METHOD = 'mtime' # 基于修改时间生成哈希
|
|
|
COMPRESS_JS_HASHING_METHOD = 'mtime'
|
|
|
|
|
|
#wr CSS压缩过滤器
|
|
|
COMPRESS_CSS_FILTERS = [
|
|
|
'compressor.filters.css_default.CssAbsoluteFilter', # 转换为绝对URL
|
|
|
'compressor.filters.cssmin.CSSCompressorFilter', # CSS压缩
|
|
|
]
|
|
|
|
|
|
#wr JS压缩过滤器
|
|
|
COMPRESS_JS_FILTERS = [
|
|
|
'compressor.filters.jsmin.SlimItFilter', # JS压缩
|
|
|
]
|
|
|
|
|
|
#wr 压缩缓存配置
|
|
|
COMPRESS_CACHE_BACKEND = 'default' # 使用默认缓存
|
|
|
COMPRESS_CACHE_KEY_FUNCTION = 'compressor.cache.simple_cachekey' # 缓存键生成函数
|
|
|
|
|
|
#wr 预编译配置(处理Sass/SCSS)
|
|
|
COMPRESS_PRECOMPILERS = (
|
|
|
('text/x-scss', 'django_libsass.SassCompiler'), # 编译SCSS
|
|
|
('text/x-sass', 'django_libsass.SassCompiler'), # 编译Sass
|
|
|
)
|
|
|
|
|
|
#wr 压缩性能优化
|
|
|
COMPRESS_MINT_DELAY = 30 # 压缩延迟(秒)
|
|
|
COMPRESS_MTIME_DELAY = 10 # 修改时间检查延迟
|
|
|
COMPRESS_REBUILD_TIMEOUT = 2592000 # 重建超时(30天)
|
|
|
|
|
|
#wr 压缩器配置
|
|
|
COMPRESS_CSS_COMPRESSOR = 'compressor.css.CssCompressor' # CSS压缩器
|
|
|
COMPRESS_JS_COMPRESSOR = 'compressor.js.JsCompressor' # JS压缩器
|
|
|
|
|
|
#wr 静态文件缓存配置(文件名包含哈希,用于缓存破坏)
|
|
|
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
|
|
|
|
|
#wr 压缩文件URL和根目录(与静态文件一致)
|
|
|
COMPRESS_URL = STATIC_URL
|
|
|
COMPRESS_ROOT = STATIC_ROOT
|
|
|
|
|
|
|
|
|
#wr 媒体文件配置(用户上传文件)
|
|
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') # 上传文件存储目录
|
|
|
MEDIA_URL = '/media/' # 媒体文件URL前缀
|
|
|
|
|
|
#wr X-Frame-Options配置:允许同源页面嵌入iframe(如Markdown编辑器预览)
|
|
|
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
|
|
|
|
|
|
|
|
#wr 默认自增字段类型(Django 3.2+新增)
|
|
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' # 使用BigInt类型自增ID
|
|
|
|
|
|
|
|
|
#wr Elasticsearch配置(若存在环境变量则启用)
|
|
|
if os.environ.get('DJANGO_ELASTICSEARCH_HOST'):
|
|
|
ELASTICSEARCH_DSL = {
|
|
|
'default': {
|
|
|
'hosts': os.environ.get('DJANGO_ELASTICSEARCH_HOST') # Elasticsearch地址
|
|
|
},
|
|
|
}
|
|
|
#wr 替换Haystack引擎为Elasticsearch
|
|
|
HAYSTACK_CONNECTIONS = {
|
|
|
'default': {
|
|
|
'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine',
|
|
|
},
|
|
|
}
|
|
|
|
|
|
|
|
|
#wr 插件系统配置
|
|
|
PLUGINS_DIR = BASE_DIR / 'plugins' # 插件目录
|
|
|
ACTIVE_PLUGINS = [ # 激活的插件列表
|
|
|
'article_copyright', # 文章版权信息插件
|
|
|
'reading_time', # 阅读时间估算插件
|
|
|
'external_links', # 外部链接处理插件
|
|
|
'view_count', # 浏览量统计插件
|
|
|
'seo_optimizer', # SEO优化插件
|
|
|
'image_lazy_loading', # 图片懒加载插件
|
|
|
'article_recommendation', # 文章推荐插件
|
|
|
]
|