2023年

整理一下在广州的City Walk路线,顺便记录一下带娃经历。

1. 概述

一般问广州本地人有啥好玩,都说没啥好玩。只缘身在此山中?因此,以非广州的广东人身份聊下这个话题。作为周边城市的居民,我觉得广州非常适合周末逛逛、带娃,切换一下生活空间,也给孩子涨涨见识。

2. 交通

总的来说,外地游客不建议自驾,并尽量乘坐公共交通工具。除非确认目的地有停车位置,并且确认行车路线不堵车。

2.1. 地铁

建议日程规划以地铁为主,虽然换乘站点或热门站点有点挤,但不会堵车。

2.2. 船

注意不是夜游珠江那种本地人都不坐的游船。最近才发现广州的码头之间有“水上巴士”,票价最低2元/人,小孩1米3以下免费。具体航班详见微信公众号“广州客轮”。可以在旅途增添乐趣。

2.3. 其它

比如共享单车。

3. 路线

3.1. 北京路步行街

地铁站[公园前] -> 动漫星城 -> 北京路 -> 天字码头

  • 动漫星城,二次元综合商城。经常周末出现cosplay聚会。
  • 北京路,商业步行街。个人比较喜欢 Baleno 特价店。
  • 北京路宋朝古迹,宋朝的地砖。
  • 大佛寺,据说千年历史。
  • 天字码头,可坐水上巴士。

3.2. 一德路

地铁站[海珠广场] -> 万菱广场 -> 一德路 -> 石室圣心大教堂 -> 清平路 -> 广州文化公园 -> 沙面岛

  • 海珠广场。印象中没逛过这广场。
  • 万菱广场,文具玩具商场。
  • 一德路,玩具、文具、海味批发街。
  • 石室圣心大教堂,宏伟且独具特色的教堂。
  • 清平路,宠物交易市场。
  • 广州文化公园,沙面附近,里面有个十三行博物馆。
  • 沙面岛,西方历史建筑群,适合拍照打卡。

3.3. 广州塔

地铁站[广州塔] -> 广州塔广场 -> APM线 -> 海心沙 -> 广州图书馆 -> 广东省博物馆

  • 广州塔,又名“小蛮腰”,广州地标建筑。顶部有很贵的摩天轮。一般不上去。
  • 广州塔广场,可以跟广州塔合影。
  • 海心沙,亚运会 2010 年在广州的开闭幕式主场馆,已改造成海心沙亚运公园。
  • APM 线,一段很短的地铁,采用透明玻璃的车头,是小朋友必争的位置。
  • 广州图书馆,特色建筑物。
  • 广东省博物馆,有 1:1 的恐龙骨复制品,包括一只 3 层楼高的长颈龙。参观需预约。

3.4. 广州大学城

地铁站[大学城北] -> 广州大学城 -> 广东科学馆 -> 岭南印象园

  • 广州大学城,有10所高校,适合省内小朋友参观立 flag。注:未知是否对外封闭。
  • 广东科学中心,很大,能逛一天,尽量避免暑假等小孩多的日子(排队浪费时间)。需购票。
  • 岭南印象园,岭南传统风格建筑群落。参观需购票。

3.5. 荔湾

地铁站[长寿路] -> 永庆坊 -> 李小龙祖居 -> 荔枝湾 -> 上下九

  • 永庆坊,特色建筑群,有个粤剧博物馆。
  • 李小龙祖居,没去过。
  • 荔枝湾,广州著名河流。
  • 上下九,商业步行街。

3.6. 大型公园

地铁站[大塘] -> 广州海珠国家湿地公园 -> 广州文化馆新馆

  • 广州海珠国家湿地公园,大型公园,据说南门有10亩樱花,花期在3月。海珠湖免费预约,湿地公园需购票。
  • 广州文化馆新馆,大型特色建筑群,主要是唐朝风格,最好穿古装拍照。需免费预约。

3.7. 动物园

介绍几个动物园相关景点。

  • 长隆野生动物世界

    • 1997 年开幕时叫“香江野生动物世界”,当时已经很震撼。现在,个人认为除了人多,没有其它缺点。
  • 广州动物园

    • 门票便宜是最大优点。未去过,未能提供参考意见。
  • 芳村花鸟鱼虫新世界

    • 大型的树木、花卉、宠物、造景市场。我觉得是,可以购买动物的动物园。

3.8. 其它

很多景点还没去过,欢迎补充路线或相关信息。

  • 白云山,广州著名的山。需购票进入。
  • 南越王博物院(王墓展区),岭南两千年的历史遗迹。成人门票10元。
  • O2Park,门口是哈利波特主题餐厅和咖啡厅,提供斗篷(15元/30分钟)和魔法棒(15元/30分钟)租赁。
  • 中山大学,广东省顶尖高校,目前封闭管理,只能门口打卡。
  • 太古仓码头,特色商业区,一般会提到日落很美。
  • 东山口,流行经典。

虽然MP3格式存在很多缺点,但是其通用性强、文件大小范围下保证一定的音质,所以存储的音乐文件还是使用MP3格式。

这里记录一下Ubuntu上的相关工具。

1. SoundConverter

获取到的音频文件,其格式可能是整盘录制成FLAC或APE,先转换成MP3格式,再切分。

2. Mp3splt

如果是整盘音频文件,一般会自带CUE文件,描述包含音轨的时间、名称等信息。音频文件转换成MP3后,需要改一下CUE文件里所指定的音频文件名,才能用Mp3splt执行切分。

参考命令格式:

mp3splt -c music.cue music.mp3

3. EasyTAG

用来修正MP3文件的Tag信息,包括内嵌图片。

公司的Web项目,处理图片延迟加载时,遇到一个图片预留占位的显示问题。记录一下相关解决方案。

1. 需求

