0%

速览

  • 项目地址:mai_ke/tools

  • 核心依赖:npm i esbuild pkg

    • esbuild:将源码及依赖打包成单一 JS 文件
    • pkg:将 JS 文件打包为可执行程序(如 .exe
  • package.json 脚本配置:

    1
    2
    3
    4
    5
    6
    {
    "scripts": {
    "start": "node src/index.js",
    "build": "esbuild src/index.js --bundle --platform=node --outfile=dist/app.js && pkg dist/app.js --targets node16-win-x64 --output dist/app.exe && echo dist/app.exe"
    }
    }

技术要点拆解

一、打包策略

  • 使用 pkg 将 Node.js 项目打包为 Windows 可执行程序;
  • 对于 import 语法支持有限,推荐先用 esbuild 打包成单一 CommonJS 文件;
  • pkg 无法打包动态 require(),必须预处理;
  • 示例命令:
    1
    pkg src/index.js -t node16-win-x64 -o dist/app.exe

二、ES6 模块兼容处理

  • Webpack 配置复杂,未采用;

  • 使用 esbuild 简单快速,推荐用法:

    1
    esbuild src/index.js --bundle --platform=node --outfile=dist/app.js
    • --bundle:将依赖打入同一文件;
    • --platform=node:打包为 Node 环境可执行;
    • --outfile:输出文件路径。

三、视频下载与进度条展示

文件下载核心逻辑位于 utils/download_file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
export async function download_file(url, output_path) {
try {
if (process.platform === 'win32') {
output_path = output_path.replace(/\//g, '\\')
}
fs.access(output_path)
console.log(`文件已存在,跳过下载: ${output_path}`)
return
} catch {
// 文件不存在,继续下载
}

const outputDir = path.dirname(output_path)
await fs.promises.mkdir(outputDir, { recursive: true })

console.log(`正在下载: "${output_path}",按 Ctrl+C 可中断`)

const { data, headers } = await axios({
url,
method: 'GET',
responseType: 'stream'
})

const totalLength = parseInt(headers['content-length'], 10)
console.log(`文件大小: ${(totalLength / 1024 / 1024).toFixed(2)} MB`)

const progressBar = new ProgressBar('下载进度 [:bar] :percent :etas', {
width: 40,
complete: '=',
incomplete: ' ',
total: totalLength
})

const writer = createWriteStream(output_path)
data.on('data', chunk => progressBar.tick(chunk.length))
data.pipe(writer)

return new Promise((resolve, reject) => {
writer.on('finish', resolve)
writer.on('error', reject)
})
}

四、Axios 配置 query 参数

通过拦截器自动将 query 字段序列化并拼接到 URL 上:

1
2
3
4
5
6
7
8
9
10
axios.interceptors.request.use(
(config) => {
if (config.query) {
const params = new URLSearchParams(config.query).toString()
config.url += (config.url.includes('?') ? '&' : '?') + params
}
return config
},
(error) => Promise.reject(error)
)

五、文件名合法化处理

防止文件名中包含非法字符导致保存失败:

1
2
3
4
export function format_file_name(name) {
const invalidChars = /[<>:"/\\|?*\x00-\x1F]/g
return name.replace(invalidChars, '_')
}

六、数据展示与调试

通过子进程方式将数据输出至 VS Code 编辑器中:

1
2
3
4
5
6
7
8
9
10
11
12
export const open_vscode_with_data = (data) => {
data = try_parse_json(data)
if (typeof data !== 'string') {
data = JSON.stringify(data, null, 2)
}

const child = spawn('code', ['-'], {
stdio: ['pipe', 'inherit', 'inherit']
})
child.stdin.write(data)
child.stdin.end()
}

后续功能展望

✅ 数据展示优化

  • 根据内容长度自动切换输出方式(控制台或编辑器);
  • 自动检测系统中是否安装 VS Code / Vim / Sublime,并优先选择可用编辑器;

✅ 下载逻辑增强

  • 支持并发下载;
  • 添加下载重试、断点续传机制;
  • UI 层支持总进度条 + 单任务进度条;

如有建议或使用问题,欢迎留言交流!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
;  搜狗输入法--自定义短语配置文件

; 自定义短语说明:
; 1、自定义短语支持多行、空格、指定位置。
; 2、每条自定义短语最多支持30000个汉字,总共支持100000条自定义短语。
; 3、自定义短语的格式如下:

; 单行的格式:
; 字符串+英文逗号+数字(指定排序位置)=短语

; 多行的格式:
; 字符串+英文逗号+数字(指定排序位置)=
; 多行短语

; 具体格式可以参考下面的实例。
; 4、最多支持100000行自定义短语。
; 5、自定义短语的用途有:快捷输入手机号、邮箱、诗词、小短文等,大家可以自由发挥。
; 6、时间函数功能。具体定义格式如下:; 字符串+英文逗号+数字(指定排序位置)=#表达式
; 注意:表达式以英文#开头,后面的表达式中的每一个函数的前面都包含有英文$。
; 函数表如下:
; 函数 含义 举例
; $year 年(4位) 2006、2008
; $year_yy 年(2位) 06、08
; $month 月 12、8、3
; $month_mm 月 12、08、03 //此函数在输入法3.1版之后(含)有效
; $day 日 3、13、22
; $day_dd 日 03、13、22 //此函数在输入法3.1版之后(含)有效
; $weekday 星期 0、1、2、5、6
; $fullhour 时(24小时制) 2、8、13、23
; $fullhour_hh 时(24小时制) 02、08、13、23 //此函数在输入法3.1版之后(含)有效
; $halfhour 时(12小时制) 2、8、10、11
; $halfhour_hh 时(12小时制) 02、08、10、11 //此函数在输入法3.1版之后(含)有效
; $ampm AM、PM(英) AM、PM(大写)
; $minute 分 02、08、15、28
; $second 秒 02、08、15、28
; $year_cn 年(中文4位) 二〇〇六
; $year_yy_cn 年(中文2位) 〇六
; $month_cn 月(中文) 十二、八、三
; $day_cn 日(中文) 三、十三、二十二
; $weekday_cn 星期(中文) 日、一、二、五、六
; $fullhour_cn 时(中文24时制) 二、八、十三、二十三
; $halfhour_cn 时(中文12时制) 二、八、一、十一
; $ampm_cn 上午下午(中文) 上午、下午
; $minute_cn 分(中文) 零二、零八、十五、二十八
; $second_cn 秒(中文) 零二、零八、十五、二十八
; 具体你可以参考这个文件最下面的例子,实际体验一下就明白了。
; 你可以用自定义短语来做一个带动态时间的多行回信落款。
; ss,1=#$year年$month月$day_dd日 $fullhour:$minute:$second

from,3=from zhiyuan_yuan@qq.com at #$year-$month_mm-$day_dd $fullhour:$minute:$second
ts,3=#$year$month_mm$day_dd$fullhour$minute$second

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

vite.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
// 使用 ele plus 的自动导入
AutoImport({
resolvers: [ElementPlusResolver()]
}),
// 使用 ele plus 的自动导入
Components({
resolvers: [ElementPlusResolver()]
})
],
resolve: {
// 在 import 中使用 @ 代替 src
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
// 使 import @/button 的时候导入 @/button/index.vue
extensions: ['.vue', '.js', '/index.js', '/index.vue']
},
// 允许内网访问
server: { host: true }
})

