Pinned Post

Recent Posts

解决 fyne 中文乱码问题

最近尝试使用 fyne 做一些简单的桌面应用,首先遇到的是中文乱码问题,目前主要有两种方案:
1. 将字体文件打包成 go 文件;
2. 使用 ENV 环境变量。

方案一:将字体打包成 go 文件

将字体生成 Go 文件

在安装了 fyne 工具后, 使用 fyne bundle --package xtheme ./fonts/SourceHanSerifSC-VF.ttf > ./app/xtheme/font.go : 将字体文件 ./fonts/SourceHanSerifSC-VF.ttf 打包到 ./app/xtheme/font.go 文件中,导出的包名为 xtheme

生成出的 font.go 文件格式如下:

// auto-generated
// Code generated by '$ fyne bundle'. DO NOT EDIT.

package xtheme

import "fyne.io/fyne/v2"

var resourceSourceHanSerifSCVFTtf = &fyne.StaticResource{
	StaticName: "SourceHanSerifSC-VF.ttf",
	StaticContent: []byte("\x00\x01..."),
	}

新建自定义主题文件

package xtheme

import (
	"image/color"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/theme"
)

type XTheme struct {
	fyne.Theme
}

func (*XTheme) Font(s fyne.TextStyle) fyne.Resource {
	return resourceSourceHanSerifSCVFTtf
}

func (m *XTheme) Size(name fyne.ThemeSizeName) float32 {
	return theme.DefaultTheme().Size(name)
}

func (m XTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
	return theme.DefaultTheme().Color(name, variant)
}

func (m XTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
	return theme.DefaultTheme().Icon(name)
}

应用自定义的主题文件

在应用中使用 SetTheme() 方法设置自定义的主题。

a := app.New()
a.Settings().SetTheme(&xtheme.XTheme{})

方案二:使用 ENV 环境变量

使用 github.com/flopp/go-findfont 这个库来通过环境变量查找字体。

import (
	"github.com/flopp/go-findfont"
	"github.com/golang/freetype/truetype"
)

func main(){
  fontFilePath := "./fonts/SourceHanSerifSC-VF.ttf" //
	fontPath, e := findfont.Find(fontFilePath)
	if e != nil {
		panic(e)
	}
	fmt.Printf("Found 'fontFile' in '%s'\n", fontPath)
	fontData, e := os.ReadFile(fontPath)
	if e != nil {
		panic(e)
	}
	_, e = truetype.Parse(fontData)
	if e != nil {
		panic(e)
	}
	os.Setenv("FYNE_FONT", fontPath)
	os.Setenv("FYNE_FONT_MONOSPACE", fontPath) // 注意:如果不设置这个环境变量会导致 widget.TextGrid 中的中文乱码

}

关于 TextGrid 组件的中文乱码问题:

在网上查到的设置环境变量的方式基本都是只设置了 FYNE_FONT_MONOSPACE 这个环境变量,
设置后 widget.TextGrid 组件内的中文仍然是乱码(其它组件内的中文能正常显示),并且通过打包字体文件到 go 文件的方式也不会对 widget.TextGrid 生效,
直到设置了 FYNE_FONT_MONOSPACE 环境变量后才解决了 TextGrid 组件的中文乱码问题。

mac 平台上交叉编译 fyne 项目到 windows 平台

直接交叉编译时,会提示 build constraints exclude all Go files in /path/go/pkg/mod/github.com/go-gl/gl@.. 的错误。编译时添加环境变量 CGO_ENABLED=1 后,提示错误 gcc_libinit_windows.c:8:10: fatal error: 'windows.h' file not found

解决方法:

安装 mingw-w64brew install mingw-w64 编译: env CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -o 'bin/project_gui.exe'

viem: TypeScript Interface for Ethereum https://github.com/wevm/viem

更换手机时,忘记备份 MFA 代码,无法登录 Oracle Cloud 网站。 解决方法: 在 Oracle Cloud 网站登录,输入邮箱密码后,会跳到一个类似如下的网址

https://idcs-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.identity.oraclecloud.com/ui/v1/signin

获取网址中 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 的值,将其替换在如下网址中进行访问:

https://idcs-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.identity.oraclecloud.com/ui/v1/myconsole?root=my-info&my-info=my_profile_security

在此页面可生成新的绕过码、修改密码、管理其它的两步认证,或进行其它安全设置。

