阅读视图

发现新文章,点击刷新页面。
☑️ ☆

WireGuard组网指南

WireGuard 是一种现代的虚拟私人网络(VPN)协议和软件,提供了高效、安全和易于配置的 VPN 解决方案,由 Jason A. Donenfeld 开发,并于 2018 年正式合并到 Linux 内核中

WireGuard 使用现代加密算法来保障通讯的安全和隐私,其代码库简短便于审计和维护,加密方式采用公钥私钥的方式进行端对端加密

支持的平台也非常广泛包括 Linux、MacOS、Windows 以及移动设备平台 IOS 和 Android

在国内适合用于代理到家中的 VPN 服务,但其流量特征较为明显且不支持混淆,所以不适合于跨境访问

1. 结论

WireGuard 适合需要在外访问家庭网络中的服务场景,例如我是家庭网络中有台 N100 的服务器(其 IP 是192.168.100.1)提供多个微型服务

在外出后,Android 设备只需打开 WireGuard 服务后,就可以访问 192.168.100.1 ,而无需像 Frp 类的内网穿透服务去访问中转服务器的端口

WireGuard 相较于 Frp 的优势:

  1. 省去了逐个逐个端口映射的烦恼
  2. 免去了维护两套 IP 的工作(本地 IP 和服务器 IP 之间来回转换)
  3. 流量加密,客户端兼容广泛

如果自行部署麻烦,可以考虑直接使用第三方容器镜像如 wg-easy ,自带 web 页面管理,非常方便

以下实践基于手动部署,减少对公网服务器的资源占用

2. 组网

这里我有 3 个设备,分别是:

  • N100服务器 192.168.100.1
  • 公网服务器 8.8.8.8
  • 移动设备 iPhone12

以下实践将实现在 5G 网络下直通访问家庭中 192.168.100.1 所有服务

2.1. 公网服务器

安装 WireGuard 服务

Bash
sudo apt update
sudo apt install wireguard

切换到 /etc/wireguard/ 目录下,生成服务端密钥对

Bash
wg genkey | tee server_privatekey | wg pubkey > server_publickey

生成 N100 内网服务器的密钥对,将在稍后的 N100 服务器配置时使用

Bash
wg genkey | tee n100_privatekey | wg pubkey > n100_publickey

生成 iPhone 密钥对,将在稍后的 iPhone 客户端配置时使用

Bash
wg genkey | tee iPhone12_privatekey | wg pubkey > iPhone12_publickey

此时 /etc/wireguard 下应该具备这些文件:

Bash
/etc/wireguard
├── iPhone12_privatekey
├── iPhone12_publickey
├── n100_privatekey
├── n100_publickey
├── server_privatekey
└── server_publickey

编辑 /etc/wireguard/wg0.conf 如下:

INI
[Interface]
PrivateKey = <server_publickey>
Address = 10.0.0.1/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE


[Peer]
# N100
PublicKey = <n100_publickey>
AllowedIPs = 192.168.100.1/32, 10.0.0.2/32

[Peer]
# iPhone12
PublicKey = <iPhone12_publickey>
AllowedIPs = 10.0.0.3/32

请自行更换上述的所有 publickey 值为刚才生成文件内容值

完成后,启动服务:

Bash
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0

记得防火墙要开放 51820 端口的 udp 权限

2.2. N100 服务器

N100 服务器安装的是 Debian12 的服务器,Debian12 已经添加 WireGuard 到仓库中了,安装如下:

Bash
sudo apt install wireguard

编辑 /etc/wireguard/wg0.conf 文件如下:

INI
[Interface]
PrivateKey = <n100_privatekey>
Address = 10.0.0.2/24

[Peer]
PublicKey = <server_publickey>
Endpoint = 8.8.8.8:51820
AllowedIPs = 10.0.0.2/24
PersistentKeepalive = 25

请自行更换上述的 publickeyprivatekey 的值

完成后,启动服务:

Bash
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0

此时可以 ping 10.0.0.1 来确认通信是否顺利,也可以从公网服务器上尝试 ping 192.168.100.1 来确认服务是否已连接

2.3. iPhone12

由于移动设备编辑配置文件相对不易,可以采用二维码的形式导入,在公网服务器上编辑一份适用于 iPhone12 的配置文件

先安装配置文件转二维码的工具:

Bash
apt install qrencode

编辑 /etc/wireguard/iPhone12.conf,内容如下:

INI
[Interface]
PrivateKey = <iPhone12_privatekey>
Address = 10.0.0.3/24

[Peer]
PublicKey = <server_publickey>
Endpoint = 8.8.8.8:51820
# 这里表示将允许 iPhone12 访问 192.168.100.1
AllowedIPs = 192.168.100.0/24
PersistentKeepalive = 25

将这份配置文件转换为二维码显示:

Bash
qrencode /etc/wireguard/iPhone12.conf

在 App Store 中安装 WireGuard,并新建配置,选择二维码形式导入

扫码导入即可,在导入成功后,切换到 5G 网络下并开启服务,然后试试访问 N100 服务器(192.168.100.1)的服务

3. 其他

通过上述的配置,即可实现异地组网,实现在任意网络都可以像访问局域网一样访问到家庭中的网络

但由于 WireGuard 只是加密流量且加密后的流量特征非常明显,用于跨境组网是很容易被阻断的,所以 WireGuard 只适用于国内中转服务器进行组网

🔲 ☆

2024北海道9天旅游攻略

1. 准备

1.1. 日本签证

日本不对国内免签,签证可选途径 淘宝/飞猪 等途径,日本的旅游签证私人无法申请,要通过官方指定的旅社进行签证,官方指定旅社列表(广州):

这次选了三年多次签证,主签所需资料:

  • 赴日申请表
  • 护照原件
  • 身份证、户口本彩色复印件
  • 近半年白底 35*45mm 彩照2张
  • 最近12个月完税证明彩色复印件
  • 最近24个月的完税证明录屏

亲属的副签需要:

  • 赴日申请表
  • 护照原件
  • 身份证、户口本彩色复印件
  • 近半年白底 35*45mm 彩照2张
  • 与主签的关系证明(结婚证、出生证、关系公证等)

三年多次出签后如无意外需要在90天内访问日本一次,如有意外耽搁行程90天内无法出行则通过旅行社向领事馆报备

回国后需要销签(三年多次需要):

  • 往返机票、酒店截图
  • 登机牌拍照
  • 日本签证页
  • 入境章拍照

1.2. 物品清单参考

必需品

类型 物品内容 备注
证件 身份证、信用卡、护照
网络 随身WI-FI或特定流量卡 淘宝
交通 Suica卡或Pasmo卡 iPhone 可以使用虚拟 Suica 卡
电量 电源转换头、充电头、数据线
其他 签字笔

其他

类型 物品
必带 一次性内裤、零钱包、常用药、洗面奶、牙刷牙膏、护肤品
衣服 上衣、裤子、袜子、手套、帽子、睡衣、围巾
女性 化妆包、干发巾
电子设备 充电宝、微单、手持稳定器、无线耳机
其他 便携煮水壶(热水)、手机支架

1.3. 交通

日本的许多交通叫法与国内有区别,大致与国内对比参考如下

日本 国内
各类列车(如:JR特急/普通列车、机场快线) 国内高铁/动车
地下铁(如:南北线、东西线) 国内地铁
路面电车(如:札幌市电、9天狗山索道线) 国内公交车

在日本如果有短期内深度体验某个地区的考虑,可以购买JR PASS(Japan Rail Pass),可以在电商平台购买,可以根据行程来看看是否划算

JR Pass是一种为游客提供的经济实惠的特殊铁路通行证,允许在指定时期内(通常是3/5/7/14/21)无限次乘坐

此外,最常见的交通卡就是关西的ICOCA卡(以大阪为代表)和关东的Suica(以东京为代表),这2张卡都可以在全国范围内使用地铁、巴士以及便利店

注:ICOCA卡和Suica卡仅是发行的地区不同,在使用上可以说没有任何区别

在 2024 年后日本推行了智能支付,在 iPhone 上使用 Apple Pay 直接添加 Suica 卡,随用随充,更加方便

1.4. 入境

在 2024 年 1 月后,日本入境流程支持线上提前申报,比入境时填写效率要高很多

入境流程在下飞机后先检疫录指纹,然后去做入境审查,随后取行李,在海关处申报后入境

入境审查与海关申报现可以提前在线上申报 VJW (Visit Japan Web)

VJW需要资料护照、机票以及一个邮箱,在申请成功会后得到一个二维码,在检疫以及入境审查处可以通过扫码快速通过

1.5. Q & A

不懂英文以及日文影响大吗?

  • 不大,日本人的英文也不怎么样,购买物品处通常有中文售货员

哪些 App 或者网站是必须的?

  • Google 地图
  • Google 翻译
  • kakuku.com 用于查找购物比价的网站

什么是免税?

  • 购物点标识提示 tax free 是针对外国游客的免消费税优惠,在购物时咨询店员是否 tax free 即可
  • 商城免税通常是现场立减或前往购物中心统一退税柜台退税(出关时,游客需要把这些小票摘掉放在指定的购物篮中)

日本有哪些需要注意的?

  • 交通:日本早晚高峰的电车拥挤程度较高,旅行者可尽量避开这个时间段乘车
  • 热水:没有,习惯喝热水的朋友请注意了,在日本普通小饭店一般仅提供自取冰水
  • 烟民:公共场合禁烟,请按照图标寻找吸烟点
  • 小费:日本没有给小费的习惯
  • 砍价:日本所有商品基本全部明码标价,可以询问店员是否有其他折扣优惠
  • 靠左行走:日本人的交通和行走、乘扶梯在大部分情况下靠左;大阪电梯靠右,可注意地上的引导标识

还有哪些小技巧?

  1. 在各大交通站和景点往往都有纸质的旅游指南,而且往往有中文版,带走看一看往往会让你发现不是那么大众的特色玩法和美食
  2. 游玩东京的话,机场和游客中心的绿色东京旅游指南小册子非常有用,不仅详细介绍了东京各个区域的景点和地图,还能够凭借册子获得很多博物馆的票价优惠,这都是本地人难以享受到的游客福利,值得好好利用
  3. 推荐需要戴眼镜的朋友在日本配一副眼镜,日本产的眼镜质量好,价格透明,服务周到,比在国内配高级眼镜要实惠很多
  4. 日本二手名包和名表往往品相新,价格便宜,且保障真品,所以超值

2. 北海道攻略

北海道根据区域划分,可以根据需要自行组合游玩的地点:

区域 景点 特色
道央区域 札幌、小樽、定山溪、登别、洞爷湖 情书取景地、火山温泉、火山不冻湖、粉雪天堂
道北区域 旭川、带广、富良野、美瑛 动物园企鹅散步、欧式村落、花田、十胜川温泉、
道东区域 网走、北见、阿赛湖、钏路 湿地动物、赏雪泡温泉、小狐狸、破冰船看流冰
道南区域 函馆 百万夜景

2.1. 札幌

札幌市(さっぽろし / Sapporo shi)是位于日本北海道道央地区的都市,为北海道首府以及石狩振兴局本部所在地

交通:

  1. 北海道城市之间往返可以全靠JR,淘宝可买3/4/5/6/7日的JR Pass票
  2. JR Pass票可以在新千岁机场下来进行兑换,乘坐时向工作人员出示即可
  3. 新千岁机场前往札幌市区有3种方案
    • 班次少但速度快的JR特别快速Airport号,33分钟,4班/day
    • 班次多但速度慢的JR快速Airport号,37分钟,5班/hour
    • 不赶时间但行李多可选高速巴士,65分钟

在札幌住宿可以很方便的去往北海道各个景点,合适作为新千岁机场落地休息的第一站

3. 规划

出发时间:2023.12.21 - 2025.12.29, 共计 8 夜 9 天

规划如下:

日期 规划 备注
10.29 3年多次签证
11.12 签证到手
12.21 广州 -> 无锡 -> 大阪关西 关西停留一夜
12.22 大板关西 -> 新千岁机场 -> 札幌 附近玩玩
12.23 札幌酒店 小樽游玩天狗山,15点前上到山顶
12.24 登别酒店 地狱谷、熊牧场
12.25 登别酒店 洞爷湖畔、简仓展望台、湖景温泉酒店
12.26 大阪酒店
12.27 大阪 City Walk 元离宫二条城、金阁寺、八坂神社、知恩院、八坂之塔、四条河原町(购物)、锦市场(吃饭)
12.28 大阪City Walk 购物、逛逛景点
12.29 大阪关西 -> 无锡 -> 广州

3.1. 机票
日期 出发时间 到达时间 出发地点 到达地点 座位号 航班号 出票平台
2024-12-21 08:05 10:20 白云机场(CAN) T1 无锡机场(WUX) T2 22E, 22F 山东航空 ZH9821 www.travelgo.com
2024-12-21 13:45 17:00 无锡机场(WUX) T1 关西机场(KIX) T1 22A, 22B 山东航空 ZH0675 www.travelgo.com
2024-12-22 08:00 09:55 关西机场(KIX) T2 新千岁机场(CTS) D 21A, 21B 日本樂桃航空 MM103 www.trip.com
2024-12-26 19:00 21:25 新千岁机场(CTS) D 关西机场(KIX) T2 20A, 20B 日本樂桃航空 MM116 www.trip.com
2024-12-29 13:10 15:05 关西机场(KIX) T1 无锡机场(WUX) T1 23F, 23E 山东航空 ZH0674 www.travelgo.com
2024-12-29 19:20 21:55 无锡机场(WUX) T2 白云机场(CAN) T1 23F, 23E 山东航空 ZH9830 www.travelgo.com

3.2. 酒店
日期 名称 价格 含税以及其他费用
12.21 - 12.22 R Hotel Kansai Airport 500.08
12.22 - 12.24 Richmond Hotel Sapporo Ekimae 1502.00
12.24 - 12.26 Toya Kanko Hotel 2522.00
12.26 - 12.29 via inn abeno tennoji 2766.12
合计 / 7290

登别酒店可以参考登别官方推荐的酒店,部分带有私汤:

4. 尾声

不足之处

  • 无锡机场是军用机场,起飞降落均是要关闭遮阳板的
  • 座位要尽量选择靠前的位置才不会被机翼挡住视线
  • 大阪入关的人太多,排队要非常久
  • 北海道的交通确实非常不方便

参考资料

☑️ ☆

systemd配置指南

systemd 是一个 2010 年诞生的系统和服务管理器,广泛用于现代Linux发行版中,负责启动和管理系统的用户空间服务、挂载文件系统、启动并管理设备、以及其他系统初始化任务

作为取代 init 系统的系统项目,systemd 通过单一的控制进程(systemd进程)来管理系统的生命周期,其采用 ini 配置文件来描述和管理服务

现代大部分 Linux 系统都由 systemd 启动和管理

1. systemd

1.1. 例子

以 sshd 服务为例,控制其启动、关闭、和重启:

Bash
sudo systemctl stop sshd
sudo systemctl start sshd
sudo systemctl restart sshd

# 检查 sshd 的服务状态
sudo systemctl status sshd

sshd 服务的开启、关闭、重启都由 /etc/systemd/system/sshd.service 定义,其内容如下:

INI
[Unit]
# 描述该服务的功能
Description=OpenBSD Secure Shell server
# 提供该服务的文档参考,包括手册页
Documentation=man:sshd(8) man:sshd_config(5)
# 指定该服务在网络服务和审计服务启动后启动
After=network.target auditd.service
# 确保在指定路径下不存在文件时,该服务才会运行
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Service]
# 从指定文件加载环境变量,如果文件不存在则忽略
EnvironmentFile=-/etc/default/ssh
# 在服务启动之前检查 sshd 配置文件的有效性
ExecStartPre=/usr/sbin/sshd -t
# 启动 sshd 服务,使用 -D 参数使其在前台运行并使用环境变量中的选项
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
# 当服务重新加载时,首先检查配置文件的有效性
ExecReload=/usr/sbin/sshd -t
# 向主进程发送 HUP 信号以重新加载配置
ExecReload=/bin/kill -HUP $MAINPID
# 设置进程终止模式为仅终止该进程,而不是其子进程
KillMode=process
# 服务失败时自动重启
Restart=on-failure
# 避免在特定情况下(退出状态为255)重启服务
RestartPreventExitStatus=255
# 指定服务类型为 notify,表示服务会通过 sd_notify 通知其状态
Type=notify
# 为 sshd 服务创建运行时目录,以存储临时文件
RuntimeDirectory=sshd
# 设置运行时目录的权限为 0755
RuntimeDirectoryMode=0755

[Install]
# 指定在 multi-user.target 启动时自动启用该服务
WantedBy=multi-user.target
# 为该服务创建一个别名,方便管理
Alias=sshd.service

接下来,将简单快速拆解一下这种 ini 文件的构成

1.2. 配置文件

systemd 采用 ini 配置文件来描述和管理服务,其主要构成有三部分: UnitService 以及 Install 三大部分

Unit 用于定义程序的基本属性和元数据,常见选项:

  • Description:单元的简短描述
  • Documentation:相关文档的链接
  • After:定义单元启动的顺序,表示当前单元在指定单元之后启动
  • Before:定义单元启动的顺序,表示当前单元在指定单元之前启动
  • Requires:表示当前单元依赖于其它单元,如果依赖的单元未能启动,当前单元也不会启动
  • Wants:类似于 Requires,但不那么严格,依赖的单元如果未能启动,当前单元仍然会尝试启动

Service 用于定义服务的行为(如执行命令、环境变量、运行用户),常见选项:

  • ExecStart:指定启动该服务时执行的命令
  • ExecStop:指定停止该服务时执行的命令
  • Restart:服务崩溃后的重启策略(如 always、on-failure 等)
  • RestartSec:服务重启的延迟时间(单位秒)
  • User:以哪个用户身份运行该服务
  • StandardOutput:输出日志方式
  • Environment:设置环境变量
  • WorkingDirectory:指定工作目录
  • Type:服务的类型(如 simple、forking、oneshot、notify、dbus)

Install 用于控制服务的启用和禁用策略,常见选项:

  • WantedBy:指定该服务单位希望在启动时被哪些目标所激活
  • RequiredBy:指定该服务单位是哪些单位所依赖的

2. 编写指南

2.1. Unit

在 Unit 部分中,AfterBefore 是较为常用的,都是用于描述启动顺序,即程序在什么情况下启动

这两者的值是一样的,常用值有:

  • basic.target:基础的系统服务启动
  • multi-user.target:多用户模式,适用于无图形界面的系统
  • graphical.target:图形用户界面模式,依赖于 multi-user.target
  • network-online.target:网络连接完全建立
  • remote-fs.target:远程文件系统挂载
  • local-fs.target:本地文件系统挂载
  • shutdown.target:系统正在关闭
  • reboot.target:系统正在重启
  • network.service:网络服务启动完成
  • dbus.service:D-Bus 服务启动
  • systemd-journald.service:日志记录服务启动
  • systemd-logind.service:用户登录管理服务

After = 在指定情况后启动

Before = 在指定情况前启动

2.2. Service

在 Service 中, Restart 和 Environment 是比较常用的,一个用于控制程序的重启逻辑,一个用于控制程序运行时的环境变量

Restart 常见值:

  • no:默认值,不采取任何操作
  • always:重启服务
  • on-success:正常退出(状态码为0)则重启
  • on-failure:非正常退出(状态码不为0)则重启
  • on-abort:服务未捕捉到任何信号(如SIGKILL 或 SIGQUIT)而退出时重启
  • on-watchdog:仅在服务因未响应 watchdog 超时而退出时重启

Environment 用于设置程序的环境变量

Type 用于帮助 systemd 判定服务是否启动成功,常见值:

  • simple:启动后不需要任何信号即认为启动成功
  • forking:当服务进程fork出一个主进程并退出,适用于传统的UNIX守护进程,它们会fork并在后台运行
  • oneshot:服务执行一次后即退出,适合一次性脚本
  • notidy:服务启动后会通过 sd_notidy 通知 systemd,适合与 systemd 进行复杂交互的程序
  • idle:服务在其他任务执行完毕后再执行,低优先级的任务

如果环境变量过多,可以使用 EnvironmentFile 选项用于指定一个环境变量的文件路径

2.3. Install

常见的 WantedBy 和 RequiredBy 值包括:

  • multi-user.target:这是最常用的目标,表示系统已进入多用户模式,但没有图形界面,通常用于服务器或没有图形界面的系统
  • graphical.target:表示用户进入了图形界面
  • basic.target:表示系统的基本初始化已完成,但没有进入多用户模式
  • default.target:这是系统启动时的默认目标,通常是 graphical.target 或 multi-user.target 的别名
  • sysinit.target:表示系统初始化阶段的目标,通常在系统启动的早期阶段使用
  • network.target:在网络已初始化后使用,适用于需要网络连接的服务
  • remote-fs.target:用于启动远程文件系统的挂载

RequiredBy 的用法与 WantedBy 类似,但它表示服务是目标的必须依赖项,如果目标被激活,则这个服务也必须被激活RequiredBy 一般用于更严格的依赖关系

这两者与 Unit 中的 Before 和 After 非常相似,但区别很大:

  • After 和 Before:用于控制启动顺序,确保单元在特定的时间点启动
  • RequiredBy 和 WantedBy:用于定义单元的依赖关系,主要影响单元的启用和禁用行为

3. 例子

3.1. 长期服务

接下来,以配置一个 easydns 程序服务为例

easydns 是一个简易 DNS 分流程序

编辑: /etc/systemd/system/easydns.service

TEXT
[Unit]
# 服务描述
Description=easydns service
# 启动顺序
After=network.target

[Service]
# 执行命令
ExecStart=/root/easydns/easydns -m=127.0.0.1:1080 -p=114.114.114.114:53 -d=/root/easydns/domain.txt
# 退出机制
Restart=always
# 内存限制
MemoryMax=64M
# 日志输出 %N表示服务名称,所以日志输出位置是 /var/log/easydns.log
StandardOutput=append:/var/log/%N.log
# 重定向错误日志到标准流
StandardError=inherit
# 运行用户
User=root

