[{"content":"计算器看起来简单：数字、运算符、小数点、等号。真写起来却容易变成一坨 if/else——刚按完 + 又按 × 怎么办？结果出来后直接敲数字呢？小数点能不能连点两次？\n这次我用 状态模式（State Pattern） 把这些规则拆开，做了一台网页计算器。在线预览：tool.dmax.wang/JavaScriptCalculator\n为什么用状态模式\r状态模式的核心就一句：对象在不同状态下，对同一事件的反应不同。\n计算器很适合这种模型。同样按 .：\n刚输入完数字 → 可以加小数点 已经有小数点了 → 应该报错 还没开机 → 提示没有开机 如果全堆在一个函数里，分支会越来越长，改一处牵一片。换成当前状态对象处理按键，每个状态只关心自己那点规则，代码反而好读。\n先画状态图\r我给计算器定了 5 个状态：\n状态 含义 典型约束 PowerOffState 关机 除 Power 外一律拒绝 UnlimitedState 普通输入 数字 / 运算符 / 小数点都可 LimitedDotKey 已出现小数点 禁止再按 . OnlyNumberKey 刚按完运算符 只能接着输数字 CalculatiedComplete 刚算完结果 再按数字会开新式子 状态之间大致这么跳：\n1 2 3 4 5 6 7 8 9 10 11 PowerOff ──Power──► Unlimited │ ┌────────────┼────────────┐ │ │ │ ▼ ▼ ▼ LimitedDot OnlyNumber （按 = 后） │ │ │ │ │ ▼ │ │ CalculatiedComplete │ │ │ └────────────┴── 按数字 / AC ──► Unlimited UnlimitedState 是默认行为的基类，其它状态继承它，只覆盖需要改的按键处理——少写很多重复代码。\n状态类：把规则写进方法里\r基类负责正常输入：\n1 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 class UnlimitedState { constructor(calculator) { this.calculator = calculator; } pressNumberKey(textContent) { this.calculator.removeErrorMsg(); this.calculator.appendNumberCharacter(textContent); this.calculator.ChangeStateTo(this.calculator.unlimitedState); } pressOperatorKey(textContent) { this.calculator.removeErrorMsg(); this.calculator.appendDotOrOperatorCharacter(textContent); // 运算符后面必须跟数字 this.calculator.ChangeStateTo(this.calculator.onlyNumberKey); } pressDotKey(textContent) { this.calculator.removeErrorMsg(); this.calculator.appendDotOrOperatorCharacter(textContent); // 小数点只能出现一次（当前数字段） this.calculator.ChangeStateTo(this.calculator.limitedDotKey); } pressEqualsKey() { this.calculator.removeErrorMsg(); this.calculator.calaulatedExpression(this.calculator.display.value); // 结果里有没有小数点，决定下一次能不能再点 . this.calculator.display.value.indexOf(\u0026#39;.\u0026#39;) === -1 ? this.calculator.ChangeStateTo(this.calculator.calculatiedComplete) : this.calculator.ChangeStateTo(this.calculator.limitedDotKey); } pressSwitchKey() { this.calculator.removeErrorMsg(); this.calculator.setPowerStateText(\u0026#39;Power:Off\u0026#39;); this.calculator.ChangeStateTo(this.calculator.powerOffState); this.calculator.display.value = \u0026#39;\u0026#39;; } pressClearKey() { this.calculator.removeErrorMsg(); this.calculator.toZero(); this.calculator.ChangeStateTo(this.calculator.unlimitedState); } } 关机状态几乎全部拦截：\n1 2 3 4 5 6 7 8 9 10 11 12 13 class PowerOffState extends UnlimitedState { pressNumberKey() { this.calculator.errorMsg(`没有开机!`, false); } pressOperatorKey() { this.calculator.errorMsg(`没有开机!`, false); } pressDotKey() { this.calculator.errorMsg(`没有开机!`, false); } pressEqualsKey() { this.calculator.errorMsg(`没有开机!`, false); } pressClearKey() { this.calculator.errorMsg(`没有开机!`, false); } pressSwitchKey() { this.calculator.removeErrorMsg(); this.calculator.ChangeStateTo(this.calculator.unlimitedState); this.calculator.display.value = \u0026#39;0\u0026#39;; this.calculator.setPowerStateText(\u0026#39;Power:On\u0026#39;); } } 其它几个状态只改不听话的那几下：\n1 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 // 已经有小数点：禁止再点 . class LimitedDotKey extends UnlimitedState { pressNumberKey(textContent) { this.calculator.removeErrorMsg(); this.calculator.appendNumberCharacter(textContent); } pressDotKey() { this.calculator.errorMsg(`不能输入\u0026#39;.\u0026#39;号！`); } } // 刚按完运算符：只能输数字 class OnlyNumberKey extends UnlimitedState { pressOperatorKey() { this.calculator.errorMsg(`只能输入数字！`); } pressDotKey() { this.calculator.errorMsg(`只能输入数字！`); } pressEqualsKey() { this.calculator.errorMsg(`只能输入数字！`); } } // 刚算完：再按数字 = 开新一行，而不是拼在结果后面 class CalculatiedComplete extends UnlimitedState { pressNumberKey(textContent) { this.calculator.removeErrorMsg(); this.calculator.newLine(textContent); this.calculator.ChangeStateTo(this.calculator.unlimitedState); } } 这样读代码的时候，不用在脑子里模拟一整棵条件树——打开某个状态类，规则就摆在眼前。\nCalculator：上下文对象\r状态类不直接操作 DOM 细节太多，真正干活的是 Calculator。它持有全部状态实例，并暴露改显示、算式、报错等方法：\n1 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 class Calculator { constructor() { this.unlimitedState = new UnlimitedState(this); this.limitedDotKey = new LimitedDotKey(this); this.onlyNumberKey = new OnlyNumberKey(this); this.calculatiedComplete = new CalculatiedComplete(this); this.powerOffState = new PowerOffState(this); // 初始关机 this.currentState = this.powerOffState; this.display = display; this.tips = tips; this.powerState = powerState; } ChangeStateTo(state) { this.currentState = state; } appendNumberCharacter(character) { this.display.value = this.display.value === \u0026#39;0\u0026#39; ? character : this.display.value + character; } appendDotOrOperatorCharacter(character) { this.display.value = this.display.value + character; } newLine(character) { this.display.value = \u0026#39;\u0026#39; + character; } toZero() { this.display.value = \u0026#39;0\u0026#39;; } calaulatedExpression(displayExpression) { const expression = displayExpression .replace(/÷/g, \u0026#39;/\u0026#39;) .replace(/×/g, \u0026#39;*\u0026#39;); // 演示项目用了 eval；正式产品建议换成安全的表达式解析 this.display.value = parseFloat(eval(expression).toFixed(15)).toString(); } errorMsg(errorMsg, inputToRed = true) { if (inputToRed) this.display.classList.add(\u0026#39;border-red-500\u0026#39;); this.tips.innerText = errorMsg; } removeErrorMsg() { this.display.classList.remove(\u0026#39;border-red-500\u0026#39;); this.tips.innerText = \u0026#39;\u0026#39;; } setPowerStateText(text) { this.powerState.innerText = text; } } 注意两点：\n切换状态只有一处入口：ChangeStateTo，谁当前当家一目了然。 eval 仅适合玩具/演示。真实计算器请用自己的 tokenizer + 运算符优先级，别把用户输入直接丢进 eval。 按键事件：一律转交给当前状态\rHTML 上按钮用 data-action 分类：number / operator / dot / equal / clear / switch。JS 只负责把点击转给 currentState：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const calculator = new Calculator(); for (const numberKey of numberKeys) { numberKey.addEventListener(\u0026#39;click\u0026#39;, function () { calculator.currentState.pressNumberKey(this.textContent); }); } for (const operatorKey of operatorKeys) { operatorKey.addEventListener(\u0026#39;click\u0026#39;, function () { calculator.currentState.pressOperatorKey(this.textContent); }); } dotKey.addEventListener(\u0026#39;click\u0026#39;, function () { calculator.currentState.pressDotKey(this.textContent); }); equalKey.addEventListener(\u0026#39;click\u0026#39;, function () { calculator.currentState.pressEqualsKey(); }); clearKey.addEventListener(\u0026#39;click\u0026#39;, function () { calculator.currentState.pressClearKey(); }); switchKey.addEventListener(\u0026#39;click\u0026#39;, function () { calculator.currentState.pressSwitchKey(); }); UI 层不知道现在能不能按小数点，它只问当前状态；状态层也不关心按钮长什么样。职责切开之后，后面加键盘快捷键或改皮肤都轻松很多。\n走一遍真实按键\r开机 → 1 → . → 5 → + → 2 → =：\nPower → PowerOff → Unlimited，显示 0 1 → 显示 1，仍在 Unlimited . → 显示 1.，切到 LimitedDotKey 此时再按 . → 提示不能输入 . 号 5 → 显示 1.5 + → 显示 1.5+，切到 OnlyNumberKey 此时按 = 或再按 + → 提示只能输入数字 2 → 显示 1.5+2，回到 Unlimited = → 算出 3.5，因为结果带小数点，进入 LimitedDotKey 再按数字比如 9 → 若在 CalculatiedComplete 会新开一行；这里因结果含 . 走了另一条分支，可按自己的产品预期再微调 规则变了，多半只改某一个状态类，而不是翻遍整个文件找 if。\nUI 与样式\r界面用 Tailwind 拼了一套浅色拟物按钮，显示框只读，关机时 Power:Off，开机后变成 Power:On。结构很薄：一块显示区 + 几排 button，重点都在状态机上。\n本地可以这样看：\n1 2 3 # 若改了 style.css，用 Tailwind CLI 重新生成 output.css npx tailwindcss -i ./src/style.css -o ./src/output.css # 直接用浏览器打开 index.html 即可 小结\r这台计算器不大，但把状态模式的味道用上了：\n同一按键，不同状态不同反应——关机、输入中、算完之后各管各的 用继承复用默认行为，子状态只覆盖例外 上下文（Calculator）管数据和副作用，状态对象管能不能按、按完去哪 事件层只管转发，不掺业务判断 设计模式不是为了显得高级，而是在规则开始变多时，给代码一个能长的骨架。下次再写向导步骤、播放器、表单校验流程，也可以先问自己一句：这件事是不是一堆状态 × 事件的组合？如果是，状态模式往往比嵌套 if 更省心。\n源码目录：tool-demo-master/JavaScriptCalculator。有兴趣可以直接打开玩，或者拿去改成支持括号、退格、科学计算——那时你会更感谢当初把状态拆开了。\n","date":"2021-09-07T21:17:07+08:00","permalink":"/p/js-state-calculator/","title":"用 JS 状态模式做一台简易计算器"},{"content":"很早就想开始写博客了。今天也是终于动手：靠着 Amazon 新用户那点免费额度，开了一台小机器，用 Hugo + Nginx 把站搭起来。静态站不折腾数据库，刚好适合我这种「先上线再说」的性格。\n下面按真实操作顺序记，命令能抄就抄。环境按当时来：\n项目 版本 云厂商 Amazon Web Services（免费套餐） 系统 Ubuntu 20.04 LTS Hugo v0.82.1 Extended Web Nginx 一、先说白嫖：Amazon 免费额度\r新账号送的 Free Tier，大致能白嫖一台小 EC2 一年（每月大约 750 小时，够一台机 7×24 开着）。我选的是：\n系统镜像：Ubuntu Server 20.04 LTS 实例规格：免费档里的 t2.micro / t3.micro（内存不大，跑静态博客绰绰有余） 磁盘：先用默认那点，博客纯静态，先不纠结扩容 安全组：先放行 22（SSH） 和 80（HTTP），有域名再说 443 控制台点「启动实例」，选密钥对，把 .pem 下好——只给你下一次，丢了就哭去。权限收紧后再登录（以 Linux / macOS 为例，Windows 用 PowerShell 也差不多）：\n1 2 chmod 400 ~/Downloads/your-key.pem ssh -i ~/Downloads/your-key.pem ubuntu@你的公网IP 第一次进去看到 Ubuntu 欢迎语，心里那句「这台机暂时是我的了」还是挺爽的。提醒自己：免费也不是无限，别乱开大盘、别挂一堆吃流量的服务，省得账单吓一跳。\n二、服务器初始化：先把屋子收拾干净\r登录后先更新软件源，装一堆「做人要有」的基础工具：\n1 2 3 4 5 6 7 sudo apt update sudo apt upgrade -y sudo apt install -y \\ nginx git curl wget unzip \\ ca-certificates ufw \\ software-properties-common 时区改成中国，不然日志时间会对不上脑子：\n1 2 sudo timedatectl set-timezone Asia/Shanghai timedatectl 防火墙开起来（别把自己锁门外，先放 SSH）：\n1 2 3 4 5 6 sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow OpenSSH sudo ufw allow \u0026#39;Nginx Full\u0026#39; sudo ufw enable sudo ufw status Nginx 装完一般就在跑了，确认一下：\n1 2 sudo systemctl enable --now nginx systemctl status nginx 浏览器先访问 http://公网IP，能看到 Nginx 欢迎页，说明 80 端口和安全组都通了。这一步过了，后面才有底气折腾博客。\n三、安装 Hugo Extended 0.82.1\rapt 源里的 Hugo 往往偏旧，我直接下官方包。2021 年 4 月 20 号刚出的 0.82.1，今天拿来用刚刚好：\n1 2 3 4 5 6 7 cd /tmp HUGO_VER=0.82.1 wget https://github.com/gohugoio/hugo/releases/download/v${HUGO_VER}/hugo_extended_${HUGO_VER}_Linux-64bit.deb sudo dpkg -i hugo_extended_${HUGO_VER}_Linux-64bit.deb hugo version 期望看到类似：\n1 hugo v0.82.1+extended linux/amd64 ... 有 extended 字样就对了，以后主题要编译 SCSS 也不慌。\n四、创建站点、装主题、写第一篇\r站点放在 /var/www 下面，目录干净点：\n1 2 3 4 5 6 7 sudo mkdir -p /var/www sudo chown -R $USER:$USER /var/www cd /var/www hugo new site myblog cd myblog git init 主题我先用社区里常见的轻量主题（以 PaperMod 为例，换成你喜欢的也行）：\n1 git submodule add https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod 写配置文件 config.toml（0.82 时代还是 toml 居多）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 cat \u0026gt; config.toml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; baseURL = \u0026#34;http://你的公网IP/\u0026#34; languageCode = \u0026#34;zh-cn\u0026#34; title = \u0026#34;dmaxの小屋\u0026#34; theme = \u0026#34;PaperMod\u0026#34; hasCJKLanguage = true [params] description = \u0026#34;个人博客记录\u0026#34; defaultTheme = \u0026#34;auto\u0026#34; ShowReadingTime = true [[menu.main]] name = \u0026#34;首页\u0026#34; url = \u0026#34;/\u0026#34; weight = 10 [[menu.main]] name = \u0026#34;归档\u0026#34; url = \u0026#34;/archives/\u0026#34; weight = 20 EOF 创建文章：\n1 hugo new posts/hello-world.md 编辑 content/posts/hello-world.md，把 draft: true 改成 false，写点内容保存。然后构建：\n1 2 hugo --minify ls public 看到一堆 index.html、css、文章目录，说明静态站已经「烤」好了。\n五、把 Nginx 指到 public\r丢掉默认站点，换成我们的博客根目录：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 sudo tee /etc/nginx/sites-available/myblog \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; server { listen 80; server_name _; root /var/www/myblog/public; index index.html; location / { try_files $uri $uri/ =404; } location ~* \\.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?)$ { expires 7d; add_header Cache-Control \u0026#34;public\u0026#34;; } } EOF sudo ln -sf /etc/nginx/sites-available/myblog /etc/nginx/sites-enabled/ sudo rm -f /etc/nginx/sites-enabled/default sudo nginx -t sudo systemctl reload nginx 再打开 http://公网IP，应该能看到博客首页，而不是 Nginx 默认页。这一刻——白嫖机 + 静态生成器 + 几行配置——个人站就活了。\n六、以后怎么更新文章\r流程固定成肌肉记忆：\n1 2 3 4 5 cd /var/www/myblog hugo new posts/又一篇.md # 编辑 Markdown，确认 draft: false hugo --minify # Nginx 直接读 public，一般不用重启 想偷懒可以丢个脚本：\n1 2 3 4 5 6 7 8 cat \u0026gt; /var/www/myblog/deploy.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash set -e cd /var/www/myblog hugo --minify echo \u0026#34;已发布 -\u0026gt; /var/www/myblog/public\u0026#34; EOF chmod +x /var/www/myblog/deploy.sh 七、有域名之后再说（今日预告）\r今天先用公网 IP 顶着。等域名备案/解析好了，再改 baseURL，并可以上 Let\u0026rsquo;s Encrypt：\n1 2 3 sudo apt install -y certbot python3-certbot-nginx # 把 Nginx 的 server_name 和 Hugo 的 baseURL 改成域名后： sudo certbot --nginx -d 你的域名.com HTTPS 小锁亮起来，博客才算「像样」一点。那是下一篇的事。\n小结\r今天完成的事：\n用 Amazon 免费额度开了 Ubuntu 20.04 小机器 初始化：更新、防火墙、Nginx、时区 安装 Hugo Extended 0.82.1 建站、装主题、生成 public Nginx 反代（其实是直出）静态文件，公网可访问 工具链很轻：没有 MySQL，没有 PHP，没有 Docker。免费机磁盘和内存都金贵，这种玩法刚刚好。\n博客名叫 dmaxの小屋——小屋虽小，钥匙在自己手里。先写到这里，今晚值得吃顿好的。\n","date":"2021-04-29T18:05:53+08:00","permalink":"/p/hello-world/","title":"白嫖 AWS 小服务器，用 Hugo 把博客从零敲上线"},{"content":"很早就想开始写博客了，主题来回纠结了三轮，今天总算付诸行动——用 AWS 新账号的免费额度开了台小机器，一个下午就把站搭了起来。\n当时的配置大致是这样：\n项目 版本 云厂商 Amazon Web Services 系统 Ubuntu 20.04 LTS Hugo v0.82.1 Extended Web Nginx AWS 的免费额度到底能干啥\r新账号的 Free Tier，每个月给 750 小时的免费额度，正好够一台实例 24 小时连续跑满一个月，这样能连续用满 12 个月。我选的配置是：\n系统镜像：Ubuntu Server 20.04 LTS 机型：t2.micro 磁盘：默认的 8G，静态博客根本用不完 安全组：只放行 22（SSH）和 80（HTTP），443 等以后有域名再说 创建实例时会让你下载密钥对，.pem 文件只给这一次机会，丢了就没地方再拿了。我把这个密钥对命名为 hugo，方便后面区分。\n第一次用这东西，上来就直接 SSH，结果 Permission denied，折腾了半天才想起来要先改权限：\n1 2 chmod 400 ~/Downloads/hugo.pem ssh -i ~/Downloads/hugo.pem ubuntu@公网IP 改完再连，屏幕上跳出了 Welcome to Ubuntu，说明系统本身没问题。\n先把系统收拾一遍\r登录之后先把软件源更新一遍：\n1 2 3 4 5 6 7 sudo apt update sudo apt upgrade -y sudo apt install -y \\ nginx git curl wget unzip \\ ca-certificates ufw \\ software-properties-common 时区改成 Asia/Shanghai：\n1 2 sudo timedatectl set-timezone Asia/Shanghai timedatectl 防火墙用 ufw。执行 ufw enable 时它会提示可能中断现有 SSH 连接——不过不用担心，我是通过 AWS 控制台自带的网页终端连的，不受影响。\n1 2 3 4 5 6 sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow OpenSSH sudo ufw allow \u0026#39;Nginx Full\u0026#39; sudo ufw enable sudo ufw status 这个顺序很重要：一定要先放行 OpenSSH 再启用防火墙，不然会把自己锁在外面，到时候只能去 AWS 控制台重启实例了。\nNginx 装完默认就在跑，确认一下：\n1 2 sudo systemctl enable --now nginx systemctl status nginx 浏览器访问 http://公网IP，能看到 Nginx 欢迎页，说明安全组和防火墙都放行成功了。\nHugo 别用 apt 装的\rUbuntu 自带源里的 Hugo 版本通常偏旧，而且不带 Extended 版，主题要编译 SCSS 就没法用了。我直接从 GitHub 下载了官方发布包，当时刚好是 4 月 20 号发布的 0.82.1：\n1 2 3 4 5 6 7 cd /tmp HUGO_VER=0.82.1 wget https://github.com/gohugoio/hugo/releases/download/v${HUGO_VER}/hugo_extended_${HUGO_VER}_Linux-64bit.deb sudo dpkg -i hugo_extended_${HUGO_VER}_Linux-64bit.deb hugo version 输出里能看到：\n1 hugo v0.82.1+extended linux/amd64 ... 带 extended 字样就说明装对了。\n建站、装主题、写第一篇文章\r站点放在 /var/www 下面，目录结构清爽一些：\n1 2 3 4 5 6 7 sudo mkdir -p /var/www sudo chown -R $USER:$USER /var/www cd /var/www hugo new site myblog cd myblog git init 主题用的 PaperMod，社区里用得比较多，教程也好找：\n1 git submodule add https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod 配置文件长这样：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 cat \u0026gt; config.toml \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; baseURL = \u0026#34;http://公网IP/\u0026#34; languageCode = \u0026#34;zh-cn\u0026#34; title = \u0026#34;dmaxの小屋\u0026#34; theme = \u0026#34;PaperMod\u0026#34; hasCJKLanguage = true [params] description = \u0026#34;个人博客记录\u0026#34; defaultTheme = \u0026#34;auto\u0026#34; ShowReadingTime = true [[menu.main]] name = \u0026#34;首页\u0026#34; url = \u0026#34;/\u0026#34; weight = 10 [[menu.main]] name = \u0026#34;归档\u0026#34; url = \u0026#34;/archives/\u0026#34; weight = 20 EOF 然后建第一篇文章：\n1 hugo new posts/aws-hugo-blog.md 有个坑得记一下：新建文章的 front matter 里默认是 draft: true，不改成 false 的话，hugo build 不会把它输出到 public 目录：\n1 2 hugo --minify ls public public 目录下出现了一堆 index.html 和文章目录，说明静态站生成成功了。\n把 Nginx 指到 public\r先禁用默认站点，把 root 换成博客目录：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 sudo tee /etc/nginx/sites-available/myblog \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; server { listen 80; server_name _; root /var/www/myblog/public; index index.html; location / { try_files $uri $uri/ =404; } location ~* \\.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?)$ { expires 7d; add_header Cache-Control \u0026#34;public\u0026#34;; } } EOF sudo ln -sf /etc/nginx/sites-available/myblog /etc/nginx/sites-enabled/ sudo rm -f /etc/nginx/sites-enabled/default sudo nginx -t sudo systemctl reload nginx 再刷新一次 http://公网IP，页面变成了自己的博客——那一刻还是挺有成就感的。：）\n以后更新文章\r流程基本固定了：\n1 2 3 4 5 cd /var/www/myblog hugo new posts/又一篇.md # 编辑 Markdown，记得把 draft 改成 false hugo --minify # Nginx 直接读 public，一般不用重启 后来我写了个小脚本偷懒：\n1 2 3 4 5 6 7 8 cat \u0026gt; /var/www/myblog/deploy.sh \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #!/bin/bash set -e cd /var/www/myblog hugo --minify echo \u0026#34;已发布 -\u0026gt; /var/www/myblog/public\u0026#34; EOF chmod +x /var/www/myblog/deploy.sh 还差一个域名\r现在还是先用着公网IP，等备案、解析都弄好了，把 baseURL 和 server_name 改成域名，再用 certbot 拉证书：\n1 2 3 sudo apt install -y certbot python3-certbot-nginx # 把 Nginx 的 server_name 和 Hugo 的 baseURL 改成域名后： sudo certbot --nginx -d 域名.com ","date":"2021-04-29T18:05:53+08:00","permalink":"/p/aws-hugo-blog/","title":"白嫖AWS服务器，从零上线博客"}]