diff --git a/djangoblog/blog_signals.py b/djangoblog/blog_signals.py index 393f441c..065de3ae 100644 --- a/djangoblog/blog_signals.py +++ b/djangoblog/blog_signals.py @@ -1,3 +1,8 @@ +# Django博客系统信号处理模块 +# 该模块用于注册和处理Django内置信号及自定义信号,实现事件驱动的业务逻辑 +# 核心功能包括:邮件发送、OAuth用户登录处理、模型保存后缓存清理/搜索引擎通知、用户登录登出缓存处理等 +# 通过信号机制解耦业务逻辑,当特定事件触发时自动执行对应处理函数 + import _thread import logging @@ -9,6 +14,7 @@ from django.core.mail import EmailMultiAlternatives from django.db.models.signals import post_save from django.dispatch import receiver +# 导入项目内部模块:评论相关、插件通知、缓存工具、站点工具、OAuth模型 from comments.models import Comment from comments.utils import send_comment_email from djangoblog.spider_notify import SpiderNotify @@ -16,51 +22,72 @@ 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'(OAuthUser的主键) oauth_user_login_signal = django.dispatch.Signal(['id']) +# 自定义信号:邮件发送信号,携带参数'emailto'(收件人列表)、'title'(邮件标题)、'content'(邮件内容) send_email_signal = django.dispatch.Signal( ['emailto', 'title', 'content']) @receiver(send_email_signal) def send_email_signal_handler(sender, **kwargs): + """ + 邮件发送信号的处理函数 + 当send_email_signal信号触发时,自动发送HTML格式邮件并记录发送日志 + """ + # 从信号参数中提取邮件相关信息 emailto = kwargs['emailto'] title = kwargs['title'] content = kwargs['content'] + # 构建HTML格式邮件(content_subtype设为html支持富文本) msg = EmailMultiAlternatives( title, content, - from_email=settings.DEFAULT_FROM_EMAIL, - to=emailto) - msg.content_subtype = "html" + from_email=settings.DEFAULT_FROM_EMAIL, # 发件人从Django配置中读取 + to=emailto) # 收件人列表 + 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为成功发送的邮件数量 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 # 标记发送失败 + log.save() # 保存日志到数据库 @receiver(oauth_user_login_signal) def oauth_user_login_signal_handler(sender, **kwargs): + """ + OAuth用户登录信号的处理函数 + 当OAuth用户登录成功后,处理用户头像(如跨域头像本地化存储)并清理侧边栏缓存 + """ + # 从信号参数中提取OAuthUser的id,查询对应的用户实例 id = kwargs['id'] 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() @@ -73,42 +100,64 @@ def model_post_save_callback( using, update_fields, **kwargs): - clearcache = False + """ + Django模型保存后信号的处理函数(post_save) + 触发时机:任何模型执行save()方法后(新增/更新) + 主要处理:搜索引擎通知、缓存清理、评论审核通过后的联动操作 + """ + clearcache = False # 标记是否需要清理全局缓存 + + # 跳过Admin操作日志模型(LogEntry)的处理,无需触发后续逻辑 if isinstance(instance, LogEntry): return + + # 若模型实例有get_full_url方法(通常是博客文章等需要对外展示的模型) 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) + logger.error("notify sipder", ex) # 记录搜索引擎通知失败的错误 + # 非浏览量更新时,标记需要清理缓存 if not is_update_views: clearcache = True + # 若保存的是评论模型实例 if isinstance(instance, Comment): + # 仅处理审核通过的评论(is_enable为True) if instance.is_enable: + # 获取评论对应的文章绝对路径 path = instance.article.get_absolute_url() site = get_current_site().domain + # 处理带端口的域名(如localhost:8000),仅保留主域名部分 if site.find(':') > 0: site = site[0:site.find(':')] + # 清理文章详情页的视图缓存(确保评论实时展示) expire_view_cache( path, servername=site, serverport=80, key_prefix='blogdetail') + # 清理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() @@ -116,7 +165,13 @@ def model_post_save_callback( @receiver(user_logged_in) @receiver(user_logged_out) def user_auth_callback(sender, request, user, **kwargs): + """ + 用户登录/登出信号的处理函数 + 触发时机:用户登录(user_logged_in)或登出(user_logged_out)后 + 主要处理:记录日志并清理侧边栏缓存(确保登录状态变化后展示最新数据) + """ + # 若用户存在且用户名有效 if user and user.username: - logger.info(user) - delete_sidebar_cache() - # cache.clear() + logger.info(user) # 记录用户登录/登出日志 + delete_sidebar_cache() # 清理侧边栏缓存 + # cache.clear() # 全局缓存清理(按需启用) \ No newline at end of file