/postcomment', # URL路径,包含文章ID参数(整数类型)
+ views.CommentPostView.as_view(), # 关联的视图类,使用as_view()方法转换为可调用视图
+ name='postcomment' # 该URL的名称,用于反向解析
+ ),
+]
\ No newline at end of file
diff --git a/src/DjangoBlog-master/comments/utils.py b/src/DjangoBlog-master/comments/utils.py
index f01dba7..32de978 100644
--- a/src/DjangoBlog-master/comments/utils.py
+++ b/src/DjangoBlog-master/comments/utils.py
@@ -1,28 +1,45 @@
-import logging
+import logging # 导入日志模块,用于记录程序运行中的日志信息
-from django.utils.translation import gettext_lazy as _
+from django.utils.translation import gettext_lazy as _ # 导入翻译函数,支持国际化文本
-from djangoblog.utils import get_current_site
-from djangoblog.utils import send_email
+from djangoblog.utils import get_current_site # 从自定义工具模块导入获取当前站点域名的函数
+from djangoblog.utils import send_email # 从自定义工具模块导入发送邮件的函数
-logger = logging.getLogger(__name__)
+logger = logging.getLogger(__name__) # 创建当前模块的日志记录器,用于记录该模块的日志
def send_comment_email(comment):
+ """
+ 发送评论相关邮件:
+ 1. 向评论作者发送评论成功的感谢邮件
+ 2. 若当前评论是回复(有父评论),向父评论作者发送回复通知邮件
+ """
+ # 获取当前网站的域名(用于拼接文章链接)
site = get_current_site().domain
+ # 邮件主题:评论感谢(支持国际化)
subject = _('Thanks for your comment')
+ # 拼接评论对应的文章访问链接(HTTPS协议)
article_url = f"https://{site}{comment.article.get_absolute_url()}"
+ # 构建给评论作者的HTML格式邮件内容(支持国际化,通过占位符注入动态数据)
html_content = _("""Thank you very much for your comments on this site
You can visit %(article_title)s
to review your comments,
Thank you again!
If the link above cannot be opened, please copy this link to your browser.
- %(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title}
+ %(article_url)s""") % {
+ 'article_url': article_url, # 文章访问链接
+ 'article_title': comment.article.title # 文章标题
+ }
+ # 评论作者的邮箱(收件人)
tomail = comment.author.email
+ # 调用发送邮件函数,向评论作者发送感谢邮件
send_email([tomail], subject, html_content)
+
try:
+ # 判断当前评论是否有父评论(即是否是回复评论)
if comment.parent_comment:
+ # 构建给父评论作者的HTML格式邮件内容(回复通知,支持国际化)
html_content = _("""Your comment on %(article_title)s
has
received a reply.
%(comment_body)s
@@ -30,9 +47,16 @@ def send_comment_email(comment):
If the link above cannot be opened, please copy this link to your browser.
%(article_url)s
- """) % {'article_url': article_url, 'article_title': comment.article.title,
- 'comment_body': comment.parent_comment.body}
+ """) % {
+ 'article_url': article_url, # 文章访问链接
+ 'article_title': comment.article.title, # 文章标题
+ 'comment_body': comment.parent_comment.body # 父评论的内容(供作者识别)
+ }
+ # 父评论作者的邮箱(收件人)
tomail = comment.parent_comment.author.email
+ # 调用发送邮件函数,向父评论作者发送回复通知邮件
send_email([tomail], subject, html_content)
+ # 捕获发送回复邮件过程中的异常(避免单个邮件发送失败影响整体流程)
except Exception as e:
- logger.error(e)
+ # 记录异常日志(便于问题排查)
+ logger.error(e)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/comments/views.py b/src/DjangoBlog-master/comments/views.py
index ad9b2b9..57ffd52 100644
--- a/src/DjangoBlog-master/comments/views.py
+++ b/src/DjangoBlog-master/comments/views.py
@@ -1,63 +1,76 @@
# Create your views here.
-from django.core.exceptions import ValidationError
-from django.http import HttpResponseRedirect
-from django.shortcuts import get_object_or_404
-from django.utils.decorators import method_decorator
-from django.views.decorators.csrf import csrf_protect
-from django.views.generic.edit import FormView
+from django.core.exceptions import ValidationError # 导入验证异常类,用于处理验证错误
+from django.http import HttpResponseRedirect # 导入HTTP重定向类,用于页面跳转
+from django.shortcuts import get_object_or_404 # 导入获取对象或返回404的工具函数
+from django.utils.decorators import method_decorator # 导入方法装饰器工具,用于为类视图方法添加装饰器
+from django.views.decorators.csrf import csrf_protect # 导入CSRF保护装饰器,防止跨站请求伪造
+from django.views.generic.edit import FormView # 导入表单视图基类,用于处理表单提交逻辑
-from accounts.models import BlogUser
-from blog.models import Article
-from .forms import CommentForm
-from .models import Comment
+from accounts.models import BlogUser # 从accounts应用导入用户模型
+from blog.models import Article # 从blog应用导入文章模型
+from .forms import CommentForm # 从当前应用导入评论表单
+from .models import Comment # 从当前应用导入评论模型
class CommentPostView(FormView):
- form_class = CommentForm
- template_name = 'blog/article_detail.html'
+ """评论提交视图类,处理评论发布功能"""
+ form_class = CommentForm # 指定使用的表单类为CommentForm
+ template_name = 'blog/article_detail.html' # 指定表单验证失败时渲染的模板
- @method_decorator(csrf_protect)
+ @method_decorator(csrf_protect) # 为dispatch方法添加CSRF保护
def dispatch(self, *args, **kwargs):
+ # 调用父类的dispatch方法,处理请求分发
return super(CommentPostView, self).dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
- article_id = self.kwargs['article_id']
- article = get_object_or_404(Article, pk=article_id)
- url = article.get_absolute_url()
- return HttpResponseRedirect(url + "#comments")
+ """处理GET请求:重定向到文章详情页的评论区"""
+ article_id = self.kwargs['article_id'] # 从URL参数中获取文章ID
+ article = get_object_or_404(Article, pk=article_id) # 获取对应的文章对象,不存在则返回404
+ url = article.get_absolute_url() # 获取文章的绝对URL
+ return HttpResponseRedirect(url + "#comments") # 重定向到文章详情页的评论区锚点
def form_invalid(self, form):
- article_id = self.kwargs['article_id']
- article = get_object_or_404(Article, pk=article_id)
+ """处理表单验证失败的情况"""
+ article_id = self.kwargs['article_id'] # 获取文章ID
+ article = get_object_or_404(Article, pk=article_id) # 获取文章对象
+ # 渲染文章详情页,传递错误的表单和文章对象(用于显示错误信息)
return self.render_to_response({
- 'form': form,
- 'article': article
+ 'form': form, # 验证失败的表单(包含错误信息)
+ 'article': article # 文章对象
})
def form_valid(self, form):
- """提交的数据验证合法后的逻辑"""
- user = self.request.user
- author = BlogUser.objects.get(pk=user.pk)
- article_id = self.kwargs['article_id']
- article = get_object_or_404(Article, pk=article_id)
+ """处理表单验证通过后的逻辑:保存评论并跳转"""
+ user = self.request.user # 获取当前登录用户
+ author = BlogUser.objects.get(pk=user.pk) # 获取用户对应的BlogUser对象
+ article_id = self.kwargs['article_id'] # 获取文章ID
+ article = get_object_or_404(Article, pk=article_id) # 获取文章对象
+ # 检查文章是否允许评论(评论状态为关闭或文章状态为草稿则不允许评论)
if article.comment_status == 'c' or article.status == 'c':
- raise ValidationError("该文章评论已关闭.")
- comment = form.save(False)
- comment.article = article
+ raise ValidationError("该文章评论已关闭.") # 抛出验证异常
+
+ comment = form.save(False) # 不立即保存表单数据,返回评论对象
+ comment.article = article # 设置评论关联的文章
+
+ # 获取博客设置,判断评论是否需要审核
from djangoblog.utils import get_blog_setting
settings = get_blog_setting()
- if not settings.comment_need_review:
- comment.is_enable = True
- comment.author = author
+ if not settings.comment_need_review: # 如果不需要审核
+ comment.is_enable = True # 直接设置评论为启用状态
+ comment.author = author # 设置评论的作者
+
+ # 处理回复功能:如果存在父评论ID,则设置父评论
if form.cleaned_data['parent_comment_id']:
parent_comment = Comment.objects.get(
- pk=form.cleaned_data['parent_comment_id'])
- comment.parent_comment = parent_comment
+ pk=form.cleaned_data['parent_comment_id']) # 获取父评论对象
+ comment.parent_comment = parent_comment # 设置当前评论的父评论
+
+ comment.save(True) # 保存评论到数据库
- comment.save(True)
+ # 重定向到文章详情页的当前评论位置(带锚点)
return HttpResponseRedirect(
"%s#div-comment-%d" %
- (article.get_absolute_url(), comment.pk))
+ (article.get_absolute_url(), comment.pk)) # 拼接URL,包含评论ID锚点
diff --git a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml
index 83e35ff..764aa81 100644
--- a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml
+++ b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml
@@ -1,48 +1,52 @@
+# Docker Compose配置文件,版本为3(指定兼容的Compose语法版本)
version: '3'
+# 定义所有服务(容器)
services:
+ # 1. Elasticsearch服务(用于全文搜索功能,集成IK中文分词器)
es:
- image: liangliangyy/elasticsearch-analysis-ik:8.6.1
- container_name: es
- restart: always
- environment:
- - discovery.type=single-node
- - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- ports:
+ image: liangliangyy/elasticsearch-analysis-ik:8.6.1 # 使用带IK分词器的ES镜像,版本8.6.1
+ container_name: es # 容器名称固定为"es",便于管理
+ restart: always # 容器退出后自动重启(确保服务持续运行)
+ environment: # 环境变量配置
+ - discovery.type=single-node # 单节点模式(无需集群,适合测试/小型部署)
+ - "ES_JAVA_OPTS=-Xms512m -Xmx512m" # 设置JVM内存大小(初始/最大均为512M,避免内存溢出)
+ ports: # 端口映射:主机9200端口 → 容器9200端口(ES默认API端口)
- 9200:9200
- volumes:
- - ./bin/datas/es/:/usr/share/elasticsearch/data/
+ volumes: # 数据卷挂载:持久化ES数据
+ - ./bin/datas/es/:/usr/share/elasticsearch/data/ # 主机目录 → 容器内ES数据存储目录
+ # 2. Kibana服务(ES的可视化管理工具,用于操作/监控ES)
kibana:
- image: kibana:8.6.1
- restart: always
- container_name: kibana
- ports:
+ image: kibana:8.6.1 # Kibana镜像,版本需与ES一致(8.6.1)
+ restart: always # 容器退出后自动重启
+ container_name: kibana # 容器名称固定为"kibana"
+ ports: # 端口映射:主机5601端口 → 容器5601端口(Kibana默认Web端口)
- 5601:5601
- environment:
- - ELASTICSEARCH_HOSTS=http://es:9200
+ environment: # 环境变量配置:指定关联的ES地址
+ - ELASTICSEARCH_HOSTS=http://es:9200 # 指向同网络内的"es"服务(容器间通过服务名通信)
+ # 3. Django博客服务(核心应用服务)
djangoblog:
- build: .
- restart: always
- command: bash -c 'sh /code/djangoblog/bin/docker_start.sh'
- ports:
+ build: . # 基于当前目录的Dockerfile构建镜像(不使用现成镜像,需本地有Dockerfile)
+ restart: always # 容器退出后自动重启
+ command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' # 容器启动后执行的命令:运行启动脚本
+ ports: # 端口映射:主机8000端口 → 容器8000端口(Django默认开发服务器端口)
- "8000:8000"
- volumes:
- - ./collectedstatic:/code/djangoblog/collectedstatic
- - ./uploads:/code/djangoblog/uploads
- environment:
- - DJANGO_MYSQL_DATABASE=djangoblog
- - DJANGO_MYSQL_USER=root
- - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E
- - DJANGO_MYSQL_HOST=db
- - DJANGO_MYSQL_PORT=3306
- - DJANGO_MEMCACHED_LOCATION=memcached:11211
- - DJANGO_ELASTICSEARCH_HOST=es:9200
- links:
+ volumes: # 数据卷挂载:持久化应用数据/静态资源
+ - ./collectedstatic:/code/djangoblog/collectedstatic # 主机静态资源目录 → 容器内静态资源目录(Nginx可直接访问)
+ - ./uploads:/code/djangoblog/uploads # 主机上传文件目录 → 容器内上传文件目录(如博客图片)
+ environment: # 环境变量配置:Django应用的关键参数(数据库、缓存、ES等)
+ - DJANGO_MYSQL_DATABASE=djangoblog # Django连接的MySQL数据库名
+ - DJANGO_MYSQL_USER=root # MySQL用户名
+ - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E # MySQL密码
+ - DJANGO_MYSQL_HOST=db # MySQL服务地址(指向同网络内的"db"服务,需额外配置db服务)
+ - DJANGO_MYSQL_PORT=3306 # MySQL端口
+ - DJANGO_MEMCACHED_LOCATION=memcached:11211 # Memcached缓存地址(指向同网络内的"memcached"服务,需额外配置)
+ - DJANGO_ELASTICSEARCH_HOST=es:9200 # ES服务地址(指向同网络内的"es"服务)
+ links: # 显式链接到其他服务(已逐步被depends_on替代,此处用于兼容)
+ - db # 链接到MySQL服务
+ - memcached # 链接到Memcached服务
+ depends_on: # 服务依赖:启动djangoblog前,先启动db服务(确保数据库就绪)
- db
- - memcached
- depends_on:
- - db
- container_name: djangoblog
-
+ container_name: djangoblog # 容器名称固定为"djangoblog"
\ No newline at end of file
diff --git a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml
index 9609af3..902b118 100644
--- a/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml
+++ b/src/DjangoBlog-master/deploy/docker-compose/docker-compose.yml
@@ -1,60 +1,67 @@
+# Docker Compose配置文件,版本为3(指定Compose语法版本)
version: '3'
+# 定义所有服务(容器)
services:
+ # 1. MySQL数据库服务(存储应用数据)
db:
- image: mysql:latest
- restart: always
- environment:
- - MYSQL_DATABASE=djangoblog
- - MYSQL_ROOT_PASSWORD=DjAnGoBlOg!2!Q@W#E
- ports:
+ image: mysql:latest # 使用最新版MySQL镜像
+ restart: always # 容器退出后自动重启(确保服务持续运行)
+ environment: # 环境变量配置(数据库初始化参数)
+ - MYSQL_DATABASE=djangoblog # 自动创建的数据库名称
+ - MYSQL_ROOT_PASSWORD=DjAnGoBlOg!2!Q@W#E # MySQL root用户密码
+ ports: # 端口映射:主机3306端口 → 容器3306端口(MySQL默认端口)
- 3306:3306
- volumes:
- - ./bin/datas/mysql/:/var/lib/mysql
- depends_on:
+ volumes: # 数据卷挂载:持久化MySQL数据
+ - ./bin/datas/mysql/:/var/lib/mysql # 主机目录 → 容器内MySQL数据存储目录
+ depends_on: # 服务依赖:启动db前先启动redis(可能用于数据库缓存等场景)
- redis
- container_name: db
+ container_name: db # 容器名称固定为"db"
+ # 2. Django博客应用服务(核心应用)
djangoblog:
- build:
- context: ../../
- restart: always
- command: bash -c 'sh /code/djangoblog/bin/docker_start.sh'
- ports:
+ build: # 构建配置
+ context: ../../ # 指定Dockerfile所在的上下文目录(上级目录的上级目录)
+ restart: always # 容器退出后自动重启
+ command: bash -c 'sh /code/djangoblog/bin/docker_start.sh' # 启动命令:执行应用启动脚本
+ ports: # 端口映射:主机8000端口 → 容器8000端口(Django应用端口)
- "8000:8000"
- volumes:
- - ./collectedstatic:/code/djangoblog/collectedstatic
- - ./logs:/code/djangoblog/logs
- - ./uploads:/code/djangoblog/uploads
- environment:
- - DJANGO_MYSQL_DATABASE=djangoblog
- - DJANGO_MYSQL_USER=root
- - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E
- - DJANGO_MYSQL_HOST=db
- - DJANGO_MYSQL_PORT=3306
- - DJANGO_REDIS_URL=redis:6379
- links:
+ volumes: # 数据卷挂载:持久化应用数据和配置
+ - ./collectedstatic:/code/djangoblog/collectedstatic # 静态资源目录(供Nginx访问)
+ - ./logs:/code/djangoblog/logs # 应用日志目录
+ - ./uploads:/code/djangoblog/uploads # 用户上传文件目录(如图片)
+ environment: # 环境变量配置(应用连接参数)
+ - DJANGO_MYSQL_DATABASE=djangoblog # 数据库名称(与db服务对应)
+ - DJANGO_MYSQL_USER=root # 数据库用户名
+ - DJANGO_MYSQL_PASSWORD=DjAnGoBlOg!2!Q@W#E # 数据库密码(与db服务对应)
+ - DJANGO_MYSQL_HOST=db # 数据库服务地址(指向同网络内的"db"服务)
+ - DJANGO_MYSQL_PORT=3306 # 数据库端口
+ - DJANGO_REDIS_URL=redis:6379 # Redis服务地址(指向同网络内的"redis"服务)
+ links: # 显式链接到其他服务(用于容器间通信)
+ - db # 链接到MySQL服务
+ - redis # 链接到Redis服务
+ depends_on: # 服务依赖:启动djangoblog前先启动db服务(确保数据库就绪)
- db
- - redis
- depends_on:
- - db
- container_name: djangoblog
+ container_name: djangoblog # 容器名称固定为"djangoblog"
+
+ # 3. Nginx服务(反向代理和静态资源服务)
nginx:
- restart: always
- image: nginx:latest
- ports:
+ restart: always # 容器退出后自动重启
+ image: nginx:latest # 使用最新版Nginx镜像
+ ports: # 端口映射:HTTP(80)和HTTPS(443)端口
- "80:80"
- "443:443"
- volumes:
- - ./bin/nginx.conf:/etc/nginx/nginx.conf
- - ./collectedstatic:/code/djangoblog/collectedstatic
- links:
- - djangoblog:djangoblog
- container_name: nginx
+ volumes: # 数据卷挂载:Nginx配置和静态资源
+ - ./bin/nginx.conf:/etc/nginx/nginx.conf # 主机Nginx配置文件 → 容器内Nginx配置文件
+ - ./collectedstatic:/code/djangoblog/collectedstatic # 静态资源目录(与djangoblog服务共享)
+ links: # 链接到djangoblog服务,实现反向代理
+ - djangoblog:djangoblog # 将djangoblog服务映射为"djangoblog"主机名
+ container_name: nginx # 容器名称固定为"nginx"
+ # 4. Redis服务(缓存服务,用于提升应用性能)
redis:
- restart: always
- image: redis:latest
- container_name: redis
- ports:
- - "6379:6379"
+ restart: always # 容器退出后自动重启
+ image: redis:latest # 使用最新版Redis镜像
+ container_name: redis # 容器名称固定为"redis"
+ ports: # 端口映射:主机6379端口 → 容器6379端口(Redis默认端口)
+ - "6379:6379"
\ No newline at end of file
diff --git a/src/DjangoBlog-master/deploy/k8s/configmap.yaml b/src/DjangoBlog-master/deploy/k8s/configmap.yaml
index 835d4ad..da23389 100644
--- a/src/DjangoBlog-master/deploy/k8s/configmap.yaml
+++ b/src/DjangoBlog-master/deploy/k8s/configmap.yaml
@@ -1,119 +1,124 @@
-apiVersion: v1
-kind: ConfigMap
+apiVersion: v1 # Kubernetes API版本,v1为稳定版本
+kind: ConfigMap # 资源类型为ConfigMap,用于存储非敏感配置数据
metadata:
- name: web-nginx-config
- namespace: djangoblog
-data:
- nginx.conf: |
- user nginx;
- worker_processes auto;
- error_log /var/log/nginx/error.log notice;
- pid /var/run/nginx.pid;
+ name: web-nginx-config # ConfigMap名称,标识该Nginx配置资源
+ namespace: djangoblog # 所属命名空间,用于资源隔离,对应djangoblog应用
+data: # 配置数据,键为文件名,值为文件内容
+ nginx.conf: | # Nginx主配置文件
+ user nginx; # Nginx进程运行的用户
+ worker_processes auto; # 工作进程数,auto表示按CPU核心数自动分配
+ error_log /var/log/nginx/error.log notice; # 错误日志路径及级别(notice级别)
+ pid /var/run/nginx.pid; # Nginx进程PID文件路径
- events {
- worker_connections 1024;
- multi_accept on;
- use epoll;
+ events { # 事件处理配置块
+ worker_connections 1024; # 每个工作进程最大连接数
+ multi_accept on; # 允许工作进程同时接受多个新连接
+ use epoll; # 使用epoll I/O模型(Linux下高效事件驱动模型)
}
- http {
- include /etc/nginx/mime.types;
- default_type application/octet-stream;
+ http { # HTTP核心配置块
+ include /etc/nginx/mime.types; # 引入MIME类型映射文件(识别文件类型)
+ default_type application/octet-stream; # 默认MIME类型(未知类型时使用)
+ # 定义日志格式,命名为main
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
+ access_log /var/log/nginx/access.log main; # 访问日志路径,使用main格式
- access_log /var/log/nginx/access.log main;
+ sendfile on; # 启用sendfile系统调用(高效传输文件)
+ keepalive_timeout 65; # 长连接超时时间(65秒)
+ gzip on; # 启用gzip压缩(减少传输数据量)
+ gzip_disable "msie6"; # 对IE6浏览器禁用gzip(兼容性处理)
- sendfile on;
- keepalive_timeout 65;
- gzip on;
- gzip_disable "msie6";
-
- gzip_vary on;
- gzip_proxied any;
- gzip_comp_level 8;
- gzip_buffers 16 8k;
- gzip_http_version 1.1;
+ # gzip压缩补充配置
+ gzip_vary on; # 启用Vary: Accept-Encoding响应头(告知代理缓存压缩/非压缩版本)
+ gzip_proxied any; # 对所有代理请求启用压缩
+ gzip_comp_level 8; # 压缩级别(1-9,8为较高压缩率)
+ gzip_buffers 16 8k; # 压缩缓冲区大小(16个8k缓冲区)
+ gzip_http_version 1.1; # 仅对HTTP/1.1及以上版本启用压缩
+ # 需压缩的文件类型(文本、JS、CSS、图片等)
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
- # Include server configurations
- include /etc/nginx/conf.d/*.conf;
+ include /etc/nginx/conf.d/*.conf; # 引入其他服务器配置文件
}
- djangoblog.conf: |
- server {
- server_name lylinux.net;
- root /code/djangoblog/collectedstatic/;
- listen 80;
- keepalive_timeout 70;
- location /static/ {
- expires max;
- alias /code/djangoblog/collectedstatic/;
+ djangoblog.conf: | # lylinux.net域名的Nginx站点配置
+ server { # 处理lylinux.net域名的服务配置
+ server_name lylinux.net; # 绑定的主域名
+ root /code/djangoblog/collectedstatic/; # 网站根目录(静态文件目录)
+ listen 80; # 监听80端口(HTTP)
+ keepalive_timeout 70; # 该站点长连接超时时间
+
+ location /static/ { # 处理静态文件请求
+ expires max; # 静态文件缓存有效期设为最大(长期缓存)
+ alias /code/djangoblog/collectedstatic/; # 静态文件实际路径
}
+ # 处理特定静态文件(如robots.txt、网站验证文件等)
location ~* (robots\.txt|ads\.txt|favicon\.ico|favion\.ico|crossdomain\.xml|google93fd32dbd906620a\.html|BingSiteAuth\.xml|baidu_verify_Ijeny6KrmS\.html)$ {
- root /resource/djangopub;
- expires 1d;
- access_log off;
- error_log off;
+ root /resource/djangopub; # 这些文件的根目录
+ expires 1d; # 缓存1天
+ access_log off; # 关闭访问日志
+ error_log off; # 关闭错误日志
}
- location / {
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header Host $http_host;
- proxy_set_header X-NginX-Proxy true;
- proxy_redirect off;
+ location / { # 处理其他所有请求(反向代理到Django)
+ # 设置代理请求头(传递客户端信息给后端)
+ proxy_set_header X-Real-IP $remote_addr; # 客户端真实IP
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 代理链IP列表
+ proxy_set_header Host $http_host; # 原始请求Host
+ proxy_set_header X-NginX-Proxy true; # 标识经Nginx代理
+ proxy_redirect off; # 禁用代理重定向
+
+ # 若请求文件不存在,反向代理到Django服务(djangoblog为K8s内部服务名)
if (!-f $request_filename) {
proxy_pass http://djangoblog:8000;
break;
}
}
}
- server {
- server_name www.lylinux.net;
- listen 80;
- return 301 https://lylinux.net$request_uri;
+ server { # 处理www.lylinux.net域名(重定向配置)
+ server_name www.lylinux.net; # 绑定的www子域名
+ listen 80; # 监听80端口
+ return 301 https://lylinux.net$request_uri; # 永久重定向到主域名HTTPS地址
}
- resource.lylinux.net.conf: |
+ resource.lylinux.net.conf: | # resource.lylinux.net子域名的配置(资源服务器)
server {
- index index.html index.htm;
- server_name resource.lylinux.net;
- root /resource/;
+ index index.html index.htm; # 默认索引文件
+ server_name resource.lylinux.net; # 绑定的资源子域名
+ root /resource/; # 资源文件根目录
- location /djangoblog/ {
- alias /code/djangoblog/collectedstatic/;
+ location /djangoblog/ { # 映射Django静态文件路径
+ alias /code/djangoblog/collectedstatic/; # 实际静态文件路径
}
- access_log off;
- error_log off;
- include lylinux/resource.conf;
+ access_log off; # 关闭访问日志
+ error_log off; # 关闭错误日志
+ include lylinux/resource.conf; # 引入通用资源配置
}
- lylinux.resource.conf: |
- expires max;
- access_log off;
- log_not_found off;
- add_header Pragma public;
- add_header Cache-Control "public";
- add_header "Access-Control-Allow-Origin" "*";
+ lylinux.resource.conf: | # 通用资源配置(被资源服务器引用)
+ expires max; # 资源缓存有效期设为最大
+ access_log off; # 关闭访问日志
+ log_not_found off; # 关闭文件未找到的错误日志
+ add_header Pragma public; # 缓存控制头(告知客户端可缓存)
+ add_header Cache-Control "public"; # 缓存控制头(公开可缓存)
+ add_header "Access-Control-Allow-Origin" "*"; # 允许跨域访问(所有域名)
---
-apiVersion: v1
-kind: ConfigMap
+apiVersion: v1 # Kubernetes API版本
+kind: ConfigMap # 资源类型为ConfigMap,存储环境变量
metadata:
- name: djangoblog-env
- namespace: djangoblog
-data:
- DJANGO_MYSQL_DATABASE: djangoblog
- DJANGO_MYSQL_USER: db_user
- DJANGO_MYSQL_PASSWORD: db_password
- DJANGO_MYSQL_HOST: db_host
- DJANGO_MYSQL_PORT: db_port
- DJANGO_REDIS_URL: "redis:6379"
- DJANGO_DEBUG: "False"
- MYSQL_ROOT_PASSWORD: db_password
- MYSQL_DATABASE: djangoblog
- MYSQL_PASSWORD: db_password
- DJANGO_SECRET_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-
+ name: djangoblog-env # ConfigMap名称,标识Django环境变量配置
+ namespace: djangoblog # 所属命名空间(与应用一致)
+data: # 环境变量键值对
+ DJANGO_MYSQL_DATABASE: djangoblog # Django连接的MySQL数据库名
+ DJANGO_MYSQL_USER: db_user # MySQL登录用户名
+ DJANGO_MYSQL_PASSWORD: db_password # MySQL登录密码
+ DJANGO_MYSQL_HOST: db_host # MySQL服务地址(K8s内部服务名或IP)
+ DJANGO_MYSQL_PORT: db_port # MySQL服务端口
+ DJANGO_REDIS_URL: "redis:6379" # Redis服务地址及端口
+ DJANGO_DEBUG: "False" # Django调试模式(生产环境关闭)
+ MYSQL_ROOT_PASSWORD: db_password # MySQL root用户密码(用于初始化)
+ MYSQL_DATABASE: djangoblog # 初始化的MySQL数据库名
+ MYSQL_PASSWORD: db_password # MySQL普通用户密码(与Django配置一致)
+ DJANGO_SECRET_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Django加密密钥(用于会话、CSRF等)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/deploy/k8s/deployment.yaml b/src/DjangoBlog-master/deploy/k8s/deployment.yaml
index 414fdcc..c448b50 100644
--- a/src/DjangoBlog-master/deploy/k8s/deployment.yaml
+++ b/src/DjangoBlog-master/deploy/k8s/deployment.yaml
@@ -1,132 +1,161 @@
+# 第一部分:Django 博客应用部署配置
+# apiVersion 指定 Kubernetes API 版本,apps/v1 是 Deployment 资源的稳定版本
apiVersion: apps/v1
+# kind 定义资源类型为 Deployment(用于管理Pod的创建和扩展)
kind: Deployment
metadata:
+ # Deployment 的名称
name: djangoblog
+ # 部署所在的命名空间(用于资源隔离)
namespace: djangoblog
+ # 为 Deployment 添加标签(用于筛选和关联资源)
labels:
app: djangoblog
spec:
+ # 副本数:指定运行的 Pod 数量为 3 个(实现高可用)
replicas: 3
+ # 选择器:用于匹配要管理的 Pod 标签(必须与下面 template.metadata.labels 一致)
selector:
matchLabels:
app: djangoblog
+ # Pod 模板:定义要创建的 Pod 的规格
template:
metadata:
+ # Pod 的标签(与上面的 selector.matchLabels 对应)
labels:
app: djangoblog
spec:
+ # 容器列表:一个 Pod 可以包含多个容器,这里定义应用容器
containers:
- - name: djangoblog
+ - name: djangoblog # 容器名称
+ # 容器使用的镜像(Django 博客应用镜像)
image: liangliangyy/djangoblog:latest
+ # 镜像拉取策略:Always 表示每次都从仓库拉取最新镜像
imagePullPolicy: Always
+ # 容器暴露的端口(Django 应用默认运行在 8000 端口)
ports:
- containerPort: 8000
+ # 从配置映射(ConfigMap)中注入环境变量
envFrom:
- configMapRef:
- name: djangoblog-env
+ name: djangoblog-env # 引用的 ConfigMap 名称
+ # 就绪探针:判断容器是否已准备好接收请求(服务发现会依赖此状态)
readinessProbe:
- httpGet:
- path: /
- port: 8000
- initialDelaySeconds: 10
- periodSeconds: 30
+ httpGet: # 通过 HTTP 请求检查就绪状态
+ path: / # 检查的路径(应用根目录)
+ port: 8000 # 检查的端口
+ initialDelaySeconds: 10 # 容器启动后延迟 10 秒开始首次检查
+ periodSeconds: 30 # 每隔 30 秒检查一次
+ # 存活探针:判断容器是否存活,若失败会重启容器
livenessProbe:
- httpGet:
- path: /
- port: 8000
- initialDelaySeconds: 10
- periodSeconds: 30
+ httpGet: # 通过 HTTP 请求检查存活状态
+ path: / # 检查的路径
+ port: 8000 # 检查的端口
+ initialDelaySeconds: 10 # 容器启动后延迟 10 秒开始首次检查
+ periodSeconds: 30 # 每隔 30 秒检查一次
+ # 资源限制:控制容器对 CPU 和内存的使用
resources:
- requests:
- cpu: 10m
- memory: 100Mi
- limits:
- cpu: "2"
- memory: 2Gi
+ requests: # 资源请求(调度时的最小需求)
+ cpu: 10m # 10 毫核 CPU(1核=1000m)
+ memory: 100Mi # 100 兆内存
+ limits: # 资源限制(容器最大可使用的资源)
+ cpu: "2" # 2 核 CPU
+ memory: 2Gi # 2 吉内存
+ # 卷挂载:将持久卷挂载到容器内的指定路径
volumeMounts:
- - name: djangoblog
- mountPath: /code/djangoblog/collectedstatic
- - name: resource
- mountPath: /resource
+ - name: djangoblog # 引用下面 volumes 中定义的卷名称
+ mountPath: /code/djangoblog/collectedstatic # 容器内的挂载路径(Django 静态文件目录)
+ - name: resource # 引用资源卷
+ mountPath: /resource # 容器内的资源文件目录
+ # 卷定义:声明需要挂载的持久卷
volumes:
- - name: djangoblog
- persistentVolumeClaim:
- claimName: djangoblog-pvc
- - name: resource
+ - name: djangoblog # 卷名称(与上面 volumeMounts.name 对应)
+ persistentVolumeClaim: # 使用持久卷声明(PVC)
+ claimName: djangoblog-pvc # 引用的 PVC 名称(需提前创建)
+ - name: resource # 资源卷名称
persistentVolumeClaim:
- claimName: resource-pvc
+ claimName: resource-pvc # 资源对应的 PVC 名称
----
+
+# 第二部分:Redis 缓存服务部署配置
+--- # 分隔符:用于在一个文件中定义多个 Kubernetes 资源
apiVersion: apps/v1
kind: Deployment
metadata:
- name: redis
- namespace: djangoblog
+ name: redis # Redis 部署名称
+ namespace: djangoblog # 同属 djangoblog 命名空间
labels:
- app: redis
+ app: redis # Redis 标签
spec:
- replicas: 1
+ replicas: 1 # Redis 单副本(简单部署,生产环境可能需要集群)
selector:
matchLabels:
- app: redis
+ app: redis # 匹配 Redis Pod 标签
template:
metadata:
labels:
- app: redis
+ app: redis # Pod 标签
spec:
containers:
- - name: redis
- image: redis:latest
+ - name: redis # 容器名称
+ image: redis:latest # Redis 官方最新镜像
+ # 镜像拉取策略:IfNotPresent 表示本地有则使用本地镜像,否则拉取
imagePullPolicy: IfNotPresent
ports:
- - containerPort: 6379
+ - containerPort: 6379 # Redis 默认端口
+ # 资源限制(Redis 对资源需求较低)
resources:
requests:
cpu: 10m
memory: 100Mi
limits:
- cpu: 200m
+ cpu: 200m # 限制最大 200 毫核 CPU
memory: 2Gi
-
+
+
+# 第三部分:MySQL 数据库部署配置
---
apiVersion: apps/v1
kind: Deployment
metadata:
- name: db
+ name: db # 数据库部署名称
namespace: djangoblog
labels:
- app: db
+ app: db # 数据库标签
spec:
- replicas: 1
+ replicas: 1 # 数据库单副本(生产环境需考虑主从或集群)
selector:
matchLabels:
- app: db
+ app: db # 匹配数据库 Pod 标签
template:
metadata:
labels:
- app: db
+ app: db # Pod 标签
spec:
containers:
- - name: db
- image: mysql:latest
+ - name: db # 容器名称
+ image: mysql:latest # MySQL 官方最新镜像
imagePullPolicy: IfNotPresent
ports:
- - containerPort: 3306
+ - containerPort: 3306 # MySQL 默认端口
+ # 从 ConfigMap 注入环境变量(如数据库密码、用户名等)
envFrom:
- configMapRef:
- name: djangoblog-env
+ name: djangoblog-env # 复用 Django 应用的环境变量配置
+ # 就绪探针:通过执行 mysqladmin ping 检查数据库是否就绪
readinessProbe:
- exec:
+ exec: # 执行命令检查
command:
- mysqladmin
- ping
- "-h"
- - "127.0.0.1"
+ - "127.0.0.1" # 数据库主机(容器内本地)
- "-u"
- - "root"
- - "-p$MYSQL_ROOT_PASSWORD"
- initialDelaySeconds: 10
- periodSeconds: 10
+ - "root" # 用户名
+ - "-p$MYSQL_ROOT_PASSWORD" # 密码(从环境变量获取)
+ initialDelaySeconds: 10 # 延迟 10 秒检查
+ periodSeconds: 10 # 每 10 秒检查一次
+ # 存活探针:同就绪探针,确保数据库存活
livenessProbe:
exec:
command:
@@ -139,6 +168,7 @@ spec:
- "-p$MYSQL_ROOT_PASSWORD"
initialDelaySeconds: 10
periodSeconds: 10
+ # 资源限制(数据库对资源需求较高)
resources:
requests:
cpu: 10m
@@ -146,38 +176,42 @@ spec:
limits:
cpu: "2"
memory: 2Gi
+ # 挂载数据库数据目录(持久化存储,避免数据丢失)
volumeMounts:
- - name: db-data
- mountPath: /var/lib/mysql
+ - name: db-data # 引用数据卷
+ mountPath: /var/lib/mysql # MySQL 数据存储路径
volumes:
- - name: db-data
+ - name: db-data # 数据卷名称
persistentVolumeClaim:
- claimName: db-pvc
-
+ claimName: db-pvc # 数据库对应的 PVC 名称
+
+
+# 第四部分:Nginx 反向代理部署配置
---
apiVersion: apps/v1
kind: Deployment
metadata:
- name: nginx
+ name: nginx # Nginx 部署名称
namespace: djangoblog
labels:
- app: nginx
+ app: nginx # Nginx 标签
spec:
- replicas: 1
+ replicas: 1 # Nginx 单副本
selector:
matchLabels:
- app: nginx
+ app: nginx # 匹配 Nginx Pod 标签
template:
metadata:
labels:
- app: nginx
+ app: nginx # Pod 标签
spec:
containers:
- - name: nginx
- image: nginx:latest
+ - name: nginx # 容器名称
+ image: nginx:latest # Nginx 官方最新镜像
imagePullPolicy: IfNotPresent
ports:
- - containerPort: 80
+ - containerPort: 80 # Nginx 默认端口
+ # 资源限制
resources:
requests:
cpu: 10m
@@ -185,67 +219,82 @@ spec:
limits:
cpu: "2"
memory: 2Gi
+ # 卷挂载:挂载配置文件和静态资源
volumeMounts:
+ # 挂载 Nginx 主配置文件(subPath 表示只挂载单个文件,而非目录)
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
+ # 挂载默认站点配置
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: djangoblog.conf
+ # 挂载资源站点配置
- name: nginx-config
mountPath: /etc/nginx/conf.d/resource.lylinux.net.conf
subPath: resource.lylinux.net.conf
+ # 挂载额外的资源配置
- name: nginx-config
mountPath: /etc/nginx/lylinux/resource.conf
subPath: lylinux.resource.conf
+ # 挂载 Django 静态文件目录(与 Django 应用共享存储)
- name: djangoblog-pvc
mountPath: /code/djangoblog/collectedstatic
+ # 挂载资源文件目录
- name: resource-pvc
mountPath: /resource
volumes:
+ # Nginx 配置卷:通过 ConfigMap 挂载配置文件(避免在镜像中硬编码配置)
- name: nginx-config
configMap:
- name: web-nginx-config
+ name: web-nginx-config # 引用的 ConfigMap 名称
+ # 挂载 Django 静态文件对应的 PVC
- name: djangoblog-pvc
persistentVolumeClaim:
claimName: djangoblog-pvc
+ # 挂载资源文件对应的 PVC
- name: resource-pvc
persistentVolumeClaim:
claimName: resource-pvc
+
+# 第五部分:Elasticsearch 搜索引擎部署配置
---
apiVersion: apps/v1
kind: Deployment
metadata:
- name: elasticsearch
+ name: elasticsearch # ES 部署名称
namespace: djangoblog
labels:
- app: elasticsearch
+ app: elasticsearch # ES 标签
spec:
- replicas: 1
+ replicas: 1 # ES 单节点(生产环境需集群)
selector:
matchLabels:
- app: elasticsearch
+ app: elasticsearch # 匹配 ES Pod 标签
template:
metadata:
labels:
- app: elasticsearch
+ app: elasticsearch # Pod 标签
spec:
containers:
- - name: elasticsearch
+ - name: elasticsearch # 容器名称
+ # 带 IK 分词器的 ES 镜像(适用于中文搜索)
image: liangliangyy/elasticsearch-analysis-ik:8.6.1
imagePullPolicy: IfNotPresent
+ # ES 环境变量配置
env:
- - name: discovery.type
+ - name: discovery.type # 单节点模式(无需集群发现)
value: single-node
- - name: ES_JAVA_OPTS
+ - name: ES_JAVA_OPTS # JVM 内存配置(根据需求调整)
value: "-Xms256m -Xmx256m"
- - name: xpack.security.enabled
+ - name: xpack.security.enabled # 关闭安全验证(简化部署)
value: "false"
- - name: xpack.monitoring.templates.enabled
+ - name: xpack.monitoring.templates.enabled # 关闭监控模板
value: "false"
ports:
- - containerPort: 9200
+ - containerPort: 9200 # ES HTTP 接口端口
+ # 资源限制(ES 对内存需求较高)
resources:
requests:
cpu: 10m
@@ -253,22 +302,25 @@ spec:
limits:
cpu: "2"
memory: 2Gi
+ # 就绪探针:检查 ES 是否就绪
readinessProbe:
httpGet:
- path: /
+ path: / # ES 健康检查路径
port: 9200
- initialDelaySeconds: 15
+ initialDelaySeconds: 15 # 延迟 15 秒(ES 启动较慢)
periodSeconds: 30
+ # 存活探针:检查 ES 是否存活
livenessProbe:
httpGet:
path: /
port: 9200
initialDelaySeconds: 15
periodSeconds: 30
+ # 挂载 ES 数据目录(持久化存储索引数据)
volumeMounts:
- name: elasticsearch-data
- mountPath: /usr/share/elasticsearch/data/
+ mountPath: /usr/share/elasticsearch/data/ # ES 数据存储路径
volumes:
- - name: elasticsearch-data
- persistentVolumeClaim:
- claimName: elasticsearch-pvc
+ - name: elasticsearch-data
+ persistentVolumeClaim:
+ claimName: elasticsearch-pvc # ES 对应的 PVC 名称
\ No newline at end of file
diff --git a/src/DjangoBlog-master/deploy/k8s/gateway.yaml b/src/DjangoBlog-master/deploy/k8s/gateway.yaml
index a8de073..c81cf30 100644
--- a/src/DjangoBlog-master/deploy/k8s/gateway.yaml
+++ b/src/DjangoBlog-master/deploy/k8s/gateway.yaml
@@ -1,17 +1,31 @@
+# Ingress 资源配置(用于管理外部访问集群内服务的规则)
+# apiVersion 指定 Kubernetes API 版本,networking.k8s.io/v1 是 Ingress 的稳定版本
apiVersion: networking.k8s.io/v1
+# kind 定义资源类型为 Ingress(用于配置外部访问规则)
kind: Ingress
metadata:
+ # Ingress 资源的名称
name: nginx
+ # 所属命名空间(与前面的部署资源保持一致,确保资源在同一命名空间内可访问)
namespace: djangoblog
spec:
+ # 指定 Ingress 控制器的类别(需提前部署对应类别的 Ingress Controller,这里使用 nginx 类型)
ingressClassName: nginx
+ # 访问规则定义(外部请求如何路由到集群内的服务)
rules:
+ # 未指定 host 表示匹配所有未被其他规则匹配的主机(可理解为默认规则)
- http:
+ # HTTP 协议的路由规则
paths:
+ # 路径规则:匹配以 / 开头的所有请求(即所有路径)
- path: /
+ # 路径匹配类型:Prefix 表示前缀匹配(/ 会匹配所有路径)
pathType: Prefix
+ # 后端服务配置:请求转发到哪个服务
backend:
service:
+ # 目标服务的名称(需提前创建名为 nginx 的 Service,关联到 nginx 部署的 Pod)
name: nginx
+ # 目标服务的端口号(对应 nginx 服务暴露的 80 端口)
port:
number: 80
\ No newline at end of file
diff --git a/src/DjangoBlog-master/deploy/k8s/pv.yaml b/src/DjangoBlog-master/deploy/k8s/pv.yaml
index 874b72f..4e48545 100644
--- a/src/DjangoBlog-master/deploy/k8s/pv.yaml
+++ b/src/DjangoBlog-master/deploy/k8s/pv.yaml
@@ -1,40 +1,44 @@
-apiVersion: v1
-kind: PersistentVolume
+# 第一部分:数据库(MySQL)专用持久卷配置
+apiVersion: v1 # PV 资源使用的 Kubernetes API 版本
+kind: PersistentVolume # 资源类型为持久卷(PV),用于提供集群级别的存储资源
metadata:
- name: local-pv-db
+ name: local-pv-db # PV 的名称,需唯一,这里明确关联数据库(db)
spec:
- capacity:
- storage: 10Gi
- volumeMode: Filesystem
- accessModes:
- - ReadWriteOnce
- persistentVolumeReclaimPolicy: Retain
- storageClassName: local-storage
- local:
- path: /mnt/local-storage-db
- nodeAffinity:
- required:
+ capacity: # 定义 PV 的存储容量
+ storage: 10Gi # 分配 10GiB 存储空间(数据库通常需要较大空间)
+ volumeMode: Filesystem # 卷模式:Filesystem 表示以文件系统形式挂载(另一种是 Block 块设备)
+ accessModes: # 访问模式:定义 PV 可被如何访问
+ - ReadWriteOnce # 仅允许单个节点以读写方式挂载(适合数据库等需独占写入的场景)
+ persistentVolumeReclaimPolicy: Retain # 回收策略:Retain 表示 PV 被释放后保留数据,需手动清理
+ storageClassName: local-storage # 存储类名称,用于与 PersistentVolumeClaim(PVC)匹配
+ local: # 声明为本地存储(使用节点上的本地磁盘,非分布式存储)
+ path: /mnt/local-storage-db # 本地存储的实际路径(需在对应节点上提前创建该目录)
+ nodeAffinity: # 节点亲和性:限制 PV 只能被特定节点使用(本地存储必须配置)
+ required: # 强制要求:必须满足以下条件才能使用该 PV
nodeSelectorTerms:
- - matchExpressions:
- - key: kubernetes.io/hostname
- operator: In
+ - matchExpressions: # 匹配规则
+ - key: kubernetes.io/hostname # 匹配节点的主机名标签
+ operator: In # 操作符:In 表示值在指定列表中
values:
- - master
----
+ - master # 仅允许主机名为 "master" 的节点使用该 PV
+
+
+# 第二部分:Django 应用静态文件专用持久卷配置
+--- # 分隔符:用于在一个文件中定义多个资源
apiVersion: v1
kind: PersistentVolume
metadata:
- name: local-pv-djangoblog
+ name: local-pv-djangoblog # PV 名称,关联 Django 应用
spec:
capacity:
- storage: 5Gi
+ storage: 5Gi # 分配 5GiB 存储空间(静态文件需求较小)
volumeMode: Filesystem
accessModes:
- - ReadWriteOnce
+ - ReadWriteOnce # 单节点读写(静态文件通常由单节点写入,多节点读取可考虑 ReadOnlyMany)
persistentVolumeReclaimPolicy: Retain
- storageClassName: local-storage
+ storageClassName: local-storage # 与前面的 PV 共用同一存储类
local:
- path: /mnt/local-storage-djangoblog
+ path: /mnt/local-storage-djangoblog # Django 静态文件的本地存储路径
nodeAffinity:
required:
nodeSelectorTerms:
@@ -42,24 +46,25 @@ spec:
- key: kubernetes.io/hostname
operator: In
values:
- - master
+ - master # 同样限制在 "master" 节点
+# 第三部分:资源文件专用持久卷配置
---
apiVersion: v1
kind: PersistentVolume
metadata:
- name: local-pv-resource
+ name: local-pv-resource # PV 名称,关联通用资源文件
spec:
capacity:
- storage: 5Gi
+ storage: 5Gi # 分配 5GiB 存储空间
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
- path: /mnt/resource/
+ path: /mnt/resource/ # 资源文件(如上传的图片、附件等)的本地存储路径
nodeAffinity:
required:
nodeSelectorTerms:
@@ -67,23 +72,25 @@ spec:
- key: kubernetes.io/hostname
operator: In
values:
- - master
+ - master # 限制在 "master" 节点
+
+# 第四部分:Elasticsearch 搜索引擎专用持久卷配置
---
apiVersion: v1
kind: PersistentVolume
metadata:
- name: local-pv-elasticsearch
+ name: local-pv-elasticsearch # PV 名称,关联 Elasticsearch
spec:
capacity:
- storage: 5Gi
+ storage: 5Gi # 分配 5GiB 存储空间(用于存储 ES 索引数据)
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
- path: /mnt/local-storage-elasticsearch
+ path: /mnt/local-storage-elasticsearch # ES 数据的本地存储路径
nodeAffinity:
required:
nodeSelectorTerms:
@@ -91,4 +98,4 @@ spec:
- key: kubernetes.io/hostname
operator: In
values:
- - master
\ No newline at end of file
+ - master # 限制在 "master" 节点
\ No newline at end of file
diff --git a/src/DjangoBlog-master/deploy/k8s/pvc.yaml b/src/DjangoBlog-master/deploy/k8s/pvc.yaml
index ef238c5..b1cff4b 100644
--- a/src/DjangoBlog-master/deploy/k8s/pvc.yaml
+++ b/src/DjangoBlog-master/deploy/k8s/pvc.yaml
@@ -1,60 +1,66 @@
-apiVersion: v1
-kind: PersistentVolumeClaim
+# 第一部分:数据库(MySQL)持久卷声明(PVC)
+# PVC 用于向 Kubernetes 请求存储资源,需与 PV 匹配后才能供 Pod 使用
+apiVersion: v1 # PVC 资源对应的 Kubernetes API 版本
+kind: PersistentVolumeClaim # 资源类型为持久卷声明(PVC)
metadata:
- name: db-pvc
- namespace: djangoblog
+ name: db-pvc # PVC 名称,需与数据库 Deployment 中引用的 PVC 名称一致
+ namespace: djangoblog # 所属命名空间,与数据库 Deployment、对应 PV 保持一致(资源隔离)
spec:
- storageClassName: local-storage
- volumeName: local-pv-db
- accessModes:
- - ReadWriteOnce
- resources:
+ storageClassName: local-storage # 存储类名称,必须与目标 PV 的 storageClassName 完全匹配(用于筛选 PV)
+ volumeName: local-pv-db # 显式指定绑定的 PV 名称(强制绑定,非必填;不指定则按条件自动匹配)
+ accessModes: # 访问模式,需与目标 PV 的 accessModes 兼容(否则无法绑定)
+ - ReadWriteOnce # 单节点读写模式,与数据库 PV 的访问模式一致(满足数据库独占写入需求)
+ resources: # 存储资源请求,定义需要的存储容量
requests:
- storage: 10Gi
+ storage: 10Gi # 请求 10GiB 存储空间,需小于或等于目标 PV 的 capacity(此处与 db PV 容量完全匹配)
----
+# 第二部分:Django 应用静态文件持久卷声明(PVC)
+--- # 资源分隔符,用于在单个文件中定义多个 Kubernetes 资源
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
- name: djangoblog-pvc
+ name: djangoblog-pvc # PVC 名称,需与 Django Deployment 中 volumeMounts 引用的 PVC 名称一致
namespace: djangoblog
spec:
- volumeName: local-pv-djangoblog
- storageClassName: local-storage
+ volumeName: local-pv-djangoblog # 显式绑定 Django 应用专用 PV
+ storageClassName: local-storage # 与 Django 应用 PV 的存储类一致
accessModes:
- - ReadWriteOnce
+ - ReadWriteOnce # 单节点读写,与 Django 应用 PV 访问模式匹配
resources:
requests:
- storage: 5Gi
+ storage: 5Gi # 请求 5GiB 存储空间,与 Django 应用 PV 容量一致(用于存储静态文件)
+
+# 第三部分:资源文件(如上传附件、图片)持久卷声明(PVC)
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
- name: resource-pvc
+ name: resource-pvc # PVC 名称,需与 Django、Nginx Deployment 中引用的资源卷 PVC 名称一致
namespace: djangoblog
spec:
- volumeName: local-pv-resource
- storageClassName: local-storage
+ volumeName: local-pv-resource # 显式绑定资源文件专用 PV
+ storageClassName: local-storage # 与资源文件 PV 的存储类一致
accessModes:
- - ReadWriteOnce
+ - ReadWriteOnce # 单节点读写,与资源文件 PV 访问模式匹配
resources:
requests:
- storage: 5Gi
+ storage: 5Gi # 请求 5GiB 存储空间,与资源文件 PV 容量一致
+
+# 第四部分:Elasticsearch(搜索引擎)持久卷声明(PVC)
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
- name: elasticsearch-pvc
+ name: elasticsearch-pvc # PVC 名称,需与 Elasticsearch Deployment 中引用的 PVC 名称一致
namespace: djangoblog
spec:
- volumeName: local-pv-elasticsearch
- storageClassName: local-storage
+ volumeName: local-pv-elasticsearch # 显式绑定 Elasticsearch 专用 PV
+ storageClassName: local-storage # 与 Elasticsearch PV 的存储类一致
accessModes:
- - ReadWriteOnce
+ - ReadWriteOnce # 单节点读写,与 Elasticsearch PV 访问模式匹配
resources:
requests:
- storage: 5Gi
-
\ No newline at end of file
+ storage: 5Gi # 请求 5GiB 存储空间,与 Elasticsearch PV 容量一致(用于存储索引数据)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/deploy/k8s/service.yaml b/src/DjangoBlog-master/deploy/k8s/service.yaml
index 4ef2931..c361b9f 100644
--- a/src/DjangoBlog-master/deploy/k8s/service.yaml
+++ b/src/DjangoBlog-master/deploy/k8s/service.yaml
@@ -1,80 +1,93 @@
-apiVersion: v1
-kind: Service
+# 第一部分:Django 应用服务(Service)
+# Service 用于为集群内的 Pod 提供稳定网络访问地址,实现 Pod 访问的负载均衡和服务发现
+apiVersion: v1 # Service 资源对应的 Kubernetes API 版本
+kind: Service # 资源类型为 Service
metadata:
- name: djangoblog
- namespace: djangoblog
+ name: djangoblog # Service 名称,需与其他组件(如 Nginx 配置)中引用的服务名一致
+ namespace: djangoblog # 所属命名空间,与 Django Deployment、其他组件保持一致(资源隔离)
labels:
- app: djangoblog
+ app: djangoblog # 服务标签,用于筛选和管理服务资源
spec:
- selector:
+ selector: # 标签选择器:通过标签匹配要管理的 Pod(必须与 Django Pod 的标签一致)
app: djangoblog
- ports:
- - protocol: TCP
- port: 8000
- targetPort: 8000
- type: ClusterIP
----
+ ports: # 端口配置:定义服务暴露的端口与 Pod 端口的映射关系
+ - protocol: TCP # 网络协议,默认 TCP(常用还有 UDP)
+ port: 8000 # 服务暴露给集群内部的端口(其他组件通过此端口访问该服务)
+ targetPort: 8000 # 服务转发请求到 Pod 的目标端口(需与 Django 容器暴露的端口一致)
+ type: ClusterIP # 服务类型:ClusterIP 表示仅在集群内部暴露服务,外部无法直接访问(适合内部组件通信)
+
+
+# 第二部分:Nginx 服务(Service)
+--- # 资源分隔符,用于在单个文件中定义多个 Kubernetes 资源
apiVersion: v1
kind: Service
metadata:
- name: nginx
+ name: nginx # Service 名称,需与 Ingress 配置中引用的服务名一致
namespace: djangoblog
labels:
app: nginx
spec:
selector:
- app: nginx
+ app: nginx # 匹配 Nginx Pod 的标签
ports:
- protocol: TCP
- port: 80
- targetPort: 80
- type: ClusterIP
+ port: 80 # 服务暴露的端口(Ingress 转发请求到该端口)
+ targetPort: 80 # 转发到 Nginx 容器暴露的 80 端口
+ type: ClusterIP # 集群内部访问(外部通过 Ingress 间接访问 Nginx 服务)
+
+
+# 第三部分:Redis 缓存服务(Service)
---
apiVersion: v1
kind: Service
metadata:
- name: redis
+ name: redis # Service 名称,需与 Django 应用配置中访问 Redis 的服务名一致
namespace: djangoblog
labels:
app: redis
spec:
selector:
- app: redis
+ app: redis # 匹配 Redis Pod 的标签
ports:
- protocol: TCP
- port: 6379
- targetPort: 6379
- type: ClusterIP
+ port: 6379 # 服务暴露的端口(Redis 默认端口)
+ targetPort: 6379 # 转发到 Redis 容器暴露的 6379 端口
+ type: ClusterIP # 仅集群内部访问(缓存服务无需外部暴露)
+
+
+# 第四部分:MySQL 数据库服务(Service)
---
apiVersion: v1
kind: Service
metadata:
- name: db
+ name: db # Service 名称,需与 Django 应用配置中访问数据库的服务名一致
namespace: djangoblog
labels:
app: db
spec:
selector:
- app: db
+ app: db # 匹配 MySQL Pod 的标签
ports:
- protocol: TCP
- port: 3306
- targetPort: 3306
- type: ClusterIP
+ port: 3306 # 服务暴露的端口(MySQL 默认端口)
+ targetPort: 3306 # 转发到 MySQL 容器暴露的 3306 端口
+ type: ClusterIP # 仅集群内部访问(数据库服务禁止外部直接访问,保障安全)
+
+
+# 第五部分:Elasticsearch 搜索引擎服务(Service)
---
apiVersion: v1
kind: Service
metadata:
- name: elasticsearch
+ name: elasticsearch # Service 名称,需与 Django 应用配置中访问 ES 的服务名一致
namespace: djangoblog
labels:
app: elasticsearch
spec:
selector:
- app: elasticsearch
+ app: elasticsearch # 匹配 Elasticsearch Pod 的标签
ports:
- protocol: TCP
- port: 9200
- targetPort: 9200
- type: ClusterIP
-
+ port: 9200 # 服务暴露的端口(ES HTTP 接口默认端口)
+ targetPort: 9200 # 转发到 ES 容器暴露的 9200 端口
+ type: ClusterIP # 仅集群内部访问(搜索引擎无需外部直接暴露)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/deploy/k8s/storageclass.yaml b/src/DjangoBlog-master/deploy/k8s/storageclass.yaml
index 5d5a14c..0063318 100644
--- a/src/DjangoBlog-master/deploy/k8s/storageclass.yaml
+++ b/src/DjangoBlog-master/deploy/k8s/storageclass.yaml
@@ -1,10 +1,20 @@
+# StorageClass 资源配置(用于定义存储资源的类型和动态供应策略)
+# apiVersion 指定 Kubernetes API 版本,storage.k8s.io/v1 是 StorageClass 的稳定版本
apiVersion: storage.k8s.io/v1
+# kind 定义资源类型为 StorageClass(用于统一管理存储资源的属性)
kind: StorageClass
metadata:
+ # StorageClass 的名称,需与前面 PV 和 PVC 中指定的 storageClassName 一致
name: local-storage
+ # 注解:设置为默认存储类(当 PVC 未指定 storageClassName 时,自动使用此存储类)
annotations:
storageclass.kubernetes.io/is-default-class: "true"
-provisioner: kubernetes.io/no-provisioner
-volumeBindingMode: Immediate
-
+spec:
+ # 存储供应器:指定用于动态创建 PV 的插件(此处使用 no-provisioner 表示不支持动态供应)
+ # 因为前面的 PV 是手动创建的本地存储,无需动态生成,所以使用此供应器
+ provisioner: kubernetes.io/no-provisioner
+ # 卷绑定模式:Immediate 表示 PVC 创建后立即尝试绑定可用的 PV(不等待 Pod 调度)
+ # 对于本地存储,若使用 WaitForFirstConsumer 模式会更合适(等待 Pod 调度后再绑定对应节点的 PV)
+ # 此处配置为 Immediate,需确保 PV 已提前创建且满足 PVC 条件
+ volumeBindingMode: Immediate
diff --git a/src/DjangoBlog-master/deploy/nginx.conf b/src/DjangoBlog-master/deploy/nginx.conf
index 32161d8..ce2582f 100644
--- a/src/DjangoBlog-master/deploy/nginx.conf
+++ b/src/DjangoBlog-master/deploy/nginx.conf
@@ -1,50 +1,82 @@
-user nginx;
+# Nginx 核心配置文件,用于处理静态资源和反向代理请求到 Django 应用
+# 全局配置段:设置 Nginx 整体运行参数
+nginx; # 标识该文件为 Nginx 配置文件(固定起始标识)
+# 工作进程数:auto 表示自动根据服务器 CPU 核心数分配(优化并发性能)
worker_processes auto;
+# 错误日志配置:指定日志路径和日志级别(notice 级别记录重要信息,不冗余)
error_log /var/log/nginx/error.log notice;
+# PID 文件路径:存储 Nginx 主进程 ID,用于管理 Nginx 进程(如重启、停止)
pid /var/run/nginx.pid;
+# 事件模块配置:控制 Nginx 网络连接相关参数
events {
+ # 单个工作进程允许的最大并发连接数(1024 为基础值,可根据服务器性能调整)
worker_connections 1024;
}
+# HTTP 模块配置:处理 HTTP 请求的核心配置,包含全局规则和虚拟主机
http {
+ # 引入 MIME 类型映射文件:定义不同文件后缀对应的 Content-Type(如 .html 对应 text/html)
include /etc/nginx/mime.types;
+ # 默认 MIME 类型:当文件类型未匹配时,默认使用二进制流类型(避免浏览器直接解析未知文件)
default_type application/octet-stream;
+ # 日志格式定义:自定义访问日志的记录字段,命名为 main
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
-
+ # 访问日志配置:使用上面定义的 main 格式,指定日志存储路径
access_log /var/log/nginx/access.log main;
+ # 开启高效文件传输模式:使用内核零拷贝技术,提升静态文件传输效率
sendfile on;
- #tcp_nopush on;
+ # tcp_nopush on; # 可选配置:开启后会累积数据包再发送,适合大文件传输,默认关闭
+ # 长连接超时时间:客户端与 Nginx 保持连接的最长时间(65 秒,超时后自动断开)
keepalive_timeout 65;
- #gzip on;
+ # gzip on; # 可选配置:开启 Gzip 压缩,减少传输带宽,默认关闭
+
+ # 虚拟主机配置:定义一个具体的站点规则(处理 80 端口的 HTTP 请求)
server {
+ # 站点根目录:默认请求的文件查找路径(此处指向 Django 静态文件目录)
root /code/djangoblog/collectedstatic/;
+ # 监听端口:该虚拟主机处理 80 端口的请求(Nginx 默认 HTTP 端口)
listen 80;
+ # 长连接超时时间:覆盖 HTTP 模块的全局配置,仅作用于当前虚拟主机
keepalive_timeout 70;
+
+ # 静态资源路径规则:匹配以 /static/ 开头的请求(处理 Django 静态文件)
location /static/ {
+ # 缓存控制:设置静态文件缓存时间为最大(浏览器会长期缓存,减少重复请求)
expires max;
+ # 路径别名:将 /static/ 请求映射到实际的静态文件目录(与 root 配合确保路径正确)
alias /code/djangoblog/collectedstatic/;
}
+
+ # 默认路径规则:匹配所有未被上面规则匹配的请求(转发到 Django 应用)
location / {
+ # 转发请求头:传递客户端真实 IP 到 Django(否则 Django 会认为请求来自 Nginx)
proxy_set_header X-Real-IP $remote_addr;
+ # 转发请求头:传递客户端 IP 列表(适用于多层代理场景)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ # 转发请求头:传递原始请求的 Host 头(确保 Django 正确识别请求域名)
proxy_set_header Host $http_host;
+ # 转发标识:告诉 Django 请求来自 Nginx 代理
proxy_set_header X-NginX-Proxy true;
+ # 关闭重定向处理:禁止 Nginx 自动修改 Django 返回的重定向地址
proxy_redirect off;
+
+ # 条件判断:如果请求的文件在 Nginx 本地不存在(非静态文件)
if (!-f $request_filename) {
+ # 反向代理:将请求转发到 Django 服务(通过 Kubernetes Service 名称 djangoblog 的 8000 端口)
proxy_pass http://djangoblog:8000;
- break;
+ break; # 跳出条件判断,不再执行后续规则
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/__init__.py b/src/DjangoBlog-master/djangoblog/__init__.py
index 1e205f4..0c523dd 100644
--- a/src/DjangoBlog-master/djangoblog/__init__.py
+++ b/src/DjangoBlog-master/djangoblog/__init__.py
@@ -1 +1,3 @@
+# Django 应用的默认配置指定
+# 作用:告诉 Django 当该应用被加载时,应使用哪个配置类进行初始化
default_app_config = 'djangoblog.apps.DjangoblogAppConfig'
diff --git a/src/DjangoBlog-master/djangoblog/admin_site.py b/src/DjangoBlog-master/djangoblog/admin_site.py
index f120405..5705829 100644
--- a/src/DjangoBlog-master/djangoblog/admin_site.py
+++ b/src/DjangoBlog-master/djangoblog/admin_site.py
@@ -1,64 +1,90 @@
+# 导入 Django 内置的 AdminSite 基础类(后台管理站点核心类)
from django.contrib.admin import AdminSite
+# 导入日志记录模型(用于记录后台操作日志)
from django.contrib.admin.models import LogEntry
+# 导入站点管理相关的默认 admin 类和模型(Django 内置的站点管理功能)
from django.contrib.sites.admin import SiteAdmin
from django.contrib.sites.models import Site
-from accounts.admin import *
-from blog.admin import *
-from blog.models import *
-from comments.admin import *
-from comments.models import *
+# 导入各应用自定义的 admin 配置和模型(将各模块的后台管理逻辑聚合到此处)
+from accounts.admin import * # 用户账户相关的 admin 配置
+from blog.admin import * # 博客核心(文章、分类等)的 admin 配置
+from blog.models import * # 博客核心模型
+from comments.admin import * # 评论相关的 admin 配置
+from comments.models import *# 评论模型
+# 导入自定义的日志条目 admin 配置(扩展日志展示功能)
from djangoblog.logentryadmin import LogEntryAdmin
-from oauth.admin import *
-from oauth.models import *
-from owntracks.admin import *
-from owntracks.models import *
-from servermanager.admin import *
-from servermanager.models import *
+from oauth.admin import * # 第三方登录(OAuth)相关的 admin 配置
+from oauth.models import * # OAuth 相关模型
+from owntracks.admin import *# 位置追踪(OwnTracks)相关的 admin 配置
+from owntracks.models import *# 位置追踪模型
+from servermanager.admin import *# 服务器管理相关的 admin 配置
+from servermanager.models import *# 服务器管理模型
+# 自定义后台管理站点类(继承自 Django 内置的 AdminSite)
class DjangoBlogAdminSite(AdminSite):
+ # 后台站点头部显示的标题(登录后顶部导航栏的文字)
site_header = 'djangoblog administration'
+ # 浏览器标签页显示的标题(页面标题)
site_title = 'djangoblog site admin'
+ # 初始化方法(调用父类构造方法,确保基础功能正常)
def __init__(self, name='admin'):
super().__init__(name)
+ # 权限控制方法:判断用户是否有权限访问后台
def has_permission(self, request):
+ # 仅允许超级用户(is_superuser=True)访问后台
return request.user.is_superuser
+ # 以下为注释掉的自定义 URL 示例(可扩展后台功能)
# def get_urls(self):
+ # # 先获取父类默认的 URL 配置
# urls = super().get_urls()
# from django.urls import path
+ # # 导入自定义视图(例如刷新缓存的视图)
# from blog.views import refresh_memcache
#
+ # # 定义自定义 URL 规则(如添加一个 /admin/refresh/ 路径)
# my_urls = [
# path('refresh/', self.admin_view(refresh_memcache), name="refresh"),
# ]
+ # # 合并默认 URL 和自定义 URL(自定义 URL 优先级更高)
# return urls + my_urls
+# 实例化自定义的后台管理站点(名称为 'admin',与默认后台路径保持一致)
admin_site = DjangoBlogAdminSite(name='admin')
-admin_site.register(Article, ArticlelAdmin)
-admin_site.register(Category, CategoryAdmin)
-admin_site.register(Tag, TagAdmin)
-admin_site.register(Links, LinksAdmin)
-admin_site.register(SideBar, SideBarAdmin)
-admin_site.register(BlogSettings, BlogSettingsAdmin)
+# 注册模型与对应的 admin 配置到自定义后台站点(实现各模型在后台的管理界面)
+# 博客核心模型注册
+admin_site.register(Article, ArticlelAdmin) # 文章模型 + 其admin配置
+admin_site.register(Category, CategoryAdmin) # 分类模型 + 其admin配置
+admin_site.register(Tag, TagAdmin) # 标签模型 + 其admin配置
+admin_site.register(Links, LinksAdmin) # 友情链接模型 + 其admin配置
+admin_site.register(SideBar, SideBarAdmin) # 侧边栏模型 + 其admin配置
+admin_site.register(BlogSettings, BlogSettingsAdmin) # 博客设置模型 + 其admin配置
-admin_site.register(commands, CommandsAdmin)
-admin_site.register(EmailSendLog, EmailSendLogAdmin)
+# 服务器管理模型注册
+admin_site.register(commands, CommandsAdmin) # 命令模型 + 其admin配置
+admin_site.register(EmailSendLog, EmailSendLogAdmin) # 邮件发送日志 + 其admin配置
-admin_site.register(BlogUser, BlogUserAdmin)
+# 用户模型注册
+admin_site.register(BlogUser, BlogUserAdmin) # 自定义用户模型 + 其admin配置
-admin_site.register(Comment, CommentAdmin)
+# 评论模型注册
+admin_site.register(Comment, CommentAdmin) # 评论模型 + 其admin配置
-admin_site.register(OAuthUser, OAuthUserAdmin)
-admin_site.register(OAuthConfig, OAuthConfigAdmin)
+# OAuth 相关模型注册
+admin_site.register(OAuthUser, OAuthUserAdmin) # OAuth用户模型 + 其admin配置
+admin_site.register(OAuthConfig, OAuthConfigAdmin) # OAuth配置 + 其admin配置
-admin_site.register(OwnTrackLog, OwnTrackLogsAdmin)
+# 位置追踪模型注册
+admin_site.register(OwnTrackLog, OwnTrackLogsAdmin) # 位置日志 + 其admin配置
+# Django 内置站点模型注册(使用默认的 SiteAdmin 配置)
admin_site.register(Site, SiteAdmin)
-admin_site.register(LogEntry, LogEntryAdmin)
+# 后台操作日志模型注册(使用自定义的 LogEntryAdmin 配置,增强日志展示)
+admin_site.register(LogEntry, LogEntryAdmin)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/apps.py b/src/DjangoBlog-master/djangoblog/apps.py
index d29e318..3dc5603 100644
--- a/src/DjangoBlog-master/djangoblog/apps.py
+++ b/src/DjangoBlog-master/djangoblog/apps.py
@@ -1,11 +1,20 @@
+# 导入 Django 的应用配置基类(所有应用配置类需继承此类)
from django.apps import AppConfig
+# 自定义应用配置类(用于 djangoblog 应用的初始化设置)
class DjangoblogAppConfig(AppConfig):
+ # 定义模型主键的默认类型:使用 BigAutoField(自增 BigInteger 类型)
+ # 替代旧版的 AutoField(自增 Integer),支持更大范围的主键值
default_auto_field = 'django.db.models.BigAutoField'
+ # 应用的名称(必须与项目中 INSTALLED_APPS 配置的名称一致)
name = 'djangoblog'
+ # 应用就绪方法:当 Django 加载完所有应用后自动调用(用于初始化操作)
def ready(self):
+ # 调用父类的 ready 方法,确保基础初始化逻辑执行
super().ready()
- # Import and load plugins here
+ # 导入并加载插件(应用启动时加载所有注册的插件)
+ # 注意:避免在模块顶部导入,防止 Django 初始化时循环导入问题
from .plugin_manage.loader import load_plugins
- load_plugins()
\ No newline at end of file
+ # 执行插件加载函数(例如注册钩子、初始化插件功能等)
+ load_plugins()
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/blog_signals.py b/src/DjangoBlog-master/djangoblog/blog_signals.py
index 393f441..ffc3b89 100644
--- a/src/DjangoBlog-master/djangoblog/blog_signals.py
+++ b/src/DjangoBlog-master/djangoblog/blog_signals.py
@@ -1,7 +1,11 @@
+# 导入线程模块:用于异步执行耗时操作(如发送邮件,避免阻塞主流程)
import _thread
+# 导入日志模块:记录操作日志和错误信息
import logging
+# 导入 Django 信号核心类:用于定义和处理自定义信号
import django.dispatch
+# 导入 Django 配置、模型、工具类:支撑信号处理中的业务逻辑
from django.conf import settings
from django.contrib.admin.models import LogEntry
from django.contrib.auth.signals import user_logged_in, user_logged_out
@@ -9,6 +13,7 @@ from django.core.mail import EmailMultiAlternatives
from django.db.models.signals import post_save
from django.dispatch import receiver
+# 导入项目自定义模型和工具函数:适配博客业务场景
from comments.models import Comment
from comments.utils import send_comment_email
from djangoblog.spider_notify import SpiderNotify
@@ -16,54 +21,73 @@ from djangoblog.utils import cache, expire_view_cache, delete_sidebar_cache, del
from djangoblog.utils import get_current_site
from oauth.models import OAuthUser
+# 初始化日志对象:用于记录当前模块的日志(如邮件发送失败、爬虫通知错误)
logger = logging.getLogger(__name__)
+# 定义自定义信号:第三方登录(OAuth)成功后触发的信号,携带用户ID参数
oauth_user_login_signal = django.dispatch.Signal(['id'])
+# 定义自定义信号:发送邮件的信号,携带收件人、标题、内容参数
send_email_signal = django.dispatch.Signal(
['emailto', 'title', 'content'])
+# 信号接收器:监听 send_email_signal 信号,触发邮件发送逻辑
@receiver(send_email_signal)
def send_email_signal_handler(sender, **kwargs):
- emailto = kwargs['emailto']
- title = kwargs['title']
- content = kwargs['content']
+ # 从信号参数中提取邮件相关信息
+ emailto = kwargs['emailto'] # 收件人列表
+ title = kwargs['title'] # 邮件标题
+ content = kwargs['content'] # 邮件内容(HTML格式)
+ # 构建 HTML 格式邮件:支持富文本内容
msg = EmailMultiAlternatives(
title,
content,
- from_email=settings.DEFAULT_FROM_EMAIL,
+ from_email=settings.DEFAULT_FROM_EMAIL, # 发件人(从项目配置中获取)
to=emailto)
- msg.content_subtype = "html"
+ msg.content_subtype = "html" # 声明邮件内容为 HTML 类型
+ # 记录邮件发送日志到数据库
from servermanager.models import EmailSendLog
log = EmailSendLog()
log.title = title
log.content = content
- log.emailto = ','.join(emailto)
+ log.emailto = ','.join(emailto) # 收件人列表转字符串存储
try:
+ # 发送邮件:返回成功发送的邮件数量
result = msg.send()
- log.send_result = result > 0
+ log.send_result = result > 0 # 发送成功标记(数量>0即为成功)
except Exception as e:
+ # 捕获发送异常,记录错误日志
logger.error(f"失败邮箱号: {emailto}, {e}")
- log.send_result = False
- log.save()
+ log.send_result = False # 标记发送失败
+ finally:
+ # 保存日志记录到数据库
+ log.save()
+# 信号接收器:监听 oauth_user_login_signal 信号,处理 OAuth 登录后的逻辑
@receiver(oauth_user_login_signal)
def oauth_user_login_signal_handler(sender, **kwargs):
+ # 从信号参数中提取 OAuth 用户ID
id = kwargs['id']
+ # 获取对应的 OAuth 用户对象
oauthuser = OAuthUser.objects.get(id=id)
+ # 获取当前站点域名(用于判断头像是否为本站地址)
site = get_current_site().domain
+
+ # 若用户头像不是本站地址(如第三方平台的远程图片),则下载并保存到本地
if oauthuser.picture and not oauthuser.picture.find(site) >= 0:
from djangoblog.utils import save_user_avatar
- oauthuser.picture = save_user_avatar(oauthuser.picture)
- oauthuser.save()
+ oauthuser.picture = save_user_avatar(oauthuser.picture) # 下载并更新头像路径
+ oauthuser.save() # 保存更新后的用户信息
+ # 删除侧边栏缓存:用户登录状态变化可能影响侧边栏内容(如显示登录用户信息)
delete_sidebar_cache()
+# 信号接收器:监听所有模型的 post_save 信号(模型保存后触发)
@receiver(post_save)
def model_post_save_callback(
sender,
@@ -73,50 +97,74 @@ def model_post_save_callback(
using,
update_fields,
**kwargs):
+ # 标记是否需要清理缓存
clearcache = False
+
+ # 跳过 Admin 操作日志(LogEntry)的处理:避免日志保存时触发不必要的逻辑
if isinstance(instance, LogEntry):
return
+
+ # 处理有 "get_full_url" 方法的模型(如 Article 文章模型)
if 'get_full_url' in dir(instance):
+ # 判断是否仅更新了 "views" 字段(文章阅读量)
is_update_views = update_fields == {'views'}
+ # 非测试环境且非阅读量更新:通知搜索引擎(如百度)收录新页面
if not settings.TESTING and not is_update_views:
try:
- notify_url = instance.get_full_url()
- SpiderNotify.baidu_notify([notify_url])
+ notify_url = instance.get_full_url() # 获取模型的完整访问链接
+ SpiderNotify.baidu_notify([notify_url]) # 调用百度爬虫通知接口
except Exception as ex:
+ # 捕获通知异常,记录错误日志
logger.error("notify sipder", ex)
+ # 非阅读量更新:标记需要清理缓存(如文章内容、标题修改)
if not is_update_views:
clearcache = True
+ # 处理 Comment 评论模型的保存逻辑
if isinstance(instance, Comment):
+ # 仅处理已启用的评论(is_enable=True)
if instance.is_enable:
+ # 获取评论所属文章的访问路径
path = instance.article.get_absolute_url()
+ # 获取当前站点域名(处理端口号,仅保留域名部分)
site = get_current_site().domain
if site.find(':') > 0:
site = site[0:site.find(':')]
+ # 清理文章详情页的视图缓存:避免显示旧评论
expire_view_cache(
path,
servername=site,
serverport=80,
key_prefix='blogdetail')
+ # 清理 SEO 处理器缓存:评论变化可能影响页面 SEO 信息
if cache.get('seo_processor'):
cache.delete('seo_processor')
+ # 清理该文章的评论列表缓存
comment_cache_key = 'article_comments_{id}'.format(
id=instance.article.id)
cache.delete(comment_cache_key)
+ # 清理侧边栏缓存:侧边栏可能显示最新评论
delete_sidebar_cache()
+ # 清理评论分页视图的缓存
delete_view_cache('article_comments', [str(instance.article.pk)])
+ # 异步发送评论通知邮件:用线程避免阻塞评论保存流程
_thread.start_new_thread(send_comment_email, (instance,))
+ # 若标记需要清理缓存,则清空全局缓存(确保最新数据生效)
if clearcache:
cache.clear()
+# 信号接收器:同时监听用户登录(user_logged_in)和登出(user_logged_out)信号
@receiver(user_logged_in)
@receiver(user_logged_out)
def user_auth_callback(sender, request, user, **kwargs):
+ # 若用户存在且用户名有效(排除异常情况)
if user and user.username:
+ # 记录用户登录/登出日志
logger.info(user)
+ # 清理侧边栏缓存:登录状态变化可能影响侧边栏(如显示/隐藏用户菜单)
delete_sidebar_cache()
- # cache.clear()
+ # cache.clear() # 注释:若需全局清缓存可启用,当前仅清理侧边栏缓存
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py b/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py
index 4afe498..ba49e48 100644
--- a/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py
+++ b/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py
@@ -1,150 +1,184 @@
+# 导入 Django 字符串处理工具:确保字符串编码兼容
from django.utils.encoding import force_str
+# 导入 Elasticsearch DSL 工具:构建 Elasticsearch 查询语句
from elasticsearch_dsl import Q
+# 导入 Haystack 核心类:实现自定义搜索后端、查询和引擎
from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query
-from haystack.forms import ModelSearchForm
-from haystack.models import SearchResult
-from haystack.utils import log as logging
+from haystack.forms import ModelSearchForm # Haystack 基础搜索表单
+from haystack.models import SearchResult # Haystack 搜索结果封装类
+from haystack.utils import log as logging # Haystack 日志工具
+# 导入项目自定义的 Elasticsearch 文档和管理器:关联博客文章模型
from blog.documents import ArticleDocument, ArticleDocumentManager
-from blog.models import Article
+from blog.models import Article # 博客核心文章模型
+# 初始化日志对象:记录搜索相关日志(如查询语句、错误信息)
logger = logging.getLogger(__name__)
+# 自定义 Elasticsearch 搜索后端:实现 Haystack 与 Elasticsearch 的底层交互
class ElasticSearchBackend(BaseSearchBackend):
def __init__(self, connection_alias, **connection_options):
+ # 调用父类构造方法,初始化 Haystack 基础搜索后端
super(
ElasticSearchBackend,
self).__init__(
connection_alias,
**connection_options)
+ # 初始化文章文档管理器:负责 Elasticsearch 索引的创建、更新、删除
self.manager = ArticleDocumentManager()
+ # 启用拼写建议功能:用于返回搜索关键词的推荐词
self.include_spelling = True
+ # 辅助方法:将模型实例转换为 Elasticsearch 文档(Document)
def _get_models(self, iterable):
+ # 若传入空列表,默认获取所有文章;否则使用传入的模型实例
models = iterable if iterable and iterable[0] else Article.objects.all()
+ # 通过文档管理器将模型转换为 Elasticsearch 可识别的文档
docs = self.manager.convert_to_doc(models)
return docs
+ # 初始化索引:创建 Elasticsearch 索引并批量添加文档
def _create(self, models):
- self.manager.create_index()
- docs = self._get_models(models)
- self.manager.rebuild(docs)
+ self.manager.create_index() # 创建 Elasticsearch 索引结构
+ docs = self._get_models(models) # 转换模型为文档
+ self.manager.rebuild(docs) # 批量写入文档到索引
+ # 删除索引中的文档:根据模型实例删除对应 Elasticsearch 记录
def _delete(self, models):
for m in models:
- m.delete()
+ m.delete() # 调用文档的 delete 方法,删除 Elasticsearch 中的对应记录
return True
+ # 重建索引:全量更新 Elasticsearch 中的文档(覆盖旧数据)
def _rebuild(self, models):
+ # 若未指定模型,默认获取所有文章
models = models if models else Article.objects.all()
- docs = self.manager.convert_to_doc(models)
- self.manager.update_docs(docs)
+ docs = self._get_models(models) # 转换模型为文档
+ self.manager.update_docs(docs) # 批量更新文档到索引
+ # Haystack 标准方法:增量更新索引(更新指定模型对应的文档)
def update(self, index, iterable, commit=True):
+ models = self._get_models(iterable) # 转换模型为文档
+ self.manager.update_docs(models) # 增量更新文档
- models = self._get_models(iterable)
- self.manager.update_docs(models)
-
+ # Haystack 标准方法:移除单个模型对应的索引记录
def remove(self, obj_or_string):
- models = self._get_models([obj_or_string])
- self._delete(models)
+ models = self._get_models([obj_or_string]) # 转换为文档
+ self._delete(models) # 删除文档
+ # Haystack 标准方法:清空索引(删除所有相关记录)
def clear(self, models=None, commit=True):
- self.remove(None)
+ self.remove(None) # 调用 remove 方法清空索引
@staticmethod
def get_suggestion(query: str) -> str:
- """获取推荐词, 如果没有找到添加原搜索词"""
-
+ """
+ 生成搜索关键词的推荐词(基于 Elasticsearch 拼写建议功能)
+ 若未找到推荐词,返回原查询词
+ """
+ # 构建 Elasticsearch 查询:匹配文章内容,并启用拼写建议
search = ArticleDocument.search() \
.query("match", body=query) \
.suggest('suggest_search', query, term={'field': 'body'}) \
- .execute()
+ .execute() # 执行查询
keywords = []
+ # 提取 Elasticsearch 返回的建议词
for suggest in search.suggest.suggest_search:
- if suggest["options"]:
+ if suggest["options"]: # 若有推荐词,取第一个
keywords.append(suggest["options"][0]["text"])
- else:
+ else: # 若无推荐词,保留原查询词
keywords.append(suggest["text"])
- return ' '.join(keywords)
+ return ' '.join(keywords) # 拼接推荐词为字符串返回
+ # Haystack 核心搜索方法:执行搜索并返回结果(带日志记录装饰器)
@log_query
def search(self, query_string, **kwargs):
- logger.info('search query_string:' + query_string)
+ logger.info('search query_string:' + query_string) # 记录查询关键词
- start_offset = kwargs.get('start_offset')
- end_offset = kwargs.get('end_offset')
+ # 获取分页参数:起始偏移量和结束偏移量(用于分页)
+ start_offset = kwargs.get('start_offset', 0)
+ end_offset = kwargs.get('end_offset') # 若为 None,Elasticsearch 会返回默认数量结果
- # 推荐词搜索
+ # 生成推荐词:根据 is_suggest 标识判断是否需要拼写建议
if getattr(self, "is_suggest", None):
suggestion = self.get_suggestion(query_string)
else:
- suggestion = query_string
+ suggestion = query_string # 不需要建议则使用原查询词
+ # 构建 Elasticsearch 查询条件(布尔查询)
+ # 1. 匹配条件:标题或内容包含推荐词,匹配度最低 70%
q = Q('bool',
should=[Q('match', body=suggestion), Q('match', title=suggestion)],
minimum_should_match="70%")
+ # 构建完整搜索请求:
+ # - 过滤条件:使用上面的 q 匹配结果,且文章状态为“已发布”(status='p')、类型为“文章”(type='a')
+ # - 不返回文档源数据(source=False):仅获取 ID 和得分,减少数据传输
+ # - 分页:按 start_offset 和 end_offset 截取结果
search = ArticleDocument.search() \
.query('bool', filter=[q]) \
.filter('term', status='p') \
.filter('term', type='a') \
.source(False)[start_offset: end_offset]
+ # 执行搜索,获取 Elasticsearch 返回结果
results = search.execute()
- hits = results['hits'].total
- raw_results = []
+ hits = results['hits'].total # 匹配到的总结果数
+ raw_results = [] # 存储 Haystack 标准格式的搜索结果
+
+ # 解析 Elasticsearch 原始结果,封装为 Haystack 的 SearchResult 格式
for raw_result in results['hits']['hits']:
- app_label = 'blog'
- model_name = 'Article'
- additional_fields = {}
+ app_label = 'blog' # 模型所属应用
+ model_name = 'Article' # 模型名称
+ additional_fields = {} # 额外字段(此处无额外信息,留空)
+ # 实例化 SearchResult:封装应用名、模型名、文档ID、匹配得分等信息
result_class = SearchResult
-
result = result_class(
app_label,
model_name,
- raw_result['_id'],
- raw_result['_score'],
+ raw_result['_id'], # Elasticsearch 中文档的 ID
+ raw_result['_score'], # 匹配得分(用于排序)
**additional_fields)
raw_results.append(result)
+
+ # 搜索结果元数据:分面(无分面需求,留空)、拼写建议
facets = {}
+ # 若推荐词与原查询词不同,返回推荐词;否则为 None
spelling_suggestion = None if query_string == suggestion else suggestion
+ # 返回 Haystack 标准格式的搜索结果
return {
- 'results': raw_results,
- 'hits': hits,
- 'facets': facets,
- 'spelling_suggestion': spelling_suggestion,
+ 'results': raw_results, # 封装后的搜索结果列表
+ 'hits': hits, # 总匹配数
+ 'facets': facets, # 分面数据(空)
+ 'spelling_suggestion': spelling_suggestion, # 拼写建议
}
+# 自定义 Elasticsearch 查询类:处理查询参数解析、格式清洗等
class ElasticSearchQuery(BaseSearchQuery):
+ # 转换日期格式:适配 Elasticsearch 的日期查询需求
def _convert_datetime(self, date):
- if hasattr(date, 'hour'):
+ if hasattr(date, 'hour'): # 若为datetime(含时分秒),格式化为年月日时分秒
return force_str(date.strftime('%Y%m%d%H%M%S'))
- else:
+ else: # 若为date(仅年月日),补全时分秒为000000
return force_str(date.strftime('%Y%m%d000000'))
+ # 清洗查询词:处理 Haystack 保留词和特殊字符,避免查询语法错误
def clean(self, query_fragment):
- """
- Provides a mechanism for sanitizing user input before presenting the
- value to the backend.
-
- Whoosh 1.X differs here in that you can no longer use a backslash
- to escape reserved characters. Instead, the whole word should be
- quoted.
- """
- words = query_fragment.split()
+ words = query_fragment.split() # 拆分查询词为单词列表
cleaned_words = []
for word in words:
+ # 处理 Haystack 保留词(如 AND、OR),转为小写(避免语法冲突)
if word in self.backend.RESERVED_WORDS:
word = word.replace(word, word.lower())
+ # 处理特殊字符(如 +、-、*):包含特殊字符的单词用引号包裹
for char in self.backend.RESERVED_CHARACTERS:
if char in word:
word = "'%s'" % word
@@ -152,32 +186,39 @@ class ElasticSearchQuery(BaseSearchQuery):
cleaned_words.append(word)
- return ' '.join(cleaned_words)
+ return ' '.join(cleaned_words) # 拼接清洗后的查询词
+ # 构建查询片段:适配自定义查询逻辑(此处直接返回查询字符串)
def build_query_fragment(self, field, filter_type, value):
return value.query_string
+ # 获取搜索结果总数:通过 get_results 结果长度计算
def get_count(self):
results = self.get_results()
return len(results) if results else 0
+ # 获取拼写建议:返回后端生成的推荐词
def get_spelling_suggestion(self, preferred_query=None):
return self._spelling_suggestion
+ # 构建搜索参数:继承父类逻辑,可自定义扩展参数
def build_params(self, spelling_query=None):
kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query)
return kwargs
+# 自定义搜索表单:扩展 Haystack 基础表单,支持“是否启用拼写建议”的控制
class ElasticSearchModelSearchForm(ModelSearchForm):
-
def search(self):
- # 是否建议搜索
+ # 根据请求参数(is_suggest)设置后端是否启用拼写建议
+ # 若 is_suggest = "no",则不启用;否则启用
self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no"
+ # 调用父类 search 方法,执行搜索并返回结果
sqs = super().search()
return sqs
+# 自定义 Elasticsearch 搜索引擎:关联后端和查询类,供 Haystack 调用
class ElasticSearchEngine(BaseEngine):
- backend = ElasticSearchBackend
- query = ElasticSearchQuery
+ backend = ElasticSearchBackend # 绑定自定义搜索后端
+ query = ElasticSearchQuery # 绑定自定义查询类
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/feeds.py b/src/DjangoBlog-master/djangoblog/feeds.py
index 8c4e851..d8e09ae 100644
--- a/src/DjangoBlog-master/djangoblog/feeds.py
+++ b/src/DjangoBlog-master/djangoblog/feeds.py
@@ -1,40 +1,61 @@
+# 导入 Django 用户模型工具:获取当前项目的用户模型(支持自定义用户模型)
from django.contrib.auth import get_user_model
+# 导入 Django 内置的 Feed 基类:用于快速实现 RSS/Atom 订阅功能
from django.contrib.syndication.views import Feed
+# 导入 Django 时间工具:处理时区和当前时间
from django.utils import timezone
+# 导入 RSS 2.0 格式生成器:指定 Feed 输出格式为 RSS 2.0 标准
from django.utils.feedgenerator import Rss201rev2Feed
+# 导入博客核心模型和工具:关联文章数据及 Markdown 解析
from blog.models import Article
-from djangoblog.utils import CommonMarkdown
+from djangoblog.utils import CommonMarkdown # 自定义 Markdown 解析工具(将文章内容转为 HTML)
+# 自定义 RSS Feed 类:继承 Django 内置 Feed 类,实现博客文章的订阅功能
class DjangoBlogFeed(Feed):
+ # 指定 Feed 生成器类型:使用 RSS 2.0 标准格式(最常用的 RSS 版本)
feed_type = Rss201rev2Feed
+ # Feed 描述信息:显示在订阅源的说明中
description = '大巧无工,重剑无锋.'
+ # Feed 标题:订阅源的名称(通常为博客名称)
title = "且听风吟 大巧无工,重剑无锋. "
+ # Feed 的链接:订阅源自身的 URL(通常指向博客首页或 Feed 专属页面)
link = "/feed/"
+ # 订阅源作者名称:从系统第一个用户的昵称获取(适合个人博客)
def author_name(self):
return get_user_model().objects.first().nickname
+ # 订阅源作者链接:指向作者的个人页面(通过用户模型的 get_absolute_url 方法获取)
def author_link(self):
return get_user_model().objects.first().get_absolute_url()
+ # 订阅源包含的项目(文章):定义要展示在 Feed 中的内容
def items(self):
+ # 筛选条件:类型为文章(type='a')、状态为已发布(status='p')
+ # 排序规则:按发布时间倒序(最新发布的文章在前)
+ # 数量限制:只显示最新的 5 篇文章
return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5]
+ # 单个项目(文章)的标题:使用文章自身的标题
def item_title(self, item):
return item.title
+ # 单个项目(文章)的描述:将 Markdown 格式的文章内容转为 HTML 后展示
def item_description(self, item):
- return CommonMarkdown.get_markdown(item.body)
+ return CommonMarkdown.get_markdown(item.body) # 调用工具类解析 Markdown
+ # 订阅源的版权信息:动态生成包含当前年份的版权声明
def feed_copyright(self):
- now = timezone.now()
- return "Copyright© {year} 且听风吟".format(year=now.year)
+ now = timezone.now() # 获取当前时间(带时区)
+ return "Copyright© {year} 且听风吟".format(year=now.year) # 格式化版权信息
+ # 单个项目(文章)的链接:指向文章详情页(通过文章模型的 get_absolute_url 方法获取)
def item_link(self, item):
return item.get_absolute_url()
+ # 单个项目(文章)的唯一标识(GUID):此处留空,Django 会默认使用 item_link 作为 GUID
def item_guid(self, item):
- return
+ return
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/logentryadmin.py b/src/DjangoBlog-master/djangoblog/logentryadmin.py
index 2f6a535..ac3f7cb 100644
--- a/src/DjangoBlog-master/djangoblog/logentryadmin.py
+++ b/src/DjangoBlog-master/djangoblog/logentryadmin.py
@@ -1,27 +1,37 @@
+# 导入 Django Admin 核心模块:用于自定义后台管理界面
from django.contrib import admin
+# 导入 Admin 日志相关常量和模型:处理日志操作类型(如删除)
from django.contrib.admin.models import DELETION
from django.contrib.contenttypes.models import ContentType
+# 导入 Django URL 和字符串处理工具:生成反向链接、处理编码和转义
from django.urls import reverse, NoReverseMatch
from django.utils.encoding import force_str
from django.utils.html import escape
from django.utils.safestring import mark_safe
+# 导入国际化工具:支持后台文字的多语言翻译
from django.utils.translation import gettext_lazy as _
+# 自定义 LogEntry Admin 类:用于在 Django 后台管理 Admin 操作日志(记录用户对模型的增删改操作)
class LogEntryAdmin(admin.ModelAdmin):
+ # 列表页筛选器:按“内容类型”(即操作的模型,如 Article、Comment)筛选日志
list_filter = [
'content_type'
]
+ # 列表页搜索框:支持按“对象名称”(如文章标题)和“操作描述”(如“修改了标题”)搜索
search_fields = [
'object_repr',
'change_message'
]
+ # 列表页可点击的链接:点击“操作时间”或“操作描述”可进入日志详情页
list_display_links = [
'action_time',
'get_change_message',
]
+
+ # 列表页展示的字段:操作时间、操作用户(带链接)、操作模型、操作对象(带链接)、操作描述
list_display = [
'action_time',
'user_link',
@@ -30,62 +40,81 @@ class LogEntryAdmin(admin.ModelAdmin):
'get_change_message',
]
+ # 权限控制:禁止添加日志(日志由系统自动生成,不允许手动添加)
def has_add_permission(self, request):
return False
+ # 权限控制:仅允许超级用户或拥有“修改日志”权限的用户查看/修改日志,且禁止 POST 请求(避免提交修改)
def has_change_permission(self, request, obj=None):
return (
request.user.is_superuser or
request.user.has_perm('admin.change_logentry')
) and request.method != 'POST'
+ # 权限控制:禁止删除日志(日志需留存,不允许手动删除)
def has_delete_permission(self, request, obj=None):
return False
+ # 自定义列表字段:操作对象(生成带链接的对象名称,点击可跳转到对象的编辑页)
def object_link(self, obj):
+ # 转义对象名称(避免 XSS 攻击)
object_link = escape(obj.object_repr)
+ # 获取操作对象的内容类型(即所属模型)
content_type = obj.content_type
+ # 若操作不是“删除”(DELETION)且内容类型存在(排除异常情况)
if obj.action_flag != DELETION and content_type is not None:
- # try returning an actual link instead of object repr string
try:
+ # 生成对象编辑页的 URL(格式:admin/应用名/模型名/change/对象ID/)
url = reverse(
'admin:{}_{}_change'.format(content_type.app_label,
content_type.model),
args=[obj.object_id]
)
+ # 将对象名称转为链接(点击跳转到编辑页)
object_link = '{}'.format(url, object_link)
except NoReverseMatch:
+ # 若无法生成链接(如模型未注册到 Admin),则保留纯文本名称
pass
+ # 标记为安全 HTML(告诉 Django 无需转义,避免链接被当作文本显示)
return mark_safe(object_link)
- object_link.admin_order_field = 'object_repr'
- object_link.short_description = _('object')
+ # 配置自定义字段的排序和显示名称
+ object_link.admin_order_field = 'object_repr' # 支持按“对象名称”排序
+ object_link.short_description = _('object') # 列表页字段显示名称(支持翻译)
+ # 自定义列表字段:操作用户(生成带链接的用户名,点击可跳转到用户的编辑页)
def user_link(self, obj):
+ # 获取用户模型的内容类型
content_type = ContentType.objects.get_for_model(type(obj.user))
+ # 转义用户名(避免 XSS 攻击)
user_link = escape(force_str(obj.user))
try:
- # try returning an actual link instead of object repr string
+ # 生成用户编辑页的 URL
url = reverse(
'admin:{}_{}_change'.format(content_type.app_label,
content_type.model),
args=[obj.user.pk]
)
+ # 将用户名转为链接(点击跳转到用户编辑页)
user_link = '{}'.format(url, user_link)
except NoReverseMatch:
+ # 若无法生成链接(如用户模型未注册到 Admin),则保留纯文本用户名
pass
return mark_safe(user_link)
- user_link.admin_order_field = 'user'
- user_link.short_description = _('user')
+ # 配置自定义字段的排序和显示名称
+ user_link.admin_order_field = 'user' # 支持按“用户”排序
+ user_link.short_description = _('user') # 列表页字段显示名称(支持翻译)
+ # 优化查询性能:预加载“内容类型”关联数据(避免列表页加载时产生大量数据库查询)
def get_queryset(self, request):
queryset = super(LogEntryAdmin, self).get_queryset(request)
return queryset.prefetch_related('content_type')
+ # 自定义批量操作:移除“批量删除”按钮(防止误删日志)
def get_actions(self, request):
actions = super(LogEntryAdmin, self).get_actions(request)
if 'delete_selected' in actions:
- del actions['delete_selected']
- return actions
+ del actions['delete_selected'] # 删除“批量删除”操作
+ return actions
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py b/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py
index 6685b7c..3205d2e 100644
--- a/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py
+++ b/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py
@@ -1,7 +1,11 @@
-ARTICLE_DETAIL_LOAD = 'article_detail_load'
-ARTICLE_CREATE = 'article_create'
-ARTICLE_UPDATE = 'article_update'
-ARTICLE_DELETE = 'article_delete'
-
-ARTICLE_CONTENT_HOOK_NAME = "the_content"
+# 文章相关操作的标识常量:用于统一管理操作类型,避免硬编码字符串导致的不一致问题
+# 场景:可用于日志记录、统计分析、权限校验等,通过常量标识具体操作
+ARTICLE_DETAIL_LOAD = 'article_detail_load' # 文章详情页加载操作标识(如用户访问某篇文章详情时使用)
+ARTICLE_CREATE = 'article_create' # 文章创建操作标识(如用户发布新文章时使用)
+ARTICLE_UPDATE = 'article_update' # 文章更新操作标识(如用户编辑已发布文章时使用)
+ARTICLE_DELETE = 'article_delete' # 文章删除操作标识(如用户删除某篇文章时使用)
+# 文章内容钩子(Hook)名称常量:用于定义文章内容处理的钩子函数/扩展点名称
+# 场景:在 Django 等框架中,可通过钩子机制对文章内容进行自定义处理(如过滤敏感词、添加水印、解析 markdown 等)
+# 例如:注册名为 "the_content" 的钩子函数,在文章内容渲染前自动执行处理逻辑
+ARTICLE_CONTENT_HOOK_NAME = "the_content"
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/settings.py b/src/DjangoBlog-master/djangoblog/settings.py
index d076bb6..3a75c92 100644
--- a/src/DjangoBlog-master/djangoblog/settings.py
+++ b/src/DjangoBlog-master/djangoblog/settings.py
@@ -13,331 +13,371 @@ import os
import sys
from pathlib import Path
+# 导入 Django 国际化工具:用于多语言文本翻译
from django.utils.translation import gettext_lazy as _
+# 自定义工具函数:将环境变量转为布尔值(处理配置的灵活性)
def env_to_bool(env, default):
- str_val = os.environ.get(env)
+ str_val = os.environ.get(env) # 从环境变量获取值
+ # 若环境变量未设置则返回默认值,否则判断字符串是否为'True'
return default if str_val is None else str_val == 'True'
-# Build paths inside the project like this: BASE_DIR / 'subdir'.
+# 项目根目录:定位到 settings.py 所在目录的父目录(项目根路径)
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
-# SECURITY WARNING: keep the secret key used in production secret!
+# 安全密钥:用于加密会话、CSRF 令牌等敏感数据(生产环境需通过环境变量设置,避免硬编码)
SECRET_KEY = os.environ.get(
'DJANGO_SECRET_KEY') or 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6'
-# SECURITY WARNING: don't run with debug turned on in production!
+# 调试模式:开发环境开启(便于调试),生产环境必须关闭(避免暴露敏感信息)
DEBUG = env_to_bool('DJANGO_DEBUG', True)
-# DEBUG = False
+# DEBUG = False # 生产环境手动关闭调试的示例
+# 测试模式标识:判断是否正在执行测试命令(如 python manage.py test)
TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test'
-# ALLOWED_HOSTS = []
+# 允许访问的主机:生产环境需指定具体域名,开发环境用'*'允许所有主机(存在安全风险,生产禁用)
+# ALLOWED_HOSTS = [] # 生产环境初始空配置(需补充具体域名)
ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com']
-# django 4.0新增配置
+# Django 4.0+ 新增配置:信任的 CSRF 来源(避免跨域 CSRF 验证失败,生产环境需指定真实域名)
CSRF_TRUSTED_ORIGINS = ['http://example.com']
-# Application definition
+# Application definition(已安装的应用:Django 内置应用 + 第三方应用 + 自定义应用)
INSTALLED_APPS = [
- # 'django.contrib.admin',
+ # Django 内置 Admin 应用:使用精简版配置(SimpleAdminConfig),减少不必要功能
+ # 'django.contrib.admin', # 完整版 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',
- 'haystack',
- 'blog',
- 'accounts',
- 'comments',
- 'oauth',
- 'servermanager',
- 'owntracks',
- 'compressor',
- 'djangoblog'
+ 'django.contrib.auth', # 认证与授权系统
+ 'django.contrib.contenttypes', # 内容类型框架(关联模型与权限)
+ 'django.contrib.sessions', # 会话管理(用户登录状态保持)
+ 'django.contrib.messages', # 消息提示系统(如登录成功提示)
+ 'django.contrib.staticfiles', # 静态文件管理(CSS/JS/图片)
+ 'django.contrib.sites', # 多站点支持(用于 RSS、OAuth 等功能)
+ 'django.contrib.sitemaps', # 站点地图生成(利于 SEO)
+ 'mdeditor', # 第三方应用:Markdown 编辑器(用于文章编写)
+ 'haystack', # 第三方应用:搜索框架(对接 Whoosh/Elasticsearch)
+ 'blog', # 自定义应用:博客核心功能(文章、分类等)
+ 'accounts', # 自定义应用:用户账户管理(登录、注册等)
+ 'comments', # 自定义应用:评论功能
+ 'oauth', # 自定义应用:第三方登录(如 GitHub、微博)
+ 'servermanager', # 自定义应用:服务器管理(命令执行、日志记录)
+ 'owntracks', # 自定义应用:位置追踪(OwnTracks 数据管理)
+ 'compressor', # 第三方应用:静态文件压缩(CSS/JS 压缩,提升加载速度)
+ 'djangoblog' # 自定义应用:项目核心配置(如信号、插件)
]
+# 中间件:处理请求/响应的钩子(按顺序执行,影响请求流程)
MIDDLEWARE = [
-
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.locale.LocaleMiddleware',
- 'django.middleware.gzip.GZipMiddleware',
- # 'django.middleware.cache.UpdateCacheMiddleware',
- 'django.middleware.common.CommonMiddleware',
- # 'django.middleware.cache.FetchFromCacheMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
- 'django.middleware.http.ConditionalGetMiddleware',
- 'blog.middleware.OnlineMiddleware'
+ 'django.middleware.security.SecurityMiddleware', # 安全相关中间件(HTTPS、XSS 防护等)
+ 'django.contrib.sessions.middleware.SessionMiddleware', # 会话管理中间件
+ 'django.middleware.locale.LocaleMiddleware', # 国际化中间件(处理多语言切换)
+ 'django.middleware.gzip.GZipMiddleware', # GZip 压缩中间件(减少响应体积)
+ # 'django.middleware.cache.UpdateCacheMiddleware', # 缓存更新中间件(注释:按需启用)
+ 'django.middleware.common.CommonMiddleware', # 通用中间件(处理 URL 重定向、404 等)
+ # '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', # 条件请求中间件(缓存协商,减少重复请求)
+ 'blog.middleware.OnlineMiddleware' # 自定义中间件:用户在线状态管理
]
+# 根 URL 配置:指定项目的主 URL 路由文件
ROOT_URLCONF = 'djangoblog.urls'
+# 模板配置:定义模板引擎、路径及上下文处理器
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [os.path.join(BASE_DIR, 'templates')],
- 'APP_DIRS': True,
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates', # 使用 Django 内置模板引擎
+ 'DIRS': [os.path.join(BASE_DIR, 'templates')], # 全局模板目录(项目根目录下的 templates)
+ 'APP_DIRS': True, # 允许从各应用的 templates 目录加载模板
'OPTIONS': {
+ # 上下文处理器:向所有模板注入全局变量(如用户信息、请求对象)
'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
- 'blog.context_processors.seo_processor'
+ 'django.template.context_processors.debug', # 调试模式变量(如 DEBUG)
+ 'django.template.context_processors.request', # 请求对象(request)
+ 'django.contrib.auth.context_processors.auth', # 认证相关变量(如 user)
+ 'django.contrib.messages.context_processors.messages', # 消息变量(如 messages)
+ 'blog.context_processors.seo_processor' # 自定义上下文处理器:注入 SEO 相关数据
],
},
},
]
+# WSGI 应用:指定项目的 WSGI 入口文件(用于部署,如 Gunicorn、uWSGI)
WSGI_APPLICATION = 'djangoblog.wsgi.application'
-# Database
-# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
-
+# Database(数据库配置:使用 MySQL 数据库)
+# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = {
'default': {
- 'ENGINE': 'django.db.backends.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),
+ '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'},
+ 'charset': 'utf8mb4'}, # 数据库字符集(支持 emoji 表情)
}}
-# Password validation
-# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
+# Password validation(密码验证规则:确保用户密码强度)
+# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
+ # 验证密码与用户属性(如用户名、邮箱)的相似度
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
+ # 验证密码最小长度(默认 8 位)
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
+ # 验证密码是否在常见弱密码列表中
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
+ # 验证密码是否纯数字
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
+
+# Internationalization(国际化配置:多语言支持)
+# https://docs.djangoproject.com/en/1.10/topics/i18n/
LANGUAGES = (
- ('en', _('English')),
- ('zh-hans', _('Simplified Chinese')),
- ('zh-hant', _('Traditional Chinese')),
+ ('en', _('English')), # 英语
+ ('zh-hans', _('Simplified Chinese')), # 简体中文
+ ('zh-hant', _('Traditional Chinese')), # 繁体中文
)
+# 翻译文件目录:指定多语言翻译文件(.po/.mo)的存放路径
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
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/1.10/howto/static-files/
+LANGUAGE_CODE = 'zh-hans' # 默认语言:简体中文
+TIME_ZONE = 'Asia/Shanghai' # 时区:上海(中国时区)
+USE_I18N = True # 启用国际化(支持多语言)
+USE_L10N = True # 启用本地化(支持区域化日期、数字格式)
+USE_TZ = False # 禁用 UTC 时间(使用本地时区存储时间,避免时区转换问题)
+# Search(搜索框架配置:Haystack + Whoosh/Elasticsearch)
HAYSTACK_CONNECTIONS = {
'default': {
+ # 搜索引擎:默认使用 Whoosh(轻量级全文搜索引擎,适合中小项目)
'ENGINE': 'djangoblog.whoosh_cn_backend.WhooshEngine',
+ # 索引文件路径:Whoosh 索引文件存储位置
'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
},
}
-# Automatically update searching index
+# 实时更新索引:当模型数据(如文章)新增/修改/删除时,自动更新搜索索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
-# Allow user login with username and password
-AUTHENTICATION_BACKENDS = [
- 'accounts.user_login_backend.EmailOrUsernameModelBackend']
-STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic')
-
-STATIC_URL = '/static/'
-STATICFILES = os.path.join(BASE_DIR, 'static')
+# Authentication(认证配置:自定义登录逻辑)
+# 允许用户通过“用户名”或“邮箱”登录(默认仅支持用户名)
+AUTHENTICATION_BACKENDS = [
+ 'accounts.user_login_backend.EmailOrUsernameModelBackend']
+# 自定义用户模型:使用 accounts 应用的 BlogUser 模型(替代 Django 内置 User 模型)
AUTH_USER_MODEL = 'accounts.BlogUser'
+# 登录 URL:未登录用户访问需认证页面时,重定向到该 URL
LOGIN_URL = '/login/'
-TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
-DATE_TIME_FORMAT = '%Y-%m-%d'
-# bootstrap color styles
-BOOTSTRAP_COLOR_TYPES = [
+# Custom Settings(自定义业务配置)
+TIME_FORMAT = '%Y-%m-%d %H:%M:%S' # 时间显示格式(如 2024-05-20 14:30:00)
+DATE_TIME_FORMAT = '%Y-%m-%d' # 日期显示格式(如 2024-05-20)
+BOOTSTRAP_COLOR_TYPES = [ # Bootstrap 颜色样式(用于前端组件,如标签、按钮)
'default', 'primary', 'success', 'info', 'warning', 'danger'
]
+PAGINATE_BY = 10 # 分页大小:每页显示 10 条数据(如文章列表、评论列表)
+CACHE_CONTROL_MAX_AGE = 2592000 # HTTP 缓存有效期:30 天(单位:秒)
+
-# paginate
-PAGINATE_BY = 10
-# http cache timeout
-CACHE_CONTROL_MAX_AGE = 2592000
-# cache setting
+# Cache(缓存配置:默认本地内存缓存,支持 Redis 扩展)
CACHES = {
'default': {
+ # 本地内存缓存(适合开发环境,生产环境建议用 Redis/Memcached)
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
- 'TIMEOUT': 10800,
- 'LOCATION': 'unique-snowflake',
+ 'TIMEOUT': 10800, # 缓存有效期:3 小时(单位:秒)
+ 'LOCATION': 'unique-snowflake', # 缓存实例标识(避免多实例冲突)
}
}
-# 使用redis作为缓存
+# 若环境变量指定 Redis 地址,则使用 Redis 作为缓存(生产环境推荐)
if os.environ.get("DJANGO_REDIS_URL"):
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
- 'LOCATION': f'redis://{os.environ.get("DJANGO_REDIS_URL")}',
+ 'LOCATION': f'redis://{os.environ.get("DJANGO_REDIS_URL")}', # Redis 连接地址
}
}
-SITE_ID = 1
+
+# Site & SEO(站点与 SEO 配置)
+SITE_ID = 1 # 站点 ID(多站点配置时区分不同站点,单站点固定为 1)
+# 百度主动推送 URL:用于文章发布后主动通知百度收录(提升 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'
-# Email:
-EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
-EMAIL_USE_TLS = env_to_bool('DJANGO_EMAIL_TLS', False)
-EMAIL_USE_SSL = env_to_bool('DJANGO_EMAIL_SSL', True)
-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
-# Setting debug=false did NOT handle except email notifications
+
+# Email(邮件配置:用于发送验证码、评论通知等)
+EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # SMTP 邮件后端
+EMAIL_USE_TLS = env_to_bool('DJANGO_EMAIL_TLS', False) # 是否启用 TLS 加密(与 SSL 二选一)
+EMAIL_USE_SSL = env_to_bool('DJANGO_EMAIL_SSL', True) # 是否启用 SSL 加密(默认启用,端口通常为 465)
+EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST') or 'smtp.mxhichina.com' # SMTP 服务器地址(默认阿里云)
+EMAIL_PORT = int(os.environ.get('DJANGO_EMAIL_PORT') or 465) # SMTP 端口(SSL 通常为 465,TLS 通常为 587)
+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 # 服务器错误通知发件人邮箱
+# 管理员邮箱:生产环境错误(如 500 异常)会发送到此邮箱
ADMINS = [('admin', os.environ.get('DJANGO_ADMIN_EMAIL') or 'admin@admin.com')]
-# WX ADMIN password(Two times md5)
+# 微信管理员密码:二次 MD5 加密(用于微信后台管理验证,具体业务自定义)
WXADMIN = os.environ.get(
'DJANGO_WXADMIN_PASSWORD') or '995F03AC401D6CABABAEF756FC4D43C7'
-LOG_PATH = os.path.join(BASE_DIR, 'logs')
+
+# Logging(日志配置:记录系统操作、错误信息)
+LOG_PATH = os.path.join(BASE_DIR, 'logs') # 日志文件目录
+# 若日志目录不存在,则创建(确保日志能正常写入)
if not os.path.exists(LOG_PATH):
os.makedirs(LOG_PATH, exist_ok=True)
LOGGING = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'root': {
- 'level': 'INFO',
- 'handlers': ['console', 'log_file'],
+ 'version': 1, # 日志配置版本(固定为 1)
+ 'disable_existing_loggers': False, # 不禁用已存在的日志器
+ 'root': { # 根日志器(所有未指定日志器的日志都会走这里)
+ 'level': 'INFO', # 日志级别(INFO:记录普通信息及以上级别)
+ 'handlers': ['console', 'log_file'], # 日志处理器(控制台 + 文件)
},
- 'formatters': {
+ 'formatters': { # 日志格式:定义日志的输出结构
'verbose': {
+ # 日志格式:时间 + 级别 + 调用位置 + 消息(便于问题定位)
'format': '[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d %(module)s] %(message)s',
}
},
- 'filters': {
- 'require_debug_false': {
+ 'filters': { # 日志过滤器:按条件过滤日志
+ 'require_debug_false': { # 仅当 DEBUG=False 时生效(生产环境)
'()': 'django.utils.log.RequireDebugFalse',
},
- 'require_debug_true': {
+ 'require_debug_true': { # 仅当 DEBUG=True 时生效(开发环境)
'()': 'django.utils.log.RequireDebugTrue',
},
},
- 'handlers': {
- 'log_file': {
+ 'handlers': { # 日志处理器:定义日志的输出方式
+ 'log_file': { # 文件处理器:按时间轮转切割日志
'level': 'INFO',
- 'class': 'logging.handlers.TimedRotatingFileHandler',
- 'filename': os.path.join(LOG_PATH, 'djangoblog.log'),
- 'when': 'D',
- 'formatter': 'verbose',
- 'interval': 1,
- 'delay': True,
- 'backupCount': 5,
- 'encoding': 'utf-8'
+ 'class': 'logging.handlers.TimedRotatingFileHandler', # 按时间轮转的处理器
+ 'filename': os.path.join(LOG_PATH, 'djangoblog.log'), # 日志文件路径
+ 'when': 'D', # 轮转周期:每天(Day)
+ 'formatter': 'verbose', # 使用 verbose 格式
+ 'interval': 1, # 轮转间隔:1 个周期(1 天)
+ 'delay': True, # 延迟创建文件(直到有日志才创建)
+ 'backupCount': 5, # 保留日志备份数:5 天
+ 'encoding': 'utf-8' # 日志文件编码
},
- 'console': {
+ 'console': { # 控制台处理器:开发环境输出到终端
'level': 'DEBUG',
- 'filters': ['require_debug_true'],
+ 'filters': ['require_debug_true'], # 仅开发环境生效
'class': 'logging.StreamHandler',
'formatter': 'verbose'
},
- 'null': {
+ 'null': { # 空处理器:用于屏蔽不需要的日志
'class': 'logging.NullHandler',
},
- 'mail_admins': {
- 'level': 'ERROR',
- 'filters': ['require_debug_false'],
+ 'mail_admins': { # 邮件处理器:生产环境错误发送到管理员邮箱
+ 'level': 'ERROR', # 仅 ERROR 级别日志触发
+ 'filters': ['require_debug_false'], # 仅生产环境生效
'class': 'django.utils.log.AdminEmailHandler'
}
},
- 'loggers': {
- 'djangoblog': {
- 'handlers': ['log_file', 'console'],
+ 'loggers': { # 自定义日志器:针对特定模块配置日志
+ 'djangoblog': { # 项目核心模块日志器
+ 'handlers': ['log_file', 'console'], # 输出到文件和控制台
'level': 'INFO',
- 'propagate': True,
+ 'propagate': True, # 是否向上传递到根日志器(此处开启)
},
- 'django.request': {
- 'handlers': ['mail_admins'],
+ 'django.request': { # Django 请求模块日志器(记录请求相关错误)
+ 'handlers': ['mail_admins'], # 错误发送到管理员邮箱
'level': 'ERROR',
- 'propagate': False,
+ 'propagate': False, # 不向上传递(避免重复记录)
}
}
}
+
+# Static Files(静态文件配置:CSS/JS/图片等)
+# 静态文件收集目录:执行 collectstatic 命令后,静态文件会汇总到此处(生产环境使用)
+STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic')
+# 静态文件 URL 前缀:前端通过 /static/ 访问静态文件
+STATIC_URL = '/static/'
+# 全局静态文件目录:项目根目录下的 static 目录(存放全局静态文件)
+STATICFILES = os.path.join(BASE_DIR, 'static')
+
+# 静态文件查找器:指定 Django 查找静态文件的路径(内置 + 第三方)
STATICFILES_FINDERS = (
- 'django.contrib.staticfiles.finders.FileSystemFinder',
- 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
- # other
- 'compressor.finders.CompressorFinder',
+ 'django.contrib.staticfiles.finders.FileSystemFinder', # 查找全局 STATICFILES 目录
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder', # 查找各应用的 static 目录
+ 'compressor.finders.CompressorFinder', # 查找压缩后的静态文件(第三方 compressor 应用)
)
-COMPRESS_ENABLED = True
-# COMPRESS_OFFLINE = True
-
+# 静态文件压缩配置(compressor 应用)
+COMPRESS_ENABLED = True # 启用压缩(生产环境建议开启,提升加载速度)
+# COMPRESS_OFFLINE = True # 离线压缩(预先生成压缩文件,生产环境推荐,此处注释按需启用)
+# CSS 压缩过滤器:绝对路径处理 + 代码压缩
COMPRESS_CSS_FILTERS = [
- # creates absolute urls from relative ones
'compressor.filters.css_default.CssAbsoluteFilter',
- # css minimizer
'compressor.filters.cssmin.CSSMinFilter'
]
+# JS 压缩过滤器:代码压缩
COMPRESS_JS_FILTERS = [
'compressor.filters.jsmin.JSMinFilter'
]
+
+# Media Files(媒体文件配置:用户上传的文件,如文章图片、头像)
+# 媒体文件存储目录:项目根目录下的 uploads 目录
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')
+# 媒体文件 URL 前缀:前端通过 /media/ 访问上传的文件
MEDIA_URL = '/media/'
+
+# X-Frame-Options:防止点击劫持(SAMEORIGIN:仅允许同域页面嵌入当前页面)
X_FRAME_OPTIONS = 'SAMEORIGIN'
+# 默认模型主键类型:Django 3.2+ 新增,指定模型默认主键为 BigAutoField(64 位自增整数)
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+# Elasticsearch 配置(按需启用:若环境变量指定 Elasticsearch 地址,则使用 Elasticsearch 替代 Whoosh)
if os.environ.get('DJANGO_ELASTICSEARCH_HOST'):
ELASTICSEARCH_DSL = {
'default': {
- 'hosts': os.environ.get('DJANGO_ELASTICSEARCH_HOST')
+ 'hosts': os.environ.get('DJANGO_ELASTICSEARCH_HOST') # Elasticsearch 连接地址
},
}
+ # 替换 Haystack 搜索引擎为自定义的 Elasticsearch 引擎
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine',
},
}
-# Plugin System
-PLUGINS_DIR = BASE_DIR / 'plugins'
+
+# Plugin System(插件系统配置:自定义插件功能)
+PLUGINS_DIR = BASE_DIR / 'plugins' # 插件目录:项目根目录下的 plugins 目录
+# 激活的插件列表:指定需要加载的插件(如文章版权、阅读时长统计等)
ACTIVE_PLUGINS = [
- 'article_copyright',
- 'reading_time',
- 'external_links',
- 'view_count',
- 'seo_optimizer'
+ 'article_copyright', # 文章版权插件
+ 'reading_time', # 阅读时长统计插件
+ 'external_links', # 外部链接处理插件
+ 'view_count', # 文章阅读量统计插件
+ 'seo_optimizer' # SEO 优化插件
]
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/sitemap.py b/src/DjangoBlog-master/djangoblog/sitemap.py
index 8b7d446..a02411f 100644
--- a/src/DjangoBlog-master/djangoblog/sitemap.py
+++ b/src/DjangoBlog-master/djangoblog/sitemap.py
@@ -1,59 +1,88 @@
+# 导入 Django 内置站点地图基类:用于快速实现标准化 sitemap.xml
from django.contrib.sitemaps import Sitemap
+# 导入 URL 反向解析工具:生成页面的绝对 URL(适配路由命名)
from django.urls import reverse
+# 导入博客核心模型:用于生成动态内容(文章、分类、标签)的站点地图
from blog.models import Article, Category, Tag
+# 静态页面站点地图类:处理无数据库关联的静态页面(如博客首页)
class StaticViewSitemap(Sitemap):
+ # 优先级:0.5(取值 0.0-1.0,值越高搜索引擎越优先抓取,静态页优先级中等)
priority = 0.5
+ # 更新频率:daily(每天更新,适合内容相对稳定但可能微调的静态页)
changefreq = 'daily'
+ # 定义要包含的静态页面:返回路由名称列表(对应 urls.py 中命名的路由)
def items(self):
- return ['blog:index', ]
+ return ['blog:index', ] # 此处仅包含博客首页(路由名为 'blog:index')
+ # 生成静态页面的 URL:通过路由名称反向解析为绝对路径
def location(self, item):
- return reverse(item)
+ return reverse(item) # item 为 items() 返回的路由名称,如 'blog:index'
+# 文章站点地图类:处理博客文章的动态页面(核心内容,SEO 关键)
class ArticleSiteMap(Sitemap):
+ # 更新频率:monthly(每月更新,适合文章发布后较少修改的场景)
changefreq = "monthly"
+ # 优先级:0.6(高于静态页和分类/标签,文章是站点核心内容,优先抓取)
priority = "0.6"
+ # 定义要包含的文章:仅筛选“已发布”状态的文章(排除草稿、私有文章)
def items(self):
return Article.objects.filter(status='p')
+ # 文章最后更新时间:用于搜索引擎判断内容是否更新,提升抓取效率
def lastmod(self, obj):
- return obj.last_modify_time
+ return obj.last_modify_time # 引用文章模型的“最后修改时间”字段
+# 分类站点地图类:处理文章分类页面(聚合类内容,辅助 SEO)
class CategorySiteMap(Sitemap):
+ # 更新频率:Weekly(每周更新,分类内容更新频率低于文章)
changefreq = "Weekly"
+ # 优先级:0.6(与文章同级,分类页是重要的内容聚合入口)
priority = "0.6"
+ # 定义要包含的分类:获取所有分类(无论是否有文章,确保分类页被收录)
def items(self):
return Category.objects.all()
+ # 分类最后更新时间:判断分类下内容是否有变化(如新增/修改文章)
def lastmod(self, obj):
- return obj.last_modify_time
+ return obj.last_modify_time # 引用分类模型的“最后修改时间”字段
+# 标签站点地图类:处理文章标签页面(细分内容聚合,补充 SEO 覆盖)
class TagSiteMap(Sitemap):
+ # 更新频率:Weekly(每周更新,标签内容更新频率与分类一致)
changefreq = "Weekly"
+ # 优先级:0.3(低于文章和分类,标签页是辅助导航入口,优先级较低)
priority = "0.3"
+ # 定义要包含的标签:获取所有标签(确保所有标签页被搜索引擎收录)
def items(self):
return Tag.objects.all()
+ # 标签最后更新时间:判断标签下内容是否有变化
def lastmod(self, obj):
- return obj.last_modify_time
+ return obj.last_modify_time # 引用标签模型的“最后修改时间”字段
+# 用户站点地图类:处理文章作者页面(展示用户发布的所有文章,提升作者页曝光)
class UserSiteMap(Sitemap):
+ # 更新频率:Weekly(每周更新,用户发布文章频率通常较低)
changefreq = "Weekly"
+ # 优先级:0.3(与标签同级,作者页是辅助内容入口)
priority = "0.3"
+ # 定义要包含的用户:获取所有发布过文章的作者(去重,避免重复收录同一用户)
def items(self):
+ # 1. 从所有文章中提取作者字段 → 2. 转集合去重 → 3. 转列表返回
return list(set(map(lambda x: x.author, Article.objects.all())))
+ # 用户页面最后更新时间:用用户“注册时间”替代(简化逻辑,也可改为用户最新文章发布时间)
def lastmod(self, obj):
- return obj.date_joined
+ return obj.date_joined # 引用用户模型的“注册时间”字段
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/spider_notify.py b/src/DjangoBlog-master/djangoblog/spider_notify.py
index 7b909e9..4d6d1cd 100644
--- a/src/DjangoBlog-master/djangoblog/spider_notify.py
+++ b/src/DjangoBlog-master/djangoblog/spider_notify.py
@@ -1,21 +1,43 @@
+# 导入日志模块:记录推送过程的成功信息与错误,便于排查问题
import logging
-
+# 导入 HTTP 请求库:用于向百度搜索引擎接口发送 POST 请求
import requests
+# 导入 Django 项目配置:获取百度主动推送的接口 URL(从 settings.py 读取)
from django.conf import settings
+# 初始化日志对象:指定日志归属为当前模块,便于区分不同模块的日志
logger = logging.getLogger(__name__)
+# 搜索引擎推送工具类:封装向百度等搜索引擎主动提交页面的逻辑
class SpiderNotify():
@staticmethod
def baidu_notify(urls):
+ """
+ 向百度搜索引擎主动推送页面(基于百度站长平台的“链接提交”接口)
+ 目的:让百度快速发现新页面,缩短收录周期(提升 SEO 效率)
+
+ Args:
+ urls: 待推送的 URL 列表(如 ['https://xxx.com/article/1/', 'https://xxx.com/article/2/'])
+ """
try:
+ # 格式化请求数据:百度接口要求 URL 以换行符(\n)分隔,拼接成字符串
data = '\n'.join(urls)
+ # 发送 POST 请求:调用 settings 中配置的百度推送接口 URL
result = requests.post(settings.BAIDU_NOTIFY_URL, data=data)
+ # 记录推送结果:将百度返回的响应文本(如成功/失败数量)写入日志
logger.info(result.text)
except Exception as e:
+ # 捕获异常(如网络错误、接口超时等),记录错误信息到日志
logger.error(e)
@staticmethod
def notify(url):
- SpiderNotify.baidu_notify(url)
+ """
+ 通用推送方法:统一入口,当前仅调用百度推送(可扩展支持其他搜索引擎)
+
+ Args:
+ url: 待推送的 URL(支持单个 URL 或 URL 列表,此处兼容百度推送的列表格式)
+ """
+ # 调用百度推送方法,实现页面提交
+ SpiderNotify.baidu_notify(url)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/tests.py b/src/DjangoBlog-master/djangoblog/tests.py
index 01237d9..9efd6d1 100644
--- a/src/DjangoBlog-master/djangoblog/tests.py
+++ b/src/DjangoBlog-master/djangoblog/tests.py
@@ -1,32 +1,29 @@
+# 导入 Django 内置的测试基类:提供单元测试所需的基础功能(如断言、测试环境初始化)
from django.test import TestCase
+# 导入项目自定义工具模块:测试其中的工具函数(如加密、Markdown解析、字典转URL参数)
from djangoblog.utils import *
+# 自定义测试类:继承 TestCase,用于测试 djangoblog 项目的工具函数功能
class DjangoBlogTest(TestCase):
+ # 测试前置方法:在每个测试方法(以 test_ 开头)执行前自动调用
+ # 用于初始化测试数据、配置测试环境等(此处暂无需初始化,留空)
def setUp(self):
pass
+ # 核心测试方法:测试 utils 模块中的多个工具函数(命名以 test_ 开头,Django 会自动识别执行)
def test_utils(self):
- md5 = get_sha256('test')
+ # 1. 测试 SHA256 加密函数(get_sha256)
+ # 对字符串 'test' 进行 SHA256 加密,获取加密结果
+ md5 = get_sha256('test') # 注意:函数名是 get_sha256,实际功能是 SHA256 加密(非 MD5,可能是命名习惯)
+ # 断言:加密结果不为空(验证函数能正常返回加密值,未抛出异常)
self.assertIsNotNone(md5)
- c = CommonMarkdown.get_markdown('''
- # Title1
-
- ```python
- import os
- ```
-
- [url](https://www.lylinux.net/)
-
- [ddd](http://www.baidu.com)
+ # 2. 测试 Markdown 解析函数(CommonMarkdown.get_markdown)
+ # 定义一段包含标题、Python代码块、超链接的 Markdown 文本
+ c = CommonMarkdown.get_markdown('''
+ # Title1 # 一级标题
- ''')
- self.assertIsNotNone(c)
- d = {
- 'd': 'key1',
- 'd2': 'key2'
- }
- data = parse_dict_to_url(d)
- self.assertIsNotNone(data)
+ ```python # Python 代码块
+ import os
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/urls.py b/src/DjangoBlog-master/djangoblog/urls.py
index 4aae58a..b7a3d07 100644
--- a/src/DjangoBlog-master/djangoblog/urls.py
+++ b/src/DjangoBlog-master/djangoblog/urls.py
@@ -13,52 +13,88 @@ Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
+# 导入项目配置:用于获取静态文件/媒体文件路径、DEBUG 状态等
from django.conf import settings
+# 导入国际化路由工具:生成带语言前缀的路由(如 /en/admin/、/zh-hans/blog/)
from django.conf.urls.i18n import i18n_patterns
+# 导入静态文件路由工具:开发环境下提供静态文件访问(生产环境需 Nginx 处理)
from django.conf.urls.static import static
+# 导入站点地图视图:关联站点地图配置,生成 sitemap.xml
from django.contrib.sitemaps.views import sitemap
+# 导入 URL 路由组件:path 用于精确匹配,include 用于引入子应用路由
from django.urls import path, include
+# 导入 re_path:支持正则表达式匹配 URL(适配复杂路由场景)
from django.urls import re_path
+# 导入 Haystack 搜索视图工厂:用于自定义搜索视图和表单
from haystack.views import search_view_factory
-from blog.views import EsSearchView
-from djangoblog.admin_site import admin_site
-from djangoblog.elasticsearch_backend import ElasticSearchModelSearchForm
-from djangoblog.feeds import DjangoBlogFeed
-from djangoblog.sitemap import ArticleSiteMap, CategorySiteMap, StaticViewSitemap, TagSiteMap, UserSiteMap
+# 导入项目自定义视图和配置:关联核心功能路由
+from blog.views import EsSearchView # 自定义 Elasticsearch 搜索视图
+from djangoblog.admin_site import admin_site # 自定义后台管理站点(替代默认 admin)
+from djangoblog.elasticsearch_backend import ElasticSearchModelSearchForm # 自定义搜索表单
+from djangoblog.feeds import DjangoBlogFeed # RSS 订阅 Feed 视图
+from djangoblog.sitemap import ( # 站点地图配置类
+ ArticleSiteMap, CategorySiteMap, StaticViewSitemap, TagSiteMap, UserSiteMap
+)
+# 站点地图聚合配置:将各类型站点地图归类,用于生成统一的 sitemap.xml
sitemaps = {
-
- 'blog': ArticleSiteMap,
- 'Category': CategorySiteMap,
- 'Tag': TagSiteMap,
- 'User': UserSiteMap,
- 'static': StaticViewSitemap
+ 'blog': ArticleSiteMap, # 文章站点地图
+ 'Category': CategorySiteMap, # 分类站点地图
+ 'Tag': TagSiteMap, # 标签站点地图
+ 'User': UserSiteMap, # 作者站点地图
+ 'static': StaticViewSitemap # 静态页面站点地图
}
-handler404 = 'blog.views.page_not_found_view'
-handler500 = 'blog.views.server_error_view'
-handle403 = 'blog.views.permission_denied_view'
+# 自定义错误页面路由:指定 404/500/403 错误时跳转的视图
+handler404 = 'blog.views.page_not_found_view' # 404 页面未找到
+handler500 = 'blog.views.server_error_view' # 500 服务器内部错误
+handle403 = 'blog.views.permission_denied_view'# 403 权限拒绝(注意变量名应为 handler403,此处可能是笔误)
+# 基础 URL 路由:不包含语言前缀的公共路由
urlpatterns = [
+ # 国际化路由:提供语言切换功能(如 /i18n/setlang/ 接口)
path('i18n/', include('django.conf.urls.i18n')),
]
+
+# 带语言前缀的路由:通过 i18n_patterns 自动添加语言前缀(如 /zh-hans/、/en/)
+# prefix_default_language=False:默认语言不显示前缀(如中文默认不显示 /zh-hans/,直接用根路径)
urlpatterns += i18n_patterns(
+ # 1. 后台管理路由:使用自定义的 admin_site(替代默认 admin),访问路径如 /admin/
re_path(r'^admin/', admin_site.urls),
+ # 2. 博客核心路由:引入 blog 应用的子路由,命名空间为 'blog'(路由名如 blog:index)
re_path(r'', include('blog.urls', namespace='blog')),
+ # 3. Markdown 编辑器路由:引入 mdeditor 第三方应用的路由,用于文章编辑时的 Markdown 预览
re_path(r'mdeditor/', include('mdeditor.urls')),
+ # 4. 评论路由:引入 comments 应用的子路由,命名空间为 'comment'
re_path(r'', include('comments.urls', namespace='comment')),
+ # 5. 用户账户路由:引入 accounts 应用的子路由(登录、注册、个人中心),命名空间为 'account'
re_path(r'', include('accounts.urls', namespace='account')),
+ # 6. 第三方登录路由:引入 oauth 应用的子路由(GitHub、微博登录),命名空间为 'oauth'
re_path(r'', include('oauth.urls', namespace='oauth')),
+ # 7. 站点地图路由:生成 sitemap.xml,供搜索引擎抓取(访问路径 /sitemap.xml)
re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap'),
+ # 8. RSS 订阅路由:提供两种访问路径(/feed/ 和 /rss/),均指向 DjangoBlogFeed 视图
re_path(r'^feed/$', DjangoBlogFeed()),
re_path(r'^rss/$', DjangoBlogFeed()),
- re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm),
- name='search'),
+ # 9. 搜索路由:使用自定义的搜索视图(EsSearchView)和表单(ElasticSearchModelSearchForm)
+ # 访问路径如 /search?q=关键词,命名为 'search'
+ re_path('^search', search_view_factory(
+ view_class=EsSearchView,
+ form_class=ElasticSearchModelSearchForm
+ ), name='search'),
+ # 10. 服务器管理路由:引入 servermanager 应用的子路由(命令执行、日志查看),命名空间为 'servermanager'
re_path(r'', include('servermanager.urls', namespace='servermanager')),
- re_path(r'', include('owntracks.urls', namespace='owntracks'))
- , prefix_default_language=False) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+ # 11. 位置追踪路由:引入 owntracks 应用的子路由(位置数据查看),命名空间为 'owntracks'
+ re_path(r'', include('owntracks.urls', namespace='owntracks')),
+ prefix_default_language=False # 默认语言不显示语言前缀
+)
+
+# 静态文件路由:开发环境下(DEBUG=True)通过 Django 提供静态文件访问(生产环境需注释,用 Nginx 处理)
+urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
+
+# 媒体文件路由:仅在 DEBUG=True(开发环境)时生效,提供用户上传文件的访问(如 /media/avatar.jpg)
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
- document_root=settings.MEDIA_ROOT)
+ document_root=settings.MEDIA_ROOT)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/utils.py b/src/DjangoBlog-master/djangoblog/utils.py
index 57f63dc..0e52b07 100644
--- a/src/DjangoBlog-master/djangoblog/utils.py
+++ b/src/DjangoBlog-master/djangoblog/utils.py
@@ -2,105 +2,173 @@
# encoding: utf-8
-import logging
-import os
-import random
-import string
-import uuid
-from hashlib import sha256
-
-import bleach
-import markdown
-import requests
-from django.conf import settings
-from django.contrib.sites.models import Site
-from django.core.cache import cache
-from django.templatetags.static import static
-
+import logging # 日志模块:记录操作信息和错误
+import os # 系统操作模块:处理文件路径、目录创建等
+import random # 随机数模块:生成验证码等随机内容
+import string # 字符串模块:提供数字、字母等常量
+import uuid # 唯一模块:生成唯一标识符(用于头像文件名)
+from hashlib import sha256 # 加密模块:提供 SHA256 加密算法
+
+import bleach # HTML 清理模块:过滤不安全的 HTML 标签(防 XSS 攻击)
+import markdown # Markdown 解析模块:将 Markdown 文本转为 HTML
+import requests # HTTP 请求模块:下载网络资源(如用户头像)
+from django.conf import settings # Django 配置:获取项目设置(如静态文件路径)
+from django.contrib.sites.models import Site # 站点模型:获取当前站点信息(域名等)
+from django.core.cache import cache # 缓存模块:操作 Django 缓存(获取/设置/删除)
+from django.templatetags.static import static # 静态文件工具:生成静态文件的 URL
+
+
+# 初始化日志对象:指定日志归属为当前模块,便于日志分类
logger = logging.getLogger(__name__)
def get_max_articleid_commentid():
+ """
+ 获取当前最大的文章 ID 和评论 ID(用于数据统计或初始化)
+
+ Returns:
+ tuple: (最大文章 ID, 最大评论 ID)
+ """
+ # 延迟导入模型:避免循环导入问题(工具模块可能被模型模块引用)
from blog.models import Article
from comments.models import Comment
+ # 返回最新文章和评论的主键(ID)
return (Article.objects.latest().pk, Comment.objects.latest().pk)
def get_sha256(str):
+ """
+ 对字符串进行 SHA256 加密(用于密码加密、唯一标识生成等)
+
+ Args:
+ str: 待加密的字符串
+
+ Returns:
+ str: 加密后的 64 位十六进制字符串
+ """
+ # 创建 SHA256 加密对象,需先将字符串转为字节流(指定编码 utf-8)
m = sha256(str.encode('utf-8'))
+ # 返回十六进制加密结果
return m.hexdigest()
def cache_decorator(expiration=3 * 60):
+ """
+ 缓存装饰器:装饰函数,将函数返回值缓存指定时间(默认 3 分钟)
+
+ 作用:减少重复计算或数据库查询,提升性能
+
+ Args:
+ expiration: 缓存有效期(秒),默认 3 分钟
+
+ Returns:
+ 装饰器函数:包装原函数,实现缓存逻辑
+ """
def wrapper(func):
def news(*args, **kwargs):
+ # 尝试生成缓存键(优先使用视图对象的 get_cache_key 方法)
try:
- view = args[0]
- key = view.get_cache_key()
+ view = args[0] # 若第一个参数是视图对象
+ key = view.get_cache_key() # 使用视图自带的缓存键
except:
- key = None
+ key = None # 非视图函数,需自定义缓存键
+
+ # 若未生成缓存键,则基于函数和参数生成唯一键
if not key:
+ # 将函数、参数转为字符串,确保唯一性
unique_str = repr((func, args, kwargs))
-
+ # 对字符串进行 SHA256 加密,生成固定长度的缓存键
m = sha256(unique_str.encode('utf-8'))
key = m.hexdigest()
+
+ # 尝试从缓存获取数据
value = cache.get(key)
if value is not None:
- # logger.info('cache_decorator get cache:%s key:%s' % (func.__name__, key))
+ # 缓存命中:返回缓存值(处理 None 的特殊标记)
if str(value) == '__default_cache_value__':
return None
else:
return value
else:
+ # 缓存未命中:执行原函数获取结果
logger.debug(
'cache_decorator set cache:%s key:%s' %
(func.__name__, key))
value = func(*args, **kwargs)
+ # 缓存结果(用特殊标记表示 None,避免缓存不生效)
if value is None:
cache.set(key, '__default_cache_value__', expiration)
else:
cache.set(key, value, expiration)
return value
-
return news
-
return wrapper
def expire_view_cache(path, servername, serverport, key_prefix=None):
'''
- 刷新视图缓存
- :param path:url路径
- :param servername:host
- :param serverport:端口
- :param key_prefix:前缀
- :return:是否成功
+ 主动刷新指定 URL 路径的视图缓存(用于数据更新后清理旧缓存)
+
+ Args:
+ path: URL 路径(如 '/article/1/')
+ servername: 服务器域名(如 'www.example.com')
+ serverport: 服务器端口(如 80)
+ key_prefix: 缓存键前缀(与视图缓存配置一致)
+
+ Returns:
+ bool: 缓存是否成功删除
'''
- from django.http import HttpRequest
- from django.utils.cache import get_cache_key
-
+ from django.http import HttpRequest # 延迟导入:避免启动时依赖冲突
+ from django.utils.cache import get_cache_key # 获取视图缓存键的工具
+
+ # 构造模拟请求对象(用于生成缓存键)
request = HttpRequest()
request.META = {'SERVER_NAME': servername, 'SERVER_PORT': serverport}
request.path = path
-
+
+ # 获取该请求对应的缓存键
key = get_cache_key(request, key_prefix=key_prefix, cache=cache)
if key:
logger.info('expire_view_cache:get key:{path}'.format(path=path))
+ # 若缓存存在,则删除
if cache.get(key):
cache.delete(key)
return True
return False
-@cache_decorator()
+@cache_decorator() # 应用缓存装饰器:缓存当前站点信息(默认 3 分钟)
def get_current_site():
+ """
+ 获取当前站点信息(域名等),从缓存获取以减少数据库查询
+
+ Returns:
+ Site: Django Site 模型实例
+ """
site = Site.objects.get_current()
return site
class CommonMarkdown:
+ """
+ Markdown 解析工具类:将 Markdown 文本转为 HTML,并支持提取目录(TOC)
+ """
@staticmethod
def _convert_markdown(value):
+ """
+ 内部方法:执行 Markdown 转换,返回 HTML 内容和目录
+
+ Args:
+ value: Markdown 格式的文本
+
+ Returns:
+ tuple: (转换后的 HTML 内容, 目录 HTML)
+ """
+ # 初始化 Markdown 解析器,启用扩展:
+ # - extra: 支持表格、脚注等扩展语法
+ # - codehilite: 代码高亮
+ # - toc: 生成目录
+ # - tables: 表格支持(extra 已包含,此处冗余可能为兼容)
md = markdown.Markdown(
extensions=[
'extra',
@@ -109,124 +177,227 @@ class CommonMarkdown:
'tables',
]
)
- body = md.convert(value)
- toc = md.toc
+ body = md.convert(value) # 转换文本为 HTML
+ toc = md.toc # 提取目录 HTML
return body, toc
@staticmethod
def get_markdown_with_toc(value):
+ """
+ 获取带目录的 Markdown 转换结果
+
+ Args:
+ value: Markdown 文本
+
+ Returns:
+ tuple: (HTML 内容, 目录 HTML)
+ """
body, toc = CommonMarkdown._convert_markdown(value)
return body, toc
@staticmethod
def get_markdown(value):
+ """
+ 获取仅包含 HTML 内容的转换结果(忽略目录)
+
+ Args:
+ value: Markdown 文本
+
+ Returns:
+ str: 转换后的 HTML 内容
+ """
body, toc = CommonMarkdown._convert_markdown(value)
return body
def send_email(emailto, title, content):
+ """
+ 发送邮件(通过信号机制触发,解耦发送逻辑)
+
+ Args:
+ emailto: 收件人列表(如 ['user@example.com'])
+ title: 邮件标题
+ content: 邮件内容(HTML 格式)
+ """
+ # 延迟导入信号:避免循环导入
from djangoblog.blog_signals import send_email_signal
+ # 发送信号,由信号接收器(如 send_email_signal_handler)处理实际发送
send_email_signal.send(
- send_email.__class__,
+ send_email.__class__, # 信号发送者(此处用当前函数的类)
emailto=emailto,
title=title,
content=content)
def generate_code() -> str:
- """生成随机数验证码"""
+ """
+ 生成 6 位数字验证码(用于邮箱验证、登录验证码等)
+
+ Returns:
+ str: 6 位数字字符串
+ """
+ # 从数字字符集中随机选择 6 个,拼接为字符串
return ''.join(random.sample(string.digits, 6))
def parse_dict_to_url(dict):
- from urllib.parse import quote
+ """
+ 将字典转换为 URL 参数字符串(如 {'a':1, 'b':2} → 'a=1&b=2')
+
+ Args:
+ dict: 键值对字典
+
+ Returns:
+ str: URL 编码后的参数字符串
+ """
+ from urllib.parse import quote # 延迟导入:避免启动依赖
+ # 对键和值进行 URL 编码(保留 '/' 不编码),再拼接为 "k=v&k2=v2" 格式
url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/'))
for k, v in dict.items()])
return url
def get_blog_setting():
+ """
+ 获取博客系统设置(如站点名称、描述等),优先从缓存获取
+
+ Returns:
+ BlogSettings: 博客设置模型实例
+ """
+ # 尝试从缓存获取
value = cache.get('get_blog_setting')
if value:
return value
else:
+ # 延迟导入模型:避免循环导入
from blog.models import BlogSettings
+ # 若数据库中无设置记录,初始化默认设置
if not BlogSettings.objects.count():
setting = BlogSettings()
- setting.site_name = 'djangoblog'
- setting.site_description = '基于Django的博客系统'
- setting.site_seo_description = '基于Django的博客系统'
- setting.site_keywords = 'Django,Python'
- setting.article_sub_length = 300
- setting.sidebar_article_count = 10
- setting.sidebar_comment_count = 5
- setting.show_google_adsense = False
- setting.open_site_comment = True
- setting.analytics_code = ''
- setting.beian_code = ''
- setting.show_gongan_code = False
- setting.comment_need_review = False
- setting.save()
+ setting.site_name = 'djangoblog' # 站点名称
+ setting.site_description = '基于Django的博客系统' # 站点描述
+ setting.site_seo_description = '基于Django的博客系统' # SEO 描述
+ setting.site_keywords = 'Django,Python' # SEO 关键词
+ setting.article_sub_length = 300 # 文章摘要长度
+ setting.sidebar_article_count = 10 # 侧边栏显示文章数
+ setting.sidebar_comment_count = 5 # 侧边栏显示评论数
+ setting.show_google_adsense = False # 是否显示谷歌广告
+ setting.open_site_comment = True # 是否开启评论功能
+ setting.analytics_code = '' # 统计代码(如百度统计)
+ setting.beian_code = '' # 备案号
+ setting.show_gongan_code = False # 是否显示公安备案
+ setting.comment_need_review = False # 评论是否需要审核
+ setting.save() # 保存默认设置
+ # 从数据库获取设置
value = BlogSettings.objects.first()
logger.info('set cache get_blog_setting')
+ # 缓存设置(默认使用 cache_decorator 的有效期,或依赖全局缓存配置)
cache.set('get_blog_setting', value)
return value
def save_user_avatar(url):
'''
- 保存用户头像
- :param url:头像url
- :return: 本地路径
+ 下载并保存用户头像到本地(用于第三方登录时的头像同步)
+
+ Args:
+ url: 头像的网络 URL
+
+ Returns:
+ str: 本地头像的静态文件 URL(如 '/static/avatar/xxx.jpg')
'''
- logger.info(url)
+ logger.info(url) # 记录头像 URL
try:
+ # 本地头像存储目录(静态文件目录下的 avatar 文件夹)
basedir = os.path.join(settings.STATICFILES, 'avatar')
+ # 发送 HTTP 请求下载头像(超时 2 秒)
rsp = requests.get(url, timeout=2)
- if rsp.status_code == 200:
+ if rsp.status_code == 200: # 下载成功
+ # 若目录不存在则创建
if not os.path.exists(basedir):
os.makedirs(basedir)
+ # 验证 URL 是否为图片格式(通过文件扩展名判断)
image_extensions = ['.jpg', '.png', 'jpeg', '.gif']
isimage = len([i for i in image_extensions if url.endswith(i)]) > 0
+ # 提取文件扩展名,默认为 .jpg
ext = os.path.splitext(url)[1] if isimage else '.jpg'
+ # 生成唯一文件名(UUID 避免冲突)
save_filename = str(uuid.uuid4().hex) + ext
logger.info('保存用户头像:' + basedir + save_filename)
+ # 写入文件到本地目录
with open(os.path.join(basedir, save_filename), 'wb+') as file:
file.write(rsp.content)
+ # 返回本地头像的静态 URL
return static('avatar/' + save_filename)
except Exception as e:
+ # 下载失败(如网络错误、超时),记录错误并返回默认头像
logger.error(e)
return static('blog/img/avatar.png')
def delete_sidebar_cache():
- from blog.models import LinkShowType
+ """
+ 删除侧边栏相关缓存(当侧边栏内容更新时调用,如新增文章、评论)
+ """
+ from blog.models import LinkShowType # 延迟导入:避免循环依赖
+ # 侧边栏缓存键格式为 "sidebar + 链接类型值"(如 sidebar0、sidebar1)
keys = ["sidebar" + x for x in LinkShowType.values]
+ # 遍历删除所有侧边栏缓存键
for k in keys:
logger.info('delete sidebar key:' + k)
cache.delete(k)
def delete_view_cache(prefix, keys):
- from django.core.cache.utils import make_template_fragment_key
+ """
+ 删除指定模板片段的缓存(用于模板中用 {% cache %} 标签缓存的内容)
+
+ Args:
+ prefix: 缓存前缀(与模板中 {% cache %} 标签的前缀一致)
+ keys: 缓存键的参数列表(与模板中 {% cache %} 标签的参数一致)
+ """
+ from django.core.cache.utils import make_template_fragment_key # 生成模板缓存键的工具
+ # 生成模板片段的缓存键
key = make_template_fragment_key(prefix, keys)
+ # 删除缓存
cache.delete(key)
def get_resource_url():
+ """
+ 获取静态资源的基础 URL(用于动态生成资源路径)
+
+ Returns:
+ str: 静态资源 URL 前缀(如 'http://example.com/static/')
+ """
if settings.STATIC_URL:
return settings.STATIC_URL
else:
+ # 若未配置 STATIC_URL,从当前站点域名生成
site = get_current_site()
return 'http://' + site.domain + '/static/'
+# HTML 清理配置:允许的标签和属性(防止 XSS 攻击)
ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 'h1',
- 'h2', 'p']
-ALLOWED_ATTRIBUTES = {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']}
+ 'h2', 'p'] # 允许的 HTML 标签
+ALLOWED_ATTRIBUTES = { # 允许的标签属性(键为标签,值为属性列表)
+ 'a': ['href', 'title'],
+ 'abbr': ['title'],
+ 'acronym': ['title']
+}
def sanitize_html(html):
- return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES)
+ """
+ 清理 HTML 内容,仅保留允许的标签和属性(防 XSS 攻击)
+
+ Args:
+ html: 原始 HTML 字符串
+
+ Returns:
+ str: 清理后的安全 HTML 字符串
+ """
+ return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES)
\ No newline at end of file
diff --git a/src/DjangoBlog-master/djangoblog/wsgi.py b/src/DjangoBlog-master/djangoblog/wsgi.py
index 2295efd..10a7649 100644
--- a/src/DjangoBlog-master/djangoblog/wsgi.py
+++ b/src/DjangoBlog-master/djangoblog/wsgi.py
@@ -6,11 +6,18 @@ It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
"""
-
+# 导入系统模块:用于设置环境变量
import os
+# 导入 Django WSGI 核心函数:生成符合 WSGI 标准的应用对象
from django.core.wsgi import get_wsgi_application
+# 设置 Django 项目的配置模块环境变量
+# 作用:告诉 Django 启动时加载哪个配置文件(此处为项目根目录下的 djangoblog.settings)
+# 部署时可通过修改该值切换配置(如 djangoblog.settings.production 对应生产环境配置)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings")
-application = get_wsgi_application()
+# 生成 WSGI 应用对象
+# 作用:将 Django 项目包装为 WSGI 兼容的应用,供 WSGI 服务器(如 Gunicorn、uWSGI)调用
+# 该对象是 Django 与 Web 服务器交互的核心入口
+application = get_wsgi_application()
\ No newline at end of file