[Install]
# 启动类型
WantedBy=multi-user.target

让 systemd 读入该配置单并设置开机自启

Bash
sudo systemctl daemon-reload
sudo systemctl enable easydns --now

查看 easydns 的运行服务状态

Bash
sudo systemctl status easydns

3.2. 定时服务

与 crontab 相比,systemd 的定时服务提供了状态监控、日志管理、重启策略控制等功能模块,能适应更复杂的运行需求

例如我们要定时执行域名 DDNS 解析,程序执行方法如下

Bash
/root/NameSilo-DDNS/namesilo-ddns --domain=chancel.me --name=demo --type=AAAA --record=$inet6_address --key=123456

创建一个 Service 描述文件,编辑: /etc/systemd/system/namesilo-ddns.service

INI
[Unit]
Description=Register NameSilo dns record

[Service]
Type=oneshot
ExecStart=/root/NameSilo-DDNS/namesilo-ddns --domain=chancel.me --name=demo --type=AAAA --record=$inet6_address --key=123456
StandardOutput=append:/var/log/%N.log
StandardError=inherit
User=root

再创建一个定时器,编辑: /etc/systemd/system/namesilo-ddns.timer

INI
[Unit]
Description=Register a DDNS domain every 30 minutes

[Timer]
OnBootSec=1min
OnUnitActiveSec=30min
Unit=namesilo-ddns.service

[Install]
WantedBy=timers.target

OnBootSec 设置了程序在系统启动后1分钟运行程序,此后 OnUnitActiveSec 规定了每30分钟执行一次

接下来启动这个定时器即可

Bash
sudo systemctl daemon-reload
sudo systemctl enable namesilo-ddns.timer --now

查看所有定时器列表

Bash
sudo systemctl list-timers

列表如下

Bash
NEXT                        LEFT        LAST                        PASSED       UNIT                         ACTIVATES                     
Wed 2024-09-04 15:15:01 CST 29min left  Wed 2024-09-04 14:45:01 CST 12s ago      namesilo-ddns.timer          namesilo-ddns.service
...
Mon 2024-09-09 01:38:12 CST 4 days left Mon 2024-09-02 01:39:50 CST 2 days ago   fstrim.timer                 fstrim.service

9 timers listed.
Pass --all to see loaded but inactive timers, too.

4. 日志配置

systemd 使用 journald 进行日志管理,你可以通过以下命令查看日志:

Bash
# 查看所有日志
journalctl

# 查看特定服务日志
journalctl -u <service_name>

日志切割的话,通常会借助 logrotate 来实现

4.1. logrotate

logrotate 是一个常用的日志管理工具,可以定期轮换、压缩和删除旧日志文件

以配置 easydns 为例

编辑:/etc/logrotate.d/easydns

Text only
/var/log/easydns.log {
    size 10M            # 设置每个日志文件的最大大小为10MB
    rotate 5            # 保留5个轮换的日志文件
    compress           # 压缩旧日志文件
    missingok           # 如果日志文件丢失,不报错
    notifempty          # 仅在日志非空时轮换
    copytruncate        # 保证在日志被写入时也可以实现轮换压缩日志
    create 0640 root root  # 创建新的日志文件时的权限
}

配置完成后,可以试运行(不会实际执行任何操作)

Bash
logrotate -d /etc/logrotate.d/easydns

通常而言, logrotate 在 crontab 或 systemd 中被预先定义为1天执行1次,可以通过检查 systemd 的定时任务来查看:

Bash
$ systemctl list-timers 
NEXT                        LEFT          LAST                        PASSED      UNIT                         ACTIVATES                     
Mon 2024-09-02 00:00:00 CST 56min left    Sun 2024-09-01 00:00:01 CST 23h ago     dpkg-db-backup.timer         dpkg-db-backup.service
Mon 2024-09-02 00:00:00 CST 56min left    Sun 2024-09-01 00:00:01 CST 23h ago     logrotate.timer              logrotate.service
Mon 2024-09-02 01:10:26 CST 2h 7min left  Sun 2024-09-01 04:48:01 CST 18h ago     man-db.timer                 man-db.service
Mon 2024-09-02 01:39:40 CST 2h 36min left Mon 2024-08-26 01:39:19 CST 6 days ago  fstrim.timer                 fstrim.service
Mon 2024-09-02 06:10:27 CST 7h left       Sun 2024-09-01 06:45:01 CST 16h ago     apt-daily-upgrade.timer      apt-daily-upgrade.service
Mon 2024-09-02 10:53:56 CST 11h left      Sun 2024-09-01 10:53:56 CST 12h ago     systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Mon 2024-09-02 14:55:51 CST 15h left      Sun 2024-09-01 21:53:03 CST 1h 9min ago apt-daily.timer              apt-daily.service
Sun 2024-09-08 03:10:20 CST 6 days left   Sun 2024-09-01 03:11:06 CST 19h ago     e2scrub_all.timer            e2scrub_all.service

8 timers listed.
Pass --all to see loaded but inactive timers, too.
☑️ ☆

使用Let'sencrypt为所有子域名永久开通HTTPS

1. Let's Encrypt简介

Let's Encrypt是一个由互联网安全研究集团(ISRG)运营的非营利证书颁发机构,免费提供X.509传输层安全(TLS)加密证书 它是世界上最大的认证机构,其赞助商包括电子前沿基金会(EFF)、Mozilla基金会、OVH、思科系统、Facebook、谷歌Chrome等

来自Wikipedia的介绍

Let's Encrypt is a non-profit certificate authority run by Internet Security Research Group (ISRG) that provides X.509 certificates for Transport Layer Security (TLS) encryption at no charge. It is the world's largest certificate authority,[2] used by more than 265 million websites,[3] with the goal of all websites being secure and using HTTPS.

所以在稳定性或安全上还是有保障的

Let's Encrypt官方提供certbot程序使得签发证书自动化,无需再手动签发证书,定时执行便可无限期签发泛域名证书

2. HTTPS

2.1. HTTPS简介

2021年,如果网站还不添加为HTTPS,大部分浏览器都会非常显眼的提示“该网站不安全”了

HTTPS(Hyper Text Transfer Protocol over SecureSocket Layer)是一种在HTTP基础上加入SSL证书校验的机制,被广泛运用于交易支付等场景

简单来说,通过HTTPS便可以确认服务器的身份,在访问 www.baidu.com 的时候,HTTPS能确保没有中间人劫持,浏览器所显示的便是百度的服务器所返回的信息

注:HTTPS的中间人劫持浏览器会提示该网站HTTPS证书不可信,这种情况说明了此时服务器的证书有问题,浏览器无法确认所访问的服务器身份

HTTPS的存在能大幅提高中间人劫持的成本,降低网站被伪造的风险

所以除非有特别的需求,否则任何有用户交互的网站都应该尽量采取HTTPS

2.2. 签发HTTP证书渠道

商业付费方案较多,一般有以下几种

  • DV SSL:域名型SSL证书,一般只能确保网站具备该域名的管理权,是廉价的SSL证书(常用于个人网站、中小型企业网站)
  • OV SSL:企业型SSL证书,证书颁发机构会确保企业身份是一个合法存在的实体,其SSL证书中包含完整的公司信息,但浏览器端不会直接显示企业名称,同时价格较贵
  • EV SSL:增强型SSL证书,与OV证书类似,但会确保网站与企业身份一致,国内浏览器可以直接在地址栏处看到公司名称,价格昂贵

商业方案一般是公司用途,作为个人用途的证书,通常考虑价格低廉或免费证书,其中阿里云与腾讯云便有免费的DV证书提供 2021-12-30-00-07-48.png

但阿里云与腾讯云的DV SSL免费证书也有不足之处

  • 一年有效期
  • 多个子域名需单独申请(一个子域名一张证书)
  • 手动部署(如果机器也在他们家则可以实现自动部署)

其中比较麻烦的是前两点,一旦超过有效期网站很容易就变得“不安全”,而众多的子域名每个单独定时去申请也很辛苦

Let's Encrypt就比较好的解决这两个痛点

3. Let's Encrypt证书签发

Let's Encrypt官方推荐certbot程序,利用这个程序我们可以做到自动签发子域名证书

由于certbot程序操作需要一定的命令行知识,降低操作难度作为替代的也可以考虑以下几个方法

  • acme.sh:用于申请Let's Encrypt证书的开源项目,操作相对简单,也支持自动更新与自动部署
  • ohttps: 可以理解为界面化的acme

下面以certbot程序为例子来申请Let's Encrypt的SSL证书

3.1. 安装certbot

首先访问 https://certbot.eff.org/instructions ,选择操作系统和HTTP服务程序,这里我选择Debian和Nginx,然后就可以看到详细的安装过程了

  1. 安装snap程序
Bash
sudo apt install snapd
  1. 使用snap安装certbot
Bash
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

3.2. certbot证书签发

证书签发分为两种,反而别是

  • 签发所有子域名(需DNS解析做校验)
  • 读取Nginx/Apache配置文件并签发证书(文件校验)

Tip:读取配置文件签发域名证书的原理是修改Nginx/Apache的配置文件,在根目录中一个验证文件,在验证成功后恢复原先配置并删除验证文件

这里以申请 chancel.me 域名作为例子

  • 签发chancel.me以下的所有子域名(3.2.1)
  • 签发chancel.me目前所有用到的子域名(3.2.2)

3.2.1. 泛域名证书签发

为整个域名签发所有子域名的SSL证书是较为常见的操作,申请通配符证书需要添加DNS的TXT字段,执行以下命令

Bash
sudo certbot certonly  -d "*.chancel.me","chancel.me" --manual --preferred-challenges dns

注意此处需要填入chancel.me,因为*.chancel.me是不包含一级域名的,所以显示填入chancel.me,这样申请下来也是一个文件

输出如下

Bash
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for *.chancel.me

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:

_acme-challenge.chancel.me.

with the following value:

asdf3rfcsadfluJSDDFff3cczKNc2CplQasd3fFDF3fdsd

Before continuing, verify the TXT record has been deployed. Depending on the DNS
provider, this may take some time, from a few seconds to multiple minutes. You can
check if it has finished deploying with aid of online tools, such as the Google
Admin Toolbox: https://toolbox.googleapps.com/apps/dig/#TXT/_acme-challenge.chancel.me.
Look for one or more bolded line(s) below the line ';ANSWER'. It should show the
value(s) you've just added.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

此时需要去域名服务商的后台管理中添加一台TXT的解析,名称是_acme-challenge,值则是上面程序输出的 “asdf3rfcsadfluJSDDFff3cczKNc2CplQasd3fFDF3fdsd”

添加完成后稍待1分钟,使用dig查询域名解析信息是否已经更新

Bash
dig -t txt _acme-challenge.chancel.me @8.8.8.8

查询输出如下

TEXT
; <<>> DiG 9.11.3-1ubuntu1.8-Ubuntu <<>> -t txt _acme-challenge.chancel.me @8.8.8.8
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37425
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;_acme-challenge.chancel.me.    IN      TXT

;; ANSWER SECTION:
_acme-challenge.chancel.me. 3600 IN     TXT     "asdf3rfcsadfluJSDDFff3cczKNc2CplQasd3fFDF3fdsd"

;; Query time: 29 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Thu Aug 04 16:43:13 CST 2022
;; MSG SIZE  rcvd: 111

解析刷新后,则在certbot中点击回车,证书签发成功,输出如下

Bash
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/chancel.me/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/chancel.me/privkey.pem
This certificate expires on 2022-03-29.
These files will be updated when the certificate renews.

查看签发的证书结构,结构如下

Bash
/etc/letsencrypt/archive/chancel.me
├── cert1.pem
├── chain1.pem
├── fullchain1.pem
└── privkey1.pem

检查签发的证书信息

Bash
sudo openssl x509 -in  /etc/letsencrypt/archive/chancel.me/cert1.pem -noout

输出如下

TEXT
Certificate:
    Data:
        Version: 3 (0x2)
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
                ...
            X509v3 Subject Alternative Name: 
                DNS:*.chancel.me

此时,所有的chancel.me子域名皆可使用 /etc/letsencrypt/archive/chancel.me 中的证书文件作为SSL证书文件

3.2.2. 单独签发域名证书

以nginx为例子,使用certbot从Nginx配置中检出域名列表

Bash
sudo certbot certonly --nginx

此时输入数字可以选择签发对应证书,或者空白直接回车签发所有识别到的证书

Bash
Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: chancel.me
2: api.chancel.me
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 2
Requesting a certificate for chancel.me

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/api.chancel.me/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/api.chancel.me/privkey.pem
This certificate expires on 2022-03-30.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

签发成功!查看签发的证书列表

Bash
/etc/letsencrypt/archive/api.chancel.me
├── cert1.pem
├── chain1.pem
├── fullchain1.pem
└── privkey1.pem

0 directories, 4 files

检查签发的证书信息

Bash
Certificate:
    Data:
        Version: 3 (0x2)
        ...
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
                ...
            X509v3 Subject Alternative Name: 
                DNS:chancel.me

3.3. Let's Encrypt证书到期自动更新

所签发的证书90天就过期了,自动续签可放在crontab中,半个月自动续签一次(如证书未过期是不会签发新的证书)

TEXT
0 3 15 */1 * /usr/bin/certbot renew --dry-run

3.4. Certbot删除证书

certbot本身提供证书删除功能

注:不要直接删除证书文件,这样做会导致certbot配置与证书文件不一致的错误

显示当前已签证书

Bash
sudo certbot certificates

删除指定证书

Bash
sudo certbot -d api.chancel.me delete

4. Nginx配置SSL证书

配置Nginx启用Https,主要就是ssl_certificate字段的配置,所有二级域名都是一致的

TEXT
server {
    listen 443 ssl;
    server_name www.chancel.me; 
    ssl_certificate /etc/letsencrypt/archive/chancel.me/fullchain1.pem;
    ssl_certificate_key /etc/letsencrypt/archive/chancel.me/privkey1.pem;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;

    location / {
        ...
    }
}
☑️ ☆

2024日本东京札幌旅游攻略

1. 准备

1.1. 日本签证

日本不对国内免签,签证可选途径

  • 淘宝
  • 飞猪

一般所需资料

  • 在职证明原件
  • 护照原件、身份证正反面、户口本整本的彩色复印件
  • 近半年白底彩照2张(3.5*4.5cm)
  • 赴日申请表和紧急联系人表
  • 财力证明(任选其一):近12个月工资卡10万、去年或近12个月纳税2500元以上税单、10万定期存款(已存3个月以上)、近半年银行流水最终余额10万

初次可能需提供超过 3w 的余额证明以及最近半年的流水账

签证通过后有效期一般是90天,在签证信息上可以看到起始日期和结束日期,可以在这段时间内任意安排出入境旅游

1.2. 物品清单参考

必带物品清单

类型 物品内容 备注
证件 身份证、信用卡、护照
信息 酒店入住信息 复印纸质备用
网络 随身WI-FI或特定流量卡 淘宝
交通 Suica卡或Pasmo卡 可在Apple Pay中申请虚拟的Suica卡
必备 电源转换头、手机电脑数据线以及充电头
电子设备 充电宝、微单、手持稳定器、无线耳机
其他 签字笔、便携煮水壶(热水)、手机支架

日用品清单

类型 物品
必带 一次性内裤、零钱包、常用药、洗面奶、牙刷牙膏、护肤品
衣服 上衣、裤子、袜子、手套、帽子、睡衣、围巾
女性 化妆包、干发巾

1.3. 交通

日本的许多交通叫法与国内有区别,大致与国内对比参考如下

日本 国内
各类列车(如:JR特急/普通列车、机场快线) 国内高铁/动车
地下铁(如:南北线、东西线) 国内地铁
路面电车(如:札幌市电、9天狗山索道线) 国内公交车

在日本如果有短期内深度体验某个地区的考虑,可以购买JR PASS(Japan Rail Pass)JR Pass是一种为游客提供的经济实惠的特殊铁路通行证,允许在指定时期内(通常是3/5/7/14/21)无限次乘坐,可以在电商平台购买,可以根据行程来看看是否划算 此外,最常见的交通卡就是关西的ICOCA卡(以大阪为代表)和关东的Suica(以东京为代表),这2张卡都可以在全国范围内使用地铁、巴士以及便利店,非常方便,他们之间的区别仅是发行的地区不同,在2024年后日本大力推行智能支付,现在不需要购买这两种卡的实体卡了,在iPhone上使用Apple Pay可以直接添加Suica卡,随用随充,非常方便

1.4. 入境

在2024年1月后,日本更新了入境流程,现在支持线上提前申报,相较于填写入境申请更加简便

入境流程通常为:下飞机->检疫->录指纹->入境审查->拿行李->海关申报->出机场自由活动