本来要解决的问题是,图片“延迟加载”。然后图片未加载之前,需要预留占位,避免加载后撑大页面(主要是为了更好看吧)。

2. 解决方案

图片延迟加载,使用<img>loading="lazy"即可。现代浏览器,当前对其支持还不错。另外,最好不用使用CSS的background加载图片,因为没有很好(或者说简单)的延迟加载解决方案。

对于图片预留占位,主要根据界面设计的布局,选择不同的处理方法。比较麻烦的是,需要自适应浏览器窗口大小的情况。如下:

  • 已知图片宽高。可以随便整。
  • 图片框固定大小。使用CSS的object-fit获得最佳显示效果。
  • 固定列数的图片框自适应浏览器窗口。按浏览器窗口自动计算图片框的宽度。
  • 图片框宽度自适应。使用动态的正方形图片框。这是最终采用的方案。

2.1. 已知图片宽高

可以设置图片按比例缩放,或者按最长边等比例缩放。这个不用说了。

2.2. 图片框固定大小

例如图片框固定,宽为300px,高为300px。图片保持比例,并完整显示,使用CSS的object-fit:contain;。代码如下:

<div style="width:300px; height:300px;">
  <img src="..." style="width:100%; height:100%; object-fit:contain;" />
</div>

2.3. 固定列数的图片框自适应浏览器窗口

一般是列表中带图片的情况,可以使用CSS的单位vwvh

  • vw,浏览器窗口宽度的1%
  • vh,浏览器窗口高度的1%

关于CSS的长度单位,详见: - CSS:层叠样式表 | MDN

缺点:

  • 如果图片的宽高,不能根据屏幕的宽高计算出来,此方案不适用。
  • 如果图片宽高不能适应图片框的比例,也是使用object-fit:contain;按比例缩放。

以下示例,按4列显示,图片框是宽高都为窗口宽度24%的正方形:

<style>
body{margin:0; padding:0;}
.ItemList{width:100%; margin:0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; justify-content: space-evenly;}
.ItemWrap{width:24%; margin:1vw 0 0; padding:0; display: block;}
.ItemWrap img{width:24vw; height:24vw; object-fit: contain;}
</style>
<ul class="ItemList">
  <li class="ItemWrap"><img src="..." loading="lazy" /></li>
  <li class="ItemWrap"><img src="..." loading="lazy" /></li>
  <li class="ItemWrap"><img src="..." loading="lazy" /></li>
  <li class="ItemWrap"><img src="..." loading="lazy" /></li>
  <li class="ItemWrap"><img src="..." loading="lazy" /></li>
  <li class="ItemWrap"><img src="..." loading="lazy" /></li>
  <li class="ItemWrap"><img src="..." loading="lazy" /></li>
  <li class="ItemWrap"><img src="..." loading="lazy" /></li>
</ul>

2.4. 图片框宽度自适应

动态宽高的正方形图片框,是比较折中和灵活的方案。可以不用知道图片宽高。图片框宽度动态计算,高度设为跟高度一致。示例如下:

<style>
body{margin:0; padding:0;}
.ItemList{width:100%; margin:0; padding:0; list-style:none; display:flex; flex-wrap:wrap; justify-content:space-evenly;}
.ItemWrap{width:24%; margin:1vw 0 0; padding:0; display:block; background-color:#c5a29c;}
.ImgWrap{width:100%; height:0; padding-bottom:100%; overflow:hidden; display:block; position:relative;}
.ImgWrap img{width:100%; height:100%; object-fit:contain; position: absolute;}
</style>
<ul class="ItemList">
  <li class="ItemWrap"><a class="ImgWrap"><img src="" loading="lazy"></a></li>
  <li class="ItemWrap"><a class="ImgWrap"><img src="" loading="lazy"></a></li>
  <li class="ItemWrap"><a class="ImgWrap"><img src="" loading="lazy"></a></li>
  <li class="ItemWrap"><a class="ImgWrap"><img src="" loading="lazy"></a></li>
  <li class="ItemWrap"><a class="ImgWrap"><img src="" loading="lazy"></a></li>
  <li class="ItemWrap"><a class="ImgWrap"><img src="" loading="lazy"></a></li>
  <li class="ItemWrap"><a class="ImgWrap"><img src="" loading="lazy"></a></li>
  <li class="ItemWrap"><a class="ImgWrap"><img src="" loading="lazy"></a></li>
</ul>

解析:

  • ul标签,使用灵活的flex样式显示列表。
  • li标签,决定列数。
  • a标签,作为图片框,限制图片显示的宽高。
  • a标签的height:0; padding-bottom:100%;,通过padding-bottom设置高度与宽度一致,是这个方案最巧妙的地方
  • a标签的overflow:hidden;,避免内嵌的图片溢出。
  • a标签的position:relative;,把宽高传给子级。
  • img标签,通过position: absolute;悬浮显示在图片框上面,其宽高获取了图片框(即父级)的宽高。

3. 后续问题

图片加载时,小图最好加载对应的缩略图。体积小,加载快,体验好。但是要考虑图片框大小,避免小图被拉大而导致模糊,降低用户观感。

1. 概述

由于历史原因,工作上,需要把Microsoft SQL Server指定数据库上定时更新的数据,同步到MySQL 8,大概一周一次。

解决方案有几个:

  • 1)采用现成的工具。

    • 但是,一时间没找到好的工具。
  • 2) 开发个程序,读取SQL Server的相关数据,再插入到MySQL。

    • 需要时间开发,且功能上具有针对性。
    • 作为长期使用的工具,这是最优的方案。
  • 3) 从SQL Server生成MySQL的insert语句,再到MySQL上执行。

    • 很多数据库管理工具都提供了数据迁移功能,例如:MySQL Workbench。
    • 如果数据结构不变,insert语句是稳定的,这方案也不错。
    • 要注意采用批量插入,提高导入性能。
  • 4) 从SQL Server导出格式化数据,例如CSV文件,再导入到MySQL。

    • 由于保存数据的CSV文件比较通用,相关的数据库管理工具都支持导入导出。
    • 但是要注意要处理数据格式、NULL数据等问题。

