前言

之前一直用的是Hexo,纯静态博客胜在节省,随便找一个托管平台就行,比如Github。

缺点也很明显,访问速度完全依赖于托管平台,尤其是Github这种原本访问困难的平台,会很影响访问效率;要嵌入评论功能很麻烦,撰写文章需要每个终端设备都要部署nodejs环境再手动push,虽然可以通过Git管理自动推送更新发布整个网站,但整体来说不如直接通过网页后端管理方便简单。

期间有考虑过转为Wordpress,后来又怕过于臃肿,万一哪一天服务器不续费了迁移又麻烦。

趁着这次腾讯云轻量应用服务器打折,租了个性能还不错的配置,顺便把博客平台也迁移至typecho。

运行环境

系统: Debian 11.1
Mariadb: v10.5.12
PHP: v7.4.28
Nginx: v1.18.0
typecho: v1.2.0-rc2

安装依赖

参照以下文章即可

Debian11部署chevereto-china(v3改版)

其中根据需要将数据库对应修改为typecho即可

部署Typecho

官方正式版下载地址

下载后解压,以 /var/www/typecho路径为例

# 创建文件夹
mkdir /var/www/typecho
# 使用wget下载压缩包
wget https://github.com/typecho/typecho/releases/download/ci/typecho.zip
# 解压
unzip typecho.zip
# 为了防止权限出现问题, 修改全部文件的权限
chmod 777 -R /var/www/typecho

文件结构如下

/var/www/typecho
├── admin
├── config.inc.php
├── index.php
├── install
├── install.php
├── LICENSE.txt
├── sw.min.js
├── usr
└── var

配置

部署完成后通过浏览器访问开始配置Typecho

开始配置

如果此前解压部署文件的时候没有改权限,这里会提示 /usr/uploads无权限写入

连接数据库

在安装依赖Mariadb过程中安装pdo_mysql,这里数据库适配器则会自动识别出来提供选择

配置管理员账号

点击 继续安装后即可通过浏览器访问进入博客主页,可以进一步配置、管理博客

自动备份

按需修改,默认定期备份到/var/www/typecho/backup内,按7天循环删除过期备份

#!/bin/bash
DB_BKP_USER="db_user"     # Enter the username for backup
DB_BKP_PASS="db_user_pass"        # Enter the password of the backup user
DB_BKP_DEST="/var/www/typecho/backup/db" # Enter the Backup directory,change this if you have someother location
FILE_BKP_DEST="/var/www/typecho/backup/file"
BKP_LOG_PATH="/var/www/typecho/backup/log"
BKP_DAYS="7" # Enter how many days backup you want
MYSQL_HOST="localhost"
BKP_DATE="$(date +"%d-%m-%Y-%H:%M:%S-%a")";
IGNORE_DB="information_schema mysql performance_schema"
[ ! -d $DB_BKP_DEST ] && mkdir -p $DB_BKP_DEST || :
[ ! -d $FILE_BKP_DEST ] && mkdir -p $FILE_BKP_DEST || :
[ ! -d $BKP_LOG_PATH ] && mkdir -p $BKP_LOG_PATH || :
MYSQL="$(which mysql)"
MYSQLDUMP="$(which mysqldump)"
GZIP="$(which gzip)"
TAR="$(which tar)"

echo -e "################\n备份时间为${BKP_DATE}################" >> ${BKP_LOG_PATH}/backup.log
echo -e "################\n备份时间为${BKP_DATE}################"

# backup database
DB_LIST="$($MYSQL -u $DB_BKP_USER -h $MYSQL_HOST -p$DB_BKP_PASS -Bse 'show databases')"
DB_LIST_PRINT="$($MYSQL -u $DB_BKP_USER -h $MYSQL_HOST -p$DB_BKP_PASS -Bse 'show databases' | tr '\n' ',' | sed 's/.$//')"

echo -e "尝试备份数据库列表: ${DB_LIST_PRINT}" >> ${BKP_LOG_PATH}/backup.log
echo -e "尝试备份数据库列表: ${DB_LIST_PRINT}"

