独书先生 Menu

git commit规范

依赖安装

1. 生成commit信息

npm install --save-dev commitizen

npx commitizen init cz-conventional-changelog --save-dev --save-exact

package.json

"scripts": {
   "commit": "git-cz"
}

2. commit校验

npm install --save-dev @commitlint/config-conventional @commitlint/cli

echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
修改文件commitlint.config.js为正常格式

3. 更新版本、生成changelog、打tag

npm i --save-dev standard-version

package.json

 "scripts": {
    "release": "standard-version"
  }

commit使用步骤

提交代码

git pull
git add .
npm run commit
npm run release -- --prerelease
git push --follow-tags origin master

commit规范

npm run commit后根据交互提示编写
1. type

type为必填项,用于指定commit的类型
# 主要type
feat:     增加新功能
fix:      修复bug

# 特殊type
docs:     只改动了文档相关的内容
style:    不影响代码含义的改动,例如去掉空格、改变缩进、增删分号
build:    构造工具的或者外部依赖的改动,例如webpack,npm
refactor: 代码重构时使用
revert:   执行git revert打印的message

# 不常使用type
test:     添加测试或者修改现有测试
perf:     提高性能的改动
ci:       与CI(持续集成服务)有关的改动
chore:    不修改src或者test的其余修改,例如构建过程或辅助工具的变动
复制代码当一次改动包括主要type与特殊type时,统一采用主要type。
  1. scope

scope也为必填项,用于描述改动的范围,例如在业务项目中可以依据菜单或者功能模块划分,如果是组件库开发,则可以依据组件划分

  1. subject

subject是commit的简短描述

  1. body

commit的详细描述,说明代码提交的详细说明。主要描述改动之前的情况及修改动机,对于小的修改不作要求,但是重大需求、更新等必须添加body来作说明。

  1. break changes

break changes指明是否产生了破坏性修改,涉及break changes的改动必须指明该项,类似版本升级、接口参数减少、接口删除、迁移等。

  1. affect issues

affect issues指明是否影响了某个问题。格式为: fix #{issue_id}
例如:

re #2
fix #14

发布版本

在多次commit之后,可能需要发布一个新版本,以下命令会自动更新版本号

npm run release -- --prerelease
git push --follow-tags origin master 

详细使用:https://github.com/conventional-changelog/standard-version

参考

  • https://juejin.im/post/5cea2c4bf265da1b6836993f
  • https://juejin.im/post/5cc4694a6fb9a03238106eb9
  • https://juejin.im/post/5d0b3f8c6fb9a07ec07fc5d0

自建node图片压缩服务

需求

图片压缩服务很常见,比如 tinypng.com,压缩图等,但是如何打造自己的图片压缩服务?

思路

首先想到使用tinypng.com提供的API,但是也是有限制或者购买服务,不符合我们自建服务的需要,我们需要完全开源的自有服务,于是了解到了imagemin,有node版本

解决方案

采用koa后台框架搭建node服务

  1. 先安装依赖
npm i imagemin imagemin-giflossy imagemin-mozjpeg imagemin-pngquant imagemin-svgo imagemin-webp
  1. 编写koa基础服务,参考https://www.liaoxuefeng.com/wiki/1022910821149312/1099849448318080

  2. 编写imagemin服务

// 安装依赖:npm i imagemin imagemin-giflossy imagemin-mozjpeg imagemin-pngquant imagemin-svgo imagemin-webp

const fs = require('fs');
const path = require('path');
const imagemin = require('imagemin');
const imageminMozjpeg = require('imagemin-mozjpeg');
const imageminPngquant = require('imagemin-pngquant');
const imageminGiflossy = require('imagemin-giflossy');
const imageminWebp = require('imagemin-webp');
const imageminSvgo = require('imagemin-svgo');

//stream pipeline
const util = require('util');
const stream = require('stream');

const imgMIMEList = {
    svg: 'image/svg+xml',
    png: 'image/png',
    jpg: 'image/jpeg',
    jpeg: 'image/jpeg',
    gif: 'image/gif',
    bmp: 'image/bmp',
    webp: 'image/webp',
    ico: 'image/x-icon',
    tif: 'image/tiff',
    psd: 'application/octet-stream',
}

//TODO:提示压缩率

