From c43fad78cd87609e5489f07f2df082770501bfaf Mon Sep 17 00:00:00 2001 From: hyt <691385292@qq.com> Date: Sat, 25 Oct 2025 23:17:12 +0800 Subject: [PATCH] =?UTF-8?q?hyt=5F=E7=AC=AC=E5=85=AD=E5=91=A8=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__init__.py | 1 + src/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 151 bytes src/article_copyright/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 169 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 0 -> 2131 bytes src/article_copyright/plugin.py | 55 +++++ src/external_links/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 166 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 0 -> 3255 bytes src/external_links/plugin.py | 86 +++++++ src/reading_time/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 164 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 0 -> 2395 bytes src/reading_time/plugin.py | 73 ++++++ src/seo_optimizer/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 165 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 0 -> 8832 bytes src/seo_optimizer/plugin.py | 219 ++++++++++++++++++ src/view_count/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 162 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 0 -> 1591 bytes src/view_count/plugin.py | 37 +++ 22 files changed, 476 insertions(+) create mode 100644 src/__pycache__/__init__.cpython-312.pyc create mode 100644 src/article_copyright/__init__.py create mode 100644 src/article_copyright/__pycache__/__init__.cpython-312.pyc create mode 100644 src/article_copyright/__pycache__/plugin.cpython-312.pyc create mode 100644 src/article_copyright/plugin.py create mode 100644 src/external_links/__init__.py create mode 100644 src/external_links/__pycache__/__init__.cpython-312.pyc create mode 100644 src/external_links/__pycache__/plugin.cpython-312.pyc create mode 100644 src/external_links/plugin.py create mode 100644 src/reading_time/__init__.py create mode 100644 src/reading_time/__pycache__/__init__.cpython-312.pyc create mode 100644 src/reading_time/__pycache__/plugin.cpython-312.pyc create mode 100644 src/reading_time/plugin.py create mode 100644 src/seo_optimizer/__init__.py create mode 100644 src/seo_optimizer/__pycache__/__init__.cpython-312.pyc create mode 100644 src/seo_optimizer/__pycache__/plugin.cpython-312.pyc create mode 100644 src/seo_optimizer/plugin.py create mode 100644 src/view_count/__init__.py create mode 100644 src/view_count/__pycache__/__init__.cpython-312.pyc create mode 100644 src/view_count/__pycache__/plugin.cpython-312.pyc create mode 100644 src/view_count/plugin.py diff --git a/src/__init__.py b/src/__init__.py index e69de29..e88afca 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/__pycache__/__init__.cpython-312.pyc b/src/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2da2903299f25d9cc7dfd0be2403412c5af3cef GIT binary patch literal 151 zcmX@j%ge<81ceMYGBknoV-N=&d}aZPOlPQM&}8&m$xy@uKECAw=B%lBQ literal 0 HcmV?d00001 diff --git a/src/article_copyright/__init__.py b/src/article_copyright/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/article_copyright/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/article_copyright/__pycache__/__init__.cpython-312.pyc b/src/article_copyright/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01c80d26bd38a1b6963fa533ef8e99420efffef7 GIT binary patch literal 169 zcmX@j%ge<81ceMYGBknoV-N=&d}aZPOlPQM&}8&m$xy@u3AL rAD@|*SrQ+wS5SG2!zMRBr8Fniu80+AFe4BbgBTx~85tRin1L(+S2rvw literal 0 HcmV?d00001 diff --git a/src/article_copyright/__pycache__/plugin.cpython-312.pyc b/src/article_copyright/__pycache__/plugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5cb84e88b6ac831ecea1f551be400bf6ae96ea00 GIT binary patch literal 2131 zcmZ`)|4$QV7=N$#rKLi3uqiBNsZNI_T8zJq5fVg1qP7fh$u1<9+B*xiU)bF>rlguG z2$d0JP65BoIhPErN{x%6$ovEL3&t&yOZ;W_uJtEz_|vi<_PlpTktuJ|=bq<%-uLRa2J z8}>DF4PH-sqoa~nE zKCGQSlbyb&E7id(`QDjVk1nW}CX9pHt!uz&m(ptHxBS2jb#OwvKdlZfGmw3JRXaYZ z8;F&IAJfJ3_i*QJN3qXAnVm{zr*6F0{=1fQ7y69oUL;TS(A(WR+}oAnP?s+f&`CV* zU#t~aMil(;41AyAxfSz_lwqi3ks@a}}#KlJj< zb@kSWIxwT1%M?`k$LAqEZS;Y5_Z&`AudSN8tp4(?Iy9<{-OK-!HtLwq^ybb^ym~YQ zH2ZK^&GZ|GA!p+m3?rKEOHE~Rcc*jHS3qm{%}xy#oO)c8h|^JQq7dkkWI+@$$dz&* z&vX7oBye&RgorFeWWXXN386z0E|K$}ox5r}r05~JV^3UmwusT7;Fmjg1)k({-7owEsw+qz=%Klu>b95R!4?0Qg07w z=YIWPNQ%`2N~{zrXRqxP>uS5}v`aU!@3VKDvCm*zPb+m1qRY?R%+Ji^C+`E)VH~Lg z522jU@U_+5buLmN1A?qDzL?w@6%|`d62yovEVvl47_2B(vsem+Ggla+0^$c)=U9S% zSL9Gs!d;H*>vW1Gz>2GY^i;2a)0J9hJzSC+fh5sfN*MrrD@IEBz(0P`Z}hVpzd+y~)r zku$bL+{Q}zN=J=(R=JYejfV`LW|HiS$dW$p^w<@~G`|7NFD0kgYK@}Si5Pr%G)S9) n%##E`JV#$VM^(?!{x{54V#_%D2Epg`K>~XHY0-w`jBe&XU%d84 literal 0 HcmV?d00001 diff --git a/src/article_copyright/plugin.py b/src/article_copyright/plugin.py new file mode 100644 index 0000000..ab14d18 --- /dev/null +++ b/src/article_copyright/plugin.py @@ -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

本文由 {article.author.username} 原创,转载请注明出处。

" + + # 将版权信息追加到文章内容末尾 + return content + copyright_info + + +# 3. 实例化插件。 +# 这会自动调用 BasePlugin.__init__,然后 BasePlugin.__init__ 会调用我们上面定义的 register_hooks 方法。 +plugin = ArticleCopyrightPlugin() diff --git a/src/external_links/__init__.py b/src/external_links/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/external_links/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/external_links/__pycache__/__init__.cpython-312.pyc b/src/external_links/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8670a60bb21ceb8b8778637b1ac84d2805a9330 GIT binary patch literal 166 zcmX@j%ge<81ceMYGBknoV-N=&d}aZPOlPQM&}8&m$xy@u2KczG$)vkyYXeuKR7lRldnHd=wir)%o6~8O3p2k392OEbtvc|5FNMuZAoEdCG{ZMTJ2nI+$EF|kiyMV;%k=<2O z6x>k~Y_JP{VH-P4Vw$8A#f(!snThRUH-A9CNFg0O+xkNrR#=}x3Z3b6`l09Uu7p)! znme<1&%O6N?mg#s&b@!yxY3TF{q^tv>B|%$^c^1Lnn@>?P5?21Si}-MqVV6q6Cnd- zATVv@jUkdERhr~YAv0xGX%lY=St%=l4CokQ%|nQ_aOAX6(@EJ3h_g-W1}OUg;j(7& z$mb$tYfRvyj39DA+Yd7$*UHCw17R4M`XZ5j5s30bZ5<6qn!NOp7GH!a+Y@$8)knaeZE@QiY4G;{ufGB&($Ihh%`JO6MzdvjDZ6?+zT^t488Gs^X3jE0pT zxdDg72)&$i&`EdmOt|0a0D}&J;}1H+kw}yabAlrr5x5>s5I6x=)t!EqcsFjU_U!dk z%SC*E3HPeqz<@vJb+C^}_;AdZnxLCNQhqIHfQkQU66RLhAPSmu^vZu)mmv_~#Sp`x zR-J$YlKC>JoF2_y`b@cXK^cCSxpF(dq})Ee+C!PE_cLE!(WI1-+ggB>)2Ecr?`ul( z;yxEC;8c*U0@oW50Tcq>n_LzZB7RO3X-)?U&10khI!Te^dqjLx$G_{3)^>@J9;xg2 zfaGWuB0i*i^%_U2(ar*;9E*UH*A4)Cm_vHh&TzUZ9s!$ zE?I;5AOWM^&RQf(jt*LL)XJYVO8IaG)q=CKhN)a&Pnt$7Jp@Y*Sx=e=O~H+YTzE8S z9wc(y?Z{<{*}%>DQ0bYdCYu(^)hTCR;XeN^z@#=m#xU|n!V(vjY8|2^I9_rn=WZyU z{sxj(!^iQC<|a*Vfsh-rM|{!YcK5%9sal6}>Z|#gtBR_B#om*zFDsKDDv1Y$CUr|{ z5m7Fj%Nx@SK|Lq{@7*s}*NpsT1RPNSU@0Us?Tuy)2)8nq+!_>J75*WvA6MRcQ2S;BaCJ>}%K8ayoZ!9sb z&z=O_tixl>@dWj2ASaFgIeh&R2|xLN^6f~h&mP-}^$;lOr`w_MBI`dAsco|*hQGxm{IzA*YU zOpINbzjsr=YDVv8PmQfW3X5wnE5sC@4>6LzuhyYyR#ZXd_xGRN`E8;2I;g}NRwGl} z1x|zcnU5Ey-YGak7Nak2(eDx!i&-3+c$IV21+(tulEu()k+P{*h{U3@iI2Pm1s6}s z<}fGm5kFp3&nA-?z_ML^E3h=Y8srTa>@*t*F@dm%3l*FqArxb2sNPYY@pD4_z*-e_ z?PBgZ00tRetb+=xN$&++;s?lnsiCcqb(H^Z(n?SYVY{3Q>D9>&1jo*YUkwkkyjV(sC3(0iQ}6R#}qZU zYhQZTzI#>Q?D8&J(Q}R)RoAPgm`P8n+?Cv!EKBX7Qk^H}I(yQcJ*nQl+0Hj0#KlcEYeh~|1Q&m}pN=vTekuvufn9hWIuYLi;CkGC zrn+B%$0drpf4t|P!)h9#13&+BE3)nTTiH)%%PQx}_N2@9jBHpgLR+^@#4pBE&em_+ zI_BEwbQ?Xp<-|zQGP&L6Ts%ym(u%o~7t$p!Oj(n=XG>~Q)*96-K9^m#(sY;!aWpO4 zX*v{PV?3sdX!^|CftDI#xOk5zm2>|Sr#gC8U`phJ|Huq3LlBK1hJSyzvfW#!DW^?>HM 标签 + # 分组1: ... (href属性后的内容和标签闭合部分) + link_pattern = re.compile(r'(]*?\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() \ No newline at end of file diff --git a/src/reading_time/__init__.py b/src/reading_time/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/reading_time/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/reading_time/__pycache__/__init__.cpython-312.pyc b/src/reading_time/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..030d2a5454af44b26341f340091a730384cfa2b2 GIT binary patch literal 164 zcmX@j%ge<81ceMYGBknoV-N=&d}aZPOlPQM&}8&m$xy@u79$%7~n;H`zpP83g m5+AQuPF^uG4J_O;N0UmFP1j=7i?MRZ$q2rPaOU_xPt`|+%M*1iW6Ut4@1Av7iF zHoy+eaBd3fHrQq&j*OA$*c6<9m+Vg&TO`ksERk2}{)mHL%YN+M_xhmf%x=p7{}AA}Uq z5(=FZAdJHKFeYF+7lpb}%QpD0Wi1Y&%pjT1`2BVCO zfx;OhMKCyJVhGC2n7RN(GG+)Z9i-DDuW4Ytj6c}i=nteGAoW5mRtH?kf$oKGCldYR z$^M(kFS`=ook{eapPLw050&1*h4=3~oV>hn`fH_kcy4lRezZSv@$~%2(ER1DbW!Ej zcw*qk`Kz~;PlvS&%4Afzdh_ArS>=aOrT02CvJmZ_yWO*J?W#FevF17tk0 zM08tS5zuPV@zs4KC4&K&Z-a!=rh0|Ko47Ho^qftezpi|9PC0!iabaxbNEz!%_h(`B z3pKd65;rbrQc7$r70RA&W%QP&WJTQV#Ch~EndI4KzaX+akE&YE_A(63YYC@Cm~mKR zxWIBv0&>#vciF)bj}U4SJyji|qn;17vOdvsuoVU>w4Vz#d)m|r6+G!GQp}>Z4%x0- zqtyZqJD#eoqQ`HnhgOAL2%=!w3M}?M`v>-4pD!NUa&K4V)UL|k$f_lH2z=>*M~-B{ z-_oBD{9k$)j4?zoy#+h*2;Qoj~{7M2jvTAx2U~(rIGw zPP3SypR{J`*yGdY>YsVs<_QK>( zFknfP5NRg_rESou4vsA=@P)V#U*dRa&z?Pd3(8B`Ksg+0FplY_;XISFp_D_3^(4;^ zmhOPJN_VuCJI%6zXJw-xosbFmbbp&{Zt@2iFUQG7jt$Cqz$>=MxR3R7PAiYnE}K(P zm2ngv*`$R@w)r^L8>IC^qCLbjf^0<_WJ*C%w(0#Pw6QEBXX#|XACyE^kk{x8)WUR& z7~ll-IYWum7H@+jkE$?S@N^G8IRx_HJ>d)lunRYW+`K{4CDTB7c-L_Ibk6RW?T?(7 z@8uLu(NN?O@Hie(Oipc;3#jvgz!7G2-_PIRhI$refAb66EH`Y;zko4LUD5 zhu$1}VQNeH^u{-0S&LSXnK$s(FBzL=Hf@g8!Hb*gv3NZluR9(;-WJb46(@5SO(6Tl zq1{tit~lx9QH#5sR+*&fpci^c%T}5WgcylKcqUDslDu5%#7xsn$OqXO^)*K-tKI4s zQeK-P4ptmK&`@39SY79q?J3^d6%B_GZA;Pnjx<))HSl(5R1Mp8pgus0qZx4#AmLAN zLeK>s8H@zEm^0l)QG$3]*>', '', 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'

预计阅读时间:{reading_minutes} 分钟

' + + # 6. 将阅读时间提示添加到文章内容开头 + return reading_time_html + content + + +# 实例化插件,自动注册到系统 +plugin = ReadingTimePlugin() \ No newline at end of file diff --git a/src/seo_optimizer/__init__.py b/src/seo_optimizer/__init__.py new file mode 100644 index 0000000..e88afca --- /dev/null +++ b/src/seo_optimizer/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package diff --git a/src/seo_optimizer/__pycache__/__init__.cpython-312.pyc b/src/seo_optimizer/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61a9e3bd86f9cba11c4a49771079af22b7674a1a GIT binary patch literal 165 zcmX@j%ge<81ceMYGBknoV-N=&d}aZPOlPQM&}8&m$xy@u@Pl9`)Xm0A=NAD@|* nSrQ+wS5SG2!zMRBr8Fniu80+AC?gOTgBTx~85tRin1L(+i-jta literal 0 HcmV?d00001 diff --git a/src/seo_optimizer/__pycache__/plugin.cpython-312.pyc b/src/seo_optimizer/__pycache__/plugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2b73af6391612376a5212112d0abea3bd64ea25 GIT binary patch literal 8832 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 + + + + + + + + ''' + # 为每个标签添加article:tag meta标签 + for tag in article.tags.all(): + meta_tags += f'' + # 添加网站名称 + meta_tags += f'' + + # 生成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'' + + # 返回完整的SEO HTML代码 + return f""" + {seo_data.get("title", "")} + + + {seo_data.get("meta_tags", "")} + {json_ld_script} + """ + + +# 实例化插件,自动注册到系统 +plugin = SeoOptimizerPlugin() diff --git a/src/view_count/__init__.py b/src/view_count/__init__.py new file mode 100644 index 0000000..8804fdf --- /dev/null +++ b/src/view_count/__init__.py @@ -0,0 +1 @@ +# This file makes this a Python package \ No newline at end of file diff --git a/src/view_count/__pycache__/__init__.cpython-312.pyc b/src/view_count/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4729442ed75f0ad5c2a037e50ded89f6c0c326fe GIT binary patch literal 162 zcmX@j%ge<81ceMYGBkknV-N=&d}aZPOlPQM&}8&m$xy@u2KczG$)vkyYXd)vJ7lRldnHd=wiO>7fK6rS0?*iK@~Pib6eeTUWjE(K3yLOQy z4yFxBKnO=slSn8Mja0RPG^s08+9-*7Z!c5`HCl;Ny^eEAz&TYt^v&+t4MB7yKhOL7 z=FNNG?C;IZ9t3M|>7PrVa0vZHmD=K+P4y&frjU$eoJ1)sVNCZ-l1Z@=ixGoPA(!CVmSYqbl+ z6w1_n2?h*1mW;2V1kKp7=A2lJa*W7kn zTf4zx`Qb#_iZ@4#_MOe)8|K1d<<8G$VbUDC10AxV>LAAc$UsKXNJPuTqe+GIt8$LS zluSruHCn3S(v(bE9UF~6A9ms@F=Q93YO-(4# zOt>onO;bCPYAk%kqLdz{sU%7%;Ftnea)!5d09tJUmuHi&|P+)LY?Mc8Gu*hV819QDi zHs4<5k*6ME7QTStwAXbyJg2Gc{-E7m^M}RCk2C(G{`Kj6<=g8H=yuuzgDon*1k)PP zV)_w(YwP8!bl2`TZ#*h3P1+}JERUMgv*r13?8j32WxhQ9ptSPq+E=q1%Qx#dA()sz z+powXr!~Rdj0UGXgQKX1V594F>a=*R88%uYh`|G|PNdER`o458h&aXfXk zXtdoS(POv>Nk>u&A%=&LlqzR|WW;D93A9lk<}=e8+$*#aH=OnLcufL#vVuM z>OR-m*VB8x=WNLEI<|}5ednm&?C8M@=TD#Q(`X%5$@h^}fPDvnTB)F4Orgq|}j z@7ioadynQnTJ^pEnA^Yl=D~bub>E@q4zpWmRJcPQp8o@L#!x>jS$@A8PM`gA!Fg1Y(oL7(YWN do}tz);UM;nCAJX!JwJ(&$M?kj_I1I+_!qwd6H@>H literal 0 HcmV?d00001 diff --git a/src/view_count/plugin.py b/src/view_count/plugin.py new file mode 100644 index 0000000..717a2bb --- /dev/null +++ b/src/view_count/plugin.py @@ -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() \ No newline at end of file