目前采用了第3个方案,CSV文件比较通用,也不用考虑怎么开发。但是偶尔会出现导入MySQL失败的问题,比如出现了NULL数据。后面应该会写个程序处理,直接生成insert语句。

2. SQL Server导入导出CSV

一般使用BCP命令。即Bulk Copy Program,是一个命令行工具,用于在SQL Server之间批量传输数据。由于微软推出了SQL Server for Linux,所以可以完全在Linux执行导入导出的操作。另外,微软提供了SQL Server for Linux的官方Docker镜像,比Windows上安装SQL Server Express更方便,非常适合开发测试使用(主要应付历史)。

官方介绍及参考文档如下:

2.1. BCP使用说明

直接运行bcp命令,会提示其用,如下:

> C:\Program Files (x86)\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn\bcp.exe

用法: bcp {dbtable | query} {in | out | queryout | format} 数据文件
  [-m 最大错误数]             [-f 格式化文件]       [-e 错误文件]
  [-F 首行]                   [-L 末行]             [-b 批大小]
  [-n 本机类型]               [-c 字符类型]         [-w 宽字符类型]
  [-N 将非文本保持为本机类型] [-V 文件格式版本]     [-q 带引号的标识符]
  [-C 代码页说明符]           [-t 字段终止符]       [-r 行终止符]
  [-i 输入文件]               [-o 输出文件]         [-a 数据包大小]
  [-S 服务器名称]             [-U 用户名]           [-P 密码]
  [-T 可信连接]               [-v 版本]             [-R 允许使用区域设置]
  [-k 保留 Null 值]           [-E 保留标识值]
  [-h"加载提示"]              [-x 生成 xml 格式化文件]
  [-d 数据库名称]

2.2. BCP导出CSV文件

CMD批处理命令参考如下:

set CUR_PATH=%~dp0
set BCP="C:\Program Files (x86)\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn\bcp.exe"
set BCP_PARAM=-S "IP,端口" -U "用户名" -P "密码" -d "数据库名" -t \t -b 1000 -c -C 65001 -k

rem 导出指定数据库表的数据
%BCP% 数据库名.dbo.表名 out %CUR_PATH%table_export.csv %BCP_PARAM%

rem 导出指定查询语句的数据
%BCP% "select查询语句" queryout %CUR_PATH%query_export.csv %BCP_PARAM%

参数说明:

  • -S 服务器名称,服务器IP与端口之间,使用英文逗号(即“,”)分隔。
  • -t 字段终止符,默认是Tab符号(即“/t”)。
  • -b 批大小,如果导出数据太多,需要分页操作,默认是1000。
  • -c 字符类型,设置导出文件的字符编码为UTF-8时,设置为“-c -C 65001”,要注意大小写。

要注意,导出的CSV文件不带字段名称。需要记录字段名称时,目前只能把字段名称插入到CSV文件的第一行,并且以数据行的分隔符号进行分隔。

2.3. BCP导入CSV文件

要先创建对应的表,才能执行导入。格式如下:

set BCP_PARAM=-S "IP,端口" -U "用户名" -P "密码" -d "数据库名" -t \t -b 1000 -c -C 65001 -k
bcp 数据库名.dbo.表名 in 数据文件.csv %BCP_PARAM%

2.4. SQL语句执行BCP命令

要注意,用户需要授权可执行xp_cmdshell的权限。官方详细说明如下:xp_cmdshell (Transact-SQL) - SQL Server | Microsoft Learn

格式如下:

exec master..xp_cmdshell 'bcp ...'

2.5. SQL Server的其它导入导出方案

3. MySQL导入导出CSV

3.1. 相关参考

参考文章:MySQL导出数据为CSV的方法

MySQL官方文档:

3.2. MySQL导入CSV

假如导入数据到数据表user_table,该表有字段id、name、remark,其中remark数据可能为NULL。

-- 导入CSV文件的SQL语句
load data infile '/var/lib/mysql-files/import_data.csv' into table user_table fields terminated by '\t' escaped by '' optionally enclosed by '"' lines terminated by '\n' ignore 1 lines (id,name,@remark) set remark=nullif(@remark,'');

说明:

  • CSV文件需要放在mysql用户有权限的目录,比如/var/lib/mysql-files/
  • fields terminated by '\t',表示CSV数据以TAB符号分隔。
  • escaped by '',设置转义字符,默认的是反斜杠(backslash:\ ),设置空值('')表示不适用转义。
  • optionally enclosed by '"',以双引号包裹单一字段的数据。
  • lines terminated by '\n',每行数据的结束符号。
  • ignore 1 lines,导入数据时跳过第一行,因为第一行是字段名称的说明。
  • (id,name,@remark),把一行数据关联到对应的字段。其中@remark是把数据赋值到变量,后面有特殊处理。
  • remark=nullif(@remark,''),表示remark字段的数据,根据@remark变量进行处理。这里是NULL数据转为空字符串。

3.3. MySQL导出CSV

-- 导出CSV文件的SQL语句
select * from user_table into outfile '/tmp/expor_data.csv' fields terminated by '\t' escaped by '\\'  optionally enclosed by '"' lines terminated by '\n' ;

说明:

  • fields terminated by "\t",表示CSV数据以TAB符号分隔。
  • escaped by '\\',设置转义字符,默认的是反斜杠(backslash:\ ),设置空值('')表示不适用转义。
  • optionally enclosed by '"',以双引号包裹单一字段的数据。
  • lines terminated by '\n',每行数据的结束符号。

4. SQL语言的concat_ws函数