for db in $DB_LIST
    do
    skipdb=-1
    if [ "$IGNORE_DB" != "" ];
    then
        for i in $IGNORE_DB
        do
           [ "$db" == "$i" ] && skipdb=1 || :
        done
    fi

    if [ "$skipdb" == "1" ] ; then
        echo -e "忽略数据库: $db" >> ${BKP_LOG_PATH}/backup.log
        echo -e "忽略数据库: $db"
    fi
     
    if [ "$skipdb" == "-1" ] ; then
        DB_BKP_FILENAME="$DB_BKP_DEST/$db.$BKP_DATE.gz"
        echo -e "备份数据库: $db 到 $DB_BKP_FILENAME" >> ${BKP_LOG_PATH}/backup.log
        echo -e "备份数据库: $db 到 $DB_BKP_FILENAME"
        $MYSQLDUMP -u $DB_BKP_USER -h $MYSQL_HOST -p$DB_BKP_PASS $db | $GZIP -9 > $DB_BKP_FILENAME
    fi
done

# backup usr
FILE_BKP_FILENAME="$FILE_BKP_DEST/usr.$BKP_DATE.tar.gz"
$TAR -czPf  $FILE_BKP_FILENAME /var/www/typecho/usr
echo -e "备份文件: usr 到 $FILE_BKP_FILENAME" >> ${BKP_LOG_PATH}/backup.log
echo -e "备份文件: usr 到 $FILE_BKP_FILENAME"

# clean expire backups
echo -e "清理过期备份..." >> ${BKP_LOG_PATH}/backup.log
echo -e "清理过期备份..."
find $DB_BKP_DEST -type f -mtime +$BKP_DAYS -delete
find $FILE_BKP_DEST -type f -mtime +$BKP_DAYS -delete

echo -e "################备份结束################\n\n" >> ${BKP_LOG_PATH}/backup.log
echo -e "################备份结束################\n\n"

额外设置/美化

Handsome主题官方页面

本站使用Handsome主题,需要付费授权使用,终身88元

主题购买授权后即可使用,具体使用文档需要付费后通过用户中心才能访问查看,下面内容只讲述部分美化内容

文章/独立页面末尾添加版权声明

插件Github地址

下载 plugin.phpusr/uploads/plugins/Copyright,在typecho插件中启用

其中声明可以使用html代码,比如:

本文采用 <a href="https://creativecommons.org/licenses/by-nc/4.0/deed.zh" target="_blank" title="版权声明协议"><span>知识共享署名-非商业性使用 4.0 国际许可协议</span></a> 进行许可。<br/>您可以自由的转载和修改,但请务必注明文章来源并且不可用于商业目的。
自定义CSS

开发者设置-自定义CSS

自定义github badge样式

用于博客底部左右侧信息

/* 自定义github badge样式 */

.github-badge {
  display: inline-block;
  border-radius: 4px;
  text-shadow: none;
  font-size: 12px;
  color: #fff;
  line-height: 14px;
  background-color: #abbac3;
  margin-bottom: 10px
}
.github-badge .badge-subject {
  display: inline-block;
  background-color: #4d4d4d;
  padding: 6px 6px 6px 6px;
  border-top-left-radius: 4px;
  border-bottom-left-radius: 4px
}

.github-badge .badge-value {
  display: inline-block;
  padding: 6px 6px 6px 6px;
  border-top-right-radius: 4px;
  border-bottom-right-radius: 4px
}

.github-badge .bg-blue {
  background-color: #007ec6
}

.github-badge .bg-orange {
  background-color: #ffa500
}

.github-badge .bg-red {
  background-color: #f00
}

.github-badge .bg-green {
  background-color: #3bca6e
}

.github-badge .bg-purple {
  background-color: #ab34e9
}
首页文章版式圆角化
/* 首页文章版式圆角化 */
.panel{
    border: none;
    border-radius: 15px;
}

.panel-small{
    border: none;
    border-radius: 15px;
}

.item-thumb{
    border-radius: 15px;  
}
首页文章图片获取焦点放大
/* 首页文章图片获取焦点放大 */
.item-thumb{
    cursor: pointer;  
    transition: all 0.6s;  
}

.item-thumb:hover{
      transform: scale(1.05);  
}

.item-thumb-small{
    cursor: pointer;  
    transition: all 0.6s;
}

.item-thumb-small:hover{
    transform: scale(1.05);
}
首页头像自动旋转
/* 首页头像自动旋转 */
.thumb-lg{
    width:130px;
}

.avatar{
    -webkit-transition: 0.4s;
    -webkit-transition: -webkit-transform 0.4s ease-out;
    transition: transform 0.4s ease-out;
    -moz-transition: -moz-transform 0.4s ease-out; 
}

.avatar:hover{
    transform: rotateZ(360deg);
    -webkit-transform: rotateZ(360deg);
    -moz-transform: rotateZ(360deg);
}

#aside-user span.avatar{
    animation-timing-function:cubic-bezier(0,0,.07,1)!important;
    border:0 solid
}