其中,入境审查与海关申报以往是需要填纸质资料,现在可以提前在线上申报VJW(即Visit Japan Web

VJW需要资料如下:

  1. 护照
  2. 机票
  3. 一个可供注册的邮箱

具体的申请可以参考网站或自行搜索攻略,在申请成功会后得到一个二维码,在检疫以及入境审查处可以通过扫码快速通过

1.5. Q & A

不懂英文以及日文影响大吗?

  • 不大,日本人的英文也不怎么样,而且中文提示以及中文店员非常多,无需担心

哪些 App 或者网站是必须的?

  • Google 地图
  • Google 翻译
  • kakuku.com 用于查找购物比价的网站

食物怎么样?

  • 日本常见的快餐食物包括拉面、乌冬面、荞麦面、米饭套餐(牛肉饭、咖喱饭、猪排饭、炸鸡、猪肉等主菜+米饭)、意面套餐等

什么是免税?

  • 购物点标识提示tax free是针对外国游客的免消费税优惠,在购物时咨询店员是否tax free即可,通常是现场立减或前往购物中心统一退税柜台退税(出关时,游客需要把这些小票摘掉放在指定的购物篮中)

日本有哪些需要注意的?

  • 交通:日本早晚高峰的电车拥挤程度较高,旅行者可尽量避开这个时间段乘车
  • 热水:没有,习惯喝热水的朋友请注意了,在日本普通小饭店一般仅提供自取冰水
  • 烟民:公共场合禁烟,请按照图标寻找吸烟点
  • 小费:日本没有给小费的习惯
  • 砍价:日本所有商品基本全部明码标价,可以询问店员是否有其他折扣优惠
  • 靠左行走:日本人的交通和行走、乘扶梯在大部分情况下靠左;大阪电梯靠右,可注意地上的引导标识

还有哪些小技巧?

  1. 在各大交通站和景点往往都有纸质的旅游指南,而且往往有中文版,带走看一看往往会让你发现不是那么大众的特色玩法和美食
  2. 游玩东京的话,机场和游客中心的绿色东京旅游指南小册子非常有用,不仅详细介绍了东京各个区域的景点和地图,还能够凭借册子获得很多博物馆的票价优惠,这都是本地人难以享受到的游客福利,值得好好利用
  3. 推荐需要戴眼镜的朋友在日本配一副眼镜,日本产的眼镜质量好,价格透明,服务周到,比在国内配高级眼镜要实惠很多
  4. 日本二手名包和名表往往品相新,价格便宜,且保障真品,所以超值

2. 札幌东京富士山

2.1. 札幌

此次出发首站是札幌,主要景点根据下图来规划

札幌的交通

  1. 北海道城市之间往返可以全靠JR,淘宝可买3/4/5/6/7日的JR Pass票
  2. JR Pass票可以在新千岁机场下来进行兑换,乘坐时向工作人员出示即可
  3. 新千岁机场前往札幌市区有3种方案
    • 班次少但速度快的JR特别快速Airport号,33分钟,4班/day
    • 班次多但速度慢的JR快速Airport号,37分钟,5班/hour
    • 不赶时间但行李多可选高速巴士,65分钟

2.2. 河口湖

河口湖查询未来7天的能见度,避免扑空

事实上,由于酒店都需要提前定,如不是深度游或者初次去,建议将酒店定在东京,这样如果天气好的话随时可以购票去河口湖玩,如果天气不好则可以留在东京购物

河口湖的交通

  1. 从东京出发河口湖可选JR富士急行线新宿-河口湖站共4130日元
  2. 也可以选择大巴,大巴可提前在highwayexpress bus购买

2.3. 东京

东京有名的景点非常多,可自行搜索,交通也发达,当然购物也是极大的方便

3. 行程规划

出发时间:2023.02.14 - 2023.02.25, 共计 11 夜 12 天

3.1. 日期表

计划如下

时间 安排 备注
2023.12.27 单次签证
2024.01.11 签证通过
2024.01.12 机票出票 广州 -> 札幌
2024.02.14 新千岁机场
2024.02.15 札幌 北海道神宫、狸小路商店街、大通公园、北海道大学、薄野欢乐街、札幌时计台
2024.02.16 小樽 小樽运河、八音盒馆、天狗山、朝里站、小樽堺町通、船见坂
2024.02.17 登别 登别地狱谷、登别温泉镇、伊达时代村、洞爷湖、熊牧场
2024.02.18 旭川+美瑛+富良野 旭川动物园、旭川拉面村、美瑛青池、白须瀑布、美瑛圣诞树、七星之树、富良野森林精灵露台
2024.02.19 河口湖
2024.02.20 河口湖 新仓山浅间神社、日川时计店
2024.02.21 河口湖 山口湖踩单车环游
2024.02.22 东京
2024.02.23 东京
2024.02.24 东京
2024.02.25 广州

3.2. 时刻表

基于大的行程,预订机票以及大巴票、团体票等,做成一个简易的时刻表用于旅行时参考

日期 时间 安排 网上值机 备注
02.14 07:30 - 10:00 白云机场T1 >> 浦东国际机场T1
11:25 - 15:00 浦东国际机场T1 >> 中部国际机场T1
16:55 - 18:35 中部国际机场T1 >> 新千岁机场D ❌(自动值机) 注意行李非直挂
02.19 13:10 - 14:55 新千岁机场D >> 东京羽田机场T1
17:15 (上⻋)巴⼠总(Busta)-(下⻋)河⼝湖
02.22 18:40 (上⻋)河⼝湖 - (下⻋)巴⼠总(Busta)
02.25 08:40 -- 11:05 东京羽田机场T3 >> 浦东国际机场T1
16:15 - 18:55 浦东国际机场T1 >> 白云国际机场T1

4. 复盘

4.1. 机票酒店费用参考

这次花费如下

项目 明细 支出 备注
机票 广州 >> 札幌 4,334 2乘客,价格含2次转机(上海、名古屋)
札幌 >> 东京 1,520 2乘客
东京 >> 广州 4,044 2乘客
酒店 札幌酒店 3,444 5夜双床房
河口湖酒店 1,384 3夜双床房
东京酒店 2,480 3夜双床房
合计 17,216

4.2. 不足之处

在这次旅游上前期计划没有考虑周到之处,大致如下

  • 机票
    • 机票应尽量考虑有直飞选直飞,中转的次数多一次都是相当吃力
    • 转机有行李直挂尽量在中转机场确认行李状况,因为行李装车是需要时间的,中转时间过短的行程是很容易行李延误,行李延误了你到目的地就没有行李箱了
    • 回程机票尽量请选择日本本地航司,本地航司支持Face Express,即快速值机、快速安检过海关,而国际航司经常是提前3个小时开放值机,排队值机+排队海关结束就登机了,无缘免税店
    • 行李延误了也不用怕,在机场登记行李延迟,然后给航司以及国内民航投诉电话12326去电投诉,要求赔偿
  • 生活
    • 北海道没有那么冷,衣服不需要多带,当地的衣服非常便宜,甚至比广州还要低40%的价格,2个人带2行李箱衣服过多了
    • 选酒店上,能有早餐就有早餐,日本的早餐选择偏少也偏贵
    • 如果行李过多又用不上的话,可以使用黑猫快递提前寄过去机场,这样就不用带非常多的行李到处穿梭了
  • 其他
    • 拨打国内私人电话是+86,但如果要拨打地方公共服务,则需要加区号并省却前面的0,比如给广州的移动10086电话就是+862010086,020是广州区号,省却前面的0换+86即可
    • 在出国前,提前开通手机的漫游服务(免费),漫游服务可以让你在日本也有SIM卡信号,只要不上网不接打电话都是免费的
    • 如果出国后才需要漫游服务会比较麻烦,你需要拨打SIM卡归属地的运营商服务并接入人工服务登记让专员处理,提供身份证+户主姓名就可以让专员在后台开通

5. 尾声

参考资料

☑️ ☆

HTTPS证书Curl报错浏览器正常

在更新证书后,使用 curl 访问我部署的网站发现出现SSL证书错误
Bash
$ curl https://www.chancel.me
curl: (60) SSL certificate problem: unable to get local issuer certificate

在浏览器端访问正常,而 curl 却显示如上错误

使用 openssl 排除一下证书问题:

Bash
openssl s_client -showcerts -connect chancel.me:443

输出如下

Bash
$ openssl s_client -showcerts -connect chancel.me:443
CONNECTED(00000003)
depth=0 CN = chancel.me
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = chancel.me
verify error:num=21:unable to verify the first certificate
verify return:1
depth=0 CN = chancel.me
verify return:1
Certificate chain
 0 s:CN = chancel.me
   i:C = AT, O = ZeroSSL, CN = ZeroSSL ECC Domain Secure Site CA
   a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA384
   v:NotBefore: Jun 26 00:00:00 2024 GMT; NotAfter: Sep 24 23:59:59 2024 GMT
-----BEGIN CERTIFICATE--MIIEBjCCA4ugAwIBAgIQLVWuBcqS3xONihxJEXDyMTAKBggqhkjOPQQDAzBLMQsw
CQYDVQQGEwJBVDEQMA4GA1UEChMHWmVyb1NTTDEqMCgGA1UEAxMhWmVyb1NTTCBF
Q0MgRG9tYWluIFNlY3VyZSBTaXRlIENBMB4XDTI0MDYyNjAwMDAwMFoXDTI0MDky
NDIzNTk1OVowFTETMBEGA1UEAxMKY2hhbmNlbC5tZTBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABHQ12fODbsvJMao6BNN2nMGRi0y2jDIBExk43H4yfTFXOYJatYU4
PCW0KN1Cwxgvf4Kb6VIPAEVe789y5GE7H9yjggKFMIICgTAfBgNVHSMEGDAWgBQP
a+ZLzjlHrvZ+kB558DCRkshfozAdBgNVHQ4EFgQUqugludSLW3PChXfCQKJHuXA+
9K0wDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYB
BQUHAwEGCCsGAQUFBwMCMEkGA1UdIARCMEAwNAYLKwYBBAGyMQECAk4wJTAjBggr
BgEFBQcCARYXaHR0cHM6Ly9zZWN0aWdvLmNvbS9DUFMwCAYGZ4EMAQIBMIGIBggr
BgEFBQcBAQR8MHowSwYIKwYBBQUHMAKGP2h0dHA6Ly96ZXJvc3NsLmNydC5zZWN0
aWdvLmNvbS9aZXJvU1NMRUNDRG9tYWluU2VjdXJlU2l0ZUNBLmNydDArBggrBgEF
BQcwAYYfaHR0cDovL3plcm9zc2wub2NzcC5zZWN0aWdvLmNvbTCCAQUGCisGAQQB
1nkCBAIEgfYEgfMA8QB2AHb/iD8KtvuVUcJhzPWHujS0pM27KdxoQgqf5mdMWjp0
AAABkFI2CNgAAAQDAEcwRQIgKIOe2YkObvn+GQd9K8MgCr9j14QqzVnAz2nPr1Ct
Tx8CIQDblmd6EXJlLZ0oQfs2ENK0+09BIsbjeUujfdRrTJVJWwB3AD8XS0/XIkdY
lB1lHIS+DRLtkDd/H4Vq68G/KIXs+GRuAAABkFI2CLsAAAQDAEgwRgIhAJDnLWJs
mc2C6n6nJJt3LTMeu4w5PxNBiOYqGcDTRgDKAiEAi0FrGPsNn75a45OGcVPPc8k3
0BlLv8Ru7DQ08FXy/okwIwYDVR0RBBwwGoIKY2hhbmNlbC5tZYIMKi5jaGFuY2Vs
Lm1lMAoGCCqGSM49BAMDA2kAMGYCMQDmY3cpMfSdL7nfpjy15MNouJSyB5DfE4OG
VVq26xsZUSYt/BsEgCBtibY/Wng4DSACMQCSneonTloCu9LvE2W5rCFsmA2cLpMo
eYfNg7axJK9iDOnP9vTDKQn7X94jHMZT8UM=
-----END CERTIFICATE--Server certificate
subject=CN = chancel.me
issuer=C = AT, O = ZeroSSL, CN = ZeroSSL ECC Domain Secure Site CA
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: ECDSA
Server Temp Key: X25519, 253 bits
SSL handshake has read 1510 bytes and written 409 bytes
Verification error: unable to verify the first certificate
New, TLSv1.2, Cipher is ECDHE-ECDSA-AES256-GCM-SHA384
Server public key is 256 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-ECDSA-AES256-GCM-SHA384
    Session-ID: 868ED2651AF3111499B9B5DBE520E70620DF31F26979CCB6B5C5A899C8CAECEE
    Session-ID-ctx: 
    Master-Key: 812652A6004A21536D8F9BCF39F87B55A374239BC4C59598CAAF98DB6AA940C4101ADE7B7648F33A3FDD743910DAE9E0
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 300 (seconds)
    TLS session ticket:
    0000 - 63 6f d7 c2 ca e2 54 26-74 ca c0 9f cc c3 79 89   co....T&t.....y.
    0010 - 68 a2 be 18 93 07 c2 9d-20 49 50 3f 54 bd 55 d0   h....... IP?T.U.
    0020 - 82 5f b8 99 9f db 05 a6-36 60 35 cf d3 d3 69 67   ._......6`5...ig
    0030 - cf 2e 57 cd e1 2f 1f 33-af 34 a8 e5 7a 0c 4d 7c   ..W../.3.4..z.M|
    0040 - 69 45 35 87 1a 47 74 38-b9 66 04 58 71 89 eb d2   iE5..Gt8.f.Xq...
    0050 - 49 de fc 35 3c 93 90 76-21 d8 d6 a6 5d a5 7b 4b   I..5<..v!...].{K
    0060 - 4e 36 95 bb 4c 98 e6 dc-37 a6 86 f4 cc b0 27 8b   N6..L...7.....'.
    0070 - 0e f1 00 b9 88 7d 23 23-d4 21 68 95 cd dc 94 21   .....}##.!h....!
    0080 - 51 60 38 43 bf 17 cd d0-b5 fb 9c 4b c2 da a9 ab   Q`8C.......K....
    0090 - 22 aa 43 7c a9 80 b0 d1-77 20 c2 28 42 f8 fb f5   ".C|....w .(B...
    00a0 - 9a ef d5 53 d3 f0 f7 1c-74 3f ee 26 b8 4d 93 ef   ...S....t?.&.M..
    00b0 - 2d 13 bf c0 f0 4a aa 22-cf 92 13 26 c9 ee a7 4b   -....J."...&...K

    Start Time: 1721189955
    Timeout   : 7200 (sec)
    Verify return code: 21 (unable to verify the first certificate)
    Extended master secret: yes

40673039367F0000:error:0A000126:SSL routines:ssl3_read_n:unexpected eof while reading:../ssl/record/rec_layer_s3.c:303:

可以看到有 unable to verify the first certificate 的错误输出,这说明证书链不完整,检查 nginx 配置

TEXT
...
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_certificate /home/chancel/.acme.sh/chancel.me_ecc/chancel.me.cer;
ssl_certificate_key /home/chancel/.acme.sh/chancel.me_ecc/chancel.me.key;

ssl_certificate 应该是全链证书,这里写成 chancel.me.cer 证书导致curl无法验证,而浏览器的访问https是自带证书链的

正确配置应该是全链证书

TEXT
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
ssl_certificate /home/chancel/.acme.sh/chancel.me_ecc/fullchain.cer;
ssl_certificate_key /home/chancel/.acme.sh/chancel.me_ecc/chancel.me.key;

再次使用 curl 检查,问题已解决

Bash
$ curl -v https://chancel.me
*   Trying 103.99.178.98:443...
* Connected to chancel.me (103.99.178.98) port 443 (#0)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=chancel.me
*  start date: Jun 26 00:00:00 2024 GMT
*  expire date: Sep 24 23:59:59 2024 GMT
*  subjectAltName: host "chancel.me" matched cert's "chancel.me"
*  issuer: C=AT; O=ZeroSSL; CN=ZeroSSL ECC Domain Secure Site CA
*  SSL certificate verify ok.
* using HTTP/2
* h2h3 [:method: GET]
* h2h3 [:path: /]
* h2h3 [:scheme: https]
* h2h3 [:authority: chancel.me]
* h2h3 [user-agent: curl/7.88.1]
* h2h3 [accept: */*]
* Using Stream ID: 1 (easy handle 0x55b2d5adcc70)
> GET / HTTP/2
> Host: chancel.me
> user-agent: curl/7.88.1
> accept: */*
> 
< HTTP/2 301 
< server: nginx/1.22.1
< date: Wed, 17 Jul 2024 04:25:45 GMT
< content-type: text/html
< content-length: 169
< location: http://www.chancel.me/
< 
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.22.1</center>
</body>
</html>
* Connection #0 to host chancel.me left intact
🔲 ☆

Too many open files in system

在Linux中,"Too many open files in system" 是指程序尝试打开的文件描述符(包括文件和网络套接字)的数量超过了系统或用户设置的限制

你可以使用 lsof 命令来检查系统中当前打开的文件描述符:

Bash
lsof | wc -l

文件描述符限制的存在是为了保护系统资源和确保系统稳定性,以下是设置文件描述符限制的几个主要原因:

  1. 系统资源保护
  2. 稳定性和性能
  3. 安全性

文件描述符限制是操作系统的一种保护机制,通过限制每个进程可以打开的文件和套接字数量,确保系统资源的合理分配和使用,防止资源耗尽,提高系统的稳定性和安全性

用户级别的限制可以通过 ulimit 来查看

Bash
ulimit -n

也可以手动增加这个值

Bash
ulimit -n <新的限制值>

描述符的配置文件位于:/etc/security/limits.conf

☑️ ☆

使用acme.sh自动续签https证书

Acme.sh 是一个开源的自动化证书管理工具,用于获取、安装、更新和部署SSL/TLS证书,使用Shell脚本编写并支持在Linux、macOS、FreeBSD和Windows等操作系统上运行

Acme.sh 基于 ACME 协议(Automatic Certificate Management Environment)工作,该协议由Let's Encrypt提出并广泛采用

ACME 协议允许用户通过自动化的方式获取和管理SSL/TLS证书,而不需要手动进行复杂的证书请求、验证和安装过程

Acme.sh 提供了一系列命令和选项,可以与各种证书颁发机构(包括Let's Encrypt)进行交互,并自动处理证书申请、域名验证和证书安装

此外还支持各种验证方法,包括HTTP验证、DNS验证和TLS-SNI验证,以满足不同环境和需求的证书获取和更新

使用可以轻松地配置和管理SSL/TLS证书,从而网站、应用程序或其他服务启用安全的HTTPS连接

1. 使用指南

1.1. 安装

Acme.sh 安装非常方便,使用官方提供的脚本即可:

Bash
curl https://get.acme.sh | sh

执行安装后,Acme.sh 会安装到目录 $HOME/.acme.sh 中,切换到该目录,执行注册用户:

Bash
cd $HOME/.acme.sh
acme.sh --register-account -m your_email@email.com

注册成功后,查看一下定时任务,Acme.sh 默认配置每天自动检查证书(有效期大于30天)并自动续签

Bash
sudo cat /var/spool/cron/crontabs/$USER

因为 Let's Encrypt 的协议会更新,所以要设置允许 Acme.sh 自动升级

Bash
acme.sh  --upgrade  --auto-upgrade

1.2. 部署

设置环境变量 Namesilo_Key 用于访问 Namesilo 的API:

Bash
# 其他域名服务商参数名称参考:https://github.com/acmesh-official/acme.sh/wiki/dnsapi
export Namesilo_Key="域名服务商API-KEY"

检查输出中没有错误后,认证通配符域名(Wildcard Certificate):

Bash
# 同样的,dns参数值参考:https://github.com/acmesh-official/acme.sh/wiki/dnsapi
acme.sh --issue --dns dns_namesilo -d chancel.me -d "*.chancel.me"

必须填 *.chancel.me 和 chancel.me ,因为 *.chancel.me 不包含 chancel.me 的域名

确认没有错误输出,查看证书列表

Bash
$ acme.sh --list                                                                                                          
Main_Domain  KeyLength  SAN_Domains   CA           Created               Renew
chancel.me   "ec-256"   *.chancel.me  ZeroSSL.com  2024-06-26T01:43:28Z  2024-08-24T01:43:28Z

证书位置位于 $HOME/.acme.sh/chancel.me_ecc/ ,如下:

TEXT
.
├── ...
├── chancel.me_ecc
│   ├── ca.cer
│   ├── chancel.me.cer
│   ├── chancel.me.conf
│   ├── chancel.me.csr
│   ├── chancel.me.csr.conf
│   ├── chancel.me.key
│   └── fullchain.cer
└── ...

2. Docker-Compose

Acme.sh 也支持容器部署,编辑一个 docker-compose.yaml 文件:

YAML
version: '3'

services:
  acme.sh:
    image: neilpang/acme.sh
    volumes:
      - ./acmesh:/acme.sh
    environment:
      - Namesilo_Key=[域名服务商API-KEY]
    command: '--issue --dns dns_namesilo -d "chancel.me" -d "*.chancel.me" --server letsencrypt --renew --log --debug 2'

这里使用的是neilpang/acme.sh镜像,然后添加一些处理:

  • 将当前路径下的 ./acmesh 目录作为镜像存放数据的卷,这样每次运行都可以自动续签证书
  • command 命令中添加了日志与详细的debug输出

运行该容器

Bash
sudo docker-compose up

检查输出没有错误后,检阅一下垆坶,证书位置位于 ./acmesh/chancel.me_ecc/ ,如下:

Bash
$ tree                                                                                            
.
├── ...
│   ├── chancel.me_ecc
│   │   ├── ca.cer
│   │   ├── chancel.me.cer
│   │   ├── chancel.me.conf
│   │   ├── chancel.me.csr
│   │   ├── chancel.me.csr.conf
│   │   ├── chancel.me.key
│   │   └── fullchain.cer
│   └── ...
└── docker-compose.yml

可以通过 openssl 工具检查证书信息:

Bash
openssl x509 -in fullchain.cer -text -noout

检查输出中 X509v3 Subject Alternative Name 这一行是否包含了指定的2个域名参数,如下:

TEXT
Certificate:
    Data:
        ...
        X509v3 extensions:
            ...
            X509v3 Subject Alternative Name: 
                DNS:*.chancel.me, DNS:chancel.me
            ...
    ...

可以看到,DNS中包含了泛域名 *.chancel.me 也包括了单域名 chancel.me

手动将docker-compose添加到crontab任务中,就可以在证书到期前30天自动更新

☑️ ☆

Linux下配置双向HTTPS证书认证

HTTPS双向认证,也称为客户端认证、双向SSL认证或双向身份验证,是一种增强的HTTPS安全协议,用于确保Web服务器和客户端之间的双向身份验证和通信加密

在传统的 HTTPS 连接中,只有服务器需要提供数字证书来验证其身份,并启用加密通信,而HTTPS双向认证要求不仅服务器要验证其身份,客户端也需要提供数字证书进行身份验证

https(Hypertext Transfer Protocol Secure)是在 http 的基础上增加 SSL 加密

通过双向认证,HTTPS确保了服务器和客户端之间的互相验证,防止中间人攻击和身份伪装

这种安全机制常用于需要更高安全性和身份验证的环境,如在线银行、电子商务和企业内部网络等

1. 单向认证 VS 双向认证

传统的 HTTPS 访问过程如下

双向认证是在上面的基础上增加多一次认证,如下

2. 实践

2.1. Nginx启用HTTPS

Nginx启用HTTPS流程:

  • 向CA证书商家申请证书
  • 下载证书,并更改NGINX配置指向证书
  • 重新读取NGINX配置即可完成HTTPS配置

先手动生产一个测试用的CA证书

自行产生的证书,浏览器会提示证书不安全

先创建并切换到存放HTTPS证书的文件夹

Bash
mkdir /etc/nginx/ssl_certs
cd /etc/nginx/ssl_certs

制作CA私钥和证书,全部配置填"."

Bash
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

制作服务器证书签发文件,除了Common Name填入域名,其他全部填入"."以便和CA根证书对应

Bash
openssl genrsa -out server.pem 1024
openssl rsa -in server.pem -out server.key
openssl req -new -key server.pem -out server.csr

使用CA证书进行签发

Bash
openssl x509 -req -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out server.crt

签发后,产生了我们需要的 crtkey 文件

编辑:/etc/nginx/conf.d/default.conf

TEXT
server {
    listen       443 ssl;
    server_name  www.example.com;
    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;
    ssl on;
    ssl_certificate /etc/nginx/ssl/server.crt;      # 服务端HTTPS的证书,不能自己产生,否则会提示证书不安全,该配置与客户端证书无关
    ssl_certificate_key /etc/nginx/ssl/server.key;  # 服务端HTTPS的证书,不能自己产生,否则会提示证书不安全,该配置与客户端证书无关
    location / {
        proxy_pass_header Server;
        proxy_pass_header X-Scheme;
        proxy_set_header Host $http_host;
        proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;
        proxy_set_header X-SSL-CERT-DN $ssl_client_s_dn;
        proxy_set_header X-SSL-CERT-VERIFY $ssl_client_verify;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://192.168.10.100:5000/;
    }
}

验证使用HTTPS访问

Bash
# 因证书自签必须使用insecure忽略证书风险,提示否则curl会返回证书不安全测试失败的结果
curl --insecure 'https://www.example.com' -v

2.2. 客户端启用证书验证

客户端证书一般就采用自己配置根证书来签发自己的客户端证书,签发客户端证书会包含几个重要的属性,分别是

  • 地区信息(国家、省份、城市)
  • 公司信息(公司名称、该证书拥有者的部门信息)
  • Common Name信息(也叫CN信息,可把一些信息存储在这里,如有效信息/客户端信息等)
  • 邮箱信息
  • 证书有效期、证书导入/导出密码

制作客户端证书的流程一般为

  1. 制作自己的CA私钥(ca.key)
  2. 使用CA私钥制造CA根证书(cacert.crt)
  3. 制作客户端私钥,并合成待签发csr文件(client.key,client.csr)
  4. 使用CA证书对客户端待签发文件进行签发产生客户端的crt证书(client.crt)
  5. 转换客户端证书为适合浏览器的证书格式(client.pfx,client.p12)

首先要编辑默认CA创建规则,根据需要自行修改一下默认的参数

编辑/etc/pki/tls/openssl.cnf

Bash
/etc/pki/CA/            # 所有资料全存放在此目录下
/etc/pki/CA/certs/      # 存放CA证书
/etc/pki/CA/crl/        # 存放证书吊销列表
/etc/pki/CA/index.txt   # 存放可用、不可用证书的数据库
/etc/pki/CA/newcerts    # 存放CA的新目录
/etc/pki/CA/cacert.pem  # 生成的自签名证书
/etc/pki/CA/serial      # 下个证书的编号,16进制,多从00或01开始
/etc/pki/CA/crlnum      # 下一个吊销证书的编号
/etc/pki/CA/crl.pem     # 下一个吊销列表
/etc/pki/CA/private/cakey.pem  # 默认密钥

# 匹配策略的可选值
match                   # 必须匹配,默认是国家、州(省)、组织名称
supplied                # 必填,为哪个主机申请证书,一般对应域名
optional                # 可选

根据具体的环境调整好上述的参数之后,我们就可以开始制作客户端证书

文件夹位置根据实际情况自行调整

生成创建证书所必须的基本文件

Bash
# 生成数据库文件,记录证书信息(包含序列号、序号、证书自定义CN信息等)
touch /etc/pki/CA/index.txt
# 生成存放证书编号的文件(一般存储即将颁发的下一个证书序号)
echo 00 > /etc/pki/CA/serial

切换到/etc/pki/CA,开始生成签证客户端的根证书

Bash
openssl  req -new -x509  -key private/cakey.pem  -days 3650 -out cacert.pem

接下来签发客户端的证书(此处有提供一键脚本执行)

Bash
# 创建存放客户端证书的文件夹
mkdir /etc/nginx/client_ssl
# 切换到存放客户端证书的目录
cd /etc/nginx/client_ssl
# 创建客户端证书私钥
(umask 066; openssl genrsa -out client_01.key 1024)
# 创建请求根证书签证的文件             
openssl  req -new -key client_01.key  -out client_01.csr
# 创建客户端证书        
openssl  ca -in client_01.csr  -out client_01.crt -days 730

client_01.crt 就是客户端证书

编辑:/etc/nginx/conf.d/default.conf

TEXT
server {
    listen       443 ssl;
    server_name  www.example.com;
    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;
    ssl on;
    ssl_certificate /etc/nginx/ssl/server.crt;      # 服务端HTTPS的证书,不能自己产生,否则会提示证书不安全,该配置与客户端证书无关
    ssl_certificate_key /etc/nginx/ssl/server.key;  # 服务端HTTPS的证书,不能自己产生,否则会提示证书不安全,该配置与客户端证书无关
    ssl_client_certificate /etc/nginx/ssl/ca.crt;   # 客户端的CA根证书,由自己的机器OPENSSL生成
    ssl_verify_client optional;                     # 是否启用客户端验证,ON为启用,OFF为不启用,如需自定义验证结果处理则填入optional
    if ($ssl_client_verify = NONE) {
        return 303 http://www.example.com/error;
    }
        location / {
            proxy_pass_header Server;
            proxy_pass_header X-Scheme;
            proxy_set_header Host $http_host;
            proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;   # 转发证书信息
            proxy_set_header X-SSL-CERT-DN $ssl_client_s_dn;        # 转发证书的CN信息
            proxy_set_header X-SSL-CERT-VERIFY $ssl_client_verify;  # 转发证书的验证结果,成功为success
            proxy_set_header X-Real-IP $remote_addr;
            proxy_pass http://192.168.10.100:5000/;
    }
}

测试客户端证书能否正常访问网页

Bash
# -v参数可以打印详细信息以便我们核查证书的详细信息
curl --insecure --key ./client.key --cert ./client.crt 'https://www.example.com' -v

以上步骤我整理为一个简易的客户端证书脚本,如下

Bash
#!/bin/bash -e
# 运行脚本前请记得设置好openssl.cnf配置文件
# 运行成功返回0,运行失败(缺少参数)返回-1
show_help() {
    echo "$0 [-h|-?|--help] [--ou ou] [--cn cn] [--email email]"
    echo "-h|-?|--help    显示帮助"
    echo "--ou            设置组织或部门名"
    echo "--cn            设置证书的DN信息"
    echo "--email         设置证书的邮箱信息"
    echo "--days          设置证书的有效时间"
    echo "--out           设置证书的输出目录(含文件名)"
}
while [[ $# -gt 0 ]]; do
    case $1 in
    -h | -\? | --help)
        show_help
        exit 0
        ;;
    --ou)
        OU="${2}"
        shift
        ;;
    --cn)
        CN="${2}"
        shift
        ;;
    --email)
        emailAddress="${2}"
        shift
        ;;
    --days)
        days="${2}"
        shift
        ;;
    --out)
        out="${2}"
        shift
        ;;
    --)
        shift
        break
        ;;
    *)
        echo -e "Error: $0 invalid option '$1'\nTry '$0 --help' for more information.\n" >&2
        exit -1
        ;;
    esac
    shift