SQL的select语句可以使用concat_ws函数,可以实现一行数据的所有字段值合并成一个字符串,并指定分隔符号。然后把查询结果保存为文本文件(包括CSV),即实现了数据导出。参考文档如下:

Windows 11的任务栏,确实很鸡肋。于是想办法做功能增强。

1. 存在问题

对比历史版本Windows(XP、7、10)的任务栏,Windows 11任务栏存在以下缺点:

  • 可自定义功能不多
  • 不支持任务平铺,即不进行分组合并显示(注,2023-11-20,发现Win11更新后可以设置任务栏图标不合并)
  • 不能添加快捷菜单集合
  • 不能调整高度,占据屏幕空间比较多

2. 可选方案

2.1. ExplorerPatcher

2.2. StartAllBack

2.3. CLaunch

  • 项目地址:CLaunch
  • 优点:免费,轻量,对系统没有任何影响。
  • 缺点:只是个弹出菜单(不能替代任务栏),不开源。

3. 解决方案

最后,我选择了基于CLaunch,实现任务栏辅助。主要是解耦,避免对系统的影响,也便于维护、迁移和备份。

3.1. 任务栏设置

任务栏设置为自动隐藏,节省屏幕空间。

注:由于任务栏可以设置图标不合并,没必要隐藏任务栏了。

3.2. 部署CLaunch

其当前最新版v4.05,由于在virustotal.com被大量杀毒软件检测出木马,所以选择了v4.04。

皮肤推荐:yurafuca/claunch-win10: Windows 10 Flavored CLaunch Skin.

我比较喜欢只有一列的布局(像《刀剑神域》游戏里的菜单),并且设置显示规则为:

  • 双击屏幕左侧边缘显示
  • 双击桌面显示
  • 快捷键 Ctrl + Shift + Q 显示
  • 任务栏添加固定图标,点击即可显示

3.3. 常用快捷方式

常用的快捷方式,会保存到指定文件夹,在CLaunch指向该文件夹,并以子菜单显示。这样维护、备份、迁移都比较简单。

3.4. 平铺显示任务列表

Windows 11自动一个平铺任务列表,按 Win + Tab 显示。可以编写vbs文件,并在Claunch设置为按钮,现实点击显示。相关代码如下:

rem 文件名:tasks.vbs
rem 运行平铺显示的任务列表,Windows默认快捷键:Win + Tab
rem 参考:https://learn.microsoft.com/en-us/windows/win32/shell/shell-windowswitcher
set objShell = CreateObject("shell.application")
objShell.WindowSwitcher
set objShell = nothing

3.5. 添加快捷功能

比如要在Claunch添加按钮,实现显示桌面,可以使用vbs模拟按键实现。相关代码如下:

rem 文件名:desktop.vbs
rem 显示桌面,Windows默认快捷键:Win + D
rem 参考:https://learn.microsoft.com/en-us/windows/win32/shell/shell-toggledesktop
set objShell = CreateObject("shell.application")
objShell.ToggleDesktop
set objShell = nothing

关于vbs模拟按键,可以参考:VBS自动按键大全,vbs基本和特殊按键 - 笨蛋敏 - 博客园

例如要实现显示Windows开始菜单,可以使用Ctrl + ESC,模拟Win按键。相关代码如下:

rem 文件名:start.vbs
rem 显示开始菜单,Windows默认快捷键:Win
Set wsShell = WScript.CreateObject("WScript.Shell")
wsShell.SendKeys "^{ESC}"
set wsShell = nothing

3.6. 通讯软件的消息提醒

通讯软件有消息时,任务栏有提醒效果。如果任务栏自动隐藏,收到消息时会自动显示。如果觉得还不够,可能会导致错过消息,可以设置通讯软件(例如TIM),收到新消息就弹出对话窗口。

3.7. 显示时间

目前没有很好的解决方案。要么就装个显示时间的软件(例如 DesktopClock),要么就弹窗显示当前时间(代码如下)。但是两个方案都不够好用。

rem 文件名:show_time.vbs
rem 弹出当前日期时间
rem 注意:本VBScript文件涉及中文显示,需要使用ANSI编码
rem
rem 参考:
rem https://learn.microsoft.com/en-us/windows/win32/shell/shell-windows

rem 弹窗标题
title = "当前时间"
rem 弹窗保持的秒数,指定时间之后自动关闭。0为不自动关闭。
holdSec = 3

curDateTime = Now()
weekdayInt = Weekday(curDateTime)
Select Case weekdayInt
  Case 1
    weekdayStr="星期日"
  Case 2
    weekdayStr="星期一"
  Case 3
    weekdayStr="星期二"
  Case 4
    weekdayStr="星期三"
  Case 5
    weekdayStr="星期四"
  Case 6
    weekdayStr="星期五"
  Case 7
    weekdayStr="星期六"
  Case Else
    weekdayStr="星期日"
End Select

msg = Year(curDateTime) & "-" & Right("0" & Month(curDateTime), 2) & "-" & Right("0" & Day(curDateTime), 2) 
msg = msg & vbCrLf & Right("0" & Hour(curDateTime), 2) & ":" & Right("0" & Minute(curDateTime), 2) & ":" & Right("0" & Second(curDateTime), 2)
msg = msg & vbCrLf & weekdayStr

rem CreateObject("Wscript.Shell").Popup msg, 3, title, 64
Set objShell = CreateObject("WScript.Shell")
objShell.Popup msg, holdSec, title, 64
Set objShell = nothing

上个月,发现“cloudflare.com”被解析为“127.0.0.1”,于是研究了一下DNS污染。

1. 检测

可以直接使用相关网站,检查各个DNS针对指定域名的解析是否正确。例如:

也可以使用命令或工具,查询使用指定DNS解析指定域名的结果。nslookup命令的示例如下:

# nslookup 域名 DNS地址
nslookup cloudflare.com 223.5.5.5