#aside-user span.avatar:hover{
    transform:rotate(360deg) scale(1.2);
    border-width:5px;
    animation:avatar .5s
}
文章标题居中
/* 文章标题居中 */
.panel h2{
    text-align: center; 
}
.post-item-foot-icon{
    text-align: center;
}
panel阴影&盒子模型边缘阴影
/* panel阴影, 阴影颜色修改rgba后面的值 */
.panel{
   box-shadow: 1px 1px 5px 5px rgba(255, 112, 173, 0.35);
    -moz-box-shadow: 1px 1px 5px 5px rgba(255, 112, 173, 0.35);
}

.panel:hover{
    box-shadow: 1px 1px 5px 5px rgba(255, 112, 173, 0.35);
    -moz-box-shadow: 1px 1px 5px 5px rgba(255, 112, 173, 0.35);
}

.panel-small{
    box-shadow: 1px 1px 5px 5px rgba(255, 112, 173, 0.35);
    -moz-box-shadow: 1px 1px 5px 5px rgba(255, 112, 173, 0.35);
}

.panel-small:hover{
    box-shadow: 1px 1px 5px 5px rgba(255, 112, 173, 0.35);
    -moz-box-shadow: 1px 1px 5px 5px rgba(255, 112, 173, 0.35);
}

/* 如果也想使盒子四周也有阴影,加上以下代码 */
.app.container {
    box-shadow: 0 0 30px rgba(255, 112, 173, 0.35);
}
博客底部左侧添加ICP备案号

开发者设置-博客底部左侧信息

<div class="github-badge">
  <a href="https://beian.miit.gov.cn" target="_blank" title="备案号">
    <span class="badge-subject">粤ICP备</span><span class="badge-value bg-green">2022020467号-1</span>
  </a>
</div>
博客底部右侧添加版权信息

开发者设置-博客底部右侧信息

<div class="github-badge">
<a href="https://www.typecho.org" target="_blank" title="由 Typecho 强力驱动">
<span class="badge-subject">Powered by</span><span class="badge-value bg-blue">Typecho</span>
</a>
</div>
 |  
<div class="github-badge">
<a href="https://www.ihewro.com/archives/489/" target="_blank" title="站点使用 handsome 主题,作者:友人C">
<span class="badge-subject">Theme</span><span class="badge-value bg-green">Handsome</span>
</a>
</div>

这样会和原来主题的底部信息重复,修改 usr/uploads/themes/handsome/component/footer.php

<footer id="footer" class="app-footer" role="footer">
    <div class="padder-sm bg-white footer_wrapper box-shadow-wrap-normal b-normal">
        <div class="pull-right hidden-xs text-ellipsis">
             <?php $this->options->BottomInfo();?>
            <?php /////////// 可以去除主题版权信息,最好保留版权信息或者添加主题信息到友链,谢谢你的理解/////////?>
           <!-- <a href="https://typecho.org/" style="margin-left: 5px" target="_blank">
                <svg style="width: 16px;height: 16px;vertical-align: -4px;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="30px" height="26px" viewBox="0 0 30 26" version="1.1">
                    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">

                        <path d="M13,26 C3.36833333,26 0,22.631 0,13 C0,3.36866667 3.36833333,0 13,0 C22.6316667,0 26,3.36866667 26,13 C26,22.631 22.6316667,26 13,26 Z M6,9 L20,9 L20,7 L6,7 L6,9 Z M6,14 L16,14 L16,12 L6,12 L6,14 Z M6,19 L18,19 L18,17 L6,17 L6,19 Z" id="icon" fill="#000000" sketch:type="MSShapeGroup"/>
                    </g>
                </svg>
            </a>
            <a class="highlightlink" href="https://www.ihewro.com/archives/489/" target="_blank">
                <span>Theme by handsome</span>
            </a> -->
            <?php ////////////////////////////////////////////////////////////////// ?>
        </div>
        <span class="text-ellipsis">© <?php echo date("Y"); ?> All rights reserved.
                <?php $this->options->BottomleftInfo(); ?>
        </span>
    </div>

</footer>

特定日期全站灰色

//网站变灰
    ncov_gray();

    function ncov_gray() {
        var date = new Date();
        this.year = date.getFullYear();
        this.month = date.getMonth() + 1;
        this.date = date.getDate();

        var currentTime = this.month + "." + this.date;
        if (currentTime == "12.13") { //如果需要添加日期, 添加||条件判断即可
            $("html").css("filter", "grayscale(1)");
        }
    }

网页标题调皮化