const fn_imagemin = async (ctx, next) => {

    const file = ctx.request.files.file;    // 获取上传文件
    const reader = fs.createReadStream(file.path);  // 创建可读流
    const ext = file.name.split('.').pop();     // 获取上传文件扩展名
    const dataMIME = "data:" + imgMIMEList[ext] + ";base64,"; //根据扩展名设定媒体类型

    const imgName = getRandomId() + '.' + ext; //压缩图片文件名
    const imgSourcePath = `storage/upload/img`; //压缩源图片文件夹
    const imgResultPath = `storage/result/img`; //压缩目标文件夹

    const imgSourcePathFile = `${imgSourcePath}/${imgName}`; //压缩源图片路径
    const imgSourcePathFileCWD = path.resolve(process.cwd(), imgSourcePathFile); //压缩源图片绝对路径

    const imgSourcePathCWD = path.resolve(process.cwd(), imgSourcePath); //源文件夹绝对路径
    const imgResultPathCWD = path.resolve(process.cwd(), imgResultPath); //目标文件夹绝对路径

    const upStream = fs.createWriteStream(imgSourcePathFileCWD); // 创建可写流 存储源图片
    // reader.pipe(upStream);// 可读流通过管道写入可写流
    const pipeline = util.promisify(stream.pipeline);
    let resultData = null;
    await pipeline(reader, upStream);
    await imageCompress(imgSourcePathFile, imgResultPath, (imgBuffer) => {

        resultData = dataMIME + imgBuffer.toString('base64');

    }).catch((err) => {
        ctx.response.status = 201
    })

    //删除图片
    setTimeout(()=>{
        deleteFile(imgSourcePathCWD,imgName);
        deleteFile(imgResultPathCWD,imgName);
    },0)

    if (file) {
        ctx.response.body = resultData;
    }
    else {
        ctx.response.status = 204;
    }

};

/**
 * 压缩图片
 * @param {String} sourcePath 
 * @param {String} resultPath 
 * @param {Function} callback 
 */
async function imageCompress(sourcePath, resultPath, callback) {
    const files = await imagemin([sourcePath], {
        destination: resultPath,
        plugins: [
            imageminMozjpeg({
                quality: 70
            }),
            imageminPngquant(),
            imageminGiflossy({
                lossy: 80
            }),
            imageminWebp(),
            imageminSvgo()
        ]
    });

    const imgBuffer = files[0].data;
    callback(imgBuffer)
}

/**
 * 获取随机id
 */
function getRandomId() {
    return Math.random().toString(36).substr(2) + Date.now().toString(36);
}


/**
 * 删除某一个包下面的需要符合格式的文件。
 * @param  {String} url  文件路径,绝对路径
 * @param  {String} name 需要删除的文件名称
 * @return {Null}   
 * @author huangh 20170123
 */
function deleteFile(url,name){
    let files = [];

    if( fs.existsSync(url) ) {    //判断给定的路径是否存在

        files = fs.readdirSync(url);    //返回文件和子目录的数组

        files.forEach(function(file,index){

            let curPath = path.join(url,file);

            if(fs.statSync(curPath).isDirectory()) { //同步读取文件夹文件,如果是文件夹,则函数回调
                deleteFile(curPath,name);
            } else {   

                if(file.indexOf(name)>-1){    //是指定文件,则删除
                    fs.unlinkSync(curPath);
                    console.log("删除文件:"+curPath);
                }
            }  
        });
    }else{
        console.log("给定的路径不存在!");
    }

}



module.exports = {
    'POST /upload/img': fn_imagemin
};

注意

本教程在于提供可行思路及核心代码,完整项目程序暂未整理.因为笔者在编写过程中发现,本地windows环境没有问题,但是docker环境下无法正常运行,所以整体的node服务仍然处于研究阶段,希望有兴趣的朋友实验成功后分享出来.

参考

https://github.com/imagemin/imagemin
https://web.dev/use-imagemin-to-compress-images/
https://www.liaoxuefeng.com/wiki/1022910821149312/1099849448318080

如何清除微信网页缓存nginx配置 | 终极解决方案亲测有效

问题

微信为了提高自身的性能,对微信网页进行了缓存,且普通方法无法及时清除,项目更新后,微信无法及时更新网页内容

解决方案:

更改服务器配置,强制不缓存入口文件,通常为网站首页(index.html),其他静态正常缓存,再针对首页中引入的资源文件(js/css)采用修改文件名及文件引入链接强制获取新的文件避免缓存。