2. 解决方案

一般设置DNS为可靠的共用DNS即可。由于是网络运营商的DNS发现的污染,所以不推荐使用三大运营商的DNS。暂时改为使用“阿里公共DNS”。

最简单的是,修改网络出口设备(例如路由器)的DNS,所有网络设备(例如手机、电脑)都使用其默认DNS。比较麻烦的是,各个网络设备各自设置DNS。

国内外的免费公共DNS,可参考:

几个比较有名的DNS如下:

3. 扩展内容

3.1. 关于nslookup命令

一般各大系统都有nslookup命令。对于Debian 11和Ubuntu 22.04,可能没有默认安装nslookup,需要手动安装。

# Debian或Ubuntu,安装nslookup命令
sudo apt install bind9-dnsutils

# 查看域名的DNS A记录解析
nslookup -type=A cloudflare.com 223.5.5.5

该命令的详细说明,可以参考man nslookup或Debian官方文档:nslookup(1) — bind9-dnsutils — Debian bookworm — Debian Manpages

3.2. 如何修改DNS配置

3.2.1. 总结

  • 路由器,进入其管理后台,修改DNS配置。
  • 网络终端设备(电脑,手机等),需要明确配置DNS的范围,一般是:全局、指定网络接口、指定代理服务。
  • 全局DNS,需确定管理DNS服务的程序,再修改其配置文件。
  • 指定网络接口的DNS,一般修改其网卡设置。
  • 代理服务的DNS,一般不走本地设置,需要参考该代理服务的配置说明。

3.2.2. 关于配置全局DNS

对于Linux(例如Debian 12),一般查看/etc/resolv.conf文件,可以了解当前使用什么DNS。直接修改该文件,可以更改当前全局DNS,例如:

# 设置DNS为阿里DNS
nameserver 223.5.5.5

但是,如果有其它程序接管了/etc/resolv.conf文件,比如systemd-resolved服务,系统重启后会该文件被重置,导致设置无效。注意各个系统的情况不同,比如:

  • Debian 11/12,默认没有安装systemd-resolved
  • Ubuntu 22.04,默认安装并启用systemd-resolved

3.2.3. 关于配置网络接口DNS

  • 图形界面,通过网络配置,修改相应的DNS。

    • 例如Ubuntu 22.04,使用NetworkManager管理。网络接口的配置文件在/etc/NetworkManager/system-connections/
  • 命令界面,一般修改配置文件/etc/network/interfaces

    • 例如Debian 11/12,其示例配置如下:
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug enp3s0
#iface enp3s0 inet dhcp
iface enp3s0 inet static
    address 192.168.0.100
    netmask 255.255.255.0
    gateway 192.168.0.1
    dns-nameservers 223.5.5.5 192.168.0.1

3.3. 管理DNS缓存

3.3.1. Linux的DNS缓存

  1. 使用systemd-resolve,适合Ubuntu 20.04及以下、Debian 11/12等。
# 清除DNS缓存
sudo systemd-resolve --flush-caches

# 查看DNS缓存情况
sudo systemd-resolve --statistics

对于Debian 11/12,需要启用systemd-resolve服务。

sudo systemctl enable systemd-resolved.service
  1. 使用resolvectl,适合Ubuntu 20.04以上。
# 清除DNS缓存
sudo resolvectl flush-caches

# 查看DNS缓存情况
sudo resolvectl statistics
  1. 重启网络服务,清除DNS缓存,适合一般Linux。
# 基于Init.d的系统
sudo /etc/init.d/networking restart

# 基于SystemD的系统
sudo service networking restart

3.3.2. Windows的DNS缓存

rem 清除DNS缓存
ipconfig /flushdns

3.3.3. Chrome浏览器的DNS缓存

Chrome本身建立了自己的DNS缓存,并提供简单的管理功能。打开链接:chrome://net-internals/#dns 即可。

利用Nginx部署了图片服务,为了实现请求图片时进行鉴权,使用了auth_request的配置。

参考官网文档:Authentication Based on Subrequest Result | NGINX Documentation

整理后的示例代码,如下:

server {
    ...... # 略过一些配置
    underscores_in_headers on; # 可选。允许请求header名称带下划线(_)

    location ^~ /images/ {
        # 配置鉴权规则
        auth_request /auth;

        # 生成鉴权URL,并添加请求参数。
        set $auth_request_uri $uri; # 创建变量,传递鉴权URL
        if ($uri ~ ^/images/(.*)$) {
            # 利用if实现对URL进行字符串替换,生成想要的鉴权URL,并带上请求参数
            # 由于静态文件,尤其是图片,一般使用GET方法,所以URL的请求参数可以作为鉴权参数
            set $auth_request_uri /auth/$1?$args;
        }

        # 将401、403状态码映射为404。即鉴权失败时,让浏览器以为文件不存在。
        # 由于“/404.html”不存在,Nginx会返回默认的404错误页面。
        error_page 401 403 =404 /404.html;

        # 设置图片存放目录,/images/有/结尾,这里也要有/结尾
        alias /opt/sites/pub_img/;
    }

    # 这里配置鉴权后端的请求,即鉴权URL
    location = /auth {
        internal; # 只能内部访问
        proxy_pass http://127.0.0.1:8080$auth_request_uri; # 设置完整的鉴权URL
        proxy_pass_request_headers on; # 默认值。转发所有原始的请求header到鉴权URL
        proxy_pass_request_body off; # 不向鉴权URL接发送原始的请求体
        proxy_set_header Content-Length ""; # 不向鉴权URL发送原始的请求体
        proxy_set_header X-Original-URI $request_uri; # 传递原始的请求URL
    }
}

