hyt_第六周注释

pull/10/head
hyt 3 months ago
parent deb2fa6a9b
commit c43fad78cd

@ -0,0 +1 @@
# This file makes this a Python package

@ -0,0 +1 @@
# This file makes this a Python package

@ -0,0 +1,55 @@
from djangoblog.plugin_manage.base_plugin import BasePlugin
from djangoblog.plugin_manage import hooks
from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME
class ArticleCopyrightPlugin(BasePlugin):
"""
文章版权声明插件
功能在文章正文末尾自动添加版权声明信息
"""
# 插件基本信息配置
PLUGIN_NAME = '文章结尾版权声明' # 插件显示名称
PLUGIN_DESCRIPTION = '一个在文章正文末尾添加版权声明的插件。' # 插件功能描述
PLUGIN_VERSION = '0.2.0' # 插件版本号
PLUGIN_AUTHOR = 'liangliangyy' # 插件作者
# 2. 实现 register_hooks 方法,专门用于注册钩子
def register_hooks(self):
"""
注册钩子函数
这个方法在插件初始化时被自动调用用于将插件的功能方法注册到系统的钩子上
"""
# 将 add_copyright_to_content 方法注册到文章内容钩子
# 当系统处理文章内容时,会自动调用这个方法来添加版权信息
hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.add_copyright_to_content)
def add_copyright_to_content(self, content, *args, **kwargs):
"""
文章内容处理函数 - 在文章末尾添加版权声明
参数:
content: str - 原始的文章内容
*args, **kwargs: 其他参数其中包含文章对象
返回值:
str - 添加了版权声明后的完整内容
"""
# 从参数中获取文章对象
article = kwargs.get('article')
# 如果没有文章对象,直接返回原内容(避免错误)
if not article:
return content
# 构建版权声明HTML代码
copyright_info = f"\n<hr><p>本文由 {article.author.username} 原创,转载请注明出处。</p>"
# 将版权信息追加到文章内容末尾
return content + copyright_info
# 3. 实例化插件。
# 这会自动调用 BasePlugin.__init__然后 BasePlugin.__init__ 会调用我们上面定义的 register_hooks 方法。
plugin = ArticleCopyrightPlugin()

@ -0,0 +1 @@
# This file makes this a Python package

@ -0,0 +1,86 @@
import re
from urllib.parse import urlparse
from djangoblog.plugin_manage.base_plugin import BasePlugin
from djangoblog.plugin_manage import hooks
from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME
class ExternalLinksPlugin(BasePlugin):
"""
外部链接处理插件
功能自动为文章中的外部链接添加安全属性防止钓鱼攻击和提升用户体验
"""
# 插件基本信息
PLUGIN_NAME = '外部链接处理器'
PLUGIN_DESCRIPTION = '自动为文章中的外部链接添加 target="_blank" 和 rel="noopener noreferrer" 属性。'
PLUGIN_VERSION = '0.1.0'
PLUGIN_AUTHOR = 'liangliangyy'
def register_hooks(self):
"""
注册钩子函数
将外部链接处理方法注册到文章内容钩子
"""
hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.process_external_links)
def process_external_links(self, content, *args, **kwargs):
"""
处理文章内容中的外部链接
参数:
content: str - 原始的文章HTML内容
*args, **kwargs: 其他参数
返回值:
str - 处理后的文章内容外部链接已添加安全属性
"""
# 导入获取当前网站域名的工具函数
from djangoblog.utils import get_current_site
# 获取当前网站的域名,用于判断链接是否为外部链接
site_domain = get_current_site().domain
# 正则表达式模式:匹配所有的 <a> 标签
# 分组1: <a ... href=" (包含href属性前的所有内容)
# 分组2: 链接URL
# 分组3: ">...</a> (href属性后的内容和标签闭合部分)
link_pattern = re.compile(r'(<a\s+(?:[^>]*?\s+)?href=")([^"]*)(".*?/a>)', re.IGNORECASE)
def replacer(match):
"""
正则替换函数 - 处理单个链接匹配
参数:
match: 正则匹配对象
返回值:
str - 处理后的链接HTML代码
"""
# 提取链接URL
href = match.group(2)
# 如果链接已经有 target 属性,则不处理(避免重复添加)
if 'target=' in match.group(0).lower():
return match.group(0)
# 解析链接URL提取域名信息
parsed_url = urlparse(href)
# 判断是否为外部链接:
# 1. 有域名 (parsed_url.netloc 不为空)
# 2. 且域名不等于当前网站域名
if parsed_url.netloc and parsed_url.netloc != site_domain:
# 为外部链接添加安全属性:
# - target="_blank": 在新标签页打开
# - rel="noopener noreferrer": 安全防护,防止钓鱼攻击
return f'{match.group(1)}{href}" target="_blank" rel="noopener noreferrer"{match.group(3)}'
# 内部链接或无效链接,返回原样
return match.group(0)
# 使用正则表达式替换所有匹配的链接
return link_pattern.sub(replacer, content)
# 实例化插件,自动注册到系统
plugin = ExternalLinksPlugin()