(其实我有点好奇为什么会有这样的东西存在)

使用 puppeteer 操作 Chrome 的 MetaMask 扩展时无法选择元素的问题

使用 puppeteer 操作 MetaMask 钱包时,能进行对指定元素进行点击、输入等操作,但使用 page.$() 之类的方法获取元素时,报错:

Error: Evaluation failed: Error: LavaMoat - property "Map" of globalThis is inaccessible under scuttling mode. To learn more visit https://github.com/LavaMoat/LavaMoat/pull/360.
  at get (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/runtime-lavamoat.js:11200:17)

解决方法: 修改扩展目录的 runtime-lavamoat.js 文件:

        const {
          scuttleGlobalThis,
          scuttleGlobalThisExceptions,
        } = { "scuttleGlobalThis": true, "others...": "" }

将此处的 scuttleGlobalThis 改为 false 即可。

尝试 NFT 防火墙

替换掉了 iptables。

配置文件 /etc/nftables.conf

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
	chain input {
		type filter hook input priority 0;
                # 允许已建立的连接
                ct state established,related accept
                # 允许回环接口(本地访问)
                iif lo accept
                # 允许 SSH 连接(如果需要)
                tcp dport 22 accept

                # 允许 DNS 查询(来自特定 IP)
                ip saddr 209.123.1.15 tcp dport {53, 80, 443} accept
                ip saddr 209.123.1.15 udp dport {53, 80, 443} accept
                ip saddr 38.100.10.10 tcp dport {53, 80, 443} accept
                ip saddr 38.100.10.10 udp dport {53, 80, 443} accept

						    # 默认禁止:
                tcp dport {53, 80, 443} drop
                udp dport {53, 80, 443} drop

	}
	chain forward {
		type filter hook forward priority 0;
	}
	chain output {
		type filter hook output priority 0;
	}
}

重载配置: nft -f /etc/nftables.conf

WireGuard 配置中排除局域网 IP:

AllowedIPs = ::/0, 1.0.0.0/8, 2.0.0.0/8, 3.0.0.0/8, 4.0.0.0/6, 8.0.0.0/7, 11.0.0.0/8, 12.0.0.0/6, 16.0.0.0/4, 32.0.0.0/3, 64.0.0.0/2, 128.0.0.0/3, 160.0.0.0/5, 168.0.0.0/6, 172.0.0.0/12, 172.32.0.0/11, 172.64.0.0/10, 172.128.0.0/9, 173.0.0.0/8, 174.0.0.0/7, 176.0.0.0/4, 192.0.0.0/9, 192.128.0.0/11, 192.160.0.0/13, 192.169.0.0/16, 192.170.0.0/15, 192.172.0.0/14, 192.176.0.0/12, 192.192.0.0/10, 193.0.0.0/8, 194.0.0.0/7, 196.0.0.0/6, 200.0.0.0/5, 208.0.0.0/4, 1.1.1.1/32, 8.8.8.8/32

puppeteer 中设置代理的认证方式居然不是 browser 级别的, 而是 page 级别的 -_-

import puppeteer, { Browser, Page, ElementHandle } from 'puppeteer';
import * as utils from "./utils";

(async () => {
    const chromeDataDir = './_chrome_data/temp-test';
    let browser = await puppeteer.launch({
        headless: false,
        userDataDir: chromeDataDir,
        devtools: false,
        args: [
            `--disable-web-security`,
            // '--disable-site-isolation-trials',
            // `--disable-extensions-except=${extensionPath}`,
            // `--load-extension=${extensionPath}`,
            // `--no-sandbox`,
            `--proxy-server=123.1.1.1:9527`
        ],
    });
    let page = await browser.newPage();
    await page.authenticate({ username: 'user-001', password: 'XXnKPccc' })
    await page.goto('https://ip.sb');
    await utils.sleepMinutes(999);
})();

在使用 puppeteer 操作 chrome 浏览器时,
当唤起浏览器插件时,无法在插件页面读、写剪贴板,
似乎这是个 bug:

https://github.com/dvdvdmt/puppeteer-clipboard-read-bug

很多时候写框架的人关注的是性能、可扩展性之类的东东, 但写业务逻辑的更关注如何方便的实现某个功能。

https://github.com/gofiber/fiber/issues/2195