说明和总结

  1. 此方式能轻松实现静态文件进行灵活的鉴权,甚至是无权限服务添加访问权限。

    • Nginx支持的鉴权配置,有4种:
  2. 使用鉴权URL时,基本目前基于请求头和URL参数的鉴权方式都支持,非常灵活。

    • proxy_pass_request_headers默认为on,会把原始请求Header转发到鉴权URL
    • 生成鉴权URL时,会把原始URL参数传过去。
  3. 鉴权URL的处理,最好跟原始URL无关,并把原始URL作为鉴权参数传过去。本示例代码做了URL字符串替换,对后面扩展不够灵活。
  4. 不建议使用Nginx实现复杂的功能。其配置文件不是代码,不是按顺序执行,也不方便调试。

记录一下今天Lubuntu 22.04.2的桌面环境(LXQt 1.2.0)罢工,以及解决办法。

问题

今天打开电脑,直接进入tty1的字符界面(明明昨晚还是好好的)。登录后执行startlxqt,显示以下提示:

qt.qpa.xcb: could not connect to display
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Available platform plugins are: eglfs, linuxfb, minimal, offscreen, vnc, xcb.

Aborted

就是LXQt不能启动,导致不能进入图形界面环境。

解决

找了几个文章都没有解决。直到看了这个:

[SOLVED]cinnamon doesn't start because of segfault in libgtk-3.so.0.2404.27

该文章提到SDDM。于是执行了安装SSDM命令sudo apt install sddm,重启后就解决了。

总结

  1. 好久没有体验过Linux图形界面的脆弱了。
  2. 这个错误被提及很多,但是涉及的问题各有不同。是Qt5的锅吗?
  3. 习惯了日常升级系统,想不到Lubuntu也踩坑。

最近玩了下Cloudflare,其丰富的免费功能,令我印象深刻。虽然其提供了一键开启的免费SSL服务,但是开发测试环境还是需要用到SSL证书。这里记录一下获取SSL证书的配置。

1. 准备

需要先准备好以下三个方面:

  • 域名

    • 可直接购买,一般按年收费。
    • eu.org提供免费域名,但申请域名的审核时长不定。
  • DNS

    • Cloudflare提供免费DNS服务,需要先免费注册账户。
    • Cloudflare设置域名解析。
  • 证书机构。推荐一直在使用“Let's Encrypt”。其具有以下三个优点:

    • 基于DNS服务验证域名所有权。通过在DNS添加指定的TXT记录实现验证。
    • 支持通配符证书。
    • 客户端程序Certbot实现证书申请和续签。

参考:

2. Cloudflare API Token

  • 登录Cloudflare并进入Dashboard:https://dash.cloudflare.com/
  • 点击右上角的用户头像,再点“My profile”。
  • 点左边菜单的“API Tokens”,再点右边的“Create Token”按钮。
  • 找到“Edit zone DNS”的行,点击右边的“Use template”按钮。
  • “Token name”填写Token名称,“Zone Resources”选“Include”、“Specific zone”、指定的域名,再点下面的“Continue to summary”按钮。
  • 最后点“Create Token”,就显示所申请的API Token了。由于只显示一次,要保存好。

3. Certbot

这里以Debian 12为例,安装、配置和使用Certbot。

Certbot官方网站:Certbot

3.1 安装

Certbot官方安装教程是使用snapd,但Debian 12的软件仓库已提供Certbot,以及cloudflare的插件。

sudo apt update
sudo apt install certbot python3-certbot-dns-cloudflare

3.2 配置

编辑文件/etc/letsencrypt/certbot-dns-cloudflare-credentials.ini,填写以下内容。其中“_cloudflare_api_token_”需要替换为第2步申请到的Cloudflare API Token。

# Cloudflare API token, get SSL for foxail.eu.org
dns_cloudflare_api_token=_cloudflare_api_token_

保存后,设置该文件的权限:

sudo chmod 600 /etc/letsencrypt/certbot-dns-cloudflare-credentials.ini

3.3 申请SSL证书

执行以下命令申请SSL证书。其中:

  • example.com需要改为实际的域名。
  • 选项--key-type ecdsa使用ECDSA密钥。ECDSA使用更短的密钥就可实现和RSA相同的安全级别,意味着更高的效率。
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/certbot-dns-cloudflare-credentials.ini \
  --key-type ecdsa \
  --domain "example.com,*.example.com"

第一次执行会提示域名所有权验证失败,按所给提示,在DNS添加相关的TXT记录即可。即添加DNS记录,类型为“TXT”,名称为“_acme-challenge”,内容为提示所给的。添加成功后,再执行一次刚才的命令。

执行命令后会提示一些问题:

  • 要求输入邮箱,可以跳过。输入邮箱的话,可以接收到证书过期或安全问题的通知。
  • 同意服务条款,输入Y并按回车。
  • 是否订阅电子前沿基金会的邮件,看需要选Y或N。

成功后会提示证书的存储位置。

3.4 更新SSL证书

第一次成功执行Certbot后,会自动创建定时任务/etc/cron.d/certbot,实现自动更新证书,该文件的内容如下:

# /etc/cron.d/certbot: crontab entries for the certbot package
#
# Upstream recommends attempting renewal twice a day
#
# Eventually, this will be an opportunity to validate certificates
# haven't been revoked, etc.  Renewal will only occur if expiration
# is within 30 days.
#
# Important Note!  This cronjob will NOT be executed if you are
# running systemd as your init system.  If you are running systemd,
# the cronjob.timer function takes precedence over this cronjob.  For
# more details, see the systemd.timer manpage, or use systemctl show
# certbot.timer.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew

3.5 删除域名

无效的域名会导致所有证书更新失败。执行以下Shell脚本可以删除指定域名(以www.example.com为例)的相关配置:

sudo rm -rf /etc/letsencrypt/live/www.example.com/
sudo rm -rf /etc/letsencrypt/archive/www.example.com/
sudo rm /etc/letsencrypt/renewal/www.example.com.conf