@ -0,0 +1 @@
# This file makes this a Python package

@ -0,0 +1,73 @@
import math
import re
from djangoblog.plugin_manage.base_plugin import BasePlugin
from djangoblog.plugin_manage import hooks
from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME
class ReadingTimePlugin(BasePlugin):
"""
阅读时间预测插件
功能自动估算文章的阅读时间并显示在文章开头帮助读者了解阅读时长
"""
# 插件基本信息
PLUGIN_NAME = '阅读时间预测'
PLUGIN_DESCRIPTION = '估算文章阅读时间并显示在文章开头。'
PLUGIN_VERSION = '0.1.0'
PLUGIN_AUTHOR = 'liangliangyy'
def register_hooks(self):
"""
注册钩子函数
将阅读时间计算方法注册到文章内容钩子
"""
hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.add_reading_time)
def add_reading_time(self, content, *args, **kwargs):
"""
计算文章阅读时间并添加到内容开头
参数:
content: str - 原始的文章HTML内容
*args, **kwargs: 其他参数
返回值:
str - 添加了阅读时间提示的文章内容
"""
# 1. 清理HTML内容提取纯文本
# 使用正则表达式移除所有HTML标签只保留文本内容
clean_content = re.sub(r'<[^>]*>', '', content)
# 移除首尾空白字符
clean_content = clean_content.strip()
# 2. 计算字数(支持中英文混合)
# 正则表达式说明:
# [\u4e00-\u9fa5] - 匹配中文字符Unicode范围
# | - 或者
# \w+ - 匹配连续的英文单词字符(字母、数字、下划线)
words = re.findall(r'[\u4e00-\u9fa5]|\w+', clean_content)
word_count = len(words)
# 3. 计算阅读时间
# 按照平均每分钟200字的速度计算适合中文阅读速度
reading_speed = 200
# 使用math.ceil向上取整确保不会出现0分钟的情况
reading_minutes = math.ceil(word_count / reading_speed)
# 4. 处理边界情况
# 如果计算出的阅读时间少于1分钟统一显示为1分钟
# 避免出现"预计阅读时间0分钟"的不合理显示
if reading_minutes < 1:
reading_minutes = 1
# 5. 生成阅读时间提示的HTML代码
# 使用灰色文字和斜体样式,视觉上不突兀
reading_time_html = f'<p style="color: #888;"><em>预计阅读时间:{reading_minutes} 分钟</em></p>'
# 6. 将阅读时间提示添加到文章内容开头
return reading_time_html + content
# 实例化插件,自动注册到系统
plugin = ReadingTimePlugin()

@ -0,0 +1 @@
# This file makes this a Python package