done
# 创建客户端证书
# 非交互式方式创建以下内容:
# 国家名(2个字母的代号)
C=CN
# 省
ST=GuangDong
# 市
L=GuangDong
# 公司名
O=TY
# 部门名
OU=${OU:-未知}
# DN信息
CN=${CN:-未知}
# 邮箱地址
emailAddress=${emailAddress:-virtual@example.com}
# 创建私钥
openssl req -utf8 -nodes -newkey rsa:2048 -keyout "${out}.key" -new -days "${days}" -out "${out}.csr" -subj "/C=${C}/ST=${ST}/L=${L}/O=${O}/OU=${OU}/CN=${CN}/emailAddress=${emailAddress}"
# 使用根证书进行进行签证
openssl ca -utf8 -batch -days 36500 -in "${out}.csr" -out "${out}.crt"
# 转换客户端证书的类型为浏览器支持的模式
openssl pkcs12 -export -inkey "${out}.key" -in "${out}.crt" -passout pass: -out "${out}.pfx"
exit 0

3. 尾语

SSL证书常用格式转换方法

Bash
# crt转pfx(p12)
openssl pkcs12 -export -inkey server.key -in server.crt -out server.pfx

# csr转pfx(p12)
openssl pkcs12 -export -inkey server.key -in server.csr -out server.pfx

# pfx转jks
keytool -importkeystore -v  -srckeystore client.pfx -srcstoretype pkcs12  -destkeystore client.keystore -deststoretype jks 

# jks转p12(pfx)
keytool -importkeystore -srckeystore client_pri.keystore -destkeystore client_pri.p12 -srcstoretype JKS -deststoretype PKCS12 -srcalias imgo.tv -destalias imgo.tv -noprompt

# pfx转x509
openssl pkcs12 -in onovps.com.pfx -nodes -out onovps.com.pem 
openssl rsa -in onovps.com.pem -out onovps.com.key
openssl x509 -in onovps.com.pem -out onovps.com.crt

资料参考

☑️ ☆

精装房装修指南(2024)

本文详细记录了装修过程中的各个细节和相关知识,欢迎指出其中的错误

1. 基础知识

1.1. 硬装和软装

在装修中,有软装和硬装2个概念:

  • 硬装指基础装修,指房屋装修中不易更换且影响到房屋主体结构的部分,包括水电改造、墙面处理、地面处理、吊顶、砌墙、门窗安装、厨卫装修等,硬装一旦完成就很难更换
  • 软装指硬装外进行装饰的元素,包括家具、窗帘、床上用品、灯具、地毯、装饰品、植物等,这些元素通常能够反映出业主的个人品味和风格,也更容易随着时间和喜好的变化而更换

将房子倒过来,会掉的部分是软装,不动的部分是硬装

毛坯房往往需要硬装+软装,精装房往往是少量硬装+软装

1.2. 一般装修顺序

装修的内容如下

  • $装修 = 设计 + 施工 + 材料$

装修顺序参考如下

由于我是精装房,所以本文装修顺序参考下图

1.3. 关于设计师

如果预算足请考虑找专业的设计师,专业的设计师根据工作内容一般可分为:

  1. 全案设计师:提供从硬装设计到软装配饰的一站式服务,包括空间规划、材料选择、家具配饰、灯光设计等各个环节,甚至包括后期的施工监理和配饰采购等服务,全案设计师的优点是能够提供全方位的服务,客户无需自己协调各个环节,但价格通常也会相对较高
  2. 软装设计师:负责室内装修中的软装设计,包括家具的选择和布局、窗帘和床上用品的选择、艺术品和装饰品的配置等,他们需要有很好的审美观和配色能力,能够根据空间的风格和功能,进行恰当的软装搭配和布置

设计师根据身份分为2种:

  1. 装修公司设计师:包括硬装到软装的所有内容一站式服务,优点是省力,缺点是设计风格固定,销售属性重、方案大众化
  2. 独立设计师:根据预算出效果图、报价表,根据报价表自行找施工方,与师傅交涉,保障装修质量,费用较高(200-400/平)

独立设计师通常收费比商业的更贵,效果更好,本文改动小,所以是请工地师傅直接作业的

无论请不请设计师,在动工前,以下是需要多注意的点:

  1. 动工前的考虑越多提升越大,多从4个点考虑:个人习惯、家庭成员身高、家电分布、使用频率,其次才是装修风格与顺眼程度,好看不如好用
  2. 在材料上选择成熟商品,成熟商品性价比不一定是最高的,但品控优于不成熟的产品
  3. 在施工阶段要多看多问多检查,实时关注施工质量,不偷懒,分阶段定时全面抽查,任何点都要及时调整,最忌装完再解决心态
  4. 不要迷信检测表和他人的评价,要相信自己的需求和感受,适合自己的才是最好的

2. 硬装指南

从这里开始,记录每一个改动的知识和细节

2.1. 阳台封窗

封窗有2种选择,大型建材商超的专业门窗店门窗散户,前者价格昂贵后者便宜

门窗散户的好处是可以看看同小区的样板房,往往在小区楼下就有分店,可以很方便地看到封窗的效果

下面是关于封窗的基础知识,如果要省钱,这些知识就必须有所掌握

去门店看门窗截面都会有对应的型号配置,在门店要重点看这个

封窗工序通常如下

  1. 签封窗合同,确认窗户型号大小,定做窗户都是需要15天以上
  2. 与商家一同前往物业报备(确认商家资质没问题)
  3. 做地面保护(地面膜、防尘膜)
  4. 拆除护栏 & 拖拉门(如有)
  5. 吊装玻璃后安装窗户

2.1.1. 型材

窗户型材

指固定窗户的框架,通常是金属经过塑性加工成形、具有一定断面形状和尺寸的实心直条

封窗按型材类型分为两种:

  • 断桥铝:指两面为铝材,中间采用塑料型断热材料(桥),做成隔热型的铝材,所以叫断桥铝,性价比高,具备隔热保温的效果
  • 系统窗:指盒装的断桥铝,会进行水密性、气密性、抗风圧、隔热、隔音、手感等一系列的测试,价格更高

型材的关键参数是壁厚,目前主流有1.4mm1.6mm1.8mm2.0mm,分别适应:

  • 10楼以下:1.4mm足以
  • 10楼以下+超大落地玻璃:1.6mm-1.8mm
  • 10楼以上:1.8mm以上

壁厚的厚度对连接强度以及承载力带来更大要求,要选择合适的壁厚而不要盲目的追求过高的壁厚

此外,铝合金的型号一般是6063-T5,预算充足可以考虑6063A-T56063-T6

注:6063表示型号,T5表示热处理时的加工工艺

窗户系列

指门窗型材横切面的宽度,会影响耐用度与整体结构稳定性,常见的包括6065708090100110120130系列,数字越大耐用性与稳定性、价格越高

系列数字在同个种类的窗户间才能对比,比如 推拉门80系列 无法与 平开窗80系列对比

选择系列取决于地理位置和型材类型,如需要抗强风地区则需要考虑80系列以上的窗户,如需要型材是窗纱一体则最好考虑100系列以上

窗纱一体因为共用边框,如果结构设计不好,那么整体性能直线下降,优点是颜值较高,南方地区非常合适因为肯定是要考虑防蚊虫

2.1.2. 玻璃

玻璃是封窗的重点,按玻璃类型分为:

  • 夹胶玻璃:由两片或多片玻璃中间夹有一层或多层极具韧性和粘结力的特殊聚合物夹层材料制成的复合玻璃产品,常见的聚合物有PVBSGPEVAPU等,工艺成熟
  • 双层中空玻璃:由两片或多片玻璃中间有一定距离的空气层构成,这种结构可以有效防止热量和声音的传输,具有良好的隔热、隔音效果
  • 三玻两腔:这是一种更先进的中空玻璃,它由三片玻璃和两个空气腔室组成,比传统的双层中空玻璃具有更强的隔热、隔音效果
  • LOW-E玻璃:低辐射玻璃,由多层金属以及化合物he在玻璃上进行镀膜的工艺,带来高透射率、高隔热,保温隔热节能(地暖)效果非常好,降低透光
  • 钢化玻璃:钢化玻璃是一种经过特殊处理的玻璃,具有更高的强度和破碎安全性,如果破碎,钢化玻璃会碎成小块,减少对人体的伤害

在封窗玻璃上,由于通常是定制大小,所以可以自由组合玻璃种类,例如要双层中空+夹胶玻璃来实现更强的隔音隔热效果

双层中空玻璃常见的有5+12A+5=2片5毫米的玻璃+12毫米的氩气填充

性价比较高的选择通常是双层中空玻璃,在南方隔音隔热都达标,国内的玻璃质量非常好,只要价格不是特别低不用太担心劣质产品问题,选择合适的规格+3C认证不会翻车的

2.1.3. 其他

隔热条

是中间的塑料型断热材料,隔热条请检查是否是尼龙PA66-GF65,这一种常用于制造隔热条的材料

PA66指的是聚酰胺66,GF65则表示这种材料中含有65%的玻璃纤维

这种含有高比例玻璃纤维的尼龙材料具有优秀的热稳定性、低热传导率、高强度和刚度等特性

密封胶条

是用于保障门窗的水密型、气密性、抗风圧性等各项性能,优选EDPM三元乙丙胶条(即汽车专用橡胶)

密封胶条预算充足选3道(外框/中间/窗扇),胶条的塔接量越大效果越好,挤压时形变越大效果越好

注胶

一般为角码注胶,预算充足选销钉注胶

签合同

合同核心要点:

  1. 合同上注明后期无任何增项,其总价包含所有费用(运输、垃圾清理、吊装、主材辅材)
  2. 仔细检查玻璃投影面积大小是否准确
  3. 付款方式先50%后,验收后无问题支付剩余50%
  4. 明确工期,在30天内完工
  5. 质保内容清晰,包括框架、玻璃、五金、胶水、纱窗的保质期时间

合同明确乙方责任:

  1. 提供公司的营业执照、施工人员具备相关资质
  2. 注明施工方出现任何安全事故、第三方损失等问题都与甲方无关、乙方将承担所有的法律和经济责任
  3. 如安装、测量、设计出现任何产品错误都将重新制作并在协商的限定期限内交付甲方,若乙方交付产品与约定不符,乙方须免费重新制作更换
  4. 安装操作需符合门窗工程安装规范要求、操作规程、安全防火规定
  5. 安装后的垃圾清运

合同明确产品信息,参考如下:

  1. 型材:规格型号、主框及窗扇壁厚、6063T5原生铝材、闭口玻璃扣条、加隔音棉、加防尘条、什么品牌三元乙丙edpm汽车级胶条、什么品牌PA66多腔隔热条、一体硫化焊接、铝合金角码、德国weiss注胶、不锈钢防坠绳、加工工厂
  2. 玻璃:5+27A+5双层钢化一体折弯、大玻璃加厚厚度、南玻原片、什么玻璃钢化厂加工,3C编码可查、卫生间磨砂,其他玻璃超白lowe、玻璃中空充氩气3A分子筛,加玻璃垫块
  3. 安装辅料:发泡剂、密封胶、外窗台2.0厚铝合金防水板、304不锈钢拉爆螺丝、外开窗防坠绳的来源以及品牌
  4. 五金:执手、铰链、传动杆、合页、风撑、锁点、定位器的品牌
  5. 纱窗:金刚网高透片纱,开窗高透隐形纱窗
  6. 最后要附窗型格局、窗户铝型材横断面图、产品数量价格明细单、工艺标准及质检报告、进口五金配件的授权书和物流单

2.1.4. 结论总结

品牌可以优选以下

  • 玻璃:南玻、台玻、耀皮、信义、新福兴
  • 隔热条:进口的恩欣格、国产的浙友、源发、优泰
  • 五金:好博、维哈根、诺托、吉利娅、威必驰、霍曼、瑞纳斯、坚朗、希美可、国强、雷圭蒂
  • 密封条:森佩理特、海达、合和、新安东、窗友、江阴海达、诺田、联和强、瑞得、瑞隆特、亿安、高士达、澳顺

10个咨询模板问题

  1. 上门测量、设计尺寸、高空作业、安装需要收费吗?
  2. 可以提供铝材的品牌以及型材的证书、深加工工厂吗?型材的宽度厚度符合国标吗?
  3. 玻璃的品牌、深加工工厂、厂家资质、供货凭证、玻璃的厚度、惰性气体的类型(氯气)是什么?
  4. 五金(执手、铰链、传动杆、合页、风撑、锁点、定位器)的品牌是什么?
  5. 隔热条的品牌是什么?不同品牌分别是如何收费的?
  6. 密封条有几道密封?是什么材质以及品牌?含胶量是多少?是圧入式还是穿入式的?
  7. 组角是什么方式?型材的颜色是如何处理的?
  8. 辅材材料(螺丝、膨胀螺丝、密封胶、发泡胶)的来源以及品牌是什么?
  9. 窗台的防水工艺是怎么做的?师傅是外包还是自有的?安全问题如何规避?
  10. 因为材料破损导致延误的工期如何处理?型体、玻璃、整体质保多少年?有漏水算质量问题吗?

2.2. 油漆

2.2.1. 买漆

乳胶漆的选择注意事项如下表

问题 方法 备注
假漆 桶身桶盖标签严丝合缝/扫码查询真伪(微信小程序) 商家假一赔十
执行标准 GB18582-2020和GB/T9756-2018 最新的2020和2018标准限制了苯VOC和重金属
优等品/一等品 耐擦洗次数分别为6000和1500 拒绝合格品
VOC含量 甲醛释放量,短期入住低于2g/L 符合国标即可
环保标识 GB/T认证、美国绿色卫士、欧盟之花等

通常来说,乳胶漆细腻无杂质无刺鼻气味,不要提前太久买漆避免沉淀有色差,根据以上原则去选择油漆极氪

附上 GB18582-2020 标准如下

  • VOC含量低于80g/L
  • 苯系列总和含量低于100mg/Kg
  • 游离甲醛含量低于50mg/kg

2.2.2. 刷漆

刷漆也是非常重要的一步,刷漆注意事项:

  1. 刷漆工序通常包含刷1遍底漆再刷2遍面漆总共3道工序,部分油工因为工钱问题不太喜欢刷底漆
  2. 刷完关闭门窗一周,避免水分蒸发过快导致起皮、开裂
  3. 腻子工艺一定要做垂直找平,否则会影响柜子安装
  4. 在油工进场前确定吊顶主灯、电动窗帘、投影仪、空调打孔以及各种位置的开关
  5. 区分油漆的种类,水性漆(易刷不耐用)和油性漆(难干但耐用)

2.3. 全屋定制

全屋定制是根据个人习惯来定制橱柜、衣柜、鞋柜、电视柜、餐边柜、床、书桌、书柜、酒柜等家私,相比成品全屋定制的优点是充分利用空间、个性化强、更美观

全屋定制的流程通常如下:

  1. 前期沟通房屋以及设计细节后交定金
  2. 现场量房后5-7天出设计图
  3. 双方沟通调整设计图直到定稿
  4. 15-20天的板材制作
  5. 3-5天的现场安装

2.3.1. 购买渠道

全屋定制的购买渠道通常有:

  1. 大品牌如索菲亚、欧派、尚品宅配、志邦家居、顾家家居
  2. 本地工厂
  3. 木工师傅
  4. 设计品牌(高端)
  5. 小品牌

全屋定制买的核心是板材质量以及安装服务两个方面,可以根据自己的预算来选择合适的购买渠道,通常设计品牌>大品牌>本地工厂>小品牌

安装服务这个也非常重要,因为板材不合适或者师傅安装手艺不好返工动辄1-2周,非常耗时耗精力

2.3.2. 板材的环保等级

板材按照环保等级可分为E1<E0(甲醛含量0.50)<Enf(甲醛含量0.25)

E0可以视为家用的最低要求了,这一块目前的板材大部分都是ENF级别

2.3.3. 制作工艺

全屋定制的核心制作工艺可分为基材、胶水、饰面材质、封边技术、五金

整理如下图:

基材的优缺点如下:

基材 优点 缺点 建议
实木板 坚固耐用、纹路自然 造价高、不防潮 成品家居、木工师傅最爱
欧松板 稳定易加工 防水一般 柜体、柜门、背景墙
颗粒板 平整稳定,不易变形 造型固定防水差 干燥区做柜门柜体均合适
密度板 可塑性强,做造型适宜 防水性差 干燥区柜门
多层板 防潮强韧度高,不易开裂 易变形 湿区柜体
实木生态板 强度高、吸音隔热 稳定性低、不防潮 干燥区柜体、背景墙

饰面的优缺点如下:

饰面 优点 缺点 建议
双饰面 耐磨耐污、易打理、款式多 无法做造型 简约风、北欧风、原木风
烤漆 色泽鲜艳、防水防潮防火、易清洗 价格高、无法修补 现代风、极简风
PET 色彩丰富、类肤感 易划、价格高、无法做造型 极简风、现代风
PVC覆膜 造型多、质感丰富、耐磨耐污、无需封边 不耐高温、遇热开裂 欧式风、田园风、中式风
实木贴皮 高端、手感好 价格高 中式风、日式风、极简风

封边技术的优缺点如下:

工艺 优点 缺点 建议
EVA 价格低 不防潮易开裂 干燥环境下对价格敏感用户优选
PUR 用胶少、密封性强、防水强 美观不如激光封边 高性价比选用
激光封边 无胶、美观、防水防潮、环保 造价昂贵 预算不差钱优选