计划分两部分总结一下Docker的学习。本文先从入门到应用开始,了解Docker并用上它。

0 总结:

本文推荐使用Docker Compose运行和管理容器。

1 概述

1.1 Docker是什么?

Docker是依赖Linux内核,通过建立应用的运行环境,实现虚拟化。其实现方式不是采用硬件级别的虚拟,而是“隔离”。就是通过隔离手段实现软件运行环境的沙箱化。

其底层依赖于Linux的chroot、namespace、cgroup等三种老技术,结合当前系统的Linux内核,建立另一个Linux运行环境,并运行指定软件。试过在Android系统部署Linux分发版(Android也是一个Linux发行版),就是使用了类似的技术。

要注意,Docker的目的不是提供一个完整的虚拟Linux,而是把运行环境和软件组合为服务,去运行和管理。例如Debian官方镜像默认没有提供“服务”(servcie)管理的功能,而是作为一个指定服务的一部分去运行。

参考资料:

1.2 为什么要用Docker?

  • 一次建立,到处运行。

    • 环境和软件一起打包,不用担心测试环境与正式环境的区别。
    • 对于Window平台可以使用WSL运行。
  • 优胜于传统虚拟机。

    • 轻,只虚拟环境。
    • 快,运行快。
    • 小,资源占用小,包括CPU、内存、硬盘等资源。
  • 管理简单。

    • 镜像作为模板,容器作为实例。
    • 多个容器可以关联组合。

例如,部署多个MySQL数据库,并配置不同的参数作对比,那么直接运行多个MySQL的Docker容器即可,其表现为运行在不同IP的多个MySQL服务。

1.3 基本概念

  1. 仓库(Repository)

  2. 镜像(Image)

    • 容器运行时所需的root文件系统(包括程序、库、资源、配置等文件),以及配置参数(包括匿名卷、环境变量、用户等)。
    • 可来理解为容器的模板。其不包含动态数据,构建后不可修改。
  3. 容器(Container)

    • 简单来说,跟面向对象程序设计中的“类”(镜像)和“实例”(容器)一样。
    • 容器包括容器配置、相关文件、相关进程。

1.4 怎样使用?

  1. 再次强调,Docker是建立应用的运行环境,不是虚拟机。所以不要在Docker容器里面跑服务,而是把整个Docker容器作为一个服务进行管理和使用。

    • 例如启动Dokcer容器时,执行一个启动脚本,运行所需软件。
  2. 一般建议把重要数据映射到容器外部的文件系统,避免删除或升级容器时同时删除相关文件,也能减少性能损耗。

    • 例如Docker部署MySQL,建议把数据目录映射出来。
  3. 升级容器时,先下载更新的镜像,再删除该容器,并使用相同的配置文件,建立同名的容器。

1.5 Docker Compose

为了更好地解决多个Docker容器协同部署、工作,诞生了“Docker Compose”。一开始使用“Python”实现的,但到了2.0,Docker官方使用Go重新实现,并以插件形式集成到docker-ce(其软件包名为docker-compose-plugin)。使用docker compose命令(代替1.0的docker-compose命令)进行Docker容器的编排。

Docker Compose另一个的优点是,借助YAML脚本和环境变量,可以轻松配置或修改Docker容器。相比之下,使用docker run命令部署的Docker容器,修改配置需要重启Docker服务(影响其它Docker容器),相当麻烦。所以建议使用Docker Compose的方式运行Docker容器,而且一般Docker会提供Docker Compose的示例配置文件。

1.6 Kubernetes

Kubernetes也称为K8s,是用于自动部署、扩缩和管理容器化应用程序的开源系统。它将组成应用程序的容器组合成逻辑单元(Pod),以便于管理和服务发现。这部分内容,计划另起一个文章再整理。

1.7 参考文档

2 安装部署

2.1 安装

官方安装教程如下:

针对Debian或Ubuntu,总结如下:

  • 推荐使用Docker官方APT仓库进行安装。一般先卸载通过系统仓库安装的自带Docker引擎。
  • 安装Docker CE版,即Community Edition(社区版,免费)。
  • 安装docker-compose插件:sudo apt install docker-compose-plugin

2.2 配置

Docker服务的配置,主要分为命令行参数和配置文档。

详见官方说明文档:

由于这里以SystemD服务的方式运行Docker服务,所以采用配置文件的方式进行配置。在采用SystemD的Linux发行版上(例如Ubuntu 16.04+、Debian 8+、CentOS 7等),对应配置文件为/etc/docker/daemon.json(如没有该文件,需要自行创建)。

修改并保存daemon.json后,一般无需重启Docker服务,发送reload信号即可。如下:

sudo systemctl daemon-reload
sudo systemctl reload docker.service

2.3 设置“仓库”镜像(mirror)网站

如果访问官方“仓库”比较慢,可以使用中国的镜像网站。在配置文件/etc/docker/daemon.json中加入:

{
  "registry-mirrors": ["https://hub-mirror.c.163.com/"]
}

其它可用的加速镜像可参考:

2.4 设置代理

这里是为了解决Docker不能访问第三方“仓库”的网址,导致不能拉取相关“镜像”文件。例如gcr.io/cadvisor/cadvisor.

在配置文件/etc/docker/daemon.json中加入:

{
  "proxies": {
    "http-proxy": "http://proxy.example.com:80",
    "https-proxy": "https://proxy.example.com:443",
    "no-proxy": "*.test.example.com,.example.org,.163.com,192.168.0.0/24,127.0. 0.0/8"
  }
}

其中,http-proxyhttps-proxy是设置相应的HTTP代理服务,no-proxy是设置不走代理的域名或IP。

2.5 设置权限阻隔

默认情况下,“容器”中的进程以root用户权限运行,与“宿主机”中的root是同一个用户。这就意味着一旦“容器”中的进程有了适当的机会,它就可以控制“宿主机”上的一切。