操作步骤:

  1. 在nginx中修改配置文件(nginx.conf),在所有的网站首页匹配的location /下增加#### kill cache的部分,如下:
location / {
    root   /mnt/dat1/test/tes-app;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
    #### kill cache
    add_header Last-Modified $date_gmt;
    add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
    if_modified_since off;
    expires off;
    etag off;
}
  1. 修改index.html中的资源引入链接,分两种情况:
    • 一是前端框架已经做好了打包后的文件名自动携带hash值,且引入链接也自动修改了,这种情况无需手动再修改文件名
    • 二是部分系统采用的原始引入方式开发,没有自动打包系统的,需要自己手动更改文件名和引入链接,如下:
<script type="text/javascript" src="js/login.js" ></script>

更改为

<script type="text/javascript" src="js/login_01.js" ></script>

且login.js重命名为login_01.js即可

html解析<号>号出现字符实体< &gt: &如何转义

问题:

在用wordpress的markdown插件写文章的时候,发现代码块偶尔无法正常解析 <>

类似这样:

暴雨直下眼迷茫,
大风骤起飕脸庞。
雷声贯耳家中藏,
草香扑鼻思故乡。
&lt;/pre&gt;&lt;/div&gt;

分析:

说明在解析文章的时候,得到的字符串为字符实体,然后网上找了通过字符串替换的方法解决这个问题

以下为相互转换的js函数

//<转义为<
function escapeHTML(str){
    str = "" + str;
    return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "&apos;");
},
//<还原为<
function unescapeHTML(str){
    str = "" + str;
    return str.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"').replace(/&apos;/g, "'");
}

解决:

运用到wordpress博客中,可以在编辑主题中,修改function.php源码,加入一段插入页脚的方法,塞入你的js代码即可,以下为新加入的页脚代码,加在function.php的头部即可,后续有新的js代码也可以写在这里:

// 自定义脚本 ------------------------------------------start
function wpb_hook_javascript_footer() {
?>
<script>
function unescapeHTML(a){
    a = "" + a;
    return a.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"').replace(/&apos;/g, "'");
}
var codeContainers = document.querySelectorAll(".codecolorer-container");
if(codeContainers.length > 0){
    for(var i = 0; i < codeContainers.length; i++){
        codeContainers[i].innerText = unescapeHTML(codeContainers[i].innerText)
    }
}
</script>
<?php
}
add_action('wp_footer', 'wpb_hook_javascript_footer');
// 自定义脚本 ------------------------------------------end

感谢:
https://www.cnblogs.com/LiuLiangXuan/p/5212155.html
https://boke112.com/post/5981.html

诗歌诗句垂直排版css

需求:

上一篇小编写了首打油诗 暴风雨 ,很多小伙伴咨询如何排版的,其实很简单运用了一个css属性 writing-mode,这个属性在国际化中经常用到,因为有的海外国家的阅读和书写习惯跟我们不一样,嗯,跟我们古文的排版有点像,也算是巧合了,有了这个属性可以运用.

Continue reading…

Linux下运行shell脚本显示cd: $’ \r\r’: No such file or directory没有那个文件或目录

问题:

本地写好一个 shell 脚本,传到服务器, 用 ./ 运行脚本文件出现 报错信息 cd: \$’ \r\r’: No such file or directory 或者是 /usr/bin/env: “bash\r”: 提示没有那个文件或目录

分析:

linux 环境下, \r 表示把光标移到行首,不显示任何内容. 猜测是自己写的脚本的格式不正确, 导致脚本在 linux 环境下解析出含有\r

解决办法:

用 vim 打开.sh 脚本文件, 重新设置文件的格式

:set ff 然后回车   再重新设置下文件格式:

:set ff=unix 然后保存退出

:wq! 回车
参考自: https://appsoftea.com/zh/linux-bash-no-such-file-or-directory/

gnvm切换node版本

问题:

npm运行项目时,报错:

Found binding for the following environments:
- Windows 64-bit with Node.js 11.x

发现此项目的npm包需要11.x版本node,那么为了不影响其他项目,可以采取管理多个node版本的方法,跟多版本python类似

Continue reading…

js实现指定时间倒计时,带标题闪烁提示

