两双筷子 - 编码
type: channel
link: https://www.dbkuaizi.com/category/code/
items: Typecho 多吉云上传插件
items: 极简 Docker 入门笔记
items: SQLite + zstd：定时任务日志压缩优化实践
items: Typecho 垃圾评论过滤插件
items: Vue3+TS 笔记
items: Yii2 学习笔记
items: Go Server HTTP 302 跳转 HTTPS
items: go:embed 嵌入 HTTPS 证书
items: Gin + go:embed 实现静态资源嵌入
items: 简单说说服务预热

Typecho 多吉云上传插件
type: item
date: 2025-10-16T13:19:00+00:00
description: 去年了解到多吉云这个CDN 厂商，感觉性价比很高，并且网友使用后的评价也很不错 @二丫讲梵。然后看了一眼现在使用的又拍云 CDN，还有一些余额没有用完。想着还是等用完了这些余额，再迁移到多吉云上去。直到今年十月前后，终于快用完了。但是迁移时发现 Typecho 没有支持多吉云的插件，有一个支持 S3 存储协议的插件，但是阅读完多吉云文档后发现，多吉云的 S3 文档与标准版 S3 规范有一些差异，不能直接使用，所以就有了这个插件。DogeUpload 插件Typecho 多吉云附件上传插件, 版本要求 Typecho >= 1.2.0 版本。直接上传附件到多吉云，且支持更新和删除附件。自定义加速域名。多吉云图片处理后缀支持。时间戳防盗链，支持自定义 Token 有效期。历史资源处理 （将 博客域名自动替换成 CDN 域名）。超轻量级设计，贯彻 Typecho 极简设计理念，插件体积仅 32 KB。代码结构简单、注释完善、使用命名空间，适合作为插件开发参考。下载地址：https://cnb.cool/dbkuaizi/blog/-/tree/main/typecho_plugin下载 "DogeUpload.zip"安装方式：将压缩包中的文件夹 DogeUpload 上传至 Typecho 插件目录 typecho/usr/plugins 下 并在插件面板启用。配置页预览更高性价比的选择博客建立后一直使用又拍云的CDN 服务，去年发现了多吉云这个CDN厂商，了解后得知 多吉云其实是一个 "二道贩子"，他们底层使用 阿里云、腾讯云等国内大厂的 CDN 节点作为基础支撑，得益于量大的原因，他们可以给出比直接购买阿里云、腾讯云CDN 更加实惠的价格。因为我前期一直在使用 又拍云，所以对又拍云的计费规则比较了解，这里就对比一下 又拍云 和 多吉云的成本差异：注：这里以 2025年10月15日 各厂商官网价格为基础作比较，若需核算成本，请以官网价格为准。又拍云云存储：0.0043 元 / GB / 天 (注：可以以当天流量使用情况进行抵扣)CDN 国内流量：0.29 元 / GBHTTPS 请求：0.05 元 / 万次HTTPS 证书免费更新多吉云云存储：0.003 元 / GB / 日CDN 国内流量：0.11 元 / GB （每月前 20 GB 免费）HTTPS 请求：0.04 元 / 万次（每月前 200W 次免费，日流量或云存储超过2TB 可联系客服免除 HTTPS 费用）HTTPS 证书续订目前免费，但官方说后期会收费，默认证书厂商 1元/次，指定厂商2元/次（后面有免费方案）我这里以 月流量 100 GB、云存储 10 GB、HTTPS 月请求数量 60W 来计算两家使用成本的差异（注：如果考虑购买资源包，成本会更低）。费用项目又拍云多吉云备注1. 云存储费用 - 计算公式10GB × 0.0043元/天 × 30天10GB × 0.003元/天 × 30天 - 小计1.29 元0.9 元 2. CDN流量费用 - 计算公式100GB × 0.29元/GB(100GB - 20GB) × 0.11元/GB多吉云每月前20GB免费- 小计29 元8.8 元 3. HTTPS请求费用 - 计算公式60万次 × 0.05元/万次0 元多吉云每月前200万次免费，60万次在免费额度内- 小计3 元0 元 4. 费用抵扣 - 规则存储费可抵扣流量费无额外抵扣规则又拍云特色- 抵扣金额1.29 元 月度最终成本 - 计算公式(CDN费 - 抵扣额) + HTTPS费存储费 + CDN费 + HTTPS费 - 总计(29 - 1.29) + 3 = 30.71 元0.9 + 8.8 + 0 = 9.7 元 大家可以参考这个费用情况核算一下，更换为多吉云后可节省多少钱，基本上都能节省 2/3 左右。HTTPS 证书更新多吉云的 CDN HTTPS 证书与又拍云有些差异，又拍云提供免费与收费的证书，且免费的证书可以免费自动续签。多吉云本身提供免费的 HTTPS 证书，但自动申请证书的服务是收费的（更新成功 默认厂商 1元/次，指定厂商 2元/次），但目前处于公测阶段，限时免费 ，具体可见官方文档： https://docs.dogecloud.com/cdn/manual-config-auto-cert。 现阶段证书是免费更新的，但官方文档中说后续肯定是会收费的，那么每年的成本是多少？以多吉云证书自动更新规则来计算，证书有效期 90 天，到期前 10 天自动更新，则每个证书有效期 记为 80 天，则每年续签 5 次，需要 5 ~10 元的证书更新费用。如果有多个CDN 加速域名，那也是一笔不不少的开销，那么是否有免费方案呢？有的，兄弟！可以自行…