jsconfig.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"compilerOptions": {
"remark": "使vscode识别 import 中的 @",
"paths": {
"@/*": [
"./src/*"
]
}
},
"exclude": [
"node_modules",
"dist"
],
}

参考文档

基本配置

1
2
3
4
5
import 'moment/dist/locale/zh-cn'
moment.locale('zh-cn')

app.config.globalProperties.$moment = moment
moment.defaultFormat = 'YYYY-MM-DD HH:mm:ss'

特殊序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const hours = moment(dt).hours()

// 判断时间段(早、中、晚)
let timePeriod = ''
if (hours < 12) {
timePeriod = '早' // 0:00 ~ 11:59 为早
} else if (hours < 14) {
timePeriod = '中午' // 12:00 ~ 13:59 为中午
} else if (hours < 18) {
timePeriod = '下午' // 14:00 ~ 17:59 为下午
} else {
timePeriod = '晚' // 18:00 ~ 23:59 为晚
}

// 格式化日期为 "MM月DD日早/中午/下午/晚:HH:mm"
return moment(dt).format(`MM月DD日${timePeriod}:HH:mm`)

背景知识

探索

cdn 网站的选择

  • 我习惯使用 unpkg, 因为其跟 npm 绑定, 软件包发布到 npm 上, 就可以使用该cdn