问题

  1. 经常需要设定一个活动倒计时提醒,指定一个时间进行倒计时
  2. 或者是自己做时间规划,做计时功能指定到几点完成任务,这时候还需要在网页标签上有提醒

解决

主要是时间的计算

代码如下

<!DOCTYPE html>
<html lang="en">

<head>
      
    <meta charset="UTF-8">
      <title>倒计时</title>
      <style>
        * {
            margin: 0;
            padding: 0;
        }

        div,p {
            font-size: 80px;
            text-align: center;
        }

        #showTime span {
            color: red;
        }

        #showTime span.time {
            color: black
        }

        body {
            padding-top: 200px;
        }
    </style>
</head>

<body>
    <div>距离 <div id="timeSpan"></div> 还有</div>
    <p id="showTime"><span></span></p>
    <script>
        var timeSet = '2020/4/13 00:10:00';
        document.querySelector('#timeSpan').innerHTML = timeSet;
        var msg = {
            time: 0,
            title: document.title,
            timer: null,
            //显示新消息提示
            show: function () {
                var title = msg.title.replace("", "").replace("【时间到啦】", "");
                //定时器,此处产生闪烁
                //由于定时器无法清除,在此调用之前先主动清除一下定时器打到缓冲效果,否则定时器效果叠加标题闪烁频率越来越快
                clearTimeout(msg.timer);
                msg.timer = setTimeout(function () {
                    msg.time++;
                    msg.show();
                    if (msg.time % 2 == 0) {
                        document.title = "【时间到啦】" + title
                    } else {
                        document.title = title
                    };
                }, 300);
                return [msg.timer, msg.title];
            },
            //取消新消息提示
            //此处起名最好不要用clear,由于关键字问题有时可能会无效
            clears: function () {
                clearTimeout(msg.timer);
                document.title = "哈哈哈";
            }
        };
        var oSpan = document.getElementsByTagName('span')[0];
        function tow(n) {
            return n >= 0 && n < 10 ? '0' + n : '' + n;
        }
        function getDate() {
            var oDate = new Date();//获取日期对象
            var oldTime = oDate.getTime();//现在距离1970年的毫秒数
            var newDate = new Date(timeSet);
            var newTime = newDate.getTime();//2019年距离1970年的毫秒数
            var second = Math.floor((newTime - oldTime) / 1000);//未来时间距离现在的秒数
            var day = Math.floor(second / 86400);//整数部分代表的是天;一天有24*60*60=86400秒 ;
            second = second % 86400;//余数代表剩下的秒数;
            var hour = Math.floor(second / 3600);//整数部分代表小时;
            second %= 3600; //余数代表 剩下的秒数;
            var minute = Math.floor(second / 60);
            second %= 60;
            var str =
                tow(day) + '<span class="time">天</span>'
                + tow(hour) + '<span class="time">小时</span>'
                + tow(minute) + '<span class="time">分钟</span>'
                + tow(second) + '<span class="time">秒</span>';
            oSpan.innerHTML = str;
            if (tow(minute) == '00' && tow(second) == '00') {
                msg.show()
                alert('时间到啦!')
            }
        }
        getDate();
        setInterval(getDate, 1000);
    </script>
</body>

</html>

网页内拖动iframe避坑方法

问题:

网页内拖动一个元素比较简单,但是如果是一个iframe,那么鼠标进入iframe内部后会失焦,因为是进入了另一个网页.失焦的结果就是拖动起来一闪一闪的.

解决:

这里采用了一个巧妙的遮罩来避免鼠标进入iframe内失焦问题,iframe用div包裹,iframe同级建一个空白div,初始时宽高为0,一旦mousedown则把宽高置为100%沾满父级的div遮住同级的iframe,这样鼠标拖动的时候就不会触发到iframe内部了, mouseup时再把div置为0,保持iframe内的正常监听.

代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>drag</title>
    <style>
        *{
            padding: 0;
            margin: 0;
        }
        body{
            background-color: #929292;
        }
        .move {
            position: absolute;
            /*设置绝对定位,脱离文档流,便于拖拽*/
            display: block;
            width: 320px;
            height: 486px;
            background-color: transparent;
            cursor: move;
            /*鼠标呈拖拽状*/
            border:10px solid transparent;
        }
        iframe{
            position: absolute;
            width: 100%;
            height: 100%;
        }
        .mask{
            position: absolute;
            cursor: move;
            user-select: none;
        }
    </style>