极简 Docker 入门笔记
type: item
date: 2025-09-18T14:06:00+00:00
description: 前言：本篇笔记内容来自于 B 站 《40分钟的Docker实战攻略，一期视频精通Docker》视频，博主 @技术爬爬虾，视频地址：https://www.bilibili.com/video/BV1THKyzBER6。Docker 核心概念Docker 容器Docker 简单来说就是通过容器化技术给应用程序封装一个独立的运行环境，每个运行环境就是一个容器，运行容器的计算机被称为 宿主机。与虚拟机最大的区别是：Docker 容器共享同一个系统内核，而每个虚拟机都包含一个完整的系统内核。所以 Docker 容器比虚拟机更轻量，启动速度更快。Docker 镜像镜像可以理解为是一个特殊的压缩包，容器就像是通过压缩包解压（创建）出来的文件，我们可以使用同一个压缩包，解压出很多份相同的文件，也可以将这个压缩包分享给他人，得到和我们一样的文件。这里所说的“文件”，就是上面提到的容器。我们也可以通过修改容器，并生成自己特定的 Docker 镜像，并将这些镜像分享给他人，其他人可以通过镜像，创建出和我们一样的容器环境。Docker 仓库Docker 仓库就是我们用来存储、分享镜像的地方。所有人都可以把自己的镜像上传到仓库里面，其他人就可以下载镜像并使用。Docker 的官方仓库是 Docker Hub (https://hub.docker.com)Docker 技术原理Docker 利用了 Linux 的两大原生功能，实现容器化：Cgroups 用来限制和隔离进程的资源使用。可以为每个容器设定 CPU、内存、网络带宽等资源的使用上限，确保容器的内存消耗不会影响到宿主机。Namespaces 用于隔离进程的资源视图，使得容器只能看到自己内部的进程ID、网络资源、文件目录，看不到宿主机的。容器本质上还是一个特殊的进程，但是当我们进入容器内部时，看起来就像是一个独立的操作系统Docker 安装Docker 是基于 Linux 的容器化技术，Windows 和 Mac 需要虚拟化一个 Linux 的子系统。所以 Docker最好的使用方式是使用 Linux系统的宿主机。Linux 系统Linux 系统可以通过包管理器安装 Docker，例如 Redhat 系可以使用 yum install docker 安装。 Alpine Linux 可以通过 apk add docker 安装Windows 安装打开 “Windows 功能”, 勾选上 Virtual Machine Paftform（虚拟机平台）、适用于 Linux 的 Windows 子系统，并重启电脑。管理员打开命令提示符，安装 wsl2wsl --set-default-version 2 wsl --update --web-download去 Docker 官网 或者 https://github.com/tech-shrimp/docker_installer/releases 下载符合自己系统的 Docker Desktop 安装包，并进行安装，安装成功后 可以在终端尝试一下：PS C:\Users\Administrator> docker --version Docker version 28.3.3, build 980b856在使用的过程中 Docker Desktop 需要一直开着，否则执行命令会出现这个报错。PS C:\Users\Administrator> docker ps error during connect: Get "http://%2F%2F.%2Fpipe%2FdockerDesktopLinuxEngine/v1.51/containers/json": open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified.Mac 安装直接在 Docker 官网下载安装包 安装就行。Docker 镜像下载镜像Docker Pull 命令用来从仓库下载镜像。docker pull docker.io/library/nginx:latest一个Docker 镜像下载地址包含 4 部分内容：docker.io: registry：仓库地址/注册表，如果是 Docker 官方仓库，则可以省略这个地址。library: 命名空间（镜像作者），为了防止不同用户上传同一个名字的镜像 发生冲突。“library" 是docker 官方仓库的命名空间，这个空间下的所有镜像都是由 Docker 官方维护。如果是官方的命名空间 则可以省略不写nginx: 镜像名latest: 标签名、版本号。写 "latest" 或者不写 表示获取最新版本的镜像。镜像库repository…