使用方式, 使用 moment 作为例子

  1. npm 网站中搜索 moment
  2. 点开 code 标签
  3. 观察目录结构, 直接目录下没有 min.js 结尾文件, 但是有个 min 目录
  4. 点开 min 目录, 发现 locales.min.js moment-with-locales.min.js moment.min.js
  5. 根据项目了解, 这三个文件分别对应 moment 本地化, moment 本体 + moment 本地化, moment 本体(不含本地化)
  6. 依据习惯, 使用 moment-with-locales.min.js
  7. 手动拼接目录: https://unpkg.com/moment/min/moment-with-locales.min.js 并点击访问
  8. 目录访问成功, 证明路径没有问题
  9. 将路径组装为 js 标签: <script src="https://unpkg.com/moment/min/moment-with-locales.min.js" />
  10. 将 js 标签放到 h5 中
  11. 在 h5 js 代码中使用: console.log(moment)

例外情况

  1. 大部分情况下, 使用 cdn 导入的包会生成一个与包名相同的变量, 如 moment 包, 会生成 moment 变量, 直接在 h5 代码中调用即可
  2. 然而少部分包, 由于使用量大, 其生成的变量名与包名不同, 而是使用一些符号, 例子如下:
    1. lodash => _
    2. jquery => $

如何寻找全局变量名, 以 lodash 为例

查看源码

  1. 浏览器中搜索: unpkg lodash, 点击进入网页版

    或者直接拼接链接进入网页版: https://unpkg.com/browse/lodash@4.17.21/
    最后一个 / 不能省略

  2. 依据经验, 搜索 lodash.js, 点开查看源码如下

也可能叫其他的, 如 index.js, lodash.mjs 等
也可以直接查看 lodash.min.js , 因为代码压缩过, 可读性不好, 需要复制下来通过编辑器格式化后查看
不想下载也可以试试通过浏览器的请求预览, 看是否能够格式化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
;(function() {
// ...

/** Detect free variable `global` from Node.js. */
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;

/** Detect free variable `self`. */
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;

/** Used as a reference to the global object. */
var root = freeGlobal || freeSelf || Function('return this')();
// ...
// Export lodash.
var _ = runInContext();

// Some AMD build optimizers, like r.js, check for condition patterns like:
if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
// Expose Lodash on the global object to prevent errors when Lodash is
// loaded by a script tag in the presence of an AMD loader.
// See http://requirejs.org/docs/errors.html#mismatch for more details.
// Use `_.noConflict` to remove Lodash from the global object.
root._ = _;

// Define as an anonymous module so, through path mapping, it can be
// referenced as the "underscore" module.
define(function() {
return _;
});
}
// Check for `exports` after `define` in case a build optimizer adds it.
else if (freeModule) {
// Export for Node.js.
(freeModule.exports = _)._ = _;
// Export for CommonJS support.
freeExports._ = _;
}
else {
// Export to the global object.
root._ = _;
}
}.call(this));
  1. 省略的都是定义方法的代码, 不会实际执行
  2. 依据经验, global, self 等全局变量是 import 之前时代的 打包约定, 这里忽略即可
  3. 所以 root 变量即为 this, 所以在 h5 中直接使用 _ 即可获得 lodash 对象, 并调用 debounce 等方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<!DOCTYPE html>
<html lang="zh-CN">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<!-- 引入axios -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<!-- moment -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment-with-locales.min.js"
integrity="sha512-4F1cxYdMiAW98oomSLaygEwmCnIP38pb4Kx70yQYqRwLVCs3DbRumfBq82T08g/4LJ/smbFGFpmeFlQgoDccgg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>