<script>
var OriginTitile=document.title;
var st;
document.addEventListener(
    'visibilitychange',function()
    {
        if(document.hidden){
            document.title="等你回来哟(๑•̀ㅂ•́)و✧";
            clearTimeout(st);
            console.log('hide');
        }
        else{
            document.title="欢迎回来ヾ(≧▽≦*)o";
            console.log('show');
            st=setTimeout(function(){document.title=OriginTitile;},2000);
            console.log('endChange=');
        }
    }
);
</script>
特定日期全站灰色

开发者设置-自定义Javascript

//网站变灰
    ncov_gray();

    function ncov_gray() {
        var date = new Date();
        this.year = date.getFullYear();
        this.month = date.getMonth() + 1;
        this.date = date.getDate();

        var currentTime = this.month + "." + this.date;
        if (currentTime == "12.13") { //如果需要判断多个日期, 添加||判断语句即可
            $("html").css("filter", "grayscale(1)");
        }
    }
评论输入框添加打字特效

原文地址

上传js文件

添加以下内容到 usr/uploads/js/commentTyping.js

(function webpackUniversalModuleDefinition(root, factory) {
    if (typeof exports === 'object' && typeof module === 'object') module.exports = factory();
    else if (typeof define === 'function' && define.amd) define([], factory);
    else if (typeof exports === 'object') exports["POWERMODE"] = factory();
    else root["POWERMODE"] = factory()
})(this, function() {
    return (function(modules) {
        var installedModules = {};
        function __webpack_require__(moduleId) {
            if (installedModules[moduleId]) return installedModules[moduleId].exports;
            var module = installedModules[moduleId] = {
                exports: {},
                id: moduleId,
                loaded: false
            };
            modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
            module.loaded = true;
            return module.exports
        }
        __webpack_require__.m = modules;
        __webpack_require__.c = installedModules;
        __webpack_require__.p = "";
        return __webpack_require__(0)
    })([function(module, exports, __webpack_require__) {
            'use strict';
            var canvas = document.createElement('canvas');
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
            canvas.style.cssText = 'position:fixed;top:0;left:0;pointer-events:none;z-index:999999';
            window.addEventListener('resize', function() {
                canvas.width = window.innerWidth;
                canvas.height = window.innerHeight
            });
            document.body.appendChild(canvas);
            var context = canvas.getContext('2d');
            var particles = [];
            var particlePointer = 0;
            var frames = 120;
            var framesRemain = frames;
            var rendering = false;
            POWERMODE.shake = true;

            function getRandom(min, max) {
                return Math.random() * (max - min) + min
            }
            function getColor(el) {
                if (POWERMODE.colorful) {
                    var u = getRandom(0, 360);
                    return 'hsla(' + getRandom(u - 10, u + 10) + ', 100%, ' + getRandom(50, 80) + '%, ' + 1 + ')'
                } else {
                    return window.getComputedStyle(el).color
                }
            }
            function getCaret() {
                var el = document.activeElement;
                var bcr;
                if (el.tagName === 'TEXTAREA' || (el.tagName === 'INPUT' && el.getAttribute('type') === 'text')) {
                    var offset = __webpack_require__(1)(el, el.selectionStart);
                    bcr = el.getBoundingClientRect();
                    return {
                        x: offset.left + bcr.left,
                        y: offset.top + bcr.top,
                        color: getColor(el)
                    }
                }
                var selection = window.getSelection();
                if (selection.rangeCount) {
                    var range = selection.getRangeAt(0);
                    var startNode = range.startContainer;
                    if (startNode.nodeType === document.TEXT_NODE) {
                        startNode = startNode.parentNode
                    }
                    bcr = range.getBoundingClientRect();
                    return {
                        x: bcr.left,
                        y: bcr.top,
                        color: getColor(startNode)
                    }
                }
                return {
                    x: 0,
                    y: 0,
                    color: 'transparent'
                }
            }
            function createParticle(x, y, color) {
                return {
                    x: x,
                    y: y,
                    alpha: 1,
                    color: color,
                    velocity: {
                        x: -1 + Math.random() * 2,
                        y: -3.5 + Math.random() * 2
                    }
                }
            }
            function POWERMODE() {
                {
                    var caret = getCaret();
                    var numParticles = 5 + Math.round(Math.random() * 10);
                    while (numParticles--) {
                        particles[particlePointer] = createParticle(caret.x, caret.y, caret.color);
                        particlePointer = (particlePointer + 1) % 500
                    }
                    framesRemain = frames;
                    if (!rendering) {
                        requestAnimationFrame(loop)
                    }
                } {
                    if (POWERMODE.shake) {
                        var intensity = 1 + 2 * Math.random();
                        var x = intensity * (Math.random() > 0.5 ? -1 : 1);
                        var y = intensity * (Math.random() > 0.5 ? -1 : 1);
                        document.body.style.marginLeft = x + 'px';
                        document.body.style.marginTop = y + 'px';
                        setTimeout(function() {
                            document.body.style.marginLeft = '';
                            document.body.style.marginTop = ''
                        }, 75)
                    }
                }
            };
            POWERMODE.colorful = false;

            function loop() {
                if (framesRemain > 0) {
                    requestAnimationFrame(loop);
                    framesRemain--;
                    rendering = true
                } else {
                    rendering = false
                }
                context.clearRect(0, 0, canvas.width, canvas.height);
                for (var i = 0; i < particles.length; ++i) {
                    var particle = particles[i];
                    if (particle.alpha <= 0.1) continue;
                    particle.velocity.y += 0.075;
                    particle.x += particle.velocity.x;
                    particle.y += particle.velocity.y;
                    particle.alpha *= 0.96;
                    context.globalAlpha = particle.alpha;
                    context.fillStyle = particle.color;
                    context.fillRect(Math.round(particle.x - 1.5), Math.round(particle.y - 1.5), 3, 3)
                }
            }
            requestAnimationFrame(loop);
            module.exports = POWERMODE
        }, function(module, exports) {
            (function() {
                var properties = ['direction', 'boxSizing', 'width', 'height',
                    'overflowX', 'overflowY', 'borderTopWidth', 'borderRightWidth',
                    'borderBottomWidth', 'borderLeftWidth', 'borderStyle', 'paddingTop',
                    'paddingRight', 'paddingBottom', 'paddingLeft', 'fontStyle',
                    'fontVariant', 'fontWeight', 'fontStretch', 'fontSize',
                    'fontSizeAdjust', 'lineHeight', 'fontFamily', 'textAlign',
                    'textTransform', 'textIndent', 'textDecoration', 'letterSpacing',
                    'wordSpacing', 'tabSize', 'MozTabSize'];
                var isFirefox = window.mozInnerScreenX != null;

                function getCaretCoordinates(element, position, options) {
                    var debug = options && options.debug || false;
                    if (debug) {
                        var el = document.querySelector('#input-textarea-caret-position-mirror-div');
                        if (el) {
                            el.parentNode.removeChild(el)
                        }
                    }
                    var div = document.createElement('div');
                    div.id = 'input-textarea-caret-position-mirror-div';
                    document.body.appendChild(div);
                    var style = div.style;
                    var computed = window.getComputedStyle ? getComputedStyle(element) : element.currentStyle;
                    style.whiteSpace = 'pre-wrap';
                    if (element.nodeName !== 'INPUT') style.wordWrap = 'break-word';
                    style.position = 'absolute';
                    if (!debug) style.visibility = 'hidden';
                    properties.forEach(function(prop) {
                        style[prop] = computed[prop]
                    });
                    if (isFirefox) {
                        if (element.scrollHeight > parseInt(computed.height)) style.overflowY = 'scroll'
                    } else {
                        style.overflow = 'hidden'
                    }
                    div.textContent = element.value.substring(0, position);
                    if (element.nodeName === 'INPUT') div.textContent = div.textContent.replace(/\s/g, "\u00a0");
                    var span = document.createElement('span');
                    span.textContent = element.value.substring(position) || '.';
                    div.appendChild(span);
                    var coordinates = {
                        top: span.offsetTop + parseInt(computed['borderTopWidth']),
                        left: span.offsetLeft + parseInt(computed['borderLeftWidth'])
                    };
                    if (debug) {
                        span.style.backgroundColor = '#aaa'
                    } else {
                        document.body.removeChild(div)
                    }
                    return coordinates
                }
                if (typeof module != "undefined" && typeof module.exports != "undefined") {
                    module.exports = getCaretCoordinates
                } else {
                    window.getCaretCoordinates = getCaretCoordinates
                }
            }())
        }
    ])
});
POWERMODE.colorful = true;  // make power mode colorful
POWERMODE.shake = false;    // turn off shake
document.body.addEventListener('input', POWERMODE);
修改主题设置

然后在 开发者设置-自定义输出body尾部的HTML代码添加以下内容

<script type="text/javascript" src="/usr/uploads/js/commentTyping.js"></script>
最后修改:2024 年 01 月 05 日
个人分享,随意打赏