SQLite + zstd：定时任务日志压缩优化实践
type: item
date: 2025-08-04T14:52:00+00:00
description: 背景我们公司有大量的 PHP 和 Shell 脚本需要定时执行。起初使用 Linux 的 crontab 管理任务，但随着业务的增长，定时任务数量一度超过 50 个，管理成本迅速上升。为了方便线上操作，当时在 GitHub 上找到了 lisijie/webcron 这个项目。它功能简单，能满足基本的定时任务管理需求，我们便直接投入使用。遇到的问题随着时间推移，我们开始遇到以下两个关键问题：日志查询影响主业务性能 Webcron 默认使用 MySQL 存储任务和日志，与业务共用数据库。当日志量激增后，任务列表页面需要联表查询日志，页面加载时间常超过 30 秒，严重影响了主业务性能。日志数据膨胀，存储压力大部分任务执行频率高（如每 10 秒一次）；部分任务每次输出大量日志（3MB+）；日志需保留 6 个月。例如：项目月记录数MySQL .ibd 文件体积A 项目120 万条1.2 GBB 项目480 万条8.9 GB如果按照此增长速度保留半年，B 项目的日志将占用约 50GB 的磁盘空间。解决方案我们决定重构整个定时任务管理系统，重点优化三方面：1. 用 SQLite 替代 MySQL 存储日志MySQL 查询开销大、影响业务，我们将日志存储迁移到 SQLite。它无需额外服务，具备完整 SQL 支持，适合做独立日志存储方案，查询性能也更可控。2. 使用 zstd 压缩日志内容观察日志内容后发现，大量输出存在重复格式，非常适合压缩。我们评估了几种压缩算法：zstd（默认级别 / L7级别）zstd + 自训练字典gzip（Go 标准库实现）压缩对比如下：压缩方案A 项目（120 万）B 项目（480 万）MariaDB .ibd 文件1.2 GB8.9 GBSQLite 明文存储856 MB665 MBgzip（默认）212 MB405 MBzstd（无字典）198 MB335 MBzstd + B 项目字典149 MB192 MBzstd L7 + B 项目字典139 MB186 MBzstd + A 项目字典95.1 MB224 MB注：字典通过 zstd --train 命令生成，使用项目历史日志训练；字典在不同项目中交叉使用效果不一；字典需维护更新，综合考虑后未在最终方案中使用。最终策略是：当单条日志长度 > 150 字节时启用 zstd 压缩，否则直接存储明文。读取时自动判断并解压。3. 分月存储日志文件为了避免单文件过大并简化清理流程，我们将日志按月分别存储，每月 1 号新建一个 SQLite 日志库文件（如 2025-08.db）。这样带来两个好处：查询范围控制在单月文件，性能更稳定；清理历史数据只需删除 .db 文件即可，维护成本极低。实际效果经过重构后，系统在实际运行中表现稳定，存储与性能均有显著改善：压缩后磁盘占用：每 10 万条日志占用约 9~10 MB；查询性能：500 万条日志文件中，查询指定任务最近 20 条记录耗时 < 20ms；支持字段：支持根据任务 ID、执行状态、执行时间进行高效查询。技术实现细节开发语言：Go 1.24数据库引擎：GORM + SQLite（无 cgo 版本）压缩库：github.com/klauspost/compress为什么不用列式压缩？日志是按行追加写入的，若采用列式压缩，需要额外拆分压缩、整合再写入逻辑，开发成本较高，不适合高频日志场景。因此我们采用了“逐行压缩”策略，开发上更轻量。灵感来源与参考本项目思路部分参考了 V2EX 上的帖子：使用 SQLite 存日志 + zstd 压缩，但实际测试中压缩比与原贴略有差异。总体来说，该方案在中小型系统中表现优异，值得借鉴。后续优化方向✅ 探索 DuckDB 替代 SQLite：支持列式存储，可能带来更高压缩比与查询性能；✅ 动态字典优化：尝试用上月日志训练下月字典，实现自动演进，提高长期压缩效率；✅ 分布式合并查询支持：支持跨月日志合并检索，便于追溯问题全链路。小结这次定时任务系统重构，核心目标是“日志系统轻量化、可维护、低资源占用”。通过 SQLite + zstd 压缩 + 分月归档的组合，我们在不依赖额外服务的前提下，成功将海量日志压缩到可控范围，并提升了查询体验。如果你的系统也面临日志存储冗余、查询卡顿等问题，或许可以尝试这种“小数据库 + 压缩”的路线。文章内容由两双筷子原创，使用 ChatGPT 进行排版优化。