解决方法有两种,一种是使用非root用户运行容器,另一种是用户命名空间(user namespace)方式。

关于用户命名空间的设置,参考下文:

3 使用

3.1 运行“容器”

到“仓库”找所需“镜像”,下载“镜像”后运行对应的“容器”。直接执行“docker run”时,如果本地没有相关“镜像”,会自动在Docker官方“仓库”下载,然后创建“容器”并运行。

一般使用软件官方提交的“镜像”,若要使用第三方“镜像”,最好确认清楚是否安全。

下面以部署Debian容器为例:

# 搜索Debian“镜像”
docker search debian

# 查看“镜像”版本,最好是访问docker官网。例如Debian镜像的版本:https://registry.hub.docker.com/_/debian/tags

# 下载Debian“镜像”。格式是,“镜像”名称:版本名称。如果不填版本名称,默认是Latest。
docker image pull debian:stable

# 列出所有已下载的“镜像”,包括隐藏的。以下三个命令的效果是一样的。
docker image ls -a
docker image list -a
docker images -a

# 查看正在运行的“容器”。以下三个命令的效果是一样的,推荐第一个。
docker container ls
docker container ps
docker container list

# 查看所有“容器”,包括未运行。
docker container ls -a

# 创建并运行“容器”。名为“debian-test”,映射两个端口(本机50080->“容器”40080,本机51080->“容器”41080),并把本地“/opt/docker-files/debian-test/opt”目录映射到“容器”的“/opt”目录。
docker run -it --name debian-test -p 50080:40080 -p 51080:41080 -v /opt/docker-files/debian-test/opt:/opt debian:stable

# 查看“容器”运行日志,检查是否报错
docker logs debian-test

3.2 进入“容器”

进入“容器”。可以使用attachexec命令,但建议使用exec并运行一个新的Bash,避免退出时影响“容器”当前运行的程序。

  1. attach命令。适合“容器”正在运行具有命令行交互的程序,比如“Bash”。以下两个命令是一样的。
# 两个命令是一样的
docker attach debian-test
docker container attach debian-test

要注意,使用attach命令进入“容器”并输入“exit”进行退出时,就等于Bash程序结束执行,“容器”随之停止运行。解决方案:

  • 方案1,“docker attach”进入容器后,按Ctrl + P + Q退出。
  • 方案2,进入容器时,加入参数“--sig-proxy=false”,退出时使用“exit”命令而不停止容器。

    • 例如:docker attach --sig-proxy=false debian-test
  1. exec命令。打开一个具有命令行交互的程序,即进入“容器”。
# 两个命令是一样的,运行Bash并进入
docker exec -it debian-test /bin/bash
docker container exec -it debian-test /bin/bash

# 使用root用户运行Bash,并设置字符编码
docker exec -u root -it debian-test env LANG=C.UTF-8 /bin/bash

退出“容器”就等于退出当前运行的Bash,对“容器”正在运行的程序无影响。

3.4 管理“容器”

# 启动“容器”
docker container start debian-test

# 停止“容器”
docker container stop debian-test

# 重启“容器”
docker container restart debian-test

# 删除“容器”
docker container rm debian-test

3.5 复制“容器”

主要思路是,把指定“容器1”生成“镜像A”,再用“镜像A”创建“容器2”,实现把“容器1“复制为”容器2“。

注意:生成“镜像”时,“容器”外部的文件,不会打包进“镜像”文件。需要手工复制这些文件,并设置映射。

假设已创建“容器”:debian-test,需要复制为另一个“容器”:debian-test2。

# 把“容器” debian-test,生成“镜像”文件
docker export debian-test > /opt/docker_image_debian-test.tar

# 引入“镜像”,并命名为 debian-test:v1
docker import /opt/docker_image_debian-test.tar debian-test:v1

# 创建另一个“容器” debian-test2
docker run -itd --name debian-test2 -p 50081:40080 -p 51081:41080 -v /opt/docker-files/debian-teset2/opt:/opt debian-test:v1 /bin/bash

3.5 Dokcer图形化管理

Docker Desktop下载地址和安装介绍:

4 使用Docker Compose

Docker Compose是以“项目”(project)作为管理单元。一个项目,是由一个或多个“服务”(service,即“容器”)组合而成。

4.1 使用

把Compose配置文件存放到指定目录,进入该目录,并执行docker compose up,即可启动。

4.2 命令说明

格式:docker compose [可选项] 命令。“可选项”和“命令”的说明如下:

可选项说明
--ansi string控制何时打印 ANSI 控制字符。string的可选值为"never"、"always"、"auto",默认为"auto"。
--compatibility以向后兼容模式运行 compose。
--env-file stringArray指定备用环境文件。
-f, --file stringArrayCompose配置文件。
--parallel int控制最大并行度,-1 为无限制。默认-1。
--profile stringArray指定要启用的配置文件。
--project-directory string指定备用工作目录。默认:首先采用所配置的第一个Compose文件的路径。
-p, --project-name string项目名称。
命令说明
build构建或重新构建服务
config以规范格式粘贴、解析和呈现Compose配置文件
cp在服务容器和本地文件系统之间复制文件或文件夹
create为服务创建容器
down停止并移除容器、网络
events从容器接收实时事件
exec在正在运行的容器中执行命令
images列出创建的容器使用的镜像
kill强制停止服务容器
logs查看容器的输出
ls列出正在运行的Compose项目
pause暂停服务
port打印端口绑定的公共端口
ps列出容器
pull拉取服务镜像
push拉取服务镜像
restart重启服务容器
rm删除停止的服务容器
run在服务上运行一次性命令
start启动服务
stop停止服务
top显示正在运行的进程
unpause取消暂停服务
up创建并启动容器
version显示 Docker Compose 版本信息