<body>
<div id="app">
{{ message }}
</div>

<script>
moment.locale('zh-CN')
moment.defaultFormat = 'YYYY-MM-DD HH:mm:ss'
moment.fn.toJSON = function () {
return this.format()
}

/**
* 格式化为字符串
* @returns 序列化后的字符串, 如 1.2:3:4:55, 表示 1天 2小时 3分 4秒 550 毫秒
*/
moment.duration.fn.format = function () {
let result = [
Math.floor(this.asDays())
+ '.' + Math.floor(this.hours()).toString().padStart(2, 0),
Math.floor(this.minutes()).toString().padStart(2, 0),
Math.floor(this.seconds()).toString().padStart(2, 0)
+ '.' + Math.floor(this.milliseconds())
].join(':').replace(/0+$/, '').replace(/\.0*$/, '').replace(/^0\./, '').replace(/^00:/, '').replace(/^00:/, '').replace(/^0+/, '')

return result
}
moment.duration.fn.toString = moment.duration.fn.format

Vue.prototype.$console = console
Vue.prototype.$moment = moment

var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
</script>
</body>

</html>

todo

  • [ ] 使用 unpkg 创建你的代码包( 简单, 高效, 可使用 unpkg 快速引用)
  • [ ] el-card 使用起来和 div 有些区别, 如 div 可以设置 max-height: 100%来限制高度, 但是元素放到 el-box 中就不可以使用了
  • [ ] 用于方便快捷生成 vue 代码的网页
  • [ ] 使用 docker 作为开发环境

js 写法中 .toString 和 + '' 的区别

  • 一些项目中会使用 + '' 来代替 .toString() 方法, 该写法在部分情况下会失效, 究其原因是因为 + '' 本质上是运算符, 调用的 valueOf 方法, 又因为大部分对象的 valueOf 和 toString 运算结果一致, 所以没有问题, 但是使用复杂的值类型对象, 如 moment 的时候, 就会出现问题

  • 现象: 一些项目中会使用 + '' 来代替 .toString, 因为第一种写法更简单

  • 隐患: + '' 中的 + 是运算符, 调用的是 valueOf, 而非 toString, 当一个对象的 valueOftoString 运行结果不一致的时候, 会出现错误

  • 举例:

vue 常用配置

1
2
Vue.prototype.$console = console
Vue.prototype.$moment = moment

moment

安装 moment

  • moment 分为 moment, moment-timezone 两个包, 据我习惯, 一般将两个包全部安装
  • npm 安装: npm install moment moment-timezone --save -y # npm
  • cdn 引用:
1
2
3
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment-with-locales.min.js"
integrity="sha512-4F1cxYdMiAW98oomSLaygEwmCnIP38pb4Kx70yQYqRwLVCs3DbRumfBq82T08g/4LJ/smbFGFpmeFlQgoDccgg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
moment.locale('zh-CN')
moment.defaultFormat = 'YYYY-MM-DD HH:mm:ss'
moment.fn.toJSON = function () {
return this.format()
}

/**
* 格式化为字符串
* @returns 序列化后的字符串, 如 1.2:3:4:55, 表示 1天 2小时 3分 4秒 550 毫秒
*/
moment.duration.fn.format = function () {
let result = [
Math.floor(this.asDays())
+ '.' + Math.floor(this.hours()).toString().padStart(2, 0),
Math.floor(this.minutes()).toString().padStart(2, 0),
Math.floor(this.seconds()).toString().padStart(2, 0)
+ '.' + Math.floor(this.milliseconds())
].join(':').replace(/0+$/, '').replace(/\.0*$/, '').replace(/^0\./, '').replace(/^00:/, '').replace(/^00:/, '').replace(/^0+/, '')

return result
}
moment.duration.fn.toString = moment.duration.fn.format

// vue 中使用
// Vue.prototype.$moment = moment

备份数据到sql

安装

  • apt install my-client -y

备份

  • mysqldump --host=1.1.1.1 --user=test --password=test db tb1 tb2 > db.sql

    将 1.1.1.1 服务器的 db 数据库 的 tb1, tb2 备份到 db.sql 文件, 账号密码分别为 test, test

mycli

mycli 是一个命令, 用于在 linux 中连接mysql数据库并进行操作

基础命令

  1. 使用参数连接: mycli -h 11.11.11.11 -P 3306 -u root -p password db_1
  2. 使用连接字符串连接: mycli mysql://root:password@11.11.11.11:3306/db_1
  3. 执行指定文件: mycli mysql://root:password@11.11.11.11:3306/db_1 < path/to/script.sql
  4. 执行指定文件并将日志记录到指定文件: mycli mysql://root:password@11.11.11.11:3306/db_1 -o output.txt < path/to/script.sql

    该命令并未测试是否有效

常用sql

该标题下所有sql都需要使用基础命令 连接之后使用

  1. 列出数据库: show databases
  2. 列出表: show tables
  3. 查看帮助: help

linux-运行当前目录下命令不加./

  • 将 ~/.zshrc 中增加 export PATH=$PATH:`pwd`

hexo 中增加快速创建博客命令

  1. 创建 new 文件并填入如下内容
    1
    2
    3
    #!/bin/zsh
    home=$(echo $HOME | sed 's/\//\\\//')
    code $(hexo new post "$1" | grep "Created" | cut -d' ' -f4- | sed 's/~/'"$home"'/')
  2. 运行 chmod +x new 赋予 new 执行权限
  3. 参照 linux-运行当前目录下命令不加.md 配置运行当前目录命令不加 ./
  4. 在博客目录中可直接运行 new 测试博客 新建文章

git同步状态检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/zsh

# 检测本地git状态
local_git_status=$(git status --porcelain)

# 检测本地git是否与远端git一致
remote_git_status=$(git ls-remote --exit-code origin HEAD)

if [ $? -ne 0 ]; then
echo "本地git与远端git不一致,无法继续执行脚本"
exit 1
fi

# 如果本地git状态不为空
if [ -n "$local_git_status" ]; then
echo "本地git状态不为空,无法继续执行脚本"
exit 1
fi

# 如果所有检查都通过,可以继续执行脚本
echo "所有检查通过,脚本可以继续执行"

git 中文乱码

git config --global core.quotepath false

评论功能

修改 themes/next/_config.yml 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
gitalk:
- enable: false
- github_id: # GitHub repo owner
- repo: # Repository name to store issues
- client_id: # GitHub Application Client ID
- client_secret: # GitHub Application Client Secret
- admin_user: # GitHub repo owner and collaborators, only these guys can initialize gitHub issues
- distraction_free_mode: true # Facebook-like distraction free mode
- # Gitalk's display language depends on user's browser or system environment
- # If you want everyone visiting your site to see a uniform language, you can set a force language value
- # Available values: en | es-ES | fr | ru | zh-CN | zh-TW
- language:
+ enable: true
+ github_id: nianwu # GitHub repo owner
+ repo: golb-gitalk # Repository name to store issues
+ client_id: xxxxxxxxx # GitHub Application Client ID
+ client_secret: xxxxxxxxxxxxxxxxxxxxxx # GitHub Application Client Secret
+ admin_user: nianwu # GitHub repo owner and collaborators, only these guys can initialize gitHub issues
+ distraction_free_mode: true # Facebook-like distraction free mode
+ # When the official proxy is not available, you can change it to your own proxy address
+ # proxy: https://cors-anywhere.herokuapp.com/https://github.com/login/oauth/access_token # This is official proxy adress
+ # Gitalk's display language depends on user's browser or system environment
+ # If you want everyone visiting your site to see a uniform language, you can set a force language value
+ # Available values: en | es-ES | fr | ru | zh-CN | zh-TW
+ language: zh-CN

ps: 其中的 proxy 选项是为了让国内能够使用评论功能, 但是此项目部署在 github, 所以能够看到博客的人不会需要代理, 故而此选项注释

参考文档