Typecho 垃圾评论过滤插件
type: item
date: 2025-01-27T15:18:00+00:00
description: 前言目前使用的垃圾评论过滤插件在添加词库时十分繁琐，需要进入插件的设置页面才能添加。但从操作的角度上来说，直接在评论管理页面进行操作是最符合直觉的，所以有了这款插件。功能兼容性要求：依赖 Typecho 1.2.0+ 版本，利用命名空间功能。无需修改数据库结构：通过插件配置表实现拦截功能，安全高效。便捷操作：支持直接在评论管理页面一键添加屏蔽词，操作直观。智能处理：当新增屏蔽关键词时，自动处理符合条件的待审评论，节省人工审核时间。属性联动拦截：插件可自动检测评论中被拉黑的属性。例如，若拉黑某个 IP 地址，可自动屏蔽该 IP 提交的所有评论内容，并将其加入屏蔽词库。详细统计：在插件管理页面显示以下统计信息：已定义的过滤规则总数累计拦截次数自动收集的屏蔽词数量适配移动端：注入拉黑操作按钮时，适配移动端小屏幕自定义提示：多种拦截提示可选，也可自定义提示内容界面评论管理页面拉黑操作插件配置页面下载去码云下载插件

Vue3+TS 笔记
type: item
date: 2024-12-01T12:31:00+00:00
description: 前言：本篇笔记内容来自于 B 站：《尚硅谷Vue3入门到实战，最新版vue3+TypeScript前端开发教程》系列视频链接地址： https://www.bilibili.com/video/BV1Za4y1r7KE/创建 Vue 工程NodeJS安装 18.3 + 的 NodeJS https://nodejs.org/初始化项目通过 Vite 初始化一个 Vue 项目 Vite 会在运行目录创建一个 项目同名的目录PS E:\code> npm create vue@latest > npx > create-vue Vue.js - The Progressive JavaScript Framework √ 请输入项目名称： ... test_vue3 √ 是否使用 TypeScript 语法？ ... 否 / 是 √ 是否启用 JSX 支持？ ... 否 / 是 √ 是否引入 Vue Router 进行单页面应用开发？ ... 否 / 是 √ 是否引入 Pinia 用于状态管理？ ... 否 / 是 √ 是否引入 Vitest 用于单元测试？ ... 否 / 是 √ 是否要引入一款端到端（End to End）测试工具？ » 不需要 √ 是否引入 ESLint 用于代码质量检测？ ... 否 / 是 ? 是否引入 Vue DevTools 7 扩展用于调试? (试验阶段) » 否 / 是之后通过 vscode 打开项目目录.运行项目运行前端项目要先看一下项目根目录的 package.json 文件，里面包含了一些项目的信息：{ "name": "test_vue3", "version": "0.0.0", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "run-p type-check \"build-only {@}\" --", "preview": "vite preview", "build-only": "vite build", "type-check": "vue-tsc --build --force" }, "dependencies": { "vue": "^3.4.29" }, "devDependencies": { "@tsconfig/node20": "^20.1.4", "@types/node": "^20.14.5", "@vitejs/plugin-vue": "^5.0.5", "@vue/tsconfig": "^0.5.1", "npm-run-all2": "^6.2.0", "typescript": "~5.4.0", "vite": "^5.3.1", "vue-tsc": "^2.0.21" } }可以看到 scripts 目录下可以 通过的 dev 命令来启动项目，通过 build 来打包编译。我们使用 npm run dev 启动项目项目结构项目入口是根目录中的 index.html 文件，里面有两个核心操作，声明了 #app 容器，所有前端的内容渲染（挂载）到这个容器上，然后引入了 main.ts 文件main.ts 文件中引入了 Vue 组件，并创建了一个 App 的组件，挂载了节点为 App 的容器上。以根目录为例，代码如下：/index.html 入口文件 Vite App /src/main.ts 文件// 引入 createApp 用于创建应用 import {createApp} from 'vue' // 引入 App 根组件 import App from './App.vue' // 挂载 组件 createApp(App).mount('#app')/src/App.vue 文件 你好啊！ 总结Vite 项目中，index.html 是项目的入口文件，在项目最外层。加载index.html后，Vite 解析 src\App.vue 根文件 123 <Person /> 注：Vue3向下兼容Vue2语法，且Vue3中的模板中可以没有根标签。Vue3 核心语法OptionsAPI 和 CompositionAPIVue2 的 API 设计是 Options （配置、选项）风格的Vue3 的 API 设计是 Composition（组合、组件） 风格的Options API 弊端Options类型的 API，数据、方法、计算属性等，是分散在：data、methods、computed中的，若想新增或者修改一个需求，就需要分别修改：data、methods、computed，不便于维护和复用。setupsetup 概述setup 是 vue3 中…