五金件决定耐用程度:

  • 铰链(核心):大品牌,务必搭配阻尼器(安全静音减震)、品牌优选萨郦奇、百隆、海蒂斯、东泰、优特、乐卡
  • 滑轨(抽屉):务必搭配阻尼器(安全静音减震),优选海蒂斯、百隆、东泰
  • 气撑(上下翻门):预算足务必随意停气撑,优选萨郦奇、意大利IF
  • 拉手:不选反弹器
  • 拉篮:优选巴斯蒂姆、格麦、诺米
  • 衣通(挂衣杆):铝合金即可

最后我是选了 柜体多层板+柜门欧松板+喷粉工艺

2.4. 其他硬装

2.4.1. 美缝剂

美缝剂是个相对不太标准的行业,说法也是非常多

美缝剂优缺点如下:

类型 优点 缺点
环氧彩砂 耐磨、防水、抗晒、颜色稳定 施工难度较高、价格较高、部分产品易变黄
聚脲美缝剂 耐黄变、抗污、防水、柔韧性好 施工要求高、假聚脲多、价格较高
普通美缝 价格便宜、颜色丰富、防水防霉 凝固后易收缩、耐久性较差

参考如上,潮湿环境如厨房和浴室可以选择聚脲美缝,阳台直射则优先使用环氧彩砂

2.4.2. 纱窗

纱窗分类:

纱窗类型 优点 缺点
金刚网 防盗防鼠 不透风不透气影响采光,孔大小蚊虫可进出
高透网(PP/Pet材质) 通风,近乎隐形 不耐用,不适合家庭有宠物
高清网(304/316不锈钢) 采光、通风以及颜值适中 介于金刚网与高透网之间

纱窗怎么选:

  1. 边框选极窄,窗框选铝合金材质
  2. 高清/高透纱网选20目以上,高清纱网选304/316材质的不锈钢

2.4.3. 窗帘

窗帘的基础知识:

  • 材质:绦纶材质遮挡强适合卧室,棉麻材质遮挡弱易变形所以适合客厅,丝绒材质厚重沉稳但易挂灰和缩水
  • 双层:幻影纱透光不透人适合卧室,金刚砂透光透风适合客厅
  • 安装:罗马杆是明杆易,安装易拆卸易清晰,但漏光且承重较差;滑轨可搭配窗帘盒实现暗装,使用上顺滑噪音小

其他要点:

  • 高温定型比自然下垂更有层次感更显高级
  • 计价方式中,定高是指高度固定,按照宽度计价,适合小卧室;定宽布是宽度固定,按照高度计价,合适复式超高户型落地窗
  • 拼色窗帘设计得当显高档但易翻车,而纯色窗帘简单耐看不易翻车
  • 韩式S钩比传统四爪钩安装方便且不易钩破窗帘
  • 韩褶在宽度4m内做2倍,宽度4m以上做1.8效果更佳

2.5. 硬装清单

户型是四房两厅两卫,硬装改造成本如下:

位置 修改项 预算 实际 备注
全屋 地砖美缝 4,000 3,800 包工包料
重新刷漆 4,500人工+3000漆 3,500人工 + 1,692油漆 2x18L五合一 + 5L五合一 + 18L底漆,立邦油漆
电位更改(70/个/1500mm) 2,000(80/个) 1,400(23个)
房间地砖更换 8,000人工 + 6,000 地砖 8,200人工 + 4,600瓷砖
电位更改23个 4,000 1,840
衣柜定制 60,000 54,449
客厅 双眼皮吊顶 6,000 取消
通铺至大阳台 6,000 5,300
系统封窗 20,000 主体1,7125+窗户1942+吊装2000
主卧 拆除衣柜 800 500
吊顶+刮腻子+乳胶漆 4,100
拆除原有吊顶 500 500
生活阳台 砌洗衣机台子 1,000 8,00
杂项 空调洞修复x2 100
阳台封窗处打胶 50
阳台刮腻子 900
总价 125,950 11,2698

封窗的窗户规格如下:

  • 产品系列:120系列断桥铝
  • 铝材宽度:2.0mm
  • 铝材品牌:新河
  • 玻璃源片品牌:信义
  • 玻璃规格:12+15A+12
  • 五金品牌:瑞纳斯
  • 高透纱网:304不锈钢
  • 隔热条:PA66-25
  • 密封条:汽车级三元乙丙复合胶条
  • 铝材腔体结构:六腔体结构
  • 售后质保:保修2年
  • 价格:团购830元/平

3. 软装

3.1. 电器

3.1.1. 空调

空调的基础知识:

  1. 匹数是指制冷量,如1P=2600w、1.5P=3500w、3P=7200w
  2. 空调行业命名是非常规范,以美的KFR-35GW为例,35 指的是 3500w 制冷,字母则是首字母缩写,k 代表空调,f 代表分体,r 代表有热泵,g 是挂机,w 是外机
  3. 定频与变频一定是变频更优秀,定频指外机只有开启与关闭,而变频是可以根据环境输送不同的温度的风,变频长时间使用省电、凉的快、精准控制出风口温度,定频忽冷忽热、耗电大只适合仓库类的房间
  4. APF(能效等级)是衡量空调质量的最关键参数,$APF=制冷制热量/耗电量$,但可以作假(如厂家自测、贴标),在宣传页上谁也不会写自己的APF值低,如果提高APF的方法不是增加换热面积,而是提高风量只会导致噪音变大
  5. 节流器分电子膨胀阀和毛细管,毛细管可靠性高但制冷量无法控制,电子膨胀阀制冷量可调节但成本较低所以是厂家重点减配对象
  6. 冷凝器的放热效率取决于铜管的粗细,就是俗话说的外机越重质量越好的来源

选空调时要注意:

  1. 高温制冷在(60度高温制冷)外机处于监狱机位时是必要的
  2. 节流器要选电子膨胀阀
  3. 能双排散热不单排
  4. 导风板选第三代旋转出风,可以左右上下出风实用性最高

3.1.2. 马桶

普通马桶怎么选:

  1. 默认标配:虹吸式、一体式、1280度完全瓷化、带缓降器
  2. 马桶水件:水件寿命约等于马桶寿命,ABS优质水件按下去有清脆感,回弹迅速
  3. 马桶管道:选全施釉管道且45毫米以上宽度

智能马桶怎么选:

  1. 默认标配:虹吸式、双水路(外分/内分)、即热式、IPX4
  2. 冲水系统:要稳定可靠选第五代重力纯机械式,水压太低选第四代泵冲式,无水箱选第二代双段虹吸
  3. 停电冲水:要不依赖电池实现的,即可以当普通马桶使用
  4. 杀菌技术:紫外线杀菌(差)、UV除菌(差)、电解水(最优,确认喷嘴和内壁都使用了)、等离子杀菌(非自动)
  5. 脚感技术:红外脚感价格低维修易但灵敏低,激光脚感比红外脚感灵敏其他一致,机械脚感指向好灵敏高,电容式灵敏高但对干湿分离有要求
  6. 除臭技术:活性炭(成本低,有效期短,更换麻烦) < 无光触媒 < 硅藻纯,无特殊要求不选活性炭
  7. 泡沫盾:闲置率高,耗材使用成本高,Pass
  8. 喷杆:易拆卸最重要,材质不锈钢或者塑料无关紧要
  9. 其他:安全防护装置怎么样?保修期多久?马桶无法安装如何处理?

3.1.3. 灯具

灯具怎么选:

  1. 灯具选大了显压抑选小了显小气,大小可参考$吊灯直径=(面积长+宽)/2$,$吸顶灯长宽=空间长宽/5$
  2. 频闪会导致眼睛累,国标上,$频闪>3125Hz$,用手机拍摄验证无频闪可将快门速度调整至$1/3200s$进行验证
  3. 灯光瓦数并不直接对应亮度,对应亮度的单位是流明,能效越好的灯说明其每瓦产生的流明更高,瓦数通常是面积大小的3~5倍,面积越大,则倍数要相对增加
  4. 色温单位是k,越低越暖黄,越高越冷白,通常来说餐厅和卧室考虑3000-3500k的暖光,客厅考虑3500-5000的中性光,厨卫考虑5000以上的冷光
  5. 显色指度的单位是CRI/RA,是一个描述光源显示物体颜色真实性的指标,正常来说要选大于90的光源灯泡最佳
  6. 三色变光灯取决于个人需求,但每次都切换灯源颜色相当麻烦,如果需要的话请购买带记忆功能的最佳
  7. 品牌选择上吸顶灯考虑欧普、雷士、松下、飞利浦;筒灯和射灯考虑飞利浦、西顿、企一、华格;吊灯考虑欧普、雷士、希尔顿、月影凯顿;智能灯光考虑小米yeelight

3.2. 沙发餐桌

沙发怎么选:

部位 优选 避开 为什么
表面材质 棉麻、磨砂布、头层皮 科技布、猫抓布 易裂开
填充海绵 密度45D、55D,材质乳胶、羽绒 公仔棉 易塌陷
沙发框架 松木、桦木、金属框架 桉木、杂木、大芯板 易变形
沙发封底 布艺+离地15cm 无纺布 久了容易碎裂,15cm方便扫地机器人
沙发弹簧 4毫米迷蛇形弹簧+绷带 纯绷带 易塌陷
沙发进深 老人50-60cm易起身,喜欢躺着80cm以上 / /
猫咪哲学 高密度磨砂布 猫抓皮 猫抓皮不透气夏天一身汗

岩板餐桌怎么选:

部位 优选 避开 为什么
岩板品牌 国产新明珠、德利丰、诺贝尔、伊诺,进口优选拉米那、施恩德、德赛斯、泰洛 / /
岩板板材 厚度大于12毫米、莫氏硬度大于9、耐高温A1级别、渗透率为0,表面光滑干净 / /
岩板支撑 多层实木、碳素钢才、铝合金,保证与地接触面积越大越好 / /

3.3. 软装清单

如下

位置 名称 型号 预算 实际 备注
全屋 扫拖机 云鲸 J4 4,000 3,752.99
(床+床头柜)x2 1800cm + 1500cm 10,000 5,168
客厅 电视机 雷鸟鹤7 85寸 7,999 7,256.48
沙发 帆船沙发2800cm x 1000cm x 76cm 8,000 6,460
立式空调 华凌KFR-72LW/N8HA1 8,000 4,291
餐厅 餐桌 1400cm x 800cm 白腊木桌腿 2,000 1,428
餐椅x4 白腊木一体 1,000 1,428
主卧 空调 4,000
次卧 空调 华凌KFR-32GW/HE1Pro 3,000 2,036
儿童房 空调 2,000
书房 空调 2,000
书桌 2000cm x 700cm x 75cm 2,564 3,770
阳台 洗烘一体 海尔HBNS100-FQ176U1</br>海尔XQG100-BD14176LU1 6,500 5,804.16
卫生间 智能马桶 瑞尔特 A6 旗舰款 3,137 2,701.00
厨房 洗碗机 海尔 H40 2,500
总计 63,145

4. 其他

4.1. 设计参考

玄关

  1. 鞋柜内部保留通气孔
  2. 玄关处要有换鞋凳、镂空层方便放杂物
  3. 玄关鞋柜崖有辅助光源包括鞋柜内、鞋柜下方、鞋柜收纳区
  4. 鞋柜下方预留20公分方便放鞋子
  5. 一键灯光方便出入时直接控制全屋灯光
  6. 鞋柜内部做活动板方便收纳大小不一的鞋子
  7. 鞋柜留电位方便后期安装烘鞋器

客餐厅

  1. 吊顶简洁更容易打扫
  2. 层高低于2.8米不做吊顶
  3. 先窗帘后家具,避免后期颜色搭配不协调
  4. 漆认准三环标志,无需追求儿童漆、零甲醛漆等
  5. 大吊灯不仅遮挡视线,拉低层高,还很难打理
  6. 开放式的储物柜,稍不整理就显乱,而且容易落灰
  7. 电视墙花里胡哨,没有必要,容易过时
  8. 浅色沙发不耐脏、深色地板会显灰
  9. 开关插座数量没预留好,后期使用不方便
  10. 客厅装波导线和罗马柱,费钱也容易过时
  11. 没有提前预估好家具尺寸,插座被挡住
  12. 地插难清扫易积灰易沾水还易绊人
  13. 圆桌其实比方桌占地方
  14. 门槛石,不美观,大通铺大气好看
  15. 餐厅慎用木地板,油污难打扫
  16. 玻璃透明茶几全是指纹印难打理
  17. 空调洞没有往外倾斜,雨水易往家里流
  18. 地毯非常难清理,配上宠物可绝杀
  19. 瓷砖要相同一批厂不易有色差,且尽量冗余5%+
  20. 踢脚线做了一整圈会导致家具没办法靠墙摆放
  21. 射灯、筒灯等装太多,闲置落灰
  22. 家庭影院要预埋音箱线
  23. L型沙发非常占空间,务必谨慎
  24. 餐厅装色温3000k左右的暖色灯可以增进食欲

厨房

  1. 烟道忘记装止逆阀
  2. 拉篮一定不能百分百抽拉出来,使用很不方便
  3. 吊柜选了上翻门,开门容易关门难
  4. 窗户刚好对着炉灶,影响炉火,也影响油烟机安装
  5. 厨房水管没有走顶,漏水漏到楼下投诉才知道
  6. 出墙管件没有先接三角再装用水器
  7. 灶台区没有比台面低 5-8 厘米
  8. 隐藏式垃圾桶容易滋生细菌
  9. 开放式厨房没装集成灶,油烟窜到房间里
  10. 厨房插座不够,小电器无法同时使用
  11. 台面做太低,每次洗碗弯腰很难受
  12. 石英石台面结实耐脏,性价比高于大理石、不锈钢
  13. 燃气没有设两个阀门,无法控制灶台和燃气热水器
  14. 地柜没做抽屉,无法分类收纳
  15. 橱柜面板别用烤漆的,有划痕,防火板材质最耐用
  16. 燃气灶一定要靠近烟道,缩短管道长度
  17. 水槽尽可能靠近主排水管,降低堵塞的几率
  18. 水槽要做好存水弯,不然有异味
  19. 厨房主光源最好是嵌入式的,操作区分区照明不背光

卧室

  1. 衣柜进深要不低于60cm
  2. 开关做双控,半夜不用起来关灯
  3. 门窗要做好隔音
  4. 卧室不要用谷仓门,隔音效果不好
  5. 空调对着床吹,对身体不好也不舒服
  6. 窗帘没选遮光,夏天刺眼的光线照得根本不能睡懒觉
  7. 衣柜没装感应灯,找衣服不方便
  8. 衣柜没做到顶,上面都是积灰
  9. 床尾登很鸡肋,后期都用来堆衣服了
  10. 榻榻米不透气造价贵,还容易有卫生死角

卫生间

  1. 没装除雾镜,每次洗完澡都看不清脸
  2. 没装防臭地漏,卫生间味道很难闻
  3. 没装电热毛巾架,毛巾湿哒哒滋生细菌
  4. 淋浴房坡度没做好,洗澡老积水
  5. 浴缸慎用,新鲜感过了被闲置,清洁也麻烦
  6. 浴室柜没做墙排,既不美观也难清洁
  7. 五金洁具买的便宜,后期容易坏,更换也麻烦

阳台

  1. 装吊扇要了解最小风速
  2. 阳台打胶,记得从窗户外打胶
  3. 开放式阳台建议安装地漏,下雨时会有雨水落进来
  4. 不要把雨水管用作洗衣机排水
  5. 阳台改家务区,防水高度至少 30 厘米

家电

  1. 跑步机和动感单车基本上都是闲置
  2. 智能马桶买双水路的
  3. 洗衣机不要买洗烘一体的
  4. 空调尽量不买立式,占地方
  5. 家电可以分期买,先买重要的,后面买需要的1. 冰箱买风冷不买直冷,买双循环不买单循环,买变频不买定频,买一级能效不买二级能效买一字单排抽,不买双排抽

4.2. 我的时间表

我的时间表参考

内容 时间 耗时 备注
收房 + 验房师验房 2023-12-10 1天 出报告让物业维修
封窗定制下单 2023-12-27 20-25天 做功课花了好几天
装空调外机 2024-01-16 1天 先装外机避免划伤封窗
全屋打拆(推拉门+地板+衣柜) 2024-01-18 3天
封窗安装 2024-01-19 2天 吊装不靠谱导致无法当天安装
房间地砖敲定 2024-01-20 1天 广东砖闭眼买
南沙的全屋定制现场沟通和看样品 2024-01-21 1天 小厂便宜无质保
房间地砖安装施工 2024-01-23 2天 踢脚线10cm太高重新订6cm的
燃气现场报装+隔天安装 2024-03-09 2天
主卧双眼皮吊顶 2024-03-14 3天
立邦油漆专卖店 2024-03-16 1天
全屋定制复尺和定金 2024-03-17 1天
电位更改 2024-03-24 3天
小阳台洗衣机台改造 2024-03-24 1天
水钻改空调孔 2024-03-27 1天
油漆涂刷 2024-04-13 3天
美缝 2024-04-20 2天
水管静音棉安装 2024-04-21 1天
全屋定制安装 2024-04-30 13天 返工以及五一假期影响
灯具安装+纱窗上门测量 2024-05-11 1天
通风等带入住 / /

目前待推进

  • 马桶更换
  • 纱窗安装
  • 全屋开荒
  • 沙发、餐桌、书桌进场
  • 洗碗机、空调、扫地机器人、电视
  • 物业费免除确认

5. 装修复盘

5.1. 装修顺序

正如前文提到的,我的装修顺序是按照顺序来的,但其实顺序本身是可以加快同步进行的

在动手之前,可以先模拟整个过程,然后合并相同工序的事情,例如:

  • 第一阶段(3天内):找封窗商家,敲定封窗细节,等待封窗制作约20-25天
  • 第二阶段(20-25天):先找硬装师傅先拆墙、地砖、吊顶以及更换电位,然后做功课购买油漆、瓷砖、插座面板、美缝剂,灯具、燃气等
  • 第三阶段(7天):硬装师傅安装地砖、美缝然后全屋定制上门敲定商家品牌以及电位细节,更换电位并等待全屋定制的制作
  • 第四阶段(20-25天):全屋定制期间,让硬装师傅刷漆、更换电位,在刷漆干了之后联系灯具师傅安装,剩下的时间去找家具、电器
  • 第五阶段(3-5天):全屋定制安装以及全屋开荒后安排家具电器进场

以上预计耗时也就在2个月左右,剩下安排通风根据个人情况安排

6. 尾声

装修参考资料

☑️ ☆

Docker打包Selenium容器实践

Python的Selenium套件对于自动化一些常规网页操作非常好用,但部署是不太省心省力的,在不同的操作系统上安装Chrome与对应版本的driver后还需要进行测试运行,相较于传统的服务器项目部署来说,安装与排错都显得比较费事。

如果将Selenium+Chrome进行Docker部署,那可以大大降低部署难度,更加关注自动化的代码本身,于是有了下面打包Selenium项目(Chrome浏览器)的实践。

1. 前期准备

1.1. web项目

使用Python写一个简单的web项目,代码结构如下

Bash
❯ tree
.
├── requirements.txt
└── web
    └── __init__.py

引入fastapi作为web框架,__init__.py文件内容如下

Python
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}

运行方法

Bash
uvicorn web:app --port=5000

此时访问http://127.0.0.1:5000/可以看到返回 Hello World,Web项目搭建结束

1.2. Selenium

Selenium支持许多浏览器,这里选择Chrome作为模拟的浏览器

Chrome历史版本下载:https://www.slimjet.com

Chromedriver历史版本下载:https://chromedriver.storage.googleapis.com

tips:Chrome版本与Chromedriver版本需对应

安装Chrome后,将chromedriver引入到项目中并修改__init__.py文件如下

Python
import os
from fastapi import FastAPI
from selenium.webdriver.chrome.options import Options
from selenium import webdriver

app = FastAPI()


@app.get("/")
async def root():
    chrome_options = Options()
    # headless 开启无显示模式,Docker容器中没有安装GUI桌面
    chrome_options.add_argument('--headless')
    chrome_options.add_argument("--disable-extensions")
    chrome_options.add_argument("--disable-dev-shm-usage")
    chrome_options.add_argument("--no-sandbox")
    c = webdriver.Chrome(executable_path=os.environ['chromedriver'],options=chrome_options)
    c.get('https://cn.bing.com/')
    return {"biying": c.page_source}

此时项目目录结构如下

Bash
❯ tree
.
├── chromedriver
├── requirements.txt
└── web
    └── __init__.py

再次运行

Bash
uvicorn web:app --port=5000

访问http://127.0.0.1:5000/可以获取到必应的HTML文档

到这里一个selenium的web项目搭建完成,保存当前引用的python包到requirements文件

Bash
pip freeze > requirements.txt

2. Docker

将上面的演示项目打包成Docker容器要考虑如何在容器中安装Python环境与Chrome,这里直接采用python:3.7.4-slim-stretch官网镜像,该镜像是以Debian为系统镜像,所以可以使用apt安装本地的Chrome.deb安装包

tips:这里不宜选版本过高的Chrome,否则在安装的时候可能会缺失一些.so文件,解决起来比较棘手

2.2. Dockerfile文件

从Chrome历史版本中挑选了81.6的Deb安装包与Chromedriver,引入到项目中,Dockerfile文件如下所示

Docker
FROM python:3.7.4-slim-stretch

ENV LANG en_US.UTF-8

WORKDIR /app

COPY ./ /app

RUN apt update && \
apt install -y /app/google-chrome.deb && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
pip install --upgrade pip && \
pip3 install -r /app/requirements.txt && \
chmod +x /app/chromedriver

ENV chromedriver=/app/chromedriver

CMD ["uvicorn", "web:app", "--host=0.0.0.0", "--port=5000"]

此时项目结构如下,新增了google-chrome.deb文件作为镜像安装的chrome浏览器