MySQL 8 中使用 JSON_TABLE 创建 JSON 临时表进行 JSON 复杂查询

MySQL 8 中使用 JSON_TABLE 创建 JSON 临时表进行 JSON 复杂查询

比如有个用户表信息表,使用 plans 字段存储了用户的套餐信息,这个表长这样:

mysql> SELECT id,email,plans from users ;
+-------+-----------+--------------------------------------------------------------------------------------------------------------------------------------------+
| id    | email     | plans                                                                                                                                      |
+-------+-----------+--------------------------------------------------------------------------------------------------------------------------------------------+
| 10000 | t0@t0.com | [{"id": 101, "name": "套餐1", "expired_at": "2024-05-01 10:12:31"}, {"id": 102, "name": "套餐2", "expired_at": "2024-08-05 07:41:16"}]     |
| 10001 | t1@t1.com | [{"name": "套餐2", "expired_at": "2023-12-11 05:07:11"}, {"name": "套餐3", "expired_at": "2023-11-08 16:02:51"}]                           |
| 10002 | t2@t2.com | [{"name": "套餐4", "expired_at": "2024-01-20 17:24:33"}]                                                                                   |
+-------+-----------+--------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

想要根据 plans 字段 JSON 数组中的 name 字段获取指定套餐的用户及到期时间,但这里试图使用 JSON_EXTRACT(plans, '$[n].expired_at') 提取 JSON 中的字段时却无法知道索引 n 的值的。

这种情况可以使用 JSON_TABLE 将 JSON 数组中的值创建临时表来解决:

mysql> SELECT
    ->     u.id,
    ->     u.email,
    ->     json_tab.plan_name,
    ->     json_tab.plan_expired_at
    -> FROM
    ->     users AS u
    ->     CROSS JOIN JSON_TABLE(
    ->         `plans`, '$[*]'
    ->         COLUMNS (
    ->             plan_id INT PATH '$.id' ERROR ON ERROR,
    ->             plan_name VARCHAR(40)PATH '$.name',
    ->             plan_expired_at datetime PATH '$.expired_at'
    ->         )
    ->     ) AS json_tab
    -> WHERE
    ->     plan_name = '套餐2';
+-------+-----------+-----------+---------------------+
| id    | email     | plan_name | plan_expired_at     |
+-------+-----------+-----------+---------------------+
| 10000 | t0@t0.com | 套餐2     | 2024-08-05 07:41:16 |
| 10001 | t1@t1.com | 套餐2     | 2023-12-11 05:07:11 |
+-------+-----------+-----------+---------------------+
2 rows in set (0.00 sec)

解决甲骨文 Ubuntu 无法连接的问题

薅了一个甲骨文的免费机器,在控制面板设置了开放端口后居然还连接不上。 似乎它预置的 Ubuntu 镜像默认是关闭了所有端口需要安装防火墙打开?

sudo apt install firewalld

sudo firewall-cmd --zone=public --permanent --add-port=9090/tcp
sudo firewall-cmd --zone=public --permanent --add-port=9090/udp

sudo firewall-cmd --reload

Caddy 通过日志模板配置案例

在 Caddyfile 中可以先定义一个名为 log 的配置模板,并且在配置模板内使用传入的参数来对每个站点使用不同的目录。
各站点导入模板并传入自己的参数即可。

(log) {
    log {
        output file /log/{args.0}/access.log {
            roll_size 100MiB
            roll_local_time
            roll_keep 10
            roll_keep_for 2160h
        }
    }
}

a.example.com {
    import log a-site-log
}

b.example.com {
    import log b-site-log
}

青菜炒牛肉

最近一直想做一份好吃的青菜炒牛肉。
想做出记忆中的味道,
然而尝试了多次都未成功。

小时候有一次在舅舅家,村里有头牛从山上摔下摔死了,
那天晚上我吃了一顿记忆中最好吃的青菜牛肉,

但我已经记不清它具体是啥味了,
甚至也记不清到底是用的啥青菜一起做的了。

Go 使用大小写来决定结构体中字段是否导出真的很蛋疼,当我需要用 gob 序列化某个结构体时不得不进行大量重命名…

最近维护了两个几年前的代码,突然意识到 Go 语言这种非常依赖 github 之类的仓库来管理第三方依赖的方式非常不靠谱啊。

一不小心某个依赖库就没了…