Yii2 学习笔记
type: item
date: 2024-09-29T11:05:44+00:00
description: Yii 2 开发文档，主要参考 Yii 2.0 权威指南 两双筷子: https://www.dbkuaizi.comYii 2 安装Composercomposer create-project --prefer-dist yiisoft/yii2-app-basic basicdownload下载地址：https://www.yiiframework.com/download修改 config/web.php 文件，给 cookieValidationKey 配置项 添加一个密钥。（使用 Composre 安装自动完成该步骤）'cookieValidationKey' => '在此处输入你的密钥',内置服务器使用 serve 命令启动 php 内置服务器，启动后可通过：http://localhost:8080/ 访问php yii serve::: alert-info默认情况下会使用 8080 端口，如果需要手动指定端口可以 加上 --port 来指定:::php yii serve --port=8888基础目录结构有部分精简，主要列出核心的目录结构 ├── assets // 目录用于存放前端资源包PHP类 ├── codeception.yml ├── commands // 控制器 ├── config // 应用配置及其他配置 │ ├── console.php // 控制台应用配置信息 │ ├── db.php // 数据库配置文件 │ ├── params.php // 参数配置文件 │ ├── test.php │ ├── test_db.php │ └── web.php // Web 应用配置信息 ├── controllers // 用于存放控制器的文件夹 ├── mail // 与邮件相关的布局文件 ├── models // 模型文件夹 ├── runtime // 记录日志和缓存，要求权限 777 ├── tests // 用于存放一些测试类 ├── vendor // composer 包及 Yii 框架核心 ├── views // 视图目录 ├── web // web服务根目录（同public） │ ├── index.php // 入口文件 ├── widgets // 存放一些常用的小挂件的类文件 ├── yii // yii 控制台入口 （同 think 与 artisan ） └── yii.bat // 与 yii 文件 功能一致，运行时可省略开头的 php 框架请求流程用户向入口脚本 web/index.php 发起请求。入口脚本加载应用配置并创建一个应用 实例去处理请求。应用通过请求组件解析请求的 路由。应用创建一个控制器实例去处理请求。控制器创建一个动作实例并针对操作执行过滤器。如果任何一个过滤器返回失败，则动作取消。如果所有过滤器都通过，动作将被执行。动作会加载一个数据模型，或许是来自数据库。动作会渲染一个视图，把数据模型提供给它。渲染结果返回给响应组件。响应组件发送渲染结果给用户浏览器。模块模块是一个独立的软件单元，由 模型、视图、控制器和其他组件组成，终端用户可以访问应用主体中已安装的模块的控制器，模块应该当作小应用主体来看待，和应用主体不同的是，模块不能单独部署，必须属于某个应用主体。可以看作是 ThinkPHP 的多应用模式模块的目录结构modules // 模块目录 └── admin // admin 模块 ├── controllers // 控制器目录 │ └── DefaultController.php // 模块默认控制器 ├── models // 模型目录 ├── Module.php // 模块 └── views // 视图目录 └── default └── index.php模块构建有两种方法，一种是 使用 Gii 工具创建，另一种是手动创建。Gii 创建模块 参考：https://www.cnblogs.com/liuwanqiu/p/6815817.html以 Admin 模块为例创建模块类<?php modules/admin/Module.php namespace app\modules\admin; class Module extends \yii\base\Module { public $controllerNamespace = 'app\modules\admin\controllers'; public function init() { parent::init(); // 在这里可以引入模块自己的配置项 \Yii::configure($this, require __DIR__ . '/config.p…