@ -0,0 +1,219 @@
import json
from django.utils.html import strip_tags
from django.template.defaultfilters import truncatewords
from djangoblog.plugin_manage.base_plugin import BasePlugin
from djangoblog.plugin_manage import hooks
from blog.models import Article, Category, Tag
from djangoblog.utils import get_blog_setting
class SeoOptimizerPlugin(BasePlugin):
"""
SEO 优化插件
功能为网站提供全面的SEO优化支持包括meta标签和JSON-LD结构化数据
提升网站在搜索引擎中的可见性和排名
"""
# 插件基本信息
PLUGIN_NAME = 'SEO 优化器'
PLUGIN_DESCRIPTION = '为文章、页面等提供 SEO 优化,动态生成 meta 标签和 JSON-LD 结构化数据。'
PLUGIN_VERSION = '0.2.0'
PLUGIN_AUTHOR = 'liuangliangyy' # 注意:这里可能有拼写错误,应该是 liangliangyy
def register_hooks(self):
"""
注册钩子函数
将SEO生成方法注册到head_meta钩子在页面头部生成SEO相关标签
"""
hooks.register('head_meta', self.dispatch_seo_generation)
def _get_article_seo_data(self, context, request, blog_setting):
"""
生成文章页面的SEO数据
参数:
context: 模板上下文包含文章等信息
request: HTTP请求对象
blog_setting: 博客设置
返回值:
dict - 包含文章页面SEO数据的字典
"""
# 从上下文中获取文章对象
article = context.get('article')
# 确保是Article实例且存在
if not isinstance(article, Article):
return None
# 生成描述移除HTML标签并截取前150个字符
description = strip_tags(article.body)[:150]
# 生成关键词:使用文章标签,如果没有则使用站点默认关键词
keywords = ",".join([tag.name for tag in article.tags.all()]) or blog_setting.site_keywords
# 生成Open Graph meta标签用于社交媒体分享
meta_tags = f'''
<meta property="og:type" content="article"/>
<meta property="og:title" content="{article.title}"/>
<meta property="og:description" content="{description}"/>
<meta property="og:url" content="{request.build_absolute_uri()}"/>
<meta property="article:published_time" content="{article.pub_time.isoformat()}"/>
<meta property="article:modified_time" content="{article.last_modify_time.isoformat()}"/>
<meta property="article:author" content="{article.author.username}"/>
<meta property="article:section" content="{article.category.name}"/>
'''
# 为每个标签添加article:tag meta标签
for tag in article.tags.all():
meta_tags += f'<meta property="article:tag" content="{tag.name}"/>'
# 添加网站名称
meta_tags += f'<meta property="og:site_name" content="{blog_setting.site_name}"/>'
# 生成JSON-LD结构化数据用于搜索引擎理解内容结构
structured_data = {
"@context": "https://schema.org",
"@type": "Article", # 文章类型
"mainEntityOfPage": {"@type": "WebPage", "@id": request.build_absolute_uri()},
"headline": article.title, # 文章标题
"description": description, # 文章描述
"image": request.build_absolute_uri(article.get_first_image_url()), # 文章首图
"datePublished": article.pub_time.isoformat(), # 发布时间
"dateModified": article.last_modify_time.isoformat(), # 修改时间
"author": {"@type": "Person", "name": article.author.username}, # 作者信息
"publisher": {"@type": "Organization", "name": blog_setting.site_name} # 发布者信息
}
# 如果没有图片移除image字段
if not structured_data.get("image"):
del structured_data["image"]
return {
"title": f"{article.title} | {blog_setting.site_name}", # 页面标题
"description": description, # 页面描述
"keywords": keywords, # 页面关键词
"meta_tags": meta_tags, # meta标签
"json_ld": structured_data # JSON-LD数据
}
def _get_category_seo_data(self, context, request, blog_setting):
"""
生成分类页面的SEO数据
参数:
context: 模板上下文
request: HTTP请求对象
blog_setting: 博客设置
返回值:
dict - 包含分类页面SEO数据的字典
"""
# 从上下文中获取分类名称
category_name = context.get('tag_name') # 注意这里变量名可能有误应该是category_name
if not category_name:
return None
# 获取分类对象
category = Category.objects.filter(name=category_name).first()
if not category:
return None
# 生成页面基本信息
title = f"{category.name} | {blog_setting.site_name}"
description = strip_tags(category.name) or blog_setting.site_description
keywords = category.name
# 生成面包屑导航的结构化数据
breadcrumb_items = [
{"@type": "ListItem", "position": 1, "name": "首页", "item": request.build_absolute_uri('/')}
]
breadcrumb_items.append(
{"@type": "ListItem", "position": 2, "name": category.name, "item": request.build_absolute_uri()})
# 面包屑导航的JSON-LD数据
structured_data = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": breadcrumb_items
}
return {
"title": title,
"description": description,
"keywords": keywords,
"meta_tags": "",
"json_ld": structured_data
}
def _get_default_seo_data(self, context, request, blog_setting):
"""
生成默认页面首页等的SEO数据
参数:
context: 模板上下文
request: HTTP请求对象
blog_setting: 博客设置
返回值:
dict - 包含默认页面SEO数据的字典
"""
# 网站类型的JSON-LD结构化数据
structured_data = {
"@context": "https://schema.org",
"@type": "WebSite",
"url": request.build_absolute_uri('/'),
"potentialAction": {
"@type": "SearchAction",
"target": f"{request.build_absolute_uri('/search/')}?q={{search_term_string}}",
"query-input": "required name=search_term_string"
}
}
return {
"title": f"{blog_setting.site_name} | {blog_setting.site_description}",
"description": blog_setting.site_description,
"keywords": blog_setting.site_keywords,
"meta_tags": "",
"json_ld": structured_data
}
def dispatch_seo_generation(self, metas, context):
"""
SEO数据分发器 - 根据当前页面类型调用相应的SEO生成方法
参数:
metas: 原始的meta标签内容
context: 模板上下文
返回值:
str - 完整的SEO相关HTML代码
"""
request = context.get('request')
if not request:
return metas
# 获取当前视图名称,用于判断页面类型
view_name = request.resolver_match.view_name
blog_setting = get_blog_setting()
seo_data = None
# 根据视图名称调用不同的SEO生成方法
if view_name == 'blog:detailbyid':
seo_data = self._get_article_seo_data(context, request, blog_setting)
elif view_name == 'blog:category_detail':
seo_data = self._get_category_seo_data(context, request, blog_setting)
# 如果没有匹配到特定页面类型使用默认SEO数据
if not seo_data:
seo_data = self._get_default_seo_data(context, request, blog_setting)
# 将JSON-LD数据转换为脚本标签
json_ld_script = f'<script type="application/ld+json">{json.dumps(seo_data.get("json_ld", {}), ensure_ascii=False, indent=4)}</script>'
# 返回完整的SEO HTML代码
return f"""
<title>{seo_data.get("title", "")}</title>
<meta name="description" content="{seo_data.get("description", "")}">
<meta name="keywords" content="{seo_data.get("keywords", "")}">
{seo_data.get("meta_tags", "")}
{json_ld_script}
"""
# 实例化插件,自动注册到系统
plugin = SeoOptimizerPlugin()

@ -0,0 +1 @@
# This file makes this a Python package

@ -0,0 +1,37 @@
from djangoblog.plugin_manage.base_plugin import BasePlugin
from djangoblog.plugin_manage import hooks
class ViewCountPlugin(BasePlugin):
"""
文章浏览次数统计插件
功能自动记录和统计文章的浏览次数
"""
# 插件基本信息
PLUGIN_NAME = '文章浏览次数统计'
PLUGIN_DESCRIPTION = '统计文章的浏览次数'
PLUGIN_VERSION = '0.1.0'
PLUGIN_AUTHOR = 'liangliangyy'
def register_hooks(self):
"""
注册钩子函数
将浏览统计方法注册到文章内容获取后的钩子
"""
hooks.register('after_article_body_get', self.record_view)
def record_view(self, article, *args, **kwargs):
"""
记录文章浏览次数
参数:
article: Article对象 - 需要记录浏览次数的文章
*args, **kwargs: 其他参数在此插件中未使用
"""
# 调用文章的viewed()方法来增加浏览次数
article.viewed()
# 实例化插件,自动注册到系统
plugin = ViewCountPlugin()
Loading…
Cancel
Save