TEXT
❯ tree
.
├── Dockerfile
├── google-chrome.deb
├── requirements.txt
└── web
    └── __init__.py

打包Docker镜像

Bash
sudo docker build -t hello-world:v22.12.27 . --no-cache

运行Docker容器

Bash
sudo docker run --name hello-world -p 5000:5000 hello-world:v22.12.27

访问http://127.0.0.1:5000/看到必应的HTML文档表示运行成功

☑️ ☆

速通正则表达式

正则表达式是用于搜索文本的技巧,对于任何需要使用计算机办公的人来讲,掌握一定量的正则表达式都有助于提升工作效率

正则表达式(Regular Expression)常缩写为RE,是计算机科学的一个文本搜索概念

正则表达式使用字符串来描述一系列匹配某个句法规则的字符串,在很多文本编辑器里,正则表达式通常被用来检索、替换匹配某个模式的文本

这篇笔记以最简单的方式介绍回顾正则表达式的例子,适合速通或偶尔查阅

最后会顺带介绍python中的正则表达式使用方法

1. 语法

阅读下面这篇文章,下面的所有的问题与示例都会围绕着这篇文章进行

TEXT
In the era of advanced communication technology, phone numbers play a vital role in connecting people worldwide. Take, for example, the number "010-20193201." This local phone number, commonly used in certain regions, helps individuals stay connected within their community. On the other hand, the international number "+13123029301" demonstrates the global reach of modern telecommunication. With this number, people can bridge continents and communicate effortlessly across borders. Whether it's a local number like "010-20193201" or an international one like "+13123029301," phone numbers facilitate seamless communication, breaking down barriers and bringing people closer together in this interconnected world.

1.1. 元字符

元字符是正则表达式最基础的构成,举一个最简单的例子,匹配"hello world"中的world,可以使用 \bworld\b匹配进行匹配

\b就是元字符之一,表示匹配单词的开始,元字符的列表如下

代码 说明
. 匹配除了换行符以外的任意字符
\w 匹配字母/数字/下划线/汉字
\s 匹配任意空白符
\d 匹配任意数字
\b 匹配单词的开始/结束
^ 匹配字符串的开始
$ 匹配字符串的结束
[abc] 匹配abc中任意一个字符,abc也可换成其他特定字符
\ 转义符,如想匹配问号,则是'\?'

元字符列表除了表达匹配的元字符外,还包括以下表达重复的元字符

代码 说明
* 重复0次或者多次
+ 重复1次或者多次
? 重复0次或者1次
{n} 重复N次
{n,} 重复N次或者更多次
{n,m} 重复N次或者M次

问题

  1. 请匹配出文章中的010开头的8位数号码
  2. 匹配文中所有大写开头的单词
  3. 请匹配出单词中包含he(不包括单词he)的单词

答案

  1. \b010-\d{8}\b
  2. \b[A-Z]\w*\b
  3. \b\w*he\w*\b

1.2. 反义

对于元字符\d的意思是匹配所有任意数字,如果需要匹配所有非数字呢?此时即是反义

代码 说明
\W 匹配所有不是字母/数字/下划线/汉字的字符,等价于[^A-Za-z0-9_]
\S 匹配所有不是空白符的条件,等价于[^ \f\n\r\t\v]
\D 匹配所有非数字的字符,等价于[^0-9]
\B 匹配不是单词开头或结束的位置
[^abc] 匹配除了abc以外的所有字符

问题

  1. 匹配文中所有不含t字母的单词

答案

  1. \b[^t\s]+\b

1.3. 范围匹配

除去上面的基础字符,还有一些常用的范围匹配写法,如下

代码 说明
[a-z] 匹配“a”到“z”范围内的任意小写字母字符
[A-Z] 匹配“A”到“Z”范围内的任意大写字母字符
[0-9] 匹配“0”到“9”范围内的所有数字

1.4. 分组

上面的语法已经足够解决绝大部分正则判断了,但需要多种情况的判定则需要更复杂一些的语法字符来辅助判断

代码 说明
| 分支条件,当筛选的数据有多种规则时,满足其中一种规则即可当成匹配
() 子表达式,也称“分组”

分组是可以被命名的,分为2种情况,手动跟自动

自动命名分组,可以使用圆括号将要匹配的模式括起来,从而创建一个分组,例如,使用正则表达式来匹配电话号码中的区号和本地号码(\d{3})-(\d{8}),其中第一个分组 (\d{3}) 是匹配区号,第二个分组 (\d{8}) 是匹配本地号码,这种情况下,分组会按照在正则表达式中出现的顺序从1开始自动进行编号

手动命名分组使用 ?P<name> 的语法来手动命名一个分组,例如,可以使用正则表达式来匹配电话号码中的国际代码和号码\+(?P<country>\d{2})(?P<number>\d+),其中,(?P<country>\d{2}) 命名了一个名为 country 的分组用于匹配国际代码,(?P<number>\d+) 命名了一个名为 number 的分组用于匹配号码

1.5. 零宽度断言

零宽度断言(zero-width assertions)是正则表达式中一种特殊的语法,用于限定匹配的位置而不消费任何字符。它们被称为零宽度断言,因为它们只检查当前位置前面或后面的字符,而不会在匹配结果中包含这些字符 零宽度断言可以用来指定一个位置,该位置需要满足某些条件才能匹配成功。它们不会实际匹配字符,而是在特定的位置进行条件判断。根据判断结果,正则表达式可以决定是否继续匹配。 常见的零宽度断言包括:

  • 正向肯定断言(Positive Lookahead):(?=...),表示在当前位置后面的字符能够匹配...。它用于查找符合某种模式的后面位置。
  • 正向否定断言(Negative Lookahead):(?!...),表示在当前位置后面的字符不能匹配...。它用于查找不符合某种模式的后面位置
  • 反向肯定断言(Positive Lookbehind):(?<=...),表示在当前位置前面的字符能够匹配...。它用于查找符合某种模式的前面位置
  • 反向否定断言(Negative Lookbehind):(?<!...),表示在当前位置前面的字符不能匹配...。它用于查找不符合某种模式的前面位置

举例从文章中抽取所有区号,可以使用\d{3}(?=-\d+)

问题

  1. 匹配所有有区号号码但不包括区号
  2. 匹配所有的手机号码但不包括区号

答案

  1. (?<=\d{3}-)\d+
  2. (?<=\+1)\d{10}

使用零宽度断言可以更精确地指定匹配位置,提供更强大的正则表达式模式匹配能力。但需要注意,不是所有的正则表达式引擎都支持零宽度断言,因此在使用时需要注意兼容性

1.6. 懒惰和贪婪

在正则表达式中,贪婪和懒惰是指匹配模式时的不同行为,默认情况下,正则表达式是贪婪模式

贪婪模式是指正则表达式尽可能地匹配最长的字符串。例如在文章中,使用正则表达式 /(\d+-\d+)/ 来匹配电话号码,它将匹配到最长的字符串 "010-20193201",而不是只匹配到 "010" 或 "20193201"

懒惰模式是指正则表达式尽可能地匹配最短的字符串。使用正则表达式 /(\d+-\d+?)/ 来匹配电话号码,它将匹配到最短的字符串 "010" 和 "20193201",而不是整个电话号码

在正则表达式中,我们可以使用 ? 符号来表示懒惰模式,使得匹配尽可能短。如果不使用 ? 符号,默认为贪婪模式。

2. Python正则表达式 - RE模块

Python中的RE模块提供与Perl语言类似的正则表达式匹配

Python字符串中使用反斜杠\来转义特殊符号,以免引发其特殊含义,例如搜索单引号的Python字符串要写成search_re = 'Chancel\'s blog'

但是正则表达式也采用了反斜杠\来表达转义,那么如果需要在Python中的正则表达式字符串放入单引号,则面临需要写成search = 'Chancel\\'s blog'的尴尬情况

为了使字符串中的反斜杠不被转义,Python允许使用在字符串前面添加r来表示字符串中的反斜杠不被转义,变成search_re = r'chancel\'s blog'

正则表达式的详细文档可以参考 官方文档 | re -- 正则表达式

以下说明基于Python版本3.9.2挑选几个重要的方法作为例子

2.1. compile

方法 re.compile(pattern, flags=0)可以编译正则表达式,用于匹配,在多次使用正则表达式的场景下可以提前编译正则表达式来提高匹配效率

例如匹配问号前的单词

Python
import re

content = 'Can you turn the declarative sentence into a rhetorical question?'
re_compile = re.compile(r'\b\w+\?', re.I)

print(re_compile)

# output: re.compile('\\b\\w+\\?', re.IGNORECASE)

Flag参数

标识 含义
re.A ASCII,让 \w, \W, \b, \B, \d, \D, \s 和 \S 只匹配ASCII
re.I IGNORECASE,忽略大小写
re.L LOCALE,由当前语言区域决定 \w, \W, \b, \B 和大小写敏感匹配
re.M MULTILINE,让'^'和'$'匹配每一行
re.S DOTALL,让'.'匹配所有字符,包含换行符!
re.X VERBOSE,允许添加注释、空白符

2.2. search

方法re.search是非常常用的一个方法,用于匹配第一个相应的匹配对象

Python
import re

result = re.search(r'o','oooo')

print(result.group(0))

# output: o

2.3. split

split可以将字符串按正则表达式全部分组