Go Server HTTP 302 跳转 HTTPS
type: item
date: 2024-08-03T08:12:00+00:00
description: HTTP 400 的问题以上一篇笔记为例，将 HTTP 协议改成了 HTTPS 协议，可以使得数据的传输更安全。但开启 HTTPS 后，在某些场景下会出现 HTTP 400 的问题。当客户端（浏览器）使用 HTTP 协议请求实际是 HTTPS 的服务，服务端就会返回 400 的错误码。如何产生的以 上图为例，Go Server 监听了 2333 端口，我服务的 IP 是 192.168.0.11。服务运行后，用浏览器访问这个服务提供的 Web 页面，所以我打开浏览器，在地址栏输入了 192.168.0.11:2333，并按下了回车键。这时，因为我输入的并不是一个标准的 URL 地址（没有指明请求协议 HTTPS / HTTP），所以浏览器会自动帮我补上一个请求协议（目前大部分是补 HTTP，不同浏览器策略不同会补充 HTTPS）。然后我的请求地址就变成了 http://192,168.0.11:2333，与 Go Server 监听的协议不同，导致最终返回了 "Client sent an HTTP request to an HTTPS server.(http code 400)"。如何解决？最简单的办法就是访问报错之后，人为修改浏览器的请求协议为 HTTPS 就行，但每次出现 400 都需要这么操作一些，感觉有点蠢。我们需要一个使用体验更好的解决方案。我们先明确一下需要实现的需求：当用户错误的访问 HTTP 协议时，自动重定向到 HTTPS 协议。这个需求如果是 Nginx、Apache 之类的 Web 服务器，就很容易实现了，只需用简单的配置即可实现 HTTP 302 重定向 HTTPS 的功能，例如 Nginx 的实现方式：error_page 497 https://$http_host$request_uri;如果是 Go Server 的话就比较麻烦了，Go 官方没有提供这样的功能，只能自己实现。最初我参考的这篇文章：如何让HTTP和HTTPs监听同一个端口？，计划在Gin 中加一个中间件，如果请求是 HTTP 就重定向到 HTTPS 协议。但实际使用中发现，多次请求之后 PeekConn.Peek() 读不到 TCP 的 Record Header，导致无法判断是是不是 TLS 报文。前几天，在 GitHub 中看到了 https://github.com/bddjr/hlfhr 这个小项目，通过劫持 net.Conn 重定向到 HTTPS，可以完美实现这个功能，且需要修改的代码量也很少。代码如下：... // 载入 embed 中的证书文件，返回证书连 cert, _ := tls.X509KeyPair(SSLCertFile, SSLKeyFile) // 因为 Gin 不支持从 embed 中载入证书，所以需要自定义证书 Server := hlfhr.New(&http.Server{ Addr: ":2333", Handler: App, // 加载 Gin 路由 TLSConfig: &tls.Config{ // 加载自签名证书 Certificates: []tls.Certificate{cert}, }, ReadHeaderTimeout: 10 * time.Second, IdleTimeout: 10 * time.Second, }) // HTTPS,启动！ Server.ListenAndServeTLS("", "") ...修改之后再访问 HTTP 时，会自动转发至 HTTPS，效果如下：PS：我 fork 了这个项目后做了一些小调整，重定向状态码改为了 307，以实现更好的兼容性（302 会把 POST 请求重定向成 GET 请求）。如果你也有和我类似的需求，可以使用我 fork 的库 go get github.com/dbkuaizi/hlfhr，否则使用 go get github.com/bddjr/hlfhr 就好。