</head>

<body>
    <div id="drag" class="move">
        <iframe  src="https://m.baidu.com" frameborder="no" border="0" marginwidth="0" marginheight="0" scrolling="no" allowtransparency="yes">可拖动区域</iframe>
        <div class="mask"></div>
    </div>

    <script>
        // 拖拽功能(主要是触发三个事件:onmousedown\onmousemove\onmouseup)
        var drag = document.getElementById('drag');
        var divMask = document.querySelector('.mask');

        // 点击某物体时,用drag对象即可,move和up是全局区域,也就是整个文档通用,应该使用document对象而不是drag对象(否则,采用drag对象时物体只能往右方或下方移动)
        drag.onmousedown = function (e) {
            var e = e || window.event // 兼容ie浏览器
            var diffX = e.clientX - drag.offsetLeft // 鼠标点击物体那一刻相对于物体左侧边框的距离=点击时的位置相对于浏览器最左边的距离-物体左边框相对于浏览器最左边的距离
            var diffY = e.clientY - drag.offsetTop

            /* 低版本ie bug:物体被拖出浏览器可是窗口外部时,还会出现滚动条,
                    解决方法是采用ie浏览器独有的2个方法setCapture()\releaseCapture(),这两个方法,
                    可以让鼠标滑动到浏览器外部也可以捕获到事件,而我们的bug就是当鼠标移出浏览器的时候,
                    限制超过的功能就失效了。用这个方法,即可解决这个问题。注:这两个方法用于onmousedown和onmouseup中 */
            if (typeof drag.setCapture !== 'undefined') {
                drag.setCapture()
            }

            divMask.style.width = '100%';
            divMask.style.height = '100%';
            document.onmousemove = function (e) {
                var e = e || window.event // 兼容ie浏览器
                var left = e.clientX - diffX
                var top = e.clientY - diffY

                // 控制拖拽物体的范围只能在浏览器视窗内,不允许出现滚动条
                if (left < 0) {
                    left = 0
                } else if (left > window.innerWidth - drag.offsetWidth) {
                    left = window.innerWidth - drag.offsetWidth
                }
                if (top < 0) {
                    top = 0
                } else if (top > window.innerHeight - drag.offsetHeight) {
                    top = window.innerHeight - drag.offsetHeight
                }

                // 移动时重新得到物体的距离,解决拖动时出现晃动的现象
                drag.style.left = left + 'px'
                drag.style.top = top + 'px'
            }
            document.onmouseup = function (e) { // 当鼠标弹起来的时候不再移动
                console.log('this', this)
                this.onmousemove = null
                this.onmouseup = null // 预防鼠标弹起来后还会循环(即预防鼠标放上去的时候还会移动)

                // 修复低版本ie bug
                if (typeof drag.releaseCapture !== 'undefined') {
                    drag.releaseCapture()
                }

                divMask.style.width = '0';
                divMask.style.height = '0';
            }
        }
    </script>
</body>

</html>

网页内拖动iframe避坑方法

Xshell、Xftp 5、6 解决“要继续使用此程序,您必须应用最新的更新或使用新版本”

问题:

打开Xshell的时候,报 “要继续使用此程序,您必须应用最新的更新或使用新版本”,如何不更新软件继续使用

解决:

  1. 找到软件安装目录下的 nslicense.dll ,比如我的在: C:\Program Files (x86)\NetSarang\Xshell 6 文件夹下
  2. 复制到自定义的文件夹, 用文本编辑软件打开(sublime / Nodepad++ / VSCode等)
  3. 搜索: 7F0C 81F9 8033 E101,将后面的 0F86 , 改成 0F83,保存文件
  4. 再把修改后的 nslicense.dll 文件粘贴回源目录覆盖即可(提示需要管理员点继续即可)
  5. 针对Xftp也是相同的操作来一遍 C:\Program Files (x86)\NetSarang\Xftp 6

最后推荐下小编现在这个网站用的服务器,是BlueHost的虚拟主机服务,支持一键快速搭建wordpress博客网站,免备案,全球CDN,通过我这个下方的链接注册还有优惠,购买后截图发给微信 liurunze— 可以返现30元现金。 免备案的BlueHost主机

感谢:
https://appsoftea.com/zh/xshell-xftp-donot-update
https://www.cnblogs.com/JasonCeng/p/11673999.html