Deprecated: Creation of dynamic property Typecho\Widget\Request::$feed is deprecated in /www/wwwroot/blog.iletter.top/var/Widget/Archive.php on line 246
白荼日记 - 技术笔记 https://blog.iletter.top/tag/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/ typecho博客Hello world插件增强 https://blog.iletter.top/archives/424.html 2025-08-07T13:59:32+08:00 后台点击用户名称旁边的hello word会出现提示。直接将该代码替换掉原本的代码即可:<?php namespace TypechoPlugin\HelloWorld; use Typecho\Plugin\PluginInterface; use Typecho\Widget\Helper\Form; use Typecho\Widget\Helper\Form\Element\Text; use Widget\Options; use Widget\Stat; use Typecho\Db; if (!defined('__TYPECHO_ROOT_DIR__')) { exit; } /** * Hello World * * @package HelloWorld * @author 白荼 * @version 1.0.0 */ class Plugin implements PluginInterface { /** * 激活插件方法,如果激活失败,直接抛出异常 */ public static function activate() { \Typecho\Plugin::factory('admin/menu.php')->navBar = __CLASS__ . '::render'; // 添加CSS和JS \Typecho\Plugin::factory('admin/header.php')->header = __CLASS__ . '::header'; } /** * 禁用插件方法,如果禁用失败,直接抛出异常 */ public static function deactivate() { } /** * 获取插件配置面板 * * @param Form $form 配置面板 */ public static function config(Form $form) { /** 分类名称 */ $name = new Text('word', null, 'Hello World', _t('说点什么')); $form->addInput($name); } /** * 个人用户的配置面板 * * @param Form $form */ public static function personalConfig(Form $form) { } /** * 在header中添加CSS和JS */ public static function header($header) { $customCssJs = ' <!-- HelloWorld Plugin CSS --> <style> .helloworld-tooltip { position: absolute; background: #fff; border: 1px solid #ddd; border-radius: 4px; padding: 15px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); z-index: 9999; min-width: 280px; display: none; font-size: 12px; } .helloworld-tooltip h4 { margin: 0 0 10px 0; padding: 0; color: #333; border-bottom: 1px solid #eee; padding-bottom: 8px; font-size: 14px; } .helloworld-tooltip .comparison { background: #f8f9fa; border-left: 3px solid #007cba; padding: 8px 12px; margin: 10px 0; border-radius: 0 4px 4px 0; font-size: 13px; } .helloworld-tooltip ul { list-style: none; padding: 0; margin: 0; } .helloworld-tooltip li { padding: 5px 0; border-bottom: 1px solid #f5f5f5; line-height: 1.5; } .helloworld-tooltip li:last-child { border-bottom: none; } .helloworld-tooltip strong { color: #666; font-weight: normal; } .helloworld-trigger { cursor: pointer; border-bottom: 1px dotted #BBBBBB; } .helloworld-trigger:hover { color: #666 !important; border-bottom-color: #666; } </style> <!-- HelloWorld Plugin JS --> <script> (function() { function initHelloWorldTooltip() { var trigger = document.querySelector(".helloworld-trigger"); var tooltip = document.querySelector(".helloworld-tooltip"); if (trigger && tooltip) { trigger.addEventListener("click", function(e) { e.preventDefault(); e.stopPropagation(); // 计算位置 var rect = trigger.getBoundingClientRect(); var tooltipWidth = tooltip.offsetWidth || 280; var tooltipHeight = tooltip.offsetHeight || 250; // 默认位置 var left = rect.left + window.scrollX; var top = rect.bottom + window.scrollY + 5; // 边界检测 if (left + tooltipWidth > window.innerWidth + window.scrollX) { left = window.innerWidth + window.scrollX - tooltipWidth - 10; } if (top + tooltipHeight > window.innerHeight + window.scrollY) { top = rect.top + window.scrollY - tooltipHeight - 5; } tooltip.style.left = left + "px"; tooltip.style.top = top + "px"; tooltip.style.display = "block"; }); // 点击其他地方关闭 document.addEventListener("click", function(e) { if (!tooltip.contains(e.target) && e.target !== trigger) { tooltip.style.display = "none"; } }); } } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initHelloWorldTooltip); } else { initHelloWorldTooltip(); } })(); </script>'; return $header . $customCssJs; } /** * 根据字数获取对应的文学著作比较 */ private static function getLiteraryComparison($wordCount) { // 文学著作字数对照表(字数 => 著作名称) $literaryWorks = [ // 百字级 50 => '几行诗句', 100 => '一段朋友圈文字', 200 => '一条微博内容', 500 => '一篇微博长文', // 千字级 1000 => '一页A4纸手写内容', 1500 => '一页打印稿', 2000 => '一篇标准作文', 3000 => '一篇博客文章', 5000 => '一篇短篇小说', 8000 => '一篇中篇小说开头', // 万字级(中国现代文学) 10000 => '《呐喊》', 12000 => '《彷徨》', 15000 => '《朝花夕拾》', 20000 => '《野草》', 25000 => '《故事新编》', 30000 => '《热风》', 35000 => '《坟》', 40000 => '《而已集》', 45000 => '《三闲集》', // 万字级(其他现代文学) 50000 => '《沉沦》(郁达夫)', 55000 => '《春风沉醉的晚上》', 60000 => '《迟桂花》', 65000 => '《茑萝集》', 70000 => '《过去》', 75000 => '《薄奠》', 80000 => '《微雪的早晨》', // 万字级(长篇小说选段) 85000 => '《骆驼祥子》前半部分', 90000 => '《二马》', 95000 => '《离婚》', 100000 => '《骆驼祥子》', 110000 => '《四世同堂》第一部分', 120000 => '《边城》', 130000 => '《湘行散记》', 140000 => '《长河》', 150000 => '《家》', 160000 => '《憩园》', 170000 => '《第四病室》', 180000 => '《春》', 190000 => '《秋》', 200000 => '《寒夜》', // 20万字级 210000 => '《激流三部曲》选段', 220000 => '《爱情三部曲》选段', 230000 => '《抗战三部曲》选段', 240000 => '《人间词话》', 250000 => '《人间喜剧》选段', 260000 => '《巴黎圣母院》', 270000 => '《悲惨世界》选段', 280000 => '《九三年》', 290000 => '《海上劳工》', 300000 => '《笑面人》', // 30万字级 310000 => '《约翰·克利斯朵夫》选段', 320000 => '《红与黑》', 330000 => '《巴马修道院》', 340000 => '《吕西安·娄凡》', 350000 => '《阿尔芒斯》', 360000 => '《法尼娜·法尼尼》', 370000 => '《瓦尼娜·瓦尼尼》', 380000 => '《红屋骑士》', 390000 => '《卡斯特罗修道院院长》', 400000 => '《意大利遗事》', // 40万字级 410000 => '《三个火枪手》', 420000 => '《基督山伯爵》', 430000 => '《黑郁金香》', 440000 => '《二十年后》', 450000 => '《布拉热洛纳子爵》', 460000 => '《玛戈王后》', 470000 => '《蒙梭罗夫人》', 480000 => '《四十五卫士》', 490000 => '《侠隐记》', 500000 => '《红楼梦》(精简版)', // 50万字级 520000 => '《水浒传》选段', 540000 => '《西游记》选段', 560000 => '《三国演义》选段', 580000 => '《儒林外史》', 600000 => '《聊斋志异》选段', 620000 => '《阅微草堂笔记》', 640000 => '《子不语》', 660000 => '《夜雨秋灯录》', 680000 => '《萤窗异草》', 700000 => '《耳食录》', // 70万字级 720000 => '《浮生六记》', 740000 => '《影梅庵忆语》', 760000 => '《秋灯琐忆》', 780000 => '《香艳丛书》选段', 800000 => '《太平广记》选段', 820000 => '《夷坚志》选段', 840000 => '《剪灯新话》', 860000 => '《剪灯余话》', 880000 => '《觅灯因话》', 900000 => '《喻世明言》', // 百万字级 920000 => '《警世通言》', 940000 => '《醒世恒言》', 960000 => '《初刻拍案惊奇》', 980000 => '《二刻拍案惊奇》', 1000000 => '《红楼梦》', 1050000 => '《镜花缘》', 1100000 => '《老残游记》', 1150000 => '《孽海花》', 1200000 => '《官场现形记》', 1250000 => '《二十年目睹之怪现状》', // 百万字级以上 1300000 => '《资本论》第一卷', 1400000 => '《莎士比亚十四行诗集》', 1500000 => '《战争与和平》', 1600000 => '《安娜·卡列尼娜》', 1700000 => '《复活》', 1800000 => '《静静的顿河》', 1900000 => '《被开垦的处女地》', 2000000 => '《一个人的遭遇》', 2100000 => '《静静的顿河》全本', 2200000 => '《钢铁是怎样炼成的》', 2300000 => '《青年近卫军》', 2400000 => '《日瓦戈医生》', 2500000 => '《追忆似水年华》第一卷', 2600000 => '《约翰·克利斯朵夫》', 2700000 => '《马丁·伊登》', 2800000 => '《美国悲剧》', 2900000 => '《嘉莉妹妹》', 3000000 => '《资本论》全三卷', 3100000 => '《莎士比亚全集》悲剧部分', 3200000 => '《莎士比亚全集》喜剧部分', 3300000 => '《莎士比亚全集》历史剧部分', 3400000 => '《莎士比亚十四行诗全集》', 3500000 => '《莎士比亚全集》', 3600000 => '《追忆似水年华》第二卷', 3700000 => '《追忆似水年华》第三卷', 3800000 => '《追忆似水年华》第四卷', 3900000 => '《追忆似水年华》第五卷', 4000000 => '《追忆似水年华》', 4200000 => '《堂吉诃德》', 4400000 => '《神曲》', 4600000 => '《荷马史诗》', 4800000 => '《失乐园》', 5000000 => '《大英百科全书》一卷', 5500000 => '《大英百科全书》两卷', 6000000 => '《牛津英语词典》一卷', 6500000 => '《不列颠百科全书》', 7000000 => '《中国大百科全书》一卷', 7500000 => '《辞海》', 8000000 => '《康熙字典》', 9000000 => '《中华大字典》', 10000000 => '《大英百科全书》全套', 12000000 => '《四库全书》选段', 15000000 => '《永乐大典》残卷', 20000000 => '《古今图书集成》选段' ]; // 找到最接近的著作 $closestWork = '几行文字'; $closestWords = 0; foreach ($literaryWorks as $words => $work) { if ($wordCount >= $words) { $closestWork = $work; $closestWords = $words; } else { break; } } // 计算倍数 if ($closestWords > 0) { $multiple = round($wordCount / $closestWords, 1); if ($multiple >= 2) { return "约等于 {$multiple} 本 {$closestWork} 的字数"; } else { return "约等于 1 本 {$closestWork} 的字数"; } } else { return "约等于几行文字的字数"; } } /** * 获取统计信息 */ private static function getStats() { $db = Db::get(); $user = \Typecho\Widget::widget('Widget_User'); // 最近登录时间 $lastLogin = $user->logged; $lastLoginText = $lastLogin ? date('Y-m-d H:i:s', $lastLogin) : '未知'; // 文章数量 $postCount = $db->fetchObject($db->select(['COUNT(cid)' => 'num']) ->from('table.contents') ->where('type = ?', 'post') ->where('authorId = ?', $user->uid))->num; // 字数统计(简单统计标题和内容长度) $posts = $db->fetchAll($db->select('title', 'text') ->from('table.contents') ->where('type = ?', 'post') ->where('authorId = ?', $user->uid)); $totalWords = 0; foreach ($posts as $post) { $totalWords += mb_strlen($post['title'], 'UTF-8'); $totalWords += mb_strlen(strip_tags($post['text']), 'UTF-8'); } // 最近文章时间 $lastPost = $db->fetchRow($db->select('created') ->from('table.contents') ->where('type = ?', 'post') ->where('authorId = ?', $user->uid) ->order('created', Db::SORT_DESC) ->limit(1)); $lastPostTime = $lastPost ? date('Y-m-d H:i:s', $lastPost['created']) : '暂无文章'; // 获取文学著作比较 $literaryComparison = self::getLiteraryComparison($totalWords); return [ 'lastLogin' => $lastLoginText, 'postCount' => $postCount, 'totalWords' => $totalWords, 'lastPostTime' => $lastPostTime, 'literaryComparison' => $literaryComparison ]; } /** * 插件实现方法 * * @access public * @return void */ public static function render() { $stats = self::getStats(); $word = Options::alloc()->plugin('HelloWorld')->word; // 获取当前登录用户的信息 $user = \Typecho\Widget::widget('Widget_User'); $userName = $user->screenName ?: $user->name; // 优先使用昵称,如果没有则使用登录名 echo '<span class="helloworld-trigger" style="color:#BBBBBB">' . htmlspecialchars($word) . '</span>'; echo '<div class="helloworld-tooltip"> <h4>欢迎回来,' . htmlspecialchars($userName) . '</h4> <div style="margin-bottom:20px;"> 您已经写了 ' . $stats['postCount'] . ' 篇文章,一共 ' . $stats['totalWords'] . ' 字,' . $stats['literaryComparison'] . '。 </div> <ul> <li><strong>最近登录:</strong>' . $stats['lastLogin'] . '</li> <li><strong>最近发文:</strong>' . $stats['lastPostTime'] . '</li> </ul> </div>'; } } typecho给后台admin界面添加图表分析 https://blog.iletter.top/archives/406.html 2025-07-27T02:32:00+08:00 觉得后台admin空荡荡的,索性加个图表自己魔改一下下。以下是完整的index.php感兴趣的可以直接下载index.php文件覆盖/admin 下面的index.php文件下载地址:https://wonder1999.lanzouu.com/ikRiy32sfnah<?php include 'common.php'; include 'header.php'; include 'menu.php'; $stat = \Widget\Stat::alloc(); ?> <script src="https://blog.iletter.top/usr/blog_img/chart.js"></script> <!-- 引入 Font Awesome 图标库 --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"> <style> /* 自定义美化样式 */ .typecho-dashboard { padding: 20px 0; } .card { background-color: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 20px; margin-bottom: 20px; } .card-title { margin-top: 0; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee; color: #333; font-size: 1.2em; } /* 欢迎区域 */ .welcome-card { background: linear-gradient(135deg, #1f1f20 0%, #0076bb 100%); color: white; } .welcome-card h3 { color: white; margin-top: 0; } .welcome-card p { font-size: 1.1em; margin-bottom: 20px; } .welcome-card a { color: #fff; text-decoration: underline; } .welcome-card a:hover { text-decoration: none; opacity: 0.9; } /* 快捷链接 */ #start-link { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 10px; } #start-link li { margin: 0; } #start-link a { display: inline-block; padding: 8px 16px; background-color: rgba(255, 255, 255, 0.2); border-radius: 4px; color: white !important; text-decoration: none; transition: background-color 0.3s ease; } #start-link a:hover { background-color: rgba(255, 255, 255, 0.3); } #start-link .balloon { background-color: #ff6b6b; color: white; border-radius: 10px; padding: 2px 6px; font-size: 0.8em; margin-left: 5px; } /* 统计卡片 */ .stat-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px; } .stat-card { background-color: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 15px; text-align: center; transition: transform 0.2s ease, box-shadow 0.2s ease; } .stat-icon { font-size: 2em; margin-bottom: 10px; color: #000000; /* 可根据不同类型调整颜色 */ } .stat-number { font-size: 1.8em; font-weight: bold; margin: 5px 0; color: #495057; } .stat-label { font-size: 0.9em; color: #6c757d; } /* 最近列表 */ .latest-link ul { list-style: none; padding: 0; margin: 0; } .latest-link li { padding: 8px 0; border-bottom: 1px solid #eee; } .latest-link li:last-child { border-bottom: none; } .latest-link .title { margin-left: 5px; } .latest-link span { color: #6c757d; font-size: 0.9em; } /* 图表区域 - 重新规划 */ .chart-container { max-width: 1200px; /* 限制图表区域最大宽度 */ margin: 0 auto; /* 水平居中 */ display: flex; flex-direction: column; gap: 20px; } .chart-row { display: flex; gap: 20px; flex-wrap: wrap; } .chart-box { flex: 1; min-width: 250px; /* 调整最小宽度以适应更多屏幕 */ background-color: #fff; border-radius: 8px; /*box-shadow: 0 2px 4px rgba(0,0,0,0.1);*/ padding: 15px; display: flex; flex-direction: column; /* 确保内部元素垂直排列 */ } .chart-box h4 { margin: 0 0 10px 0; /* 调整标题边距 */ text-align: center; color: #333; flex-shrink: 0; /* 防止标题被压缩 */ } .chart-box canvas { width: 100% !important; /* 确保画布宽度占满容器 */ max-height: 300px; /* 限制图表最大高度 */ flex-grow: 1; /* 画布占据剩余空间 */ } /* 响应式调整 */ @media (max-width: 768px) { .stat-cards { grid-template-columns: repeat(2, 1fr); } .chart-row { flex-direction: column; /* 小屏幕时堆叠 */ } .chart-box { min-width: 100%; /* 小屏幕时占满宽度 */ } #start-link { flex-direction: column; } } /* 中等屏幕调整 - 让图表在中等屏幕也能两列显示 */ @media (min-width: 769px) and (max-width: 1100px) { .chart-row { /* 在这个范围内,允许换行,但.chart-box的flex行为会使其尽可能并排 */ } .chart-box { /* 可以微调 min-width 来控制换行点 */ min-width: calc(50% - 10px); } } </style> <div class="main"> <div class="container typecho-dashboard"> <?php include 'page-title.php'; ?> <div class="row typecho-page-main"> <!-- 欢迎卡片 --> <div class="col-mb-12" role="main"> <div class="card welcome-card"> <h3><?php _e('欢迎使用 Typecho'); ?></h3> <p><?php _e('目前有 <em>%s</em> 篇文章, 并有 <em>%s</em> 条关于你的评论在 <em>%s</em> 个分类中.', $stat->myPublishedPostsNum, $stat->myPublishedCommentsNum, $stat->categoriesNum); ?></p> <ul id="start-link" class="clearfix"> <?php if ($user->pass('contributor', true)): ?> <li><a href="<?php $options->adminUrl('write-post.php'); ?>"><i class="fas fa-pen"></i> <?php _e('撰写新文章'); ?></a></li> <?php if ($user->pass('editor', true) && 'on' == $request->get('__typecho_all_comments') && $stat->waitingCommentsNum > 0): ?> <li> <a href="<?php $options->adminUrl('manage-comments.php?status=waiting'); ?>"><i class="fas fa-comments"></i> <?php _e('待审核的评论'); ?></a> <span class="balloon"><?php $stat->waitingCommentsNum(); ?></span> </li> <?php elseif ($stat->myWaitingCommentsNum > 0): ?> <li> <a href="<?php $options->adminUrl('manage-comments.php?status=waiting'); ?>"><i class="fas fa-comments"></i> <?php _e('待审核评论'); ?></a> <span class="balloon"><?php $stat->myWaitingCommentsNum(); ?></span> </li> <?php endif; ?> <?php if ($user->pass('editor', true) && 'on' == $request->get('__typecho_all_comments') && $stat->spamCommentsNum > 0): ?> <li> <a href="<?php $options->adminUrl('manage-comments.php?status=spam'); ?>"><i class="fas fa-trash-alt"></i> <?php _e('垃圾评论'); ?></a> <span class="balloon"><?php $stat->spamCommentsNum(); ?></span> </li> <?php elseif ($stat->mySpamCommentsNum > 0): ?> <li> <a href="<?php $options->adminUrl('manage-comments.php?status=spam'); ?>"><i class="fas fa-trash-alt"></i> <?php _e('垃圾评论'); ?></a> <span class="balloon"><?php $stat->mySpamCommentsNum(); ?></span> </li> <?php endif; ?> <?php if ($user->pass('administrator', true)): ?> <li><a href="<?php $options->adminUrl('manage-posts.php'); ?>"><i class="fas fa-list"></i> <?php _e('文章管理'); ?></a></li> <li><a href="<?php $options->adminUrl('plugins.php'); ?>"><i class="fas fa-plug"></i> <?php _e('插件管理'); ?></a></li> <li><a href="<?php $options->adminUrl('options-general.php'); ?>"><i class="fas fa-cog"></i> <?php _e('系统设置'); ?></a></li> <?php endif; ?> <?php endif; ?> </ul> </div> </div> <!-- 统计卡片 --> <div class="col-mb-12"> <div class="stat-cards"> <div class="stat-card"> <a href="<?php $options->adminUrl('manage-posts.php'); ?>"> <div class="stat-icon"><i class="fas fa-file-alt"></i></div> <div class="stat-number"><?php echo $stat->myPublishedPostsNum; ?></div> <div class="stat-label"><?php _e('文章'); ?></div> </a> </div> <div class="stat-card"> <a href="<?php $options->adminUrl('manage-comments.php'); ?>"> <div class="stat-icon"><i class="fas fa-comment"></i></div> <div class="stat-number"><?php echo $stat->myPublishedCommentsNum; ?></div> <div class="stat-label"><?php _e('评论'); ?></div> </a> </div> <div class="stat-card"> <a href="<?php $options->adminUrl('manage-categories.php'); ?>"> <div class="stat-icon"><i class="fas fa-folder"></i></div> <div class="stat-number"><?php echo $stat->categoriesNum; ?></div> <div class="stat-label"><?php _e('分类'); ?></div> </a> </div> <div class="stat-card"> <a href="<?php $options->adminUrl('manage-tags.php'); ?>"> <div class="stat-icon"><i class="fas fa-tags"></i></div> <div class="stat-number"><?php echo $stat->tagsNum; ?></div> <div class="stat-label"><?php _e('标签'); ?></div> </a> </div> <!-- 可根据需要添加更多统计 --> </div> </div> <!-- 左侧内容:最近文章 --> <div class="col-mb-12 col-tb-6" role="complementary"> <div class="card"> <h3 class="card-title"><?php _e('最近发布的文章'); ?></h3> <?php \Widget\Contents\Post\Recent::alloc('pageSize=10')->to($posts); ?> <ul class="latest-link"> <?php if ($posts->have()): ?> <?php while ($posts->next()): ?> <li> <span><?php $posts->date('n.j'); ?></span> <a href="<?php $posts->permalink(); ?>" class="title"><?php $posts->title(); ?></a> </li> <?php endwhile; ?> <?php else: ?> <li><em><?php _e('暂时没有文章'); ?></em></li> <?php endif; ?> </ul> </div> </div> <!-- 右侧内容:最近评论 --> <div class="col-mb-12 col-tb-6" role="complementary"> <div class="card"> <h3 class="card-title"><?php _e('最近得到的回复'); ?></h3> <ul class="latest-link"> <?php \Widget\Comments\Recent::alloc('pageSize=10')->to($comments); ?> <?php if ($comments->have()): ?> <?php while ($comments->next()): ?> <li> <span><?php $comments->date('n.j'); ?></span> <a href="<?php $comments->permalink(); ?>" class="title"><?php $comments->author(false); ?></a>: <?php $comments->excerpt(35, '...'); ?> </li> <?php endwhile; ?> <?php else: ?> <li><?php _e('暂时没有回复'); ?></li> <?php endif; ?> </ul> </div> </div> <!-- 图表显示区域 --> <div class="col-mb-12" role="complementary"> <div class="card"> <h3 class="card-title" style="color: #333;"><?php _e('文章统计数据'); ?></h3> <div class="chart-container"> <div class="chart-row"> <!-- 月度发布图表 --> <div class="chart-box"> <h4><?php _e('月度文章发布趋势'); ?></h4> <canvas id="monthlyPostChart" height="250"></canvas> </div> <!-- 状态分布图表 --> <div class="chart-box"> <h4><?php _e('文章状态分布'); ?></h4> <canvas id="statusChart" height="250"></canvas> </div> </div> <div class="chart-row"> <!-- 分类文章统计 --> <div class="chart-box"> <h4><?php _e('各分类文章数量'); ?></h4> <canvas id="categoryChart" height="250"></canvas> </div> <!-- 标签文章统计 --> <div class="chart-box"> <h4><?php _e('热门标签文章数量 (Top10)'); ?></h4> <canvas id="tagChart" height="250"></canvas> </div> </div> </div> </div> </div> </div> </div> </div> <?php include 'copyright.php'; include 'common-js.php'; ?> <script> $(document).ready(function() { <?php // ==================== PHP 数据计算部分 ==================== // 1. 月度文章统计数据 $db = Typecho_Db::get(); // 月度文章统计 $select = $db->select() ->from('table.contents') ->where('type = ?', 'post') ->where('status = ?', 'publish'); $posts = $db->fetchAll($select); $monthlyData = []; $monthLabels = []; $currentYear = date('Y'); $currentMonth = date('n'); // 初始化最近12个月的数据 for ($i = 11; $i >= 0; $i--) { $year = $currentYear; $month = $currentMonth - $i; if ($month <= 0) { $month += 12; $year--; } $key = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT); $monthlyData[$key] = 0; $monthLabels[$key] = $year . '年' . $month . '月'; } // 统计实际数据 foreach ($posts as $post) { $created = $post['created']; $year = date('Y', $created); $month = date('m', $created); $key = $year . '-' . $month; if (isset($monthlyData[$key])) { $monthlyData[$key]++; } } // 2. 文章状态统计 $published = $db->fetchObject($db->select(['COUNT(*)' => 'count']) ->from('table.contents') ->where('type = ?', 'post') ->where('status = ?', 'publish'))->count; $draft = $db->fetchObject($db->select(['COUNT(*)' => 'count']) ->from('table.contents') ->where('type = ?', 'post_draft') // 注意:Typecho 草稿通常在 contents 表中 type 为 post_draft // ->where('status = ?', 'publish') // 草稿状态通常不是 publish,可以省略或检查实际结构 )->count; // 3. 分类文章统计 $categories = $db->fetchAll($db->select()->from('table.metas')->where('type = ?', 'category')); $categoryData = []; $categoryLabels = []; foreach ($categories as $category) { $count = $db->fetchObject($db->select(['COUNT(*)' => 'count']) ->from('table.relationships') ->where('mid = ?', $category['mid']))->count; if ($count > 0) { // 只显示有文章的分类 $categoryLabels[] = $category['name']; $categoryData[] = (int)$count; } } // 4. 标签文章统计 (取前10个) $tags = $db->fetchAll($db->select()->from('table.metas')->where('type = ?', 'tag')); $tagData = []; $tagLabels = []; foreach ($tags as $tag) { $count = $db->fetchObject($db->select(['COUNT(*)' => 'count']) ->from('table.relationships') ->where('mid = ?', $tag['mid']))->count; if ($count > 0) { $tagLabels[] = $tag['name']; $tagData[] = (int)$count; } } // 按数量排序,取前10个 array_multisort($tagData, SORT_DESC, $tagLabels); $topTagLabels = array_slice($tagLabels, 0, 10); $topTagData = array_slice($tagData, 0, 10); // 准备图表数据 $chartMonthlyData = [ 'labels' => array_values($monthLabels), 'values' => array_values($monthlyData) ]; $chartStatusData = [ 'published' => (int)$published, 'draft' => (int)$draft ]; $chartCategoryData = [ 'labels' => $categoryLabels, 'values' => $categoryData ]; $chartTagData = [ 'labels' => $topTagLabels, 'values' => $topTagData ]; ?> // ==================== JavaScript 图表渲染部分 ==================== // 1. 月度文章图表 var monthlyData = <?php echo json_encode($chartMonthlyData); ?>; if (monthlyData.labels && monthlyData.labels.length > 0) { var ctx1 = document.getElementById('monthlyPostChart').getContext('2d'); new Chart(ctx1, { type: 'line', data: { labels: monthlyData.labels, datasets: [{ label: '每月发文数', data: monthlyData.values, borderColor: 'rgb(54, 162, 235)', // 调整为蓝色系 backgroundColor: 'rgba(54, 162, 235, 0.1)', tension: 0.3, // 稍微增加曲线张力 fill: true }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { precision: 0 }, grid: { color: 'rgba(0, 0, 0, 0.05)' // 网格线颜色 } }, x: { grid: { color: 'rgba(0, 0, 0, 0.05)' } } }, plugins: { legend: { display: false // 隐藏图例,因为只有一个数据集 }, tooltip: { mode: 'index', intersect: false } } } }); } // 2. 文章状态图表 var statusData = <?php echo json_encode($chartStatusData); ?>; if (statusData) { var ctx2 = document.getElementById('statusChart').getContext('2d'); new Chart(ctx2, { type: 'doughnut', data: { labels: ['已发布', '草稿'], datasets: [{ data: [statusData.published, statusData.draft], backgroundColor: [ 'rgba(75, 192, 192, 0.8)', // 已发布 - 青色 'rgba(255, 205, 86, 0.8)', // 草稿 - 黄色 ], borderWidth: 1, borderColor: '#fff' }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { padding: 15, usePointStyle: true } }, tooltip: { callbacks: { label: function(context) { return context.label + ': ' + context.raw; } } } }, cutout: '60%' // 增加中间空心部分 } }); } // 3. 分类文章图表 var categoryData = <?php echo json_encode($chartCategoryData); ?>; if (categoryData.labels && categoryData.labels.length > 0) { var ctx3 = document.getElementById('categoryChart').getContext('2d'); new Chart(ctx3, { type: 'bar', data: { labels: categoryData.labels, datasets: [{ label: '文章数量', data: categoryData.values, backgroundColor: 'rgba(153, 102, 255, 0.7)', // 紫色系 borderColor: 'rgba(153, 102, 255, 1)', borderWidth: 1, borderRadius: 4, // 条形圆角 barPercentage: 0.7, // 调整条形宽度 categoryPercentage: 0.8 }] }, options: { indexAxis: 'x', // 保持为垂直柱状图 responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { precision: 0 }, grid: { color: 'rgba(0, 0, 0, 0.05)' } }, x: { grid: { display: false // 隐藏 X 轴网格线 } } }, plugins: { legend: { display: false }, tooltip: { mode: 'index', intersect: false } } } }); } // 4. 标签文章图表 var tagData = <?php echo json_encode($chartTagData); ?>; if (tagData.labels && tagData.labels.length > 0) { var ctx4 = document.getElementById('tagChart').getContext('2d'); new Chart(ctx4, { type: 'bar', data: { labels: tagData.labels, datasets: [{ label: '文章数量', data: tagData.values, backgroundColor: 'rgba(255, 159, 64, 0.7)', // 橙色系 borderColor: 'rgba(255, 159, 64, 1)', borderWidth: 1, borderRadius: 4, barPercentage: 0.7, categoryPercentage: 0.8 }] }, options: { indexAxis: 'x', responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { precision: 0 }, grid: { color: 'rgba(0, 0, 0, 0.05)' } }, x: { grid: { display: false } } }, plugins: { legend: { display: false }, tooltip: { mode: 'index', intersect: false } } } }); } }); </script> <script> $(document).ready(function () { }); </script> <?php include 'footer.php'; ?> 其他插件推荐:[post cid="405" /] 为typecho博客添加Gotify插件通知 https://blog.iletter.top/archives/405.html 2025-07-27T02:05:00+08:00 受限于typecho博客没有通知,自己写了一个博客有评论就通知的gotify插件,脚本使用之前的python改的。这是效果下载地址插件+app+docker部署https://wonder1999.lanzouu.com/iB6DK32mdwcf代码 <?php namespace TypechoPlugin\GotifyNotify; use Typecho\Plugin\PluginInterface; use Typecho\Widget\Helper\Form; use Typecho\Widget\Helper\Form\Element\Text; use Widget\Options; use Typecho\Plugin\Exception as PluginException; use Typecho\Widget\Exception as WidgetException; use Typecho\Db\Exception as DbException; use Utils; if (!defined('__TYPECHO_ROOT_DIR__')) { exit; } /** * 当用户评论时通过 Gotify 发送通知 * * @package GotifyNotify * @author 白荼 * @version 1.3.0 * @link https://gotify.net */ class Plugin implements PluginInterface { /** * 激活插件方法 * * @access public * @return void * @throws PluginException */ public static function activate() { \Typecho\Plugin::factory('Widget_Feedback')->finishComment = __CLASS__ . '::requestService'; \Typecho\Plugin::factory('Widget_Comments_Edit')->finishComment = __CLASS__ . '::requestService'; \Typecho\Plugin::factory('Widget_Service')->sendGotify = __CLASS__ . '::sendGotify'; } /** * 禁用插件方法 * * @access public * @return void * @throws PluginException */ public static function deactivate() { // 通常不需要显式移除 } /** * 获取插件配置面板 * * @param Form $form 配置面板 * @access public * @return void */ public static function config(Form $form) { // Gotify 服务器地址 $serverUrl = new Text('serverUrl', NULL, '', _t('Gotify 服务器地址'), _t('例如: http://your-gotify-server.com')); $form->addInput($serverUrl->addRule('required', _t('Gotify 服务器地址不能为空'))); // 应用 Token $appToken = new Text('appToken', NULL, '', _t('应用 Token'), _t('在 Gotify 中创建应用后获得的 Token')); $form->addInput($appToken->addRule('required', _t('应用 Token 不能为空'))); // 通知标题 $title = new Text('title', NULL, '博客有新评论', _t('通知标题'), _t('收到新评论时的推送标题')); $form->addInput($title); // 消息优先级 (Priority) - 使用 Text 输入框 $priority = new Text('priority', NULL, '1', _t('消息优先级'), _t('设置 Gotify 消息的优先级 (自己在Gorify定义的Priority)')); $form->addInput($priority); } /** * 个人用户的配置面板 * * @param Form $form * @access public * @return void */ public static function personalConfig(Form $form) { // 个人配置通常为空 } /** * 评论通知回调 - 触发异步服务 * 这个方法在评论完成后被调用 * * @access public * @param $comment (Widget_Comments_Edit 或 Widget_Feedback 的实例) * @return void * @throws PluginException */ public static function requestService($comment) { // 检查评论对象是否有效 if (!isset($comment->coid) || !$comment->have()) { error_log("GotifyNotify: Invalid comment object received in requestService."); return; } $coid = $comment->coid; $options = Options::alloc()->plugin('GotifyNotify'); // 检查必要配置 if (empty($options->serverUrl) || empty($options->appToken)) { error_log("GotifyNotify: Missing server URL or app token, skipping notification for comment ID {$coid}."); return; } // 调用 Widget_Service 中注册的异步方法 // 将评论 ID 传递给异步服务 try { error_log("GotifyNotify: Calling async service for comment ID {$coid}."); Utils\Helper::requestService('sendGotify', $coid); } catch (\Exception $e) { error_log("GotifyNotify: Failed to call async service for comment ID {$coid}. Error: " . $e->getMessage()); } } /** * 异步发送 Gotify 通知 * 这个方法由 Widget_Service 调用 * * @param integer $coid 评论ID * @access public * @return void * @throws WidgetException * @throws DbException */ public static function sendGotify(int $coid) { error_log("GotifyNotify Service: Starting to process comment ID {$coid}."); // 获取插件配置 $options = Options::alloc()->plugin('GotifyNotify'); // 移除了对 $options->enable 的检查,插件默认启用 // 重新获取评论对象 try { $commentWidget = Utils\Helper::widgetById('comments', $coid); } catch (WidgetException $e) { error_log("GotifyNotify Service: Failed to get comment widget for ID {$coid}. Error: " . $e->getMessage()); return; } if (!$commentWidget->have()) { error_log("GotifyNotify Service: Comment widget is empty for ID {$coid}."); return; } // 检查必要配置 if (empty($options->serverUrl) || empty($options->appToken)) { error_log("GotifyNotify Service: Missing configuration for comment ID {$coid}."); return; } try { // 构造通知内容 $title = $options->title ?: '博客有新评论'; $message = self::buildMessage($commentWidget); // 传入 widget 对象 // 获取优先级设置 $priority = $options->priority ?? '1'; // 默认优先级为 1 error_log("GotifyNotify Service: Prepared message for comment ID {$coid}. Title: {$title}, Priority: {$priority}"); // 发送通知,传入优先级 self::sendGotifyMessage($options->serverUrl, $options->appToken, $title, $message, $priority); error_log("GotifyNotify Service: Successfully sent notification for comment ID {$coid}."); } catch (\Exception $e) { // 记录详细错误信息 error_log("GotifyNotify Service: Failed to send notification for comment ID {$coid}. Error: " . $e->getMessage()); error_log("GotifyNotify Service: Stack trace: " . $e->getTraceAsString()); } } /** * 构造通知消息内容 * * @param \Widget\Base\Comments $comment 评论对象 * @return string 消息内容 */ private static function buildMessage($comment) { $content = ''; if (is_object($comment) && $comment->have()) { // 从评论对象获取信息 $author = $comment->author ?? '匿名用户'; $mail = $comment->mail ?? ''; $url = $comment->url ?? ''; $text = $comment->text ?? ''; // $ip = $comment->ip ?? ''; // Widget\Base\Comments 通常不直接暴露 IP $content = "作者: {$author}\n"; if (!empty($mail)) { $content .= "邮箱: {$mail}\n"; } if (!empty($url)) { $content .= "网站: {$url}\n"; } // if (!empty($ip)) { // $content .= "IP: {$ip}\n"; // } $content .= "内容: {$text}\n"; $content .= "时间: " . date('Y-m-d H:i:s', $comment->created) . "\n"; // 使用评论创建时间 $content .= "文章: " . $comment->title . "\n"; // 文章标题 $content .= "链接: " . $comment->permalink . "\n"; // 评论链接 } else { // 兼容其他情况 $content = "收到新评论,请登录后台查看 (评论ID: " . ($comment->coid ?? 'unknown') . ")"; } return $content; } /** * 发送 Gotify 消息 * * @param string $serverUrl Gotify 服务器地址 * @param string $appToken 应用 Token * @param string $title 消息标题 * @param string $message 消息内容 * @param string $priority 消息优先级 * @throws \Exception */ private static function sendGotifyMessage($serverUrl, $appToken, $title, $message, $priority = '1') { // 清理 URL $serverUrl = rtrim($serverUrl, '/'); // 构造请求 URL (使用查询参数传递 token) $url = $serverUrl . '/message'; // 准备表单数据,包含 priority $data = [ 'title' => $title, 'message' => $message, 'priority' => $priority // 使用传入的优先级 ]; // 准备查询参数 $params = ['token' => $appToken]; $fullUrl = $url . '?' . http_build_query($params); error_log("GotifyNotify: Sending request to {$fullUrl}"); error_log("GotifyNotify: POST data: " . print_r($data, true)); // 使用 cURL 发送请求 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $fullUrl); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); // 发送表单数据 curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: application/json' ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 根据你的服务器 SSL 配置调整 curl_setopt($ch, CURLOPT_USERAGENT, 'Typecho GotifyNotify Plugin/1.3.0'); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); error_log("GotifyNotify: cURL Response Code: {$httpCode}"); error_log("GotifyNotify: cURL Response Body: {$response}"); error_log("GotifyNotify: cURL Error (if any): {$error}"); // 检查响应 if ($response === false) { throw new \Exception('cURL error: ' . $error); } if ($httpCode >= 200 && $httpCode < 300) { // 成功 error_log("GotifyNotify: Message sent successfully."); // 可以进一步检查返回的 JSON $result = json_decode($response, true); if (!$result) { error_log("GotifyNotify: Warning - Could not decode JSON response, but HTTP status was OK."); } return; // 成功返回 } else { // 失败 throw new \Exception('HTTP error: ' . $httpCode . ', response: ' . $response); } } } python每日检查网站ssl证书是否过期 https://blog.iletter.top/archives/402.html 2025-07-26T23:58:48+08:00 目的是检查网站是否过期,过期前几天进行通知import subprocess from datetime import datetime, timedelta, timezone import requests from datetime import datetime from apscheduler.schedulers.blocking import BlockingScheduler scheduler = BlockingScheduler() # 发送通知请求 def send_msg_to_gotify(title, msg): url = "http://152.136.153.72:8385/message" params = {"token": "AI.53prwavAZsoC"} current_time = datetime.now() # 表单数据 data = { "title": title, "message": msg, "priority": "0" } try: response = requests.post( url, params=params, data=data ) print("Response Body:", response.text) except requests.exceptions.RequestException as e: print("请求失败:", e) def check_ssl_certificate_expiration(web_site, out_date): # 执行 openssl 命令获取证书信息 try: result = subprocess.run( ["openssl", "x509", "-in", "fullchain.pem", "-noout", "-dates"], capture_output=True, text=True, check=True, cwd=f"C:\\Certbot\\live\\{web_site}" ) except subprocess.CalledProcessError as e: print("执行 openssl 命令失败:", e) return # 解析输出,提取 notAfter 日期 output = result.stdout not_after_str = None for line in output.splitlines(): if line.startswith("notAfter="): not_after_str = line.split("=", 1)[1].strip() break if not not_after_str: print("未找到 notAfter 信息") return # 解析日期字符串为 datetime 对象(使用 GMT 时间) try: date_format = "%b %d %H:%M:%S %Y GMT" not_after_date = datetime.strptime(not_after_str, date_format).replace(tzinfo=timezone.utc) except ValueError as e: print("日期解析失败:", e) return # 获取当前 UTC 时间 current_date = datetime.now(timezone.utc) # 计算时间差 delta = not_after_date - current_date # 判断是否在 15 天内且未过期 if 0 <= delta.days <= out_date: print(f"⚠️ SSL 证书({web_site})将在 {delta.days} 天后过期,请及时续期!") send_msg_to_gotify('SSL即将过期', f'SSL 证书({web_site})将在 {delta.days} 天后过期,请及时更新并重启nginx服务') elif delta.days < 0: print(f"❌ SSL 证书({web_site})已过期!") send_msg_to_gotify('SSL即将过期', f'SSL 证书({web_site})已过期,请及时更新并重启nginx服务') else: print(f"✅ SSL 证书({web_site})有效期超过 {out_date} 天,无需处理。") # 执行检查 @scheduler.scheduled_job('cron', hour=8, minute=30, misfire_grace_time=3600) def tick(): check_ssl_certificate_expiration('cx.sdasinfo.org.cn', 15) try: scheduler.start() print('定时任务成功执行') except Exception as e: scheduler.shutdown() print('定时任务执行失败') finally: exit() 去除宝塔界面一些烦人的东西 https://blog.iletter.top/archives/400.html 2025-07-25T13:18:00+08:00 更新之后越来越难用了,自己懒得配置nginx,mysql,这些环境,开心版的又怕后门,自己改后端的js文件看的脑瓜子疼,反正都是界面,直接js删掉一些dom元素就行了。有需要的可以拿走。// ==UserScript== // @name 宝塔面板优化 - 界面美化 // @namespace http://tampermonkey.net/ // @version 1.3 // @description 移除指定 class 元素、替换特定 span 文字内容,并删除包含“需求反馈”的 span // @match http://152.136.153.72:9999/* // @grant none // ==/UserScript== (function () { 'use strict'; console.log('⚠ 脚本准备加载'); // 定义需要移除的 class 组合 const selectorsToRemove = [ '.h-auto.mt-\\[1\\.2rem\\]', // h-auto mt-[1.2rem] '.svgtofont-desired.\\!mr-4px.\\!text-\\[16px\\]', // svgtofont-desired !mr-4px !text-[16px] '.icon-end-time-free.icon-unpaid-ltd', // icon-end-time-free icon-unpaid-ltd '.el-card.is-always-shadow.is-always-shadow.\\!border-\\[\\#efefef\\]', // el-card is-always-shadow is-always-shadow !border-[#efefef] '.icon-end-time.icon-unpaid-ltd' // icon-end-time icon-unpaid-ltd ]; // 移除指定 class 的元素 const removeElementsByClass = () => { selectorsToRemove.forEach(selector => { document.querySelectorAll(selector).forEach(el => el.remove()); }); }; // 替换指定 span 的文字内容 const replaceSpanText = () => { const spanSelector = 'span.mr-1rem.ml-\\[-1\\.5rem\\]'; document.querySelectorAll(spanSelector).forEach(span => { span.textContent = '精简版'; }); }; // 删除 span/button 包含 指定字符 的 span 元素 const removeSpansWithText = () => { const spans = document.querySelectorAll('span'); spans.forEach(span => { if (span.textContent.trim() == '需求反馈') { span.remove(); } }); const buttons = document.querySelectorAll('button'); buttons.forEach(span => { if (span.textContent.trim() == '立即体验') { span.remove(); } }); }; // 删除指定class 包含置顶字符的元素 const removeClassWithText =()=>{ const parents = document.querySelectorAll('.header-child-tab'); parents.forEach(span => { if ( span.textContent.trim() == '安全检测' || span.textContent.trim() == '违规词检测' || span.textContent.trim() == 'PHP网站安全' || span.textContent.trim() == '入侵防御' || span.textContent.trim() == '系统加固' || span.textContent.trim() == '扫描感知' || span.textContent.trim() == '日志审计' || span.textContent.trim() == 'SSH登录日志' ) { span.remove(); } }); const classparents = document.querySelectorAll('.el-collapse-item'); classparents.forEach(span => { if ( span.textContent.trim().includes('top5') ) { span.remove(); } }); } // 删除包含特定子元素的 el-col 元素 const removeParentIfChildExists = () => { // 获取所有 el-col.el-col-6.wrapper-item 元素 const parents = document.querySelectorAll('.el-col.el-col-6.wrapper-item'); parents.forEach(parent => { // 检查子元素是否包含指定的 class const child = parent.querySelector('.absolute.-left-1\\.5.-top-1'); if (child) { parent.remove(); // 移除父元素 } }); }; // 综合执行函数 const removeElements = () => { removeElementsByClass(); replaceSpanText(); removeSpansWithText(); removeParentIfChildExists(); removeClassWithText(); }; // 等待dom加载完毕后执行综合函数 let timeoutId; const debouncedRemove = () => { clearTimeout(timeoutId); timeoutId = setTimeout(removeElements, 200); }; let runCount = 0; const maxRuns = 5; const observer = new MutationObserver((mutations, obs) => { if (runCount >= maxRuns) { obs.disconnect(); return; } obs.disconnect(); debouncedRemove(); runCount++; obs.observe(document.body, { childList: true, subtree: true }); }); observer.observe(document.body, { childList: true, subtree: true }); // 点击一次执行一次综合函数 let clickTimeout; document.addEventListener('click', () => { clearTimeout(clickTimeout); clickTimeout = setTimeout(removeElements, 200); }); // 初始执行(延迟 1 秒,等待 DOM 加载) setTimeout(() => { removeElements(); }, 1000); })(); ubuntu server设置samba共享服务 https://blog.iletter.top/archives/399.html 2025-07-25T13:17:00+08:00 在 Ubuntu Server 上搭建 Samba 文件共享服务 是一个非常常见的需求,适用于局域网内的文件共享、打印机共享等场景。下面是一个完整教程 ,帮助你快速搭建一个基础的 Samba 服务,并允许其他用户访问你的共享目录。第一步:安装 Sambasudo apt update sudo apt install samba -y安装完成后,Samba 会自动启动。第二步:备份默认配置文件(可选)Samba 的主配置文件位于:/etc/samba/smb.conf建议先做一个备份:sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.bak第三步:添加共享目录配置你可以选择共享一个现有目录,比如 /home/dellevin/shared。1. 创建共享目录(如果还没有)mkdir -p /home/dellevin/shared2. 编辑 Samba 配置文件sudo nano /etc/samba/smb.conf在文件末尾添加如下内容(以共享 shared 目录为例):[Shared] comment = Shared Folder path = /home/dellevin/shared browseable = yes read only = no writable = yes valid users = dellevin[Shared]:共享名称(客户端看到的名字)path:要共享的目录路径read only = no 和 writable = yes 表示允许写入valid users:允许访问该共享的用户第四步:设置 Samba 用户密码你需要为允许访问的用户设置 Samba 密码(即使该用户已存在 Linux 系统中):sudo smbpasswd -a dellevin系统会提示你输入并确认 Samba 密码。⚠️ 注意:这个密码可以和系统密码不同。第五步:重启 Samba 服务sudo systemctl restart smbd第六步:设置开机自启sudo systemctl enable smbd第七步:检查是否运行正常systemctl status smbd确保服务状态是 active (running)。第八步:从 Windows 或 Linux 客户端访问Windows 访问方式:打开“此电脑”或资源管理器,在地址栏输入:\\你的Ubuntu服务器IP地址例如:\\192.168.1.100然后输入用户名 dellevin 和你在 smbpasswd 中设置的密码即可访问。Linux 访问方式(如 Ubuntu 桌面):打开文件管理器(如 Nautilus),按下 Ctrl + L 输入:smb://192.168.1.100或者使用命令行挂载:sudo mount -t cifs //192.168.1.100/Shared /mnt/shared -o user=dellevin可选:配置防火墙(UFW)如果你启用了防火墙 UFW,需要开放 Samba 所需端口:sudo ufw allow 'Samba'总结:常用命令一览表操作命令安装 Sambasudo apt install samba配置文件位置/etc/samba/smb.conf添加共享目录在配置文件中添加[ShareName]块设置 Samba 用户密码sudo smbpasswd -a username重启 Sambasudo systemctl restart smbd开机自启sudo systemctl enable smbd查看服务状态systemctl status smbd客户端访问地址\\IP地址或smb://IP地址 linux注册服务为系统服务并开机自启 https://blog.iletter.top/archives/398.html 2025-07-15T22:22:23+08:00 因为安装了frp穿透服务,每次开启都要手动启动, 所以索性注册为系统服务,并实现开机自启。编写配置文件sudo vim /etc/systemd/system/frp-panel.service[Unit] Description=FRP Panel Service After=network.target [Service] User=root WorkingDirectory=/home/dellevin/frp ExecStart=/home/dellevin/frp/frp-panel client -s 4f448b4a-1c0f-486d-9f42-aed90f288da5 -i admin.c.home-services --api-url http://152.136.153.72:9000 --rpc-url grpc://152.136.153.72:9001 Restart=always RestartSec=10s [Install] WantedBy=multi-user.target说明:User=:运行该服务的用户(这里是 root)WorkingDirectory=:程序运行时的工作目录ExecStart=:要执行的完整命令Restart=always:异常退出后自动重启RestartSec=10s:重启前等待 10 秒重新加载 systemd 配置sudo systemctl daemon-reexec sudo systemctl daemon-reload启用服务开机自启sudo systemctl enable frp-panel.service启动服务(或重启系统测试)sudo systemctl start frp-panel.service查看运行状态:sudo systemctl status frp-panel.service如果显示 active (running),说明服务已成功运行。日志查看方式你可以使用 journalctl 查看服务日志:journalctl -u frp-panel.service -f取消开机自启的命令sudo systemctl disable 服务名称查看当前是否已取消成功你可以运行以下命令查看该服务的状态:systemctl is-enabled frp-panel如果输出是:disabled说明已经成功取消开机启动了 ubuntu-services挂载外置硬盘 https://blog.iletter.top/archives/397.html 2025-07-15T22:20:55+08:00 软件包升级sudo apt upgradelsblk命令用于列出所有可用的存储设备及其分区信息df -h 查看挂载点、文件系统类型、已用空间、可用空间等。root@dellevin-ubuntu:/# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS loop0 7:0 0 63.9M 1 loop /snap/core20/2318 loop1 7:1 0 87M 1 loop /snap/lxd/29351 loop2 7:2 0 38.8M 1 loop /snap/snapd/21759 sda 8:0 0 119.2G 0 disk ├─sda1 8:1 0 1M 0 part ├─sda2 8:2 0 2G 0 part /boot └─sda3 8:3 0 117.2G 0 part └─ubuntu--vg-ubuntu--lv 253:0 0 117.2G 0 lvm / sdb 8:16 0 465.8G 0 disk └─sdb1 8:17 0 465.8G 0 part root@dellevin-ubuntu:/# df -h Filesystem Size Used Avail Use% Mounted on tmpfs 578M 1.2M 577M 1% /run /dev/mapper/ubuntu--vg-ubuntu--lv 115G 7.1G 102G 7% / tmpfs 2.9G 0 2.9G 0% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock /dev/sda2 2.0G 130M 1.7G 8% /boot tmpfs 578M 4.0K 578M 1% /run/user/1000可以看到sdb并没有挂载sudo mkfs.ext4 /dev/sdb1 将这块硬盘格式化为 ext4 文件系统root@dellevin-ubuntu:/mnt# sudo mkfs.ext4 /dev/sdb1 mke2fs 1.46.5 (30-Dec-2021) /dev/sdb1 contains a ntfs file system labelled '系统' Proceed anyway? (y,N) y Creating filesystem with 122096000 4k blocks and 30531584 inodes Filesystem UUID: 86424462-d372-4894-89b2-3186bb52b237 Superblock backups stored on blocks: 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968, 102400000 Allocating group tables: done Writing inode tables: done Creating journal (262144 blocks): done Writing superblocks and filesystem accounting information: donesudo mount /dev/sdb1 /mnt/500Data 创建挂载点并挂载硬盘root@dellevin-ubuntu:/mnt# mkdir 500Data root@dellevin-ubuntu:/mnt# ls 500Data root@dellevin-ubuntu:/mnt# sudo mount /dev/sdb1 /mnt/500Data root@dellevin-ubuntu:/mnt# df -h | grep sdb1 /dev/sdb1 458G 28K 435G 1% /mnt/500Data root@dellevin-ubuntu:/mnt# df -h Filesystem Size Used Avail Use% Mounted on tmpfs 578M 1.2M 577M 1% /run /dev/mapper/ubuntu--vg-ubuntu--lv 115G 7.1G 102G 7% / tmpfs 2.9G 0 2.9G 0% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock /dev/sda2 2.0G 130M 1.7G 8% /boot tmpfs 578M 4.0K 578M 1% /run/user/1000 /dev/sdb1 458G 28K 435G 1% /mnt/500Data设置开机自动挂载sudo blkid | grep /dev/sdb1 查看硬盘UUIDroot@dellevin-ubuntu:/mnt/500Data# sudo blkid | grep /dev/sdb1 /dev/sdb1: UUID="86424462-d372-4894-89b2-3186bb52b237" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="1ea79eea-01"vim /etc/fstab 然后编辑 fstab 添加一行:UUID=86424462-d372-4894-89b2-3186bb52b237 /mnt/500Data ext4 defaults 0 2 windows server 2019 docker安装 https://blog.iletter.top/archives/365.html 2025-07-14T17:11:31+08:00 windows server 2019 docker安装最近把家里的旧服务器搬到出租屋里面来了,准备当服务器用来着,二十年前的老机器了。想着用微信hook的功能,所以选择的windows server。配置好一些环境之后开始配置docker环境除了一些问题。启用Hyper-V和Containers功能运行管理员权限powershellInstall-WindowsFeature -Name Hyper-V,Containers -IncludeAllSubFeature -IncludeManagementTools -Verbose配置安装源Install-Module -Name DockerMsftProvider -Repository PSGallery -Verbose安装Docker运行管理员权限powershell,国内安装可能会因为网络原因失败,可以尝试手动安装,如果按照成功这下面步骤不用进行了Install-Package -Name docker -ProviderName DockerMsftProvider -Verbose下载文件PS C:\Users\Administrator\Desktop> Invoke-WebRequest -UseBasicParsing -OutFile D:\docker-28.3.2.zip https://download.docker.com/win/static/stable/x86_64/docker-28.3.2.zip文件下载地址https://download.docker.com/win/static/stable/x86_64/配置系统环境变量Path注册为系统服务dockerd --register-service -H npipe:// -H tcp://0.0.0.0:2375 --config-file "D:\Env\docker\config\daemon.json"配置文件内容D:\Env\docker\config\daemon.json{ "dns": ["114.114.114.114", "8.8.8.8"], "data-root": "D:\\Env\\docker\\data", "registry-mirrors": ["https://registry.docker-cn.com"] }其他命令设置Docker开机启动Set-Service -Name docker -StartupType Automatic启动 Docker 服务Start-Service docker重启 Docker 服务Restart-Service Docker -Force停止 Docker 服务Stop-Service Dockerdocker-compose下载https://github.com/docker/compose/releases1.下载适用于 Windows 的 docker-compose-Windows-x86_64.exe 文件。2.将文件重命名为 docker-compose.exe 并移动到 Docker 安装目录(如 D:\Env\docker)ps:安装完了我才发现,windows server 2019的docker不支持linux的容器,要想弄,还要一个wsl支持,或者升级机器配置。想想还是算了,直接换ubuntu 的server版了 建筑类网站爬虫 https://blog.iletter.top/archives/364.html 2025-07-04T20:26:41+08:00 最近帮我同学写相关建筑类网站的爬虫以及前后端搜索界面功能,其实技术要点一个没有,涉及到加密的网站我也是放弃爬虫,解密太麻烦了。简单的网站都是一套的逻辑爬虫,大家可以参考一下。有兴趣的话帮忙点个start支持一下前后端系统以及数据库https://gitee.com/wonder19991209/mohurd\_search\_sys爬虫脚本https://gitee.com/wonder19991209/mohurd-spider