go:embed 嵌入 HTTPS 证书
type: item
date: 2024-06-30T15:33:00+00:00
description: 在上一篇文章中，我们使用 go:embed 功能，成功将静态资源文件嵌入到可执行程序中，这次我们尝试将 https 证书嵌入到 程序中，并实现 Gin+HTTPS 功能。为什么需要 HTTPS？首先我们需要了解一下，为什么需要在程序中嵌入一个 HTTPS 服务。在上一篇笔记的开头我们提到，通过嵌入资源文件可以让最终软件在分发、部署时更简单，那我们服务本身就需要提供一个 Web 页面供用户进行操作。既然是 Web 页面，就一定会涉及到浏览器与服务端数据交互的场景。如果服务需要通过非本地 IP（既，不是 127.0.0.1 需要通过网络设备远程访问，包括但不限于 局域网、公网等）来访问，那么我建议你一定要通过 HTTPS 协议进行数据交互。三点原因如下通过 HTTPS 协议可以加密传输数据，防止中间人攻击，保障用户数据安全。在企业内网或高校校园网等内部网络中，为确保安全，通常会使用一些网络防火墙产品。使用 HTTP 协议可能导致两个问题：一是防火墙记录明文请求数据，导致敏感数据泄露；二是防火墙解析服务器返回的数据后拦截响应，导致服务异常。浏览器厂商越来越重视安全问题，目前很多前端的 API 函数 只允许在 HTTPS 协议中调用如果使用 HTTPS 协议可以避免这些问题，就可以尽可能避免这些问题。内网服务也需要 HTTPS 吗一些开发者可能认为：“我的服务只部署在内部网络中，不对公众网络开放，即使不使用 HTTPS 协议，安全性也不会有问题。”对此我的观点是：“作为开发者，我们不能依赖环境来保障项目的安全性。内网环境并非绝对安全，我们应该尽可能提高项目自身的安全级别。所有的安全防御手段只有在攻击发生前实施才有效，一旦发生数据泄露，后续的补救措施也无法弥补损失。”内网 HTTPS 证书无效问题如果内网没有域名、即使使用证书，浏览器也会标记证书无效，那么在内网使用 HTTPS 证书还有意义吗？在内网环境中，由于没有域名，我们的 HTTPS 证书有两个来源，第一种是使用 CA 机构颁发的授信证书，但这种证书只会颁发给域名，给内网 IP 使用时会提示证书无效。第二种，是自签名证书，可以签给ip地址的，但自签名的证书不会被客户端浏览器信任，所以会也会提示证书无效。即使内网环境中证书不被信任，我们仍应该使用 HTTPS 证书，不被信任的证书仍可以用于通信数据的加密、数据完整性的校验，保障数据不被中间人获取、篡改。例如 1Panel Web 管理面板就支持开启 "面板 SSL" 功能，虽然开启后自签名的证书不被信任，但仍可以通过 HTTPS 提升访问的安全性。自签名证书获取证书的方式有两种，一种是可信 CA 机构签发的域名证书，这种不做赘述，这次我们用自签名证书。要实现自签名证书需要电脑安装 openssl 这个软件，如果是 Linux 系统，绝大部分发行版都会带。如果是 Windows 系统，如果有安装 Git 则可以直接在 Git Bash 终端中直接使用 openssl。首先在 终端中进入项目目录，在需要存放证书的目录执行命令生成证书，第一步 先生成私钥（.key 文件）openssl genpkey -algorithm RSA -out server.key -pkeyopt rsa_keygen_bits:2048第二步 生成证书（.ctr 或 pem） 文件，-days 表示证书自签发之日起有效天数，我一般写 3650 天（10 年），因为证书嵌入到二进制文件中分发给用户，不能每年换证书文件。openssl req -new -x509 -sha256 -key server.key -out server.crt -days 365（可选）如果需要 PEM 格式的证书文件，需要执行如下命令合并cat server.key server.crt > server.pem嵌入证书，Gin 实现 HTTPS首先，通过 go:embed 在编译时，将证书加载到 SSLCertFile、SSLCertFile 变量中，然后通过 自定义 HTTP 配置的方式加载证书，不用引入任何多余的包。ackage main import ( "crypto/tls" "embed" "github.com/gin-gonic/gin" "io/fs" "net/http" ) var ( //go:embed common/cert/server.crt SSLCertFile []byte //go:embed common/cert/server.key SSLKeyFile []byte //go:embed web/* WebDir embed.FS App *gin.Engine ) func main() { App = gin.New()…

Gin + go:embed 实现静态资源嵌入
type: item
date: 2024-05-18T12:46:30+00:00
description: Go 语言单文件 Web 服务实现使用 Go 语言开发一些简单的 Web 项目时，往往需要使用一些前端静态资源文件。那有没有什么办法可以把前端资源嵌入到 Go 程序中，最好可以和 Go 程序的接口共用一个 HTTP 服务，这样在运行是也不需要考虑跨域的问题，而且单文件服务部署相比后端程序 + 静态文件的部署方式更简单。如何嵌入资源文件Go 在 1.16 版本增加了一个特性 go:embed，可以在编译时嵌入文件或者目录，我们可以使用这个命令实现资源文件的嵌入。示例项目目录如下：ginweb ├── go.mod ├── go.sum ├── main.go // 主程序 └── web // 需要嵌入的资源文件 ├── index.html // 静态资源文件 └── photo.jpg // 静态资源文件例如通过 go:embed 将项目中的 web 目录下的所有文件进行嵌入，这时 WebDir 变量就是一个虚拟的文件系统，可以直接当作文件进行操作。var ( //go:embed web/* WebDir embed.FS )使用 HTTP 服务加载资源// 通过 Gin 的 StaticFS() 方法，可以将嵌入的资源起一个 HTTP 服务，现在访问 2333 端口默认就会加载 /static/index.html 文件。package main import ( "embed" "github.com/gin-gonic/gin" "io/fs" "net/http" ) var ( //go:embed web/* WebDir embed.FS App *gin.Engine ) func main() { App = gin.New() staticFp, _ := fs.Sub(WebDir, "web") // 起一个静态资源 HTTP 服务 App.StaticFS("/static", http.FS(staticFp)) // 这是一个接口 App.GET("/api", func(context *gin.Context) { context.JSONP(http.StatusOK, gin.H{ "code": 0, "message": "hi,dbkuaizi", }) }) App.Run(":2333") }利用 NoRoute 去掉 /static 目录上面我们虽然实现了静态资源的加载，但为了区分 api 接口和静态资源，不得不多加一层 /static 静态资源目录，其实有更巧妙的方法免除掉这个目录。我们可以利用 gin.NoRoute() 方法，这个方法的本意是当请求的接口不存在时，用于处理 404 相关的操作。我们可以在接口匹配失败后尝试加载静态资源，请求地址如下：package main import ( "embed" "github.com/gin-gonic/gin" "io/fs" "net/http" ) var ( //go:embed web/* WebDir embed.FS App *gin.Engine ) func main() { App = gin.New() // 这是一个接口 App.GET("/api", func(context *gin.Context) { context.JSONP(http.StatusOK, gin.H{ "code": 0, "message": "hi,dbkuaizi", }) }) // 嵌入的 embed 就是一个文件系统，获取 web 目录下的资源文件 staticFp, _ := fs.Sub(WebDir, "web") // 所有请求先匹配 api 路由，如果没匹配到就当作静态资源文件处理 App.NoRoute(gin.WrapH(http.FileServer(http.FS(staticFp)))) // 实现 api 端口、服务器端口共用一个 App.Run(":2333") }在使用引用静态文件时，将 Web 目录当作根目录来使用，例如: dbkuaizi.com hi,dbkuaizi 最终效果：

简单说说服务预热
type: item
date: 2024-04-16T13:09:17+00:00
description: 问题什么是服务预热，一般出现在 qps 密集的场景，也就是我们说的高并发场景。例如：一个抢购服务，会在商品开售的时间，或者自动扩容后，QPS 瞬间会从 0 或者极少的一个量级上升到很高的一个量级。在服务刚启动时，Redis 缓存库一般都是空的、数据库连接池也只会有少量数据库连接。在这个时候如果有大量请求到达服务端，这时服务端会先去 Redis 中拿缓存数据，拿不到之后会直接穿透到数据库层拿数据，但从数据库拿数据需要有数据库连接，若连接池没有足够的连接，就需要在短时间内创建大量的数据库连接，造成短时间内所有的请求都会穿透到数据库层，对数据库造成压力。磁盘、CPU、以及服务自身资源占用的表飙升，造成接口响应超时，甚至可能会拖垮数据库服务。并且 CPU 、内存、磁盘资源的突然飙升，又会触发新一轮的扩容，造成循环扩容的问题。自动预热要解决这个问题，就要在服务启动后先进行服务预热，再将流量切过来。例如:在程序初始化时，将数据库中的数据先缓存到 Redis 中，同时在数据库连接池中缓存多个数据库连接。若有用到线程池的地方，也可以根据业务需求，提前创建空闲的线程。在做完这些之后，再将请求切进来，就不会造成上述问题了请求预热还有一种方案，适合自动扩容场景。在服务启动完成后，先切少量的流量过来，由这些请求完成数据的预热工作，然后在逐步增加请求的量级。笔记参考：https://www.bilibili.com/video/BV1zt421G7qZ