使用 frps 以 stcp 方式转发 ssh 流量

使用 frp 的 stcp 方式进行流量转发比起 直接使用 tcp 的方式相对增加了一些安全性,但也更繁琐(除了在受控机需要安装 frp 外还需要在主控方也安装)。

主控机

[common]
server_addr = 1.2.3.4
server_port = 7000

[secret_ssh]
type = stcp
# stcp 的访问者身份:  'server' or 'visitor'
role = visitor
# 要访问的 stcp 代理的名字
server_name = secret_ssh
# sk 需要与受控机需要保持一致
sk = MY_SK
# 绑定本地端口用于访问 SSH 服务
bind_addr = 127.0.0.1
bind_port = 6000

受控机:

[common]
server_addr = 1.2.3.4
server_port = 7000

[secret_ssh]
type = stcp
sk = MY_SK
local_ip = 127.0.0.1
local_port = 22

然后就可以在主控机上通过 ssh -oPort=6000 ubuntu@127.0.0.1 连接到受控机的 22 端口

ZeroTier 创建私有服务器节点(Moon节点)

没有公网 IP 的 NAS 无异于被阉割的太监。现在各运营商对公网 IP 管理政策来看怕是不会再有了,所以还是另寻它路。

目前常见的有 FRP、NPS、 ZeroTier 等方案。

FRP 是自己搭建一个公网服务器,所有流量经过公网服务器中转。ZeroTier 默认使用官方的一组根服务器进行节点查找。

ZeroTier 也允许自己在服务器上搭建自己的节点,这种节点称之为 moon 节点。 (官方根节点称之为行星节点,自建节点叫月球节点,很合情合理是吧 )

定义自己的 moon 世界

首先使用官方脚本安装 ZeroTier:

curl -s https://install.zerotier.com/ | sudo bash

安装完成后会自动创建服务,并且显示节点 ID:


Created symlink /etc/systemd/system/multi-user.target.wants/zerotier-one.service → /lib/systemd/system/zerotier-one.service.

*** Enabling and starting ZeroTier service...
Synchronizing state of zerotier-one.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable zerotier-one

*** Waiting for identity generation...

*** Success! You are ZeroTier address [ abcxxxxx123 ].

ZeroTier 将被安装在 /var/lib/zerotier-one/ 位置,该目录下包含自己的身份信息文件 identity.publicidentity.secret (相当于公钥和私钥)。

使用 zerotier-idtool 工具从 identity.public 生成 moon 配置文件 moon.json

zerotier-idtool initmoon /var/lib/zerotier-one/identity.public >> moon.json

生成的 moon.json 文件结构如下:

{
 "id": "3f80270f25",
 "objtype": "world",
 "roots": [
  {
   "identity": "xxxx",
   "stableEndpoints": []
  }
 ],
 "signingKey": "xxxx",
 "signingKey_SECRET": "xxxx",
 "updatesMustBeSignedBy": "xxxx",
 "worldType": "moon"
}

编辑上一步生成的 moon.json 文件中的 roots 部份,向 roots.stableEndpoints 部份添加稳定节点信息:

{
  "stableEndpoints": [ "服务器IP:9993" ]
}

使用 zerotier-idtool genmoon moon.json 命令得到一个签名后的文件(使用 moon.json 中定义的密钥签名),文件名类似于: 000000<nodeID>.moon

加入自己的 moon

在各节点,安装完 ZeroTier 后, 在程序安装目录(Linux:/var/lib/zerotier-one、 Mac: /Library/Application Support/ZeroTier/One )内创建名为 moons.d 的目录,将上一步在服务器节点生成的类似于 000000<nodeID>.moon 的文件复制到 moons.d 目录内。

在其它节点,可以通过2种方式加入到 moon 节点: - 将上面创建的世界定义文件 000000<nodeID>.moon 复制到节点自己的 moons.d 目录,并重启服务; - 使用命令 sudo zerotier-cli orbit 000000<nodeID> 000000<nodeID> 加入 moon, orbit 命令的第1个参数是 Moon 节点ID(moon.json 文件的 id 字段), 第2个参数是 moon.json 文件中 roots 字段定义的任意节点,可以与第1个参数相同。

验证

然后在常规节点使用 sudo zerotier-cli listpeers 查看节点时,就能看到 moon 节点的类型已经从 LEAF 变成了 MOON