Python
import re
>>> re.split(r'\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split(r'(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split(r'\W+', 'Words, words, words.', 1)
['Words', 'words, words.']
>>> re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE)
['0', '3', '9']

Python的正则表达式还有非常多的知识,如果正则表达式基础过关,也可以直接参考官方例子

regular-expression-examples

3. 尾声

参考资料

☑️ ☆

常见的排序算法

现代大部分编程语言已经很少需要直接写排序算法了,但学习排序算法的原理依旧是一件非常有意思的事

下文是学习几个常见的排序算法笔记,如有错漏,请不吝指正

1. 前言

常见的算法概览如下

排序算法 是否稳定 最好时间复杂度 最差时间复杂度 平均时间复杂度 空间复杂度
冒泡排序(Bubble sort) $O(n)$ $O(n^2)$ $O(n^2)$ $O(1)$
插入排序(Insertion sort) $O(n)$ $O(n^2)$ $O(n^2)$ $O(1)$
希尔排序(Shell sort) $O(n log n)$ $O(n^2)$ 取决于步长序列 $O(1)$
快速排序(Quicksort) $O(n log n)$ $O(n^2)$ $O(n log n)$ $O(log n)$
选择排序(Selection sort) $O(n^2)$ $O(n^2)$ $O(n^2)$ $O(1)$
归并排序(Merge sort) $O(n log n)$ $O(n log n)$ $O(n log n)$ $O(n)$
计数排序(Counting sort) $O(n + k)$ $O(n + k)$ $O(n + k)$ $O(n + k)$

这些复杂度的值是一般情况下的估计值,实际应用中可能会有所变化。

1.1. 稳定性

算法的稳定性是指对于待排序列中相同项的原来次序不能被算法改变则称该算法稳定,如按数字顺序来排序下列的组合

TEXT
[(2, A), (1, B), (2, C), (3, D)]

那么在排序之后则会得到

TEXT
[(1, B), (2, A), (2, C), (3, D)]

这里可以看到,在排序后,两个值为 2 的元素(2, A), (2, C)仍然保持了它们在原始序列中的相对顺序,我们称这种算法为稳定的排序算法

1.2. 复杂度

复杂度则区分时间复杂度(Time complexity)空间复杂度(Space Complexity)

概念定义如下

  • 时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大$O$符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的情况
  • 空间复杂度是指计算机科学领域完成一个算法所需要占用的存储空间,一般是输入参数的函数。它是算法优劣的重要度量指针,一般来说,空间复杂度越小,算法越好

在不同的场景下,需要对时间和空间的复杂度有所取舍

常见的复杂度通常包含如下

  • $O(1)$:无论输入规模的大小,执行次数都保持恒定。例如,直接访问数组中的特定元素、执行固定次数的操作等
  • $O(n)$:执行次数与输入规模成线性关系。例如,遍历数组、对列表进行线性搜索等
  • $O(log n)$:执行次数与输入规模的对数关系。例如,二分查找算法
  • $O(n^2)$:执行次数与输入规模的平方关系。例如,嵌套循环的冒泡排序算法
  • $O(n log n)$:执行次数与输入规模的线性对数关系。例如,归并排序、快速排序等分治算法

2. 排序算法

2.1. 冒泡排序(Bubble Sort)

冒泡排序是一种简单的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成

实现思路

  1. 循环数组,从左向右比较相邻2个值的大小,并根据大小交换位置,第一次循环结束时最大的数字已经在最右边
  2. 循环n-1次,即获得排序后的顺序

Python代码实现如下

Python
def bubble_sort(sort_list):
    bubble_list_length = len(sort_list)
    while bubble_list_length > 0:
        for _i in range(0, bubble_list_length-1):
            if sort_list[_i] > sort_list[_i + 1]:
                sort_list[_i], sort_list[_i +
                                         1] = sort_list[_i+1], sort_list[_i]
        bubble_list_length -= 1
    return sort_list

2.2. 插入排序(Insertion Sort)

插入排序是一种简单直观的排序算法,通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入

实现思路

  1. 获取数组长度n,循环range(1,n),获得A0,A1,比较A0,A1大小并交换位置
  2. 第二次循环,获得A0,A1,A2,比较A0,A1,A2,从右往左比较交换位置,以此类推

Python代码实现如下

Python
def insert_sort(sort_list):
    length = len(sort_list)
    for i in range(1, length):
        for j in range(i, 0, -1):
            if sort_list[j] < sort_list[j-1]:
                sort_list[j], sort_list[j-1] = sort_list[j-1], sort_list[j]
    return sort_list

2.3. 希尔排序(Shell Sort)

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是不稳定排序算法

实现思路

  1. 选择一个增量序列(increment sequence),通常为n/2,n/4,n/8等。增量序列的选择是希尔排序的关键,不同的增量序列可能会影响算法的性能
  2. 根据选定的增量序列,将待排序序列分割为多个子序列,每个子序列中的元素相互间隔增量个位置
  3. 对每个子序列分别进行插入排序,即对每个子序列中的元素进行比较和交换操作,以使子序列中的元素有序
  4. 逐渐减小增量序列,重复步骤2和步骤3,直到增量序列减小到1
  5. 最后一轮增量序列为1时,进行最后一次插入排序,完成排序

Python代码实现如下

Python
def diminishing_increment_sort(sort_list):
    length = len(sort_list)
    h = 1
    while h < length/3:
        h = 3*h+1
    while h >= 1:
        for i in range(h, length):
            j = i
            while j >= h and sort_list[j] < sort_list[j-h]:
                sort_list[j], sort_list[j-h] = sort_list[j-h], sort_list[j]
                j -= h
        h = h//3
    return sort_list

2.4. 快速排序(Quick Sort)

快速排序又称划分交换排序(partition-exchange sort),简称快排。最早由东尼·霍尔提出。快速排序$O(nlogn)$通常明显比其他算法更快,因为它的内部循环(inner loop)可以在大部分处理器架构上很有效率地达成。

快速排序的内部循环通常使用指针操作和元素交换,而不需要额外的数组或列表操作

实现思路

  1. 取任意一个数作为基准数(第一个/倒数第一均可),并在源数组中删除该数
  2. 建立两个新数组,分别为左数组和右数组
  3. 循环源数组,将每一个数与基准数进行比较,较小的归入左数组,较大的归入右数组
  4. 递归调用自身,将左数组跟右数组递归调用至数组大小小于2,将所有数组合并即可

Python代码实现如下

Python
def quick_sort(sort_list):
    if len(sort_list) > 2:
        mid = sort_list[0]
        sort_list.remove(mid)
        left_list = []
        right_list = []
        for i in range(0, len(sort_list)):
            if sort_list[i] < mid:
                left_list.append(sort_list[i])
            if sort_list[i] > mid:
                right_list.append(sort_list[i])
        return quick_sort(left_list) + [mid] + quick_sort(right_list)
    else:
        return sort_list

2.5. 选择排序(Selection sort)

选择排序是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

实现思路

  1. 创建新数组,循环当前数据,pop出最小数,添加到新数组,以此类推

Python代码实现如下

Python
def selection_sort(sort_list):
    def _select_min_number(temp_list):
        if len(temp_list) is 0:
            return None
        min_number = temp_list[0]
        for _n in temp_list:
            if _n < min_number:
                min_number = _n
        return min_number

2.6. 归并排序(Merge Sort)

归并排序是创建在归并操作上的一种有效的排序算法,1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。

实现思路

  1. 将待排序序列不断地分割成两个子序列,直到每个子序列只有一个元素。
  2. 逐个合并相邻的子序列,直到所有子序列合并为一个有序序列。

Python代码实现如下

Python
def mergeSort(arr):
    if len(arr) <= 1:
        return arr

    mid = len(arr) // 2
    left = mergeSort(arr[:mid])
    right = mergeSort(arr[mid:])

    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 0

    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1

    result.extend(left[i:])
    result.extend(right[j:])

    return result

2.7. 计数排序(Counting Sort)

计数排序是一个非比较排序算法,该算法于1954年由 Harold H. Seward 提出。优势在于在对一定范围内的整数排序时,它的复杂度为$Ο(n+k)$(其中k是整数的范围),快于任何比较排序算法。 当然这是一种牺牲空间换取时间的做法,而且当$O(k)$>$O(nlog(n))$的时候其效率反而不如归并排序,堆排序等比较排序算法*

实现思路

  1. 新建一个一样大小的数组
  2. 循环旧数组,取出第一个数A1,再循环一遍旧数组,逐一比较,记所有小于A1的数为p,记所有等于A1的数为q
  3. 赋值给新数组下标为[p]-[p+q]范围的数为A1,以此类推

Python代码实现如下

Python
def counting_sort(sort_list):
    length = len(sort_list)
    new_list = [None]*length
    for i in range(length):
        p = 0
        q = 0
        for j in range(length):
            if sort_list[j]<sort_list[i]:
                p+=1
            if sort_list[j]==sort_list[i]:
                q+=1
        for k in range(p,p+q):
            new_list[k] = sort_list[i]
    return new_list

3. 尾声

参考资料

☑️ ☆

Go语言的递归算法

1. 说明

递归(Recursion)算法,指一种通过重复将问题分解为相同子问题而解决问题的方法,递归可以实现与循环类似的效果

需要使用递归的场景通常具备以下特征

  1. 原始问题可分解成子问题且子问题与原始问题一致
  2. 有明确的终止条件

递归的常见应用如下

  1. 递归数据求解(Fibonacci函数)
  2. 二叉树、广义表等明显具有递归特性的数据结构形式
  3. 适用于递归解法的典型问题(如Hanoi问题)

递归算法并不常用,在大多数编程语言中其运行效率较低,占用栈空间更大

在递归调用的每一层中都使用了栈存储,递归次数过多易造成栈溢出

例如对于Python语言而言,每进入一个函数调用,都会增加一层栈帧,栈大小不是无限的从而造成栈溢出

2. 例子

2.1. 求数N的阶乘

数据N的阶乘公式:$n!=n\times(n-1)\times(n-2)...\times1$

代码如下

Go
package main

import (
    "fmt"
    "time"
)

func Factorial(n int64) int64 {
    if n < 2 {
    	return int64(n)
    }
    return n * Factorial(n-1)
}

func main() {
    var n int64
    var number int64 = 1

    fmt.Printf("Input n value: ")
    fmt.Scan(&n)

    var start = time.Now()
    result = Factorial(n)
    var elapsed = time.Since(start)

    fmt.Printf("Factorial %v! result is %v\nTotal times is %v", number, result, elapsed)
}

输出如下

Bash
➜  go run main.go
Factorial 25! result is 7034535277573963776
Total times is 298ns

2.2. Fibonacci

Fibonacci即斐波那契数列,即一个特殊数列(0 1 1 2 3 5 8 ...),特征如下

  • 前二个数字为0、1或1、1
  • 从第三个数字开始的值是前两个数字之和

Go求解斐波那契数列函数如下

Go
package main

import (
    "fmt"
    "time"
)

func Fibonacci(n int) int {
    if n == 1 || n == 2 {
    	return 1
    }
    return Fibonacci(n-1) + Fibonacci(n-2)
}

func main() {
    var number int = 50

    var start = time.Now()
    var result int = Fibonacci(number)
    var elapsed = time.Since(start)

    fmt.Printf("Fibonacci %vth result is %v\nTotal times is %v", number, result, elapsed)
}

输出如下

Bash
➜ go run main.go
Fibonacci 50th result is 12586269025
Total times is 40.354931919s

3. 效率对比

递归的缺点在开头就已经说过了,因为其重复调用会导致栈溢出,而且在大多数编程语言中递归的效率也不如普通循环

以Fibonacci函数为例,改写为循环

Go
package main

import (
    "fmt"
    "time"
)

func main() {
    var number int = 50

    var n1 int = 0
    var n2 int = 1
    var result int = 0

    var start = time.Now()
    for n := 2; n <= number; n++ {
    	result = n1 + n2
    	n1 = n2
    	n2 = result
    }

    var elapsed = time.Since(start)
    
    fmt.Printf("Fibonacci %vth result is %v\nTotal times is %v", number, result, elapsed)

}

输出如下

Bash
➜ go run main.go
Fibonacci 50th result is 12586269025
Total times is 226ns

可以看到时间差距非常明显,这也是递归算法在实际应用中较为劣势的一点

4. 尾调用消除

前文提到,对于递归调用而言性能较差的原因是在递归调用的每一层中都使用了栈存储

过多的栈帧占用导致内存占用呈现一个波峰上升的趋势,对递归调用而言,优化其执行效率的思路就是减少其栈帧的产生

在函数编程语言中,语言标准通常会要求编译器或运行平台实现尾调用消除

尾调用 (tail call) 指的是一个函数的最后一条语句也是一个返回调用函数的语句(Wiki),以Fibonacci为例写一个go语言的尾调用消除例子如下

Go
package main

import (
    "fmt"
    "time"
)

func Fibonacci(n int, n1 int, n2 int) int {
    if n == 0 {
    	return n1
    }
    return Fibonacci(n-1, n2, n1+n2)
}

func main() {
    var number int = 50

    var start = time.Now()
    var n1 int = 0
    var n2 int = 1
    var result int = Fibonacci(number, n1, n2)
    var elapsed = time.Since(start)

    fmt.Printf("Fibonacci %vth result is %v\nTotal times is %v", number, result, elapsed)
}
Bash
➜ go run main.go
Fibonacci 50th result is 12586269025
Total times is 443ns

可以看到,执行效率几乎可以媲美循环写法

🔲 ☆

Linux下使用Bash遍历文件夹语法解析

1. 概述

经常需要使用bash批量处理文件格式,每次都要查询一些基础语法很麻烦

所以以遍历文件夹目录处理图片为例总结一些使用Bash遍历处理文件夹时常见的语法

2. 遍历

Bash遍历的方法比较简单,与大部分编程语言的for循环没有明显区别

Bash
#/bin/sh
#author: chancel.yang

for file in ./*
do
    if test -f "$file"
    then
        echo -e "$file"
    fi
done

脚本输出如下

Bash
➜ bash demo.sh  
./a.png  
./b.png  
./c.jpg  
./demo.sh

这种遍历在大部分情况下都很实用

3. 路径处理

上面的输出中会发现执行的脚本demo.sh也在其中,而且只能针对当前路径进行处理

那么试试添加自定义路径,然后将demo脚本放到其他位置

Bash
#/bin/sh  
#author: chancel.yang  

path="/mnt/SDA/TMP/test"  

for file in $path/*  
do  
if test -f "$file"
then  
    echo -e "$file"
fi  
done

输出如下

Bash
➜ bash /mnt/SDA/TMP/demo.sh  
/mnt/SDA/TMP/test/a.png  
/mnt/SDA/TMP/test/b.png  
/mnt/SDA/TMP/test/c.jpg

在实际处理中,可以将$path调整为入参,脚本如下

Bash
#/bin/sh  
#author: chancel.yang  
for file in $1/*  
do  
if test -f "$file"
then  
    echo -e "$file"
fi  
done

执行如下

Bash
➜ bash demo.sh "/mnt/SDA/TMP/test"
/mnt/SDA/TMP/test/a.png
/mnt/SDA/TMP/test/b.png
/mnt/SDA/TMP/test/c.jpg

$0是文件名是名称,如demo.sh

除了$[number]获取传递进来的参数外,还有一些常见的特殊参数值可以直接使用

参数 说明
$# 参数总个数
$$ 当前PID号

4. 提取文件名

上面添加自定义路径之后,输出 $file 也携带了路径信息,实际使用时多要用到文件名,扩展名等

下面演示如何提取文件路径、文件名(带扩展名)、文件名、扩展名

Bash
#/bin/sh  
#author: chancel.yang  

path="/mnt/SDA/TMP/test"  

for file in $path/*  
do  
if test -f "$file"
then
    echo "dirname: $(dirname $file)"
    echo "filename: $(basename $file)"  
    filename=$(basename $file)  
    echo "filename(suffix): ${filename%%.*}"
    echo "suffix: ${filename#*.}"
fi  
done

执行后输出如下

Bash
➜ bash demo.sh  
dirname: /mnt/SDA/TMP/test  
filename: a.png  
filename(suffix): a  
suffix: png  
dirname: /mnt/SDA/TMP/test  
filename: b.png  
filename(suffix): b  
suffix: png  
dirname: /mnt/SDA/TMP/test  
filename: c.jpg  
filename(suffix): c  
suffix: jpg

如果后缀有多个,如a.tar.gz,其他不常见的提取如下

Bash
filename='a.tar.gz'
# a.tar
${file%.*}
# gz
${file##*.}

5. 条件判断

5.1. test与[]

Bash提供test关键字进行条件判断,偶尔也会看到if [ string1 != string2 ]

上面俩个写法是等价的,即test[两者是等价的,都是Bash中的关键字,区别是[要求最后一个参数必须是]

由于[是一个关键字,而]是一个参数,所以必须有空格,[string1 != string2]这种没有空格的写法是错误的

由于两者是等价的,所以以下针对test进行说明,将脚本中的test condition等价替换成[ condition ]也是成立的

5.2. 判断运算符

以下是常见的条件判断示例脚本

Bash
#/bin/sh
#author: chancel.yang

a=100
b=200
c=100

echo -e "a="$a", b="$b", c="$c

if test "$a" -eq "$c"
then
    echo -e "a = c"
fi

if test "$a" -ne "$b"
then
    echo -e "a != b"
fi

if test "$b" -gt "$a"
then
    echo -e "b > a"
fi


dirname=`pwd`
filename=$dirname/demo.sh

echo -e "dirname: "$dirname", filename: "$filename

if test -d "$dirname"
then
    echo -e $dirname" is a folder"
fi
if test -f "$filename"
then
    echo -e $filename" is a file"
fi

执行输出如下

Bash
➜ bash demo.sh
a=100, b=200, c=100
a = c
a != b
b > a
dirname: /mnt/SDA/TMP, filename: /mnt/SDA/TMP/demo.sh
/mnt/SDA/TMP is a folder
/mnt/SDA/TMP/demo.sh is a folder

条件判断数值类型时,其常见运算符参数列表如下

参数 说明
-eq 相等
-ne 不相等
-gt >
-ge >=
-lt <
-le <=

判断条件文件时,其常见运算符参数列表如下

参数 说明
-e 文件存在
-r 文件存在且可读
-w 文件存在且可写
-x 文件存在且可执行
-s 文件存在且内容不为空
-d 是目录
-f 是文件

文件与数值内容的判断相对简单,接下来看看比较复杂的字符串判定

test中,判断字符串为空时,-z表示当字符串为空时返回True,-n表示字符串不为空时返回True

Bash
#/bin/sh
#author: chancel.yang

string_1=""
string_2="hello"

if test -z "$string_1"
then
    echo -e "string_1 is null"
fi

if test -n "$string_2"
then
    echo -e "string_2 is not null"
fi

执行输出如下

Bash
➜ bash demo.sh  
string_1 is null  
string_2 is not null

使用test进行判断时,推荐使用双引号括起来变量,否则因为变量内容带空格而达不到预期的执行效果

考虑到变量如路径可能是用户输入的,除了双引号来避免变量路径中带有空格外,还可以使用[[]]来避免内容拆分

5.3. 特殊的[[]]

[test判断时都会对内容进行拆分,如test="go to"会被识别成goto两个参数

[不同,[[]]均是命令,]]并不是参数

以下代码演示两者的不同

Bash
#/bin/sh
#author: chancel.yang

a="go to"

# 此处会报错
if test $a == "go to"
then
    echo -e "$a == 'go to'"
fi

# 此处添加双引号可以正确判断
if test "$a" == "go to"
then
    echo -e "$a == 'go to'"
fi

# 此处无需双引号也可以正确判断
if [[ $a == "go to" ]]
then
    echo -e "$a == 'go to'"
fi

输出如下

Bash
➜  bash demo.sh
demo.sh: line 7: test: too many arguments
go to == 'go to'
go to == 'go to'

6. 图片转换例子

到这里,写一个遍历脚本的基本语法已经齐全,下面是一个转换指定目录中所有文件为webp图片的遍历脚本,以供参考

Bash
#/bin/sh
#author: chancel.yang
#date: 2022-06-14
show_help() {
    echo "$0 [-h|-?|--help] [--who me] [--why hhh]"
    echo "-h|-?|--help    显示帮助"
    echo "--path          文件夹路径"
}

while [[ $# -gt 0 ]]; do
    case $1 in
    -h | -\? | --help)
        show_help
        exit 0
        ;;
    --in)
        in="${2}"
        shift
        ;;
    --out)
        out="${2}"
        shift
        ;;
    *)
        echo -e "Error: $0 invalid option '$1'\nTry '$0 --help' for more information.\n" >&2
        exit -1
        ;;
    esac
    shift
done

if !(test "$in")
then
    echo "Please run --help for usage"
    exit
fi
if !(test "$out")
then
    echo "Please run --help for usage"
    exit
fi

if !(test -d "$in")
then
    echo "Error: $in is not a folder"
    exit
fi

if test -f "$out"
then
    echo "Error: $out is a file"
    exit
fi

if [ ! -d "$out" ]; then
  mkdir $out
fi

for file in $in/*
do
    filename=$(basename $file)
    if test -f "$file"
    then
        /usr/bin/ffmpeg -i $file -c:v libwebp $out/${filename%%.*}.webp
        echo "$file convert to $out/$filename.webp"
    fi
done

echo -e "Convert success"

参考资料

☑️ ☆

Cent7安装Nginx配置双向验证

最近要做一个客户端认证的方案,查找了网上不少证书方案,发现在Linux下做证书的例子很少,更多的是实践参考,关于证书的知识点又比较少,这里简单归纳一下

本次实验基于Cent7OS

1. SSL双向认证

1.1. 什么是双向认证

双向认证的意思是既要客户端认可服务端(即HTTPS,避免伪造的服务端站点),也要服务端认可客户端,类似于网银等支付场景时可以避免某些恶意的客户端进行访问

https全称为(Hypertext Transfer Protocol Secure),与HTTP不同的地方在于TCP与HTTP之间存在一个加密/身份验证层,提供了身份验证于加密通讯的方法

采用HTTPS的服务端必须从CA(Certificate Authority)处申请一个证明服务器用途的证书(包含了私钥),客户/浏览器内置了CA厂商的证书(包含公钥)

2. 单双认证流程

单向认证与双向认证的区别如下

单向认证(https)

  1. 浏览器发送SSL版本信息
  2. 服务端接收到随机数以及SSL版本信息,返回自己的SSL版本信息以及证书、随机数等信息
  3. 浏览器读取证书中的证书所有者、有效期等信息进行一一校验,再在已知的CA机构颁发的证书(内置于浏览器)中寻找相对应的证书信息进行检验,检验证书正确后将自己支持的对称加密方案发送给服务端
  4. 服务端选择浏览器支持对称加密算法,将选择的加密算法使用公钥进行加密并发送给浏览器
  5. 浏览器接收到加密方式之后使用私钥进行解密并产生随机码作为对称加密密钥,使用服务端公钥加密并发送给服务端
  6. 服务端接受到加密密匙之后,使用私钥对加密信息进行解密,获得了对称加密的密钥,使用对称加密加密接下来所有通信信息与浏览器进行通信

双向认证

  1. 浏览器发送SSL版本信息
  2. 服务端接收到随机数以及SSL版本信息,返回自己的SSL版本信息以及证书、随机数等信息
  3. 浏览器读取证书中的证书所有者、有效期等信息进行一一校验,再在已知的CA机构颁发的证书(内置于浏览器)中寻找相对应的证书信息进行检验,检验证书正确后将自己的证书以及公钥发送到服务端,随后再次发送自己支持的对称加密算法列表
  4. 服务端接收客户端证书,对证书进行检验,检验证书正确后,选择浏览器支持对称加密算法,将选择的加密算法使用公钥进行加密并发送给浏览器
  5. 浏览器接收到加密方式之后使用私钥进行解密并产生随机码作为对称加密密钥,使用服务端公钥加密并发送给服务端
  6. 服务端接受到加密密匙之后,使用私钥对加密信息进行解密,获得了对称加密的密钥,使用对称加密加密接下来所有通信信息与浏览器进行通信

3. 双向认证实践

3.1. Nginx启用HTTPS

服务端启用HTTPS配置简单,一般流程为

  • 向CA证书商家申请证书
  • 下载证书,并更改NGINX配置指向证书
  • 重新读取NGINX配置即可完成HTTPS配置

申请证书的流程网上很多,这里我们可以先手动CA产生一个本地的HTTPS证书(在生产环境下使用这种方法,会提示证书不安全)

先创建并切换到存放HTTPS证书的文件夹

Bash
mkdir /etc/nginx/ssl_certs
cd /etc/nginx/ssl_certs

制作CA私钥和证书,全部配置填"."

Bash
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

制作服务器证书签发文件,除了Common Name填入域名,其他全部填入"."以便和CA根证书对应

Bash
openssl genrsa -out server.pem 1024
openssl rsa -in server.pem -out server.key
openssl req -new -key server.pem -out server.csr

使用CA证书进行签发

Bash
openssl x509 -req -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out server.crt

到这里我们就得到了配置https必要的crt文件与key文件,Nginx的配置如下

TEXT
vim /etc/nginx/conf.d/default.conf
server {
    listen       443 ssl;
    server_name  www.example.com;
    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;
    ssl on;
    ssl_certificate /etc/nginx/ssl/server.crt;      # 服务端HTTPS的证书,不能自己产生,否则会提示证书不安全,该配置与客户端证书无关
    ssl_certificate_key /etc/nginx/ssl/server.key;  # 服务端HTTPS的证书,不能自己产生,否则会提示证书不安全,该配置与客户端证书无关
    location / {
        proxy_pass_header Server;
        proxy_pass_header X-Scheme;
        proxy_set_header Host $http_host;
        proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;
        proxy_set_header X-SSL-CERT-DN $ssl_client_s_dn;
        proxy_set_header X-SSL-CERT-VERIFY $ssl_client_verify;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://192.168.10.100:5000/;
    }
}

验证使用HTTPS访问

Bash
# 因证书自签必须使用insecure忽略证书风险,提示否则curl会返回证书不安全测试失败的结果
curl --insecure 'https://www.example.com' -v

在实际开发中,我们更多的申请CA证书签发的SSL证书

3.2. 客户端启用证书验证

客户端证书一般就采用自己配置根证书来签发自己的客户端证书,签发客户端证书会包含几个重要的属性,分别是

  • 地区信息(国家、省份、城市)
  • 公司信息(公司名称、该证书拥有者的部门信息)
  • Common Name信息(也叫CN信息,可把一些信息存储在这里,如有效信息/客户端信息等)
  • 邮箱信息
  • 证书有效期、证书导入/导出密码

制作客户端证书的流程一般为

  1. 制作自己的CA私钥(ca.key)
  2. 使用CA私钥制造CA根证书(cacert.crt)
  3. 制作客户端私钥,并合成待签发csr文件(client.key,client.csr)
  4. 使用CA证书对客户端待签发文件进行签发产生客户端的crt证书(client.crt)
  5. 转换客户端证书为适合浏览器的证书格式(client.pfx,client.p12)

我们来制作第一张客户端证书

首先要编辑默认CA创建规则,根据需要自行修改一下默认的参数

编辑/etc/pki/tls/openssl.cnf

Bash
/etc/pki/CA/            # 所有资料全存放在此目录下
/etc/pki/CA/certs/      # 存放CA证书
/etc/pki/CA/crl/        # 存放证书吊销列表
/etc/pki/CA/index.txt   # 存放可用、不可用证书的数据库
/etc/pki/CA/newcerts    # 存放CA的新目录
/etc/pki/CA/cacert.pem  # 生成的自签名证书
/etc/pki/CA/serial      # 下个证书的编号,16进制,多从00或01开始
/etc/pki/CA/crlnum      # 下一个吊销证书的编号
/etc/pki/CA/crl.pem     # 下一个吊销列表
/etc/pki/CA/private/cakey.pem  # 默认密钥

# 匹配策略的可选值
match                   # 必须匹配,默认是国家、州(省)、组织名称
supplied                # 必填,为哪个主机申请证书,一般对应域名
optional                # 可选

根据具体的环境调整好上述的参数之后,我们就可以开始制作客户端证书

文件夹位置根据实际情况自行调整

生成创建证书所必须的基本文件

Bash
# 生成数据库文件,记录证书信息(包含序列号、序号、证书自定义CN信息等)
touch /etc/pki/CA/index.txt
# 生成存放证书编号的文件(一般存储即将颁发的下一个证书序号)
echo 00 > /etc/pki/CA/serial

切换到/etc/pki/CA,开始生成签证客户端的根证书

Bash
openssl  req -new -x509  -key private/cakey.pem  -days 3650 -out cacert.pem

接下来签发客户端的证书(此处有提供一键脚本执行)

Bash
# 创建存放客户端证书的文件夹
mkdir /etc/nginx/client_ssl
# 切换到存放客户端证书的目录
cd /etc/nginx/client_ssl
# 创建客户端证书私钥
(umask 066; openssl genrsa -out client_01.key 1024)
# 创建请求根证书签证的文件             
openssl  req -new -key client_01.key  -out client_01.csr
# 创建客户端证书        
openssl  ca -in client_01.csr  -out client_01.crt -days 730

到这里我们就生产了客户端所需的证书,修改Nginx的配置

TEXT
server {
    listen       443 ssl;
    server_name  www.example.com;
    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;
    ssl on;
    ssl_certificate /etc/nginx/ssl/server.crt;      # 服务端HTTPS的证书,不能自己产生,否则会提示证书不安全,该配置与客户端证书无关
    ssl_certificate_key /etc/nginx/ssl/server.key;  # 服务端HTTPS的证书,不能自己产生,否则会提示证书不安全,该配置与客户端证书无关
    ssl_client_certificate /etc/nginx/ssl/ca.crt;   # 客户端的CA根证书,由自己的机器OPENSSL生成
    ssl_verify_client optional;                     # 是否启用客户端验证,ON为启用,OFF为不启用,如需自定义验证结果处理则填入optional
    if ($ssl_client_verify = NONE) {
        return 303 http://www.example.com/error;
    }
        location / {
            proxy_pass_header Server;
            proxy_pass_header X-Scheme;
            proxy_set_header Host $http_host;
            proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;   # 转发证书信息
            proxy_set_header X-SSL-CERT-DN $ssl_client_s_dn;        # 转发证书的CN信息
            proxy_set_header X-SSL-CERT-VERIFY $ssl_client_verify;  # 转发证书的验证结果,成功为success
            proxy_set_header X-Real-IP $remote_addr;
            proxy_pass http://192.168.10.100:5000/;
    }
}

测试客户端证书能否正常访问网页

Bash
# -v参数可以打印详细信息以便我们核查证书的详细信息
curl --insecure --key ./client.key --cert ./client.crt 'https://www.example.com' -v

如果嫌弃上面的方法太繁琐,可以直接使用下面这个脚本生成客户端证书

Bash
#!/bin/bash -e
# 运行脚本前请记得设置好openssl.cnf配置文件
# 运行成功返回0,运行失败(缺少参数)返回-1
show_help() {
    echo "$0 [-h|-?|--help] [--ou ou] [--cn cn] [--email email]"
    echo "-h|-?|--help    显示帮助"
    echo "--ou            设置组织或部门名"
    echo "--cn            设置证书的DN信息"
    echo "--email         设置证书的邮箱信息"
    echo "--days          设置证书的有效时间"
    echo "--out           设置证书的输出目录(含文件名)"
}
while [[ $# -gt 0 ]]; do
    case $1 in
    -h | -\? | --help)
        show_help
        exit 0
        ;;
    --ou)
        OU="${2}"
        shift
        ;;
    --cn)
        CN="${2}"
        shift
        ;;
    --email)
        emailAddress="${2}"
        shift
        ;;
    --days)
        days="${2}"
        shift
        ;;
    --out)
        out="${2}"
        shift
        ;;
    --)
        shift
        break
        ;;
    *)
        echo -e "Error: $0 invalid option '$1'\nTry '$0 --help' for more information.\n" >&2
        exit -1
        ;;
    esac
    shift
done
# 创建客户端证书
# 非交互式方式创建以下内容:
# 国家名(2个字母的代号)
C=CN
# 省
ST=GuangDong
# 市
L=GuangDong
# 公司名
O=TY
# 部门名
OU=${OU:-未知}
# DN信息
CN=${CN:-未知}
# 邮箱地址
emailAddress=${emailAddress:-virtual@example.com}
# 创建私钥
openssl req -utf8 -nodes -newkey rsa:2048 -keyout "${out}.key" -new -days "${days}" -out "${out}.csr" -subj "/C=${C}/ST=${ST}/L=${L}/O=${O}/OU=${OU}/CN=${CN}/emailAddress=${emailAddress}"
# 使用根证书进行进行签证
openssl ca -utf8 -batch -days 36500 -in "${out}.csr" -out "${out}.crt"
# 转换客户端证书的类型为浏览器支持的模式
openssl pkcs12 -export -inkey "${out}.key" -in "${out}.crt" -passout pass: -out "${out}.pfx"
exit 0

4. 结束语

SSL证书常用格式转换

Bash
# crt转pfx(p12)
openssl pkcs12 -export -inkey server.key -in server.crt -out server.pfx

# csr转pfx(p12)
openssl pkcs12 -export -inkey server.key -in server.csr -out server.pfx

# pfx转jks
keytool -importkeystore -v  -srckeystore client.pfx -srcstoretype pkcs12  -destkeystore client.keystore -deststoretype jks 

# jks转p12(pfx)
keytool -importkeystore -srckeystore client_pri.keystore -destkeystore client_pri.p12 -srcstoretype JKS -deststoretype PKCS12 -srcalias imgo.tv -destalias imgo.tv -noprompt

# pfx转x509
openssl pkcs12 -in onovps.com.pfx -nodes -out onovps.com.pem 
openssl rsa -in onovps.com.pem -out onovps.com.key
openssl x509 -in onovps.com.pem -out onovps.com.crt

资料参考

☑️ ☆

Linux下用KVM虚拟Windows7

在Linux下,KVM的体验十分优秀

刚好Nas需要安装一个 Windows7 虚拟系统来使用迅雷、百度网盘等下载工具,于是打算在Bash下纯手动敲命令行来创建虚拟机

本文是在 Debian 系的 openmediavault 上测试通过,大部分Debian发行版操作步骤是差不多的

其他 Linux 发行版可能有些许细微不同,请根据真实情况参考本文进行安装

1. 准备

1.1. 程序安装

首先检查虚拟化支持,有flags输出支持虚拟化

Bash
grep '(vmx|svm)' --color=always /proc/cpuinfo

安装环境

Bash
sudo apt-get install kvm qemu-kvm bridge-utils virtinst libvirt-clients libvirt-daemon-system

网上大部分资料提到的 libvirt-bin 在许多包管理器上都已经被拆分为libvirt-daemon-system和libvirt-clients了,根据情况安装这两个,libvirt-clients可以支持windows安装阶段的vnc访问

上述程序说明

  • kvm:KVM核心
  • qemu-kvm:KVM的设备模拟,由开源虚拟化软件QEMU中的管理工具演变而来
  • bridge-utils:桥接网卡的集成管理工具
  • libvirt-clients/libvirt-daemon-system:虚拟机命令行管理工具
  • virtinst:虚拟机创建工具

1.2. 桥接网络

建立桥接网络环境可能会导致网络断开,如果不是特别熟练,不建议远程操作

首先创建桥接网卡

Bash
brctl addbr br0

使用ip a查看机器实际网络接口,如下

Bash
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master br0 state UP group default qlen 1000
    link/ether 70:85:c2:82:20:27 brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:bd:b3:99:14 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
5: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 70:85:c2:82:20:27 brd ff:ff:ff:ff:ff:ff
    inet 192.168.11.11/24 brd 192.168.11.255 scope global br0
       valid_lft forever preferred_lft forever

可以看到 enp1s0 是我的真实网卡,这里已经桥接了,所以看起来是没有IP地址的,建立br0与真实网卡的桥接

Bash
brctl addif br0 enp1s0

配置桥接网卡的IP获取模式,编辑 /etc/network/interfaces 文件

动态获取IP配置

TEXT
# 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
auto br0
iface br0 inet dhcp
bridge_ports enp1s0 
bridge_stp off
bridge_fd 0
bridge_maxwait 0

静态IP配置

TEXT
# This file describes the network interfaces available on your system
 # and how to activate them. For more information, see interfaces(5).

 # The loopback network interface
 auto lo br0
 iface lo inet loopback

 # Bridge setup
 iface br0 inet static
    bridge_ports eth0 eth1
        address 192.168.1.2
        broadcast 192.168.1.255
        netmask 255.255.255.0
        gateway 192.168.1.1

无论采用哪一种配置,都要注释或删掉enp1s0的配置

最后启用br0网卡

Bash
ifup br0

1.3. VNC监听配置

VNC是一种远程配置,用于安装系统时远程(类似于微软的远程桌面)

编辑/etc/libvirt/qemu.conf,配置好如下字段

Bash
vnc_listen = "0.0.0.0"
vnc_password = "XYZ12345" #密码任意设置
spice_listen = "0.0.0.0" #debian6 不需配置这个

配置完成后重启虚拟机配置

TEXT
sudo systemctl restart libvirt-guests.service

2. 创建虚拟机

首先要创建虚拟机需要使用的磁盘空间

Bash
SUDO qemu-img create -f qcow2 windows7.qcow2 32G

接着创建Windows7虚拟机

Bash
# 创建一个名为windows7的虚拟机,操作系统类型为windows
# 使用KVM虚拟化技术,启用硬件虚拟化扩展,分配2048MB的内存给虚拟机
# 分配2个虚拟CPU给虚拟机
# 使用网络桥接模式,网络接口模型为e1000
# 创建一个名为windows7.qcow2的磁盘镜像文件,使用IDE总线连接
# 启用VNC图形界面访问,监听5900端口
# 使用cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408.iso文件作为光盘镜像
sudo virt-install --name=windows7 --os-type=windows \
--virt-type=kvm --hvm --ram=2048 \
--vcpus=2 --network bridge=br0,model=e1000 \
--disk path=windows7.qcow2,bus=ide \
--graphics vnc,port=5900 \
--cdrom=cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408.iso

创建完成后,使用sudo netstat -tlnp查看一下5900端口是否正在监听,如有,则可用vnc客户端连接至5900端口

2.1. 虚拟机管理

列出当前所有虚拟机

Bash
sudo virsh list --all

启动/关闭/销毁虚拟机

Bash
sudo virsh start windows7
sudo virsh shutdown windows7
sudo virsh destroy windows7 && virsh undefine windows7

最后,如果遇到动态IP获取下,虚拟机没有获取到IP,可能是防火墙问题

如果是iptables的话,开放如下

Bash
sudo iptables -A INPUT -p udp --dport 67:68 --sport 67:68 -j ACCEPT

如果是firewalld防火墙,开放如下

Bash
firewall-cmd --permanent --zone=public --remove-interface=enp0s3
firewall-cmd --permanent --zone=internal --add-interface=enp0s3
firewall-cmd --permanent --zone=internal --add-port=67/udp
firewall-cmd --permanent --zone=internal --add-port=68/udp
firewall-cmd --reload
☑️ ☆

Windows下借助WSL搭建PHP开发环境

在Windows下调试PHP一直使用WAMPWNMP套件,本文记录一次实践手动在Windows下安装PHP开发环境的过程

这次开发的项目是基于Nginx+Yii的PHP项目,Nginx是需要安装Nchan插件的,在Windows下Nginx编译与插件安装少嫌麻烦,好在微软推出了WSL/WSL2

这次在Windows下借助WSL+Xdebug来完成开发环境的安装,实现的效果是Visual Studio Code借助XdebugWSL开发Yii项目

Visual Studio Code 以下简称 VSCode,方便描述

1. WSL

1.1. 说明

Nchan是一个基于Nginx的插件,用于实现高性能、可扩展的实时消息传递和流媒体功能。它提供了多种功能,包括发布-订阅消息传递、长轮询、HTTP流、WebSocket等,使得构建实时应用程序和实时通信变得更加简单和高效。

WSL(Windows Subsystem for Linux)是Windows操作系统中的一个功能,它允许用户在Windows系统上运行Linux环境和应用程序。WSL提供了一个兼容层,使得Linux二进制文件能够在Windows上运行,而无需虚拟机或双启动系统。

系统环境

  • Windows 10:VSCode + Xdebug远程调试 + PHP源码
  • WSL(Ubuntu子系统):Nginx + MySQL + PHP-FPM7.2 + PHP源码

大致步骤

  1. WSL下编译安装Nginx(nginx插件Nchan)
  2. WSL下PHP7.2的安装与配置
  3. Windows下PHP7.2的安装与配置
  4. Windows下调试PHP

1.2. 开启WSL

要开启WSL(Windows Subsystem for Linux),请按照以下步骤进行操作:

  1. 打开“控制面板”:在Windows操作系统中,点击任务栏上的“开始”按钮,然后搜索并打开“控制面板”
  2. 进入“程序”设置:在控制面板中,找到并点击“程序”选项
  3. 启用“适用于Linux的Windows子系统”功能:在“程序”设置页面中,点击“启用或关闭Windows功能”链接
  4. 勾选“适用于Linux的Windows子系统”选项:在“Windows功能”对话框中,找到并勾选“适用于Linux的Windows子系统”选项
  5. 点击“确定”并等待安装完成:点击“确定”按钮后,Windows将开始安装WSL组件,安装完成后重启系统
  6. 安装Linux发行版:重新启动后,你需要从Microsoft Store,这里选择Ubuntu的子系统
  7. 安装完成后,在开始菜单中找到并点击已安装的Linux发行版的图标。这将启动WSL,并为你提供一个Linux shell终端窗口。在该终端中,你可以执行Linux命令和运行Linux应用程序

在成功进入子系统Ubuntu后,开始安装Nginx,Nginx官方提供deb包直接安装

  1. nginx-common.ubuntu.deb
  2. nginx-extras.ubuntu.deb

第一个是Nginx,第二个是nchan包,下载后直接安装

Bash
sudo dpkg -i nginx-common.ubuntu.deb
sudo dpkg -i nginx-extras.ubuntu.deb

编辑/etc/nginx/conf.d/dev.conf,内容如下

TEXT
server{
    charset      utf-8;
    server_name 127.0.0.1;

    root  /var/www/nginx;
    index index.php index.html;

    access_log  /var/log/nginx/dev-access.log ;
    error_log   /var/log/nginx/dev-error.log;

    # 以下是本地开发Nchan插件所需配置,无需理会
    location = /sub {
        nchan_subscriber;
        nchan_channel_id $arg_id;
        nchan_message_timeout 5s;
    }
    location = /pub {
        nchan_publisher;
        nchan_channel_id $arg_id;
        nchan_message_timeout 5s;
    }
}

1.3. PHP7.2

直接安装php7.2

Bash
# 安装源
sudo apt -y install software-properties-common python-software-properties
# 更新源
sudo add-apt-repository ppa:ondrej/php && sudo apt-get update
# 安装php7.2
sudo apt-get -y install php7.2
# 安装常用扩展(根据需要自选)
sudo apt-get -y install php7.2-fpm php7.2-mysql php7.2-curl php7.2-json php7.2-mbstring php7.2-xml  php7.2-intl 
# 可供参考的扩展包(根据需要自选)
sudo apt-get install php7.2-gd
sudo apt-get install php7.2-soap
sudo apt-get install php7.2-gmp    
sudo apt-get install php7.2-odbc       
sudo apt-get install php7.2-pspell     
sudo apt-get install php7.2-bcmath   
sudo apt-get install php7.2-enchant    
sudo apt-get install php7.2-imap       
sudo apt-get install php7.2-ldap       
sudo apt-get install php7.2-opcache
sudo apt-get install php7.2-readline   
sudo apt-get install php7.2-sqlite3    
sudo apt-get install php7.2-xmlrpc
sudo apt-get install php7.2-bz2
sudo apt-get install php7.2-interbase
sudo apt-get install php7.2-pgsql      
sudo apt-get install php7.2-recode     
sudo apt-get install php7.2-sybase     
sudo apt-get install php7.2-xsl
sudo apt-get install php7.2-cgi        
sudo apt-get install php7.2-dba 
sudo apt-get install php7.2-phpdbg     
sudo apt-get install php7.2-snmp       
sudo apt-get install php7.2-tidy       
sudo apt-get install php7.2-zip

编辑/etc/php/7.2/fpm/pool.d/www.conf文件,内容如下

TEXT
...
; 用户修改为本机用户(注意:Nginx要和php-fpm的用户一致,同时也要对代码目录有读写权限)
user = chancel
group = chancel
...

; 本地的php的端口号(与Nginx通信的端口号)
listen = 127.0.0.1:9000

启动PHP7.2

Bash
sudo service php7.2-fpm start

配置XDebg,首先检查php版本

Bash
sudo apt-get install php-xdebug

编译/etc/php/7.2/php.ini,加载Xdebug

TEXT
; 这里根据Xdebug.so 进行填写
zend_extension=xdebug.so 
[XDebug]
; 启用远程调试
xdebug.remote_enable = on
xdebug.remote_autostart = 1
; WSL与Windows共享网关,此处填127.0.0.1,如果是远程虚拟机则填写调试机器的IP
xdebug.remote_host = 127.0.0.1
; 远程XDebug配置的端口号,这个端口号在后面Windows10里配置,注意不要填9000,因为WSL里面的php-fpm已经使用这个端口了
xdebug.remote_port = 9001
xdebug.remote_connect_back = 1
xdebug.auto_trace = 1
xdebug.collect_includes = 1
xdebug.collect_params = 1
; 日志的位置
xdebug.remote_log = /tmp/xdebug.log

1.4. MariaDB

安装启动如下

Bash
sudo apt install mariadb-server
sudo service mysql start
sudo mysql_secure_installation

到这里,WSL里面的PHP/XDdebug/Nginx/MySQL基本就配置完了,其中的Nginx连接PHP部分需填写配置,这里给出一份参考

TEXT
location ~ \.php(.*)$ {
           fastcgi_pass   127.0.0.1:9000;
           fastcgi_index  index.php;
           fastcgi_split_path_info  ^((?U).+\.php)(/?.+)$;
           fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
           fastcgi_param  PATH_INFO  $fastcgi_path_info;
           fastcgi_param  PATH_TRANSLATED  $document_root$fastcgi_path_info;
           include        fastcgi_params;
}

2. Windows

2.1. PHP

php7.2安装配置

  1. 下载PHP7.2
  2. 解压PHP7.2到指定目录,并添加到Path中
  3. cmd中运行php -i查看php是否安装成功

Xdebug的安装参考官方:https://xdebug.org/docs/install#windows

2.2. VSCode

下载安装VBC:下载安装VSCode

打开后在左侧寻找插件菜单,安装PHP Debug插件

紧接着按下ctrl+shift+p,输入user setting,选择用户设置,搜索php,选择编辑json,添加下面的内容

JSON
{
    "phpformatter.composer": true,
    "php.validate.executablePath": "C:\\dev\\php7.2\\php.exe",
    "php.suggest.basic": false,
    "php.executablePath": "C:\\dev\\php7.2\\php.exe",
    "php-cs-fixer.executablePath": "${extensionPath}\\php-cs-fixer.phar",
    "[php]": {
        "editor.defaultFormatter": "kokororin.vscode-phpfmt"
    },
    "php-cs-fixer.lastDownload": 1558509222941,
}

使用VSCode打开php源码,编辑.vscode/launch.json,内容如下

JSON
{
    "name": "Listen for XDebug",
    "type": "php",
    "request": "launch",
    "stopOnEntry":false,
    "localSourceRoot": "${workspaceRoot}",
    "serverSourceRoot": "/home/wwwroot/weiphp.dev",
    "port": 9001
},
{
    "name": "Launch currently open script",
    "type": "php",
    "request": "launch",
    "program": "${file}",
    "cwd": "${fileDirname}",
    "port": 9000
}

按F5选择PHP环境调试,打好断点即可

3. 尾声

参考资料

☑️ ☆

Git入门使用指南

本文将记录入门的git命令,适用于有其他版本管理经验的人快速了解git的常见操作

Git 是一个分布式版本控制系统,用于跟踪代码更改并协同开发项目

Git 支持在本地进行代码更改,将这些更改推送到远程存储库,以方便其他协作人员拉取以及同步修改进度

git提供的3个主要特性分别是:

  1. 分布式
  2. 版本控制
  3. 多人协作

以上3个特性在开发工作中都提供了非常强大的支持

1. 安装

git官网:https://git-scm.com/

git官方支持Windows/MacOS/Linux等,在Windows下安装git还可以顺带获得bash环境

这也是很多开发人员在Windows上使用Linux Bash环境的常用方法

在安装完成后,请在CLI中设置git账户信息,这个账户信息会在提交时作为默认值

Bash
git config --global user.name [username]
git config --global user.email [gmail@gmail.com]

2. 使用

以下会针对git的常见操作做介绍,分别是仓库、文件、版本以及分支的使用方法

2.1. 仓库管理

在创建一个代码项目后,切换到该代码项目下,初始化仓库,请放心,这不会对代码项目有任何污染

Bash
git init  # 仓库初始化
git add . # 追踪文件

在仓库初始化后,还添加了对所有文件的追踪

如果你需要忽略文件,可以在项目根目录下添加一个.gitignore文件,忽略规则写法如下:

TEXT
# .gitignore文件注释以#开头
file.txt            # 忽略特定文件
directory/          # 忽略特定目录
*.log               # 忽略所有以 .log 结尾的文件
/logs/              # 忽略根目录下的 logs 目录
src/*.txt           # 忽略 src 目录下所有以 .txt 结尾的文件
!important.txt      # 不忽略 important.txt 文件
!/docs/             # 不忽略根目录下的 docs 目录
*.zip               # 忽略所有 .zip 文件
!important.zip      # 但不忽略 important.zip 文件
/logs               # 忽略根目录下的 logs 目录
logs/               # 忽略任意位置的 logs 目录

写好忽略规则后,就可以绑定远程空白仓库

Bash
git remote add origin http://example/example.git    # 绑定远程空白仓库
git push -u origin main                             # 将当前分支绑定到远程仓库的main分支

通常一次正常的代码开发流程如下

Bash
# 拉取同步到最新代码
git clone http://example/example.git

# 切换新的本地分支
git checkout main

# 更新分支代码到最新
git pull origin main

# 切换到新功能分支进行代码开发
git checkout -b faeture-branch

# 进行代码开发
...

# 提交代码开发
git commit -m "update code to ..."

# 切换到主分支并合并代码
git checkout main
git pull
git merge faeture-branch

# 推送代码到远程仓库
git push origin main

第一步的克隆是针对有原始代码的仓库,也可以是上面的空白仓库创建与绑定

2.2. 文件管理

git最主要的是提供了文件管理,在修改文件后,可以与原始仓库对比看看修改了哪些文件

Bash
git status

可以丢弃对某个文件的更改

Bash
git checkout -- file.txt

可以查看文件修改历史

Bash
git log -- file.txt

2.3. 版本管理

git实现对代码版本的定义,通过借助tag来区分不同版本的代码

常见的tag操作如下

Bash
git tag                         # 查看所有tag
git tag v1.0                    # 添加当前仓库版本为v1.0版本tag
git tag v1.0 8a3                # 为commit为8a3开头的提交添加v1.0版本的tag
git tag v1.0 -m "v1.0 version"  # 为tag添加注释
git push origin v1.0            # 推送v1.0的标签到远程
git push origin --tags          # 推送本地所有标签到远程仓库
git tag -d v1.0                 # 删除标签

通常在一次版本代码开发完成后,都会打上tag并推送到远程仓库,tag方便帮助定位问题以及自动化部署集成

2.4. 分支管理

每次修改代码完成后都会推送到远程仓库的具体分支下,常见分支包括:

  • main/master分支:通常用于发布生产环境,具备最新稳定版本的代码分支,是受保护的分支
  • develop分支:用于集成各个功能特性分支的中央分支
  • Faeture分支:用于开发单个功能的分支
  • Hotfix分支:用于修复生产环境紧急问题的分支,修复后及时删除
  • Release分支:在发布新版本之前的进行最后测试的分支

分支的常见操作如下

Bash
git branch -a                                   # 查看所有分支(包括远程仓库)
git branch remote -v                            # 查看远程仓库的分支
git checkout -b feature-branch                  # 从当前分支切换并创建feature-branch的功能分支
git checkout -b feature-branch origin/main      # 拉取远程main分支并创建切换到本地feature-branch分支
git branch --set-upstream-to = origin/main main # 将本地分支main关联到远程仓库的main分支
git merge feature-branch                        # 合并feature-branch功能分支到当前分支
git branch -D feature-branch                    # 删除feature-branch分支

分支用于区分不同的工作情况,包括:

  • 本地常规功能开发
  • 本地修复生产环境问题
  • 测试环境进行版本测试
  • ...

如果没有分支,以上情况都在main分支工作将会变得十分混乱

许多CI/CD部署基于分支和版本(tag)进行管理,合理规范使用分支和标签是必要的

3. 其他

3.1. git stash

当你修改了代码,却因为各种原因(如生产环境紧急修复)需要紧急修改代码,会发现无法切分分支,因为当前分支有未保存的工作

git stash可以帮助你暂存当前工作目录的修改,让你可以在不想提交修改的情况下切换到其他分支进行其他操作

使用如下:

Bash
# 将当前工作区修改暂存
git stash

# 查看暂存列表
git stash list

# 切分到其他分支完成工作
...

# 切换回来当前分支
...

# 恢复方法一:恢复最近的暂存并将其从 stash 中移除
git stash pop

# 恢复方法二:恢复最近的暂存但不移除stash中的记录
git stash apply

# 有多个stash,需要恢复指定的stash
git stash apply stash@{n}

# 丢弃最新的stash
git stash drop

# 丢弃指定的stash
git stash drop stash@{n}

3.2. Gitflow

Gitflow 是一种基于分支管理的工作流程,旨在帮助团队有效地组织和管理代码库中的开发和发布流程

Gitflow通常包含主分支(main/master)、开发分支(develop)、特性分支(feature)、发布分支(release)以及修复分支(hotfix)

代码通常从特性分支开始编写,完成功能后合并到开发分支,在汇聚多个特性分支后合并到release分支进行发布前的测试和验证,通过测试验证后合并到main/master分支并发布产品,在线上发现紧急问题后创建修复分支并合并到main/master分支中修复问题

Gitflow是比较常见的工作流,也有其他的工作流包括Gitlabflow、Githubflow等,可自行查阅

另外一个值得注意的是,Gitflow也有推荐的提交注释格式,包含:

  1. 使用动词开头:如FixAddUpdate等来描述此次提交的目的
  2. 描述语法:应该使用"一般现在时"来描述提交内容
  3. 分隔行:可以使用空行来分隔标题和正文,以增加可读性

示例如下

TEXT
Add feature to allow users to reset passwords

- Implemented a new feature to allow users to reset their passwords
- Added UI components for password reset form
- Fixed bug related to password reset functionality

Issue #123

遵循规范的提交注释格式可以使提交历史更清晰易读,方便团队协作和代码维护

3.3. Pull Requet(PR)

在Git中,PR代表Pull Request,是一种用于协作和代码审查的机制,常见于github

当你在需要在一个非本人拥有的代码仓库中开发新功能或修复bug时,通常会基于某个分支进行开发,当你完成开发并希望将你的代码合并到目标分支(如主分支)时,你可以创建一个Pull Request来请求仓库的所有者审阅你的代码,审阅通过后你的代码将合并到主目标的分支中

PR多依赖于界面操作,其步骤通常如下:

  1. Fork代码仓库到自己账户下
  2. Clone代码仓库并创建分支修改代码
  3. 合并代码并推送代码到自己的远程仓库
  4. 向目标代码仓库发起PR请求

4. 尾语

以上是git的常见用途,git的知识还有非常多,随着使用会愈发惊叹这个程序的不可思议

❌