通过脚本自动部署Hugo博客:使用rsync高效上线(支持SSH Key与普通方式)

前言 使用 Hugo 构建静态博客后,部署步骤不应成为发布的阻碍。本文提供一个通用 deploy.sh 脚本,支持: ✅ SSH 密钥方式(推荐) ✅ SSH 密码方式(基础) ✅ 自动备份远程旧版本 ✅ 一键构建并部署 Hugo 项目 一、基础配置项 脚本开头包含以下配置项,根据你实际情况进行修改: # === 配置项 === USER="your_ssh_user" # SSH 用户名 HOST="your_server_ip" # 服务器 IP 或域名 PORT="22" # SSH 端口 REMOTE_DIR="/var/www/html" # 网站部署目录 LOCAL_DIR="public" # Hugo 输出目录 PRODUCTION_URL="https://www.yourname.com" # 你的网站正式地址 二、版本一:使用 SSH 密钥方式(推荐) 1. 生成 SSH 密钥 在本地终端执行: ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 按提示选择存储路径(默认 ~/.ssh/id_rsa),不要设置密码短语(passphrase)可实现无感连接。 2. 将公钥复制到服务器 ssh-copy-id -i ~/.ssh/id_rsa.pub user@your_server_ip 或者手动将 ~/.ssh/id_rsa.pub 内容追加到服务器的 ~/.ssh/authorized_keys。 3. 完整脚本示例(支持自动备份) #!/bin/bash # === 配置项 === USER="your_ssh_user" HOST="your_server_ip" PORT="22" REMOTE_DIR="/var/www/html" LOCAL_DIR="public" PRODUCTION_URL="https://www.yourname.com" SSH_KEY="$HOME/.ssh/id_rsa" ENABLE_BACKUP=true BACKUP_DIR="/var/www/backup_$(date +%Y%m%d_%H%M%S)" echo "📦 正在构建 Hugo 博客..." hugo || { echo "❌ Hugo 构建失败"; exit 1; } if [ "$ENABLE_BACKUP" = true ]; then echo "📁 正在备份旧版本..." ssh -p $PORT -i "$SSH_KEY" $USER@$HOST "cp -r $REMOTE_DIR $BACKUP_DIR" fi echo "🚀 开始部署..." rsync -avz -e "ssh -p $PORT -i $SSH_KEY" --delete $LOCAL_DIR/ $USER@$HOST:$REMOTE_DIR && echo "✅ 部署完成!访问:$PRODUCTION_URL" || echo "❌ 部署失败" 三、版本二:使用 SSH 密码方式(简单) 若不使用 SSH Key,可修改 rsync 命令如下: ...

2025年06月09日 · 1 分钟 · 199 字 · Silas

使用Go语言实现插件化开发:构建可扩展的高内聚低耦合系统架构

原因 近期,我正在筹备一个开源项目,该项目是一个能快速生成后台代码的工具,采用了Hertz+Gorm+Gen+Vben Admin技术栈。在开发过程中,我始终在思考如何实现框架与后续业务的分离,以使快速生成的业务代码更加清晰,且业务的修改不会对框架产生影响。这样也能更好地迭代这个开源项目,并在使用过程中不断完善。 在凝视着vscode,陷入沉思时,我突然灵光一闪,想到了插件化。目前许多工具都正在转变为插件化,它们在提供最基础的功能的同时,也允许用户以自己的方式增强这个工具。 开始 Go语言在插件化方面非常的方便,只需要将插件路由注册到主路由上就可以使用了。接下来我将实现过程记录一下: 一、在框架中加入插件功能 1、先创建一个Plugin 插件模式接口化文件 package plugin import "github.com/cloudwego/hertz/pkg/route" const ( OnlyFuncName = "Plugin" ) // Plugin 插件模式接口化 type Plugin interface { // Register 注册路由 Register(group *route.RouterGroup) // RouterPath 用户返回注册路由 RouterPath() string } 2、然后批量初始化插件路由 package initialize import ( "fmt" …… ) //插件初始化,注册上面创建的接口文件 func PluginInit(group *route.RouterGroup, Plugin ...plugin.Plugin) { for i := range Plugin { PluginGroup := group.Group(Plugin[i].RouterPath()) Plugin[i].Register(PluginGroup) } } // 安装插件 func InstallPlugin(Router *server.Hertz) { PublicGroup := Router.Group("") fmt.Println("无鉴权插件安装==》", PublicGroup) PrivateGroup := Router.Group("",middleware.JwtMiddleware.MiddlewareFunc()) fmt.Println("鉴权插件安装==》", PrivateGroup) …… } 3、在主路由上调用上面的函数 package initialize import ( "context" "github.com/cloudwego/hertz/pkg/app" "github.com/cloudwego/hertz/pkg/app/server" ) func Routers() *server.Hertz { Router := server.Default() InstallPlugin(Router) // 安装插件 //下面继续写主框架中的路由地址 …… return Router } 4、在man中调用路由 ...

2025年04月06日 · 2 分钟 · 311 字 · Silas

在H5和UniApp项目中实现微信扫码登录的完整解决方案

前言 在一个项目中需要增加“微信扫码登录”,以前没有弄过这个功能,然后网上的各种资源都非常的乱,导致走了很多的弯路,现在将可行方案记录一下。 相关的文档地址:微信登录功能 / 网站应用微信登录开发指南 环境 前端: 使用uniapp-vue框架,是做的H5和小程序多端开发,这里只是说的H5部分。 后端: go 开始 先明白流程是什么样的: 微信扫码相关设置 申请“微信开发者平台”的AppID和AppSecret 在“微信开发者平台”–>“管理中心”–>“网站应用”中配置回调地址,回调地址只需要配置域名就可以,不需要http或者https开头!!,例如:www.abc.com。 获取扫码二维码 在需要显示二维码的页面直接执行下面代码: function setWxerwma() { const s = document.createElement('script') s.type = 'text/javascript' s.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js' const wxElement = document.body.appendChild(s) wxElement.onload = function () { const obj = new WxLogin({ self_redirect: false, id: 'weixinLogin', // 需要显示的容器id appid: 'xxxxxx', // 微信开放平台appid wx******* scope: 'snsapi_login', // 网页默认即可 redirect_uri: encodeURIComponent('xxxxxxxxxxxxxx'), // 授权成功后回调的url state: Math.ceil(Math.random() * 1000), // 可设置为简单的随机数加session用来校验 style: 'black', // 提供"black"、"white"可选。二维码的样式 href: 'data:text/css;base64,LmltcG93ZXJCb3ggLnFyY29kZSB7bWFyZ2luLXRvcDowO30KLmltcG93ZXJCb3ggLnRpdGxlIHtkaXNwbGF5OiBub25lO30=', // 外部css文件url,需要https }) } } href:样式的设置,如果是外部css,则需要https,直接写样式就需要转换成base64,Base64编码转换工具,Base64加密解密,追加在“data:text/css;base64,”后面。 样式例如: .impowerBox .qrcode {width: 200px;} .impowerBox .title {display: none;} .impowerBox .info {width: 200px;} .status_icon {display: none} .impowerBox .status {text-align: center;} redirect_uri:回调地址,如果与当前网站域名不同就会在扫码后出现跨域错误,所以最好是给一个当前网站的单独页面触发回调接口。例如:https://a.abc.com/login 回调页面 直接代码。因为是中间页面,所以不用设计,只是闪跳一下。 <template> <div>处理登录中……</div> </template> <script> import { wxLoginCallback, getUserInfo } from '@/api/user.ts'; export default { onLoad () { // 解析 URL 中的 code 参数 const query = this.$route.query; const code = query.code; if (code) { this.handleWXLoginCallback(code); // 处理回调 } }, methods: { handleWXLoginCallback (code) { wxLoginCallback(code).then(async (res) => { if (res.code === 0) { uni.setStorageSync('token', res.data.token); uni.redirectTo({ url: '/pages/index/index' }); } else { uni.redirectTo({ url: '/pages/index/index?openSign=1' }); uni.setStorageSync('unionId', res.msg); } }); }, } } </script> 我当前实现的逻辑为: ...

2025年04月05日 · 2 分钟 · 315 字 · Silas

Macbook M1 芯片安装 node-sass 报错解决

Mac m1芯片下运行vue项目,报错 npm ERR! gyp ERR! command "/opt/homebrew/Cellar/nvm/0.39.1_1/versions/node/v16.14.2/bin/node" "/Library/project/study/vue/every_day_pages_saber/node_modules/node-sass/node_modules/.bin/node-gyp" "rebuild" npm ERR! gyp ERR! cwd /Library/project/study/vue/every_day_pages_saber/node_modules/node-sass npm ERR! gyp ERR! node -v v16.14.2 npm ERR! gyp ERR! node-gyp -v v7.1.2 npm ERR! gyp ERR! not ok Error: Node Sass does not yet support your current environment: OS X Unsupported architecture (arm64)或者 npm ERR! gyp ERR! command "/opt/homebrew/Cellar/nvm/0.39.1_1/versions/node/v16.14.2/bin/node" "/Library/project/study/vue/every_day_pages_saber/node_modules/node-sass/node_modules/.bin/node-gyp" "rebuild" npm ERR! gyp ERR! cwd /Library/project/study/vue/every_day_pages_saber/node_modules/node-sass npm ERR! gyp ERR! node -v v16.14.2 npm ERR! gyp ERR! node-gyp -v v7.1.2 npm ERR! gyp ERR! not ok 这个问题是当前node版本太高,而适配的node-sass并不支持arm架构导致,那么解决思路有两个: ...

2025年02月04日 · 1 分钟 · 163 字 · Silas

H5、uniapp、VUE+TS使用火山引擎-流式语音识别,进行语音转文字

H5、uniapp使用火山引擎-流式语音识别,进行语音转文字。后端使用go语言,我使用的是前端vue中录制,然后讲音频数据传递给go,go在直接上传返回识别的文字内容。 前端实现: 在前端,使用recorder-core插件来实现录制MP3文件。 npm install recorder-core 完整代码如下: <template> <view class="ar-footer"> <slot> <view class="ar-footer-button"> <image class="ar-footer-img" :src="keyboardPng" v-if="mode === 1" @click="setMode(2)" /> <image class="ar-footer-img" :src="voicePng" v-else @click="setMode(1)" /> </view> <view class="ar-footer-wrapper"> <view class="ar-footer-text" v-if="mode === 1"> <input type="text" class="ar-footer-input" v-model="text" placeholder="输入文字..." @keydown="handleKeydown" /> <view class="ar-footer-send" @click="send">发送</view> </view> <button class="ar-footer-voice" v-else @touchstart="startVoiceRecord" @touchend="endVoiceRecord" @mousedown="startVoiceRecord" @mouseup="endVoiceRecord">按住说话</button> </view> </slot> </view> </template> <script setup lang="ts"> import { ref } from 'vue' import keyboardPng from '../../../static/ai-images/keyboard.png' import voicePng from '../../../static/ai-images/voice.png' import { getPartnerList } from '@/api/ars_api'; import Recorder from 'recorder-core' import 'recorder-core/src/engine/mp3' import 'recorder-core/src/engine/mp3-engine' import 'recorder-core/src/extensions/waveview' const mode = ref(1) const text = ref('') const props = defineProps({ onSend: { type: Function, required: true } }) // 处理键盘事件 const handleKeydown = (event: KeyboardEvent) => { if (event.key === 'Enter') { send() } } const setMode = (val: number) => { if (val === 2) { recOpen(); } else { rec.close(); rec = null; } mode.value = val } const send = () => { props.onSend(text.value) text.value = '' } // 模拟按住说话功能 let rec: any; let wave: any; const startVoiceRecord = async () => { if (!rec) { console.error("未打开录音"); return } rec.start(); console.log("已开始录音"); }; const endVoiceRecord = () => { if (!rec) { console.error("未打开录音"); return } rec.stop(async (blob: Blob, duration: number) => { const result = await getPartnerList(blob); props.onSend(result.data.result[0].text) text.value = '' }, (err: any) => { console.error("结束录音出错:" + err); rec.close(); rec = null; }); }; const recOpen = async () => { try { rec = Recorder({ type: "mp3", sampleRate: 16000, bitRate: 16, onProcess: (buffers: any, powerLevel: any, bufferDuration: any, bufferSampleRate: any, newBufferIdx: any, asyncEnd: any) => { // 可实时绘制波形,实时上传(发送)数据 if (wave) wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate); } }); // 打开录音,获得权限 rec.open(() => { console.log("录音已打开"); if (wave) { // 创建音频可视化图形绘制对象 wave = Recorder.WaveView({ elem: wave }); } }, (msg: string, isUserNotAllow: boolean) => { console.log((isUserNotAllow ? "UserNotAllow," : "") + "无法录音:" + msg); }); } catch (error) { console.error('无法获取麦克风权限:', error); } } </script> ars_api上传接口: ...

2024年12月17日 · 7 分钟 · 1412 字 · Silas