G

GoodBoyboy 's Blog|惬意小屋-点滴记忆

GoodBoyboy 's Blog

新增外设漫步者G1500bar

起因 之前的那个音响坏了,用了一年多,过保没多久,没办法,只能重新买个了 物色 在网上物色了一会,知道百元价位就纯听个响,也就没多苛求什么了 找了一圈,看上了g1500bar,主要还是这个外观设计比较戳我,7.1声道什么的无所谓,效果还不如不开 但是价格有点贵,日常价是199,但是经常打折到170左右 然而就当我打算买的时候,我发现这玩意居然也能用国补,能减20块钱,但总所周知,下半年的国补真的一言难尽,只能用“如补”来形容 现在湖南国补改成了每月一号抢,再看这发券量,能抢上就有鬼了,之前听有些人说抢了40多分钟才抢到,为了那20块钱浪费我半个小时实在没必要,少吃一顿就节省出来了。。。 最近打算不等了,等它降价到170多就买 下单 今天发现它上了京东的国家补贴,不用去逆天云闪付抢券,又去搜刮了几块钱的券,折合下来也差不多是170多 下单的时候买了个8块的三年三包故障无忧焕新,不想再被搞心态,过保就报废。。。 因为今早下单下得早,今日达下午六点就到学校驿站了 使用 听个响,中规中矩吧 试了一下麦克风,也没像网上说的那样不堪,感觉收音还不错 一开始都还不知道下面那个灯条是rgb,以为就一个颜色,看了说明书才发现原来是rgb灯条,还能音频响应(喜 而且当初也是当usb音响买的,突然想起这玩意也能用蓝牙连接,可以当蓝牙音响玩了(doge 因为电脑A口就两个,一个A口接了个USB HUB,一开始我把音响插HUB上,系统里看到了设备,但是就是无法输出声音,然后Windows就提示无法正常启动这个USB设备 我想这玩意又要从USB取电,又要传递数据,会不会是HUB的问题,接到另一个A口上后果然就正常了 然后这个音响还有软件,软件里可以调的功能还比较多,试了一圈下来我就开了个清晰度增强和动态低音,这两个功能确实是能让我明显感到变化,特别是动态低音,效果还不错(喜 来张桌面图(doge 后记 总体来说还不错,又是满意的一次购物体验😁

2025/9/18
articleCard.readMore

欢迎招行万事达加入卡包大家庭

前言 上个星期去网点,结果网点说系统还没升级,只能申请金葵花卡,让我等一个星期后再看看,来之前还可以先打网点电话问问。 等了一个星期,刚好今天下午没课,择日不如撞日,那就今天吧(doge) 保险起见先call一下网点电话,问了一下网点现在可以开普卡了,于是打车猛猛去网点 开卡 招行分行果然没让我失望,开卡流程顺畅无比,没额外要求任何证明材料,一张身份证即可,而且柜员业务熟练,裤裤就帮我开好了 而且限额直接就是学生能开的顶级,每日5k, 我之前那张招行的一类卡还是花了一个月才从2k升到了5k 最后小姐姐问我能不能100块存三个月定期,他们有任务,能不能帮个忙。既然人家服务这么好,这点小忙能帮还是会帮的啦,便存了100块的三个月定期。 暂时没绑在Google play上,因为我怕把地区弄回国区了,不过PayPal上倒是绑了试了一下,可以正常绑定。 这张卡是普卡(但是卡背面印刷着platinum debit),支持开为二类户,完全不影响原先的一类卡,而且支持APP查询预授权,无每月150元的账户管理费 卡支付来说,100%冻结比例,支持3ds(非强制),支持自动兑换货币(不占用每人每年5w美元额度),人民币账户,支持绑定微信支付宝,境外消费5‰返现,每月免首笔境外ATM取款手续费,境外ATM查询手续费、货币兑换手续费全免,而且听说汇率走的是万事达的汇率,比银行汇率优惠一丢丢 可以说国内万事达借记卡中,将它称之为神卡不过分吧 后记 自从中信的金貔貅支持自动兑换货币后,邮储那张万事达在我心中的地位就跌入底谷了,毕竟强制3ds这一点真的就拒了很多海外网站

2025/9/16
articleCard.readMore

博客十周年啦🎉

今天逛了逛博客,发现已经十周年了诶😄 小学 还记得最开始那会,自己什么也不懂,不懂网站,不懂域名,更不用说html,js之类的,只是觉得这些网站很神奇。 在网上兜兜转转,用搜索引擎搜索,去大佬群里问,后来不知道在哪里看到,找到了一个叫领地网的网站,开启了我最初的博客生涯 其实那会领地网的模式有点类似现在的csdn,你在它的平台上发文章,而且只能写文字,也插入不了html和js 不过确实是让我在互联网中,第一次有了一块自己的小空间,开启了我的博客1.0 后来,领地网升级改版,这个网站也就不了了之 之后转向了安卓APP,在网上找了一个叫叮铛APP的APP设计平台,都是模块化设计,不用代码知识,对于当时的我来说刚刚好 还记得那会在官方群里和各个大佬交流,特别有趣 只是好景不长,平台从2.0升级到3.0后就改成收费的了,当时群里听说3.0要出来了还特别兴奋,结果出来后就改成收费,没办法,只好放弃app 初中 到初一后,家里有了电脑,我开始接触更多信息,了解到虚拟主机,便在网上找免费的虚拟主机 我记得最开始接触的是一个叫3vfree的虚拟主机网站,提供100M的免费空间,仅支持静态html和asp 虽然配置低,但是当时也玩得起劲,还在网上找了许多asp网站程序,玩得不亦乐乎,只是这个免费虚拟主机会在你的网站底部自动加上广告。。。 后面又了解到云邦互联这个网站,提供免费虚拟主机,但是好像只有半个月有效期,续期需要去各个论坛发广告审核,用了一段日子,直到一次因为审核晚了数据都被清了,就再也没用这个网站 那会还买了自己第一个域名,不过因为有点中二,后来换成了现在的域名 之后又断断续续找了很多免费的虚拟主机网站,但是都不稳定,而且那会重心也从网站转向写程序 安卓app暂时没头绪,无意间了解到C#的netframwork,开始写winform程序 一直到初三上吧,重心才又开始转向网站,中间这段混乱的时间算是我的博客2.0 时间来到初三,当时阿里云好像是有个学生机器优惠,每个月只要9.9,那会非443端口的网站还不需要备案,我便用那个优惠机器搭网站 那会开始了解到宝塔面板,最开始Windows宝塔面板还有设置GUI,可以在GUI里面调整网站配置,后面倒是全搬到web面板中了 也多亏了宝塔,让我的网站搭建之路很顺畅,不用折腾MySQL,apache这些环境 也是从这里开始,我用上了真正的博客程序,emlog,搭配了一个当时emlog中超好看的叫colorful的主题,开始我的博客3.0 高中 到高一的时候,emlog从5.x升级到了7.x,emlog5被爆出有漏洞,但是colorful主题并不支持emlog7,没办法,只好舍弃了emlog 与此同时,我又了解到了wordpress,便搭建了一个wordpress,把文章全部迁移到了wordpress 但众所周知,wordpress十分臃肿,阿里的9.9小鸡根本扛不住,再加上寒假那会我还要那它开mc服务器(狠狠压榨了🤣),大部分时间其实都花在怎么优化wordpress,不过好在在我高中学业繁忙之前弄好了 到了高二高三,就真的没有时间去打理网站了,一直忙着学业,而且那会又突然喜欢上写程序,白天课余时间构思,在纸上写程序框架和逻辑,写伪代码,星期天就用3个小时的时间在家里实现 别人周末打游戏,我周末就写程序😂 不知不觉,高中生活转瞬即逝,转眼就进入了大学生活 大学 到了大学,那时间就是大把大把的多😆 给自己的博客换了一个好看的主题,叫pjax sakura,同时也开始混博客圈,算是正式进入博客4.0 我记得应该是和 晓空 互换了第一个友情链接,他博客现在用的主题就是pjax sakura 我的博客同时也于2022年12月10日加入了开往,于2022年12月31日加入了十年之约 后来是实在忍受不了wordpress的臃肿,在了解到hexo后就直接转了hexo,用上了butterfly主题(也就是现在用的这个) 同时也给博客做了版本控制: 未完待续 一路陪伴,感谢有你❤️

2025/9/4
articleCard.readMore

ESP32 S3开发小结

小结 在历经状态机设计,爆栈,中文字体缺失,URL编码问题,C++野指针,页面竞争,任务假死,二维码无法扫描等亿系列问题后,终于弄好了。 目标 采用OAuth2的设备码授权方式,设备请求用户代码并向用户展示,由用户在自己的设备上进行授权,授权完成后ESP32拿到令牌,然后对用户资源进行访问。 已实现功能 系统 采用FreeRTOS任务模式,实现多任务并发,结合排他锁解决并发竟态问题。 WIFI 支持断线重连,当出现断线时自动切换任务。 UI 支持切换不同页面,为不同功能页提供显示功能。 OAuth 支持自动轮询授权情况,并支持当授权链接失效时自动重新获取以及生成二维码。 不足 感觉还不是最佳实践,系统健壮性不高,无法支撑复杂功能(未验证) 屏幕还是太小了,128*64的分辨率一个正常包含链接的二维码都塞不下去,目前只能用二维码展示用户授权所需的用户代码,无法展示完整授权链接,可能需要制作一个专用APP用于扫码来拼接授权链接并在用户端调起授权。 下一步方向 寻找最佳实践的设计,增加系统健壮性,目前状态机设计还有提升空间 支持复杂的交互操作,支持16键输入控制系统以及页面,实现菜单选择等 接入NFC控件,便于用户进行碰一碰授权操作 接入蓝牙通信,动态控制设备连接附近WIFI(可能需要制作配套APP) 接入MQTT,实现远程控制(已实现样例)

2025/8/23
articleCard.readMore

ESP32 S3外设小记

前言 最近玩ESP32玩得起劲(太好玩了),光做软件上的编程还差点意思,昨天只实现了MQTT远程点灯,于是今天打算利用好这30多个GPIO口玩玩硬件上的。 踩坑 因为是第一次玩,什么硬件知识都不懂,顶多了解了一点课上学的模电数电知识(还是非电气专业的课程,属于是基础中的基础),也不敢乱接(金属膜电阻都不会认,100欧的电阻找了半天),于是猛猛问ChatGPT 之前买了好几个有趣的外设,例如红外接收器,显示屏,有按键的数码管。 感觉有按键的数码管更好玩,16个按键可以设置16个状态,不用老是重新烧写(每次编译时间还不短。。。) 找客服要了一份资料,本来是打算只要一个管脚图的,客服发了一堆资料(手册,电路图等等),感觉事情的不简单 里面找到了一份TM1638B(驱动芯片)的Arduino工程文件,打开一看200多行代码(给刚点完灯的小白亿点点震惊) 尝试看了一下代码,嗯,完全看不懂。。。 不过无所谓,打算先烧上去再说,看看怎么玩 编译完成后,问了问ChatGPT该怎么组装 然而我发现这玩意的工作电压是5V,问了一下ChatGPT,才发现ESP32 S3是3.3V,两者不能直接连接 详细问了一下,才知道要电平转换,而且查了一下资料,这个芯片最低高电平电压是0.7*VDD,也就是至少要3.5V,3.3V完全不够,并且从TM1638B向ESP32传输数据时5V直接进GPIO口也会烧坏板子 没办法,那只能去淘宝物色一下电平转换的模块了 尝试 又看了一下红外接收器,发现也要5V,不过显示屏倒只要3.3V就能驱动,于是找ChatGPT又开始问东问西 然后就又了解到I2C是开漏输出,需要外部上拉电阻,4.4k欧或者10k欧,用于上拉信号,但是我没有4.4k欧的,只好用10k欧的 组装好电路后,找ChatGPT要了一份代码,就当我以为ok时,刷完固件显示屏直接花屏。。。 折腾过去又折腾过来,从I2C地址到线材通信等等,最后发现是我芯片提供错了,我用的这个OLED显示屏驱动芯片是SH1106,我给ChatGPT说成另一款屏幕的SSD1306了😅 ChatGPT一个流汗黄豆塞我脸上🤣 换了新的代码终于是能跑了。

2025/8/22
articleCard.readMore

Arduino+VSCode开发ESP32-S3

前言 Arduino IDE对于我来说太难用了,不过本质还是C++开发,理论上VSCode也可以。 安装 Arduino CLI Arduino不仅提供了IDE,也提供有命令行工具 首先安装Arduino CLI,在 https://docs.arduino.cc/arduino-cli/installation/ 中找到适合自己系统的Arduino CLI并进行安装。 安装完成后输入arduino-cli help core确保已经正常安装。 配置 Arduino CLI 首先需要初始化Arduino CLI 1 arduino-cli config init 配置“用户目录”,该目录会用于存放第三方库等等,推荐和config设置为一个路径 1 arduino-cli config set directories.user "用户目录路径" 然后添加官方的加速源,否则可能下载会十分慢 1 arduino-cli config set board_manager.additional_urls https://jihulab.com/esp-mirror/espressif/arduino-esp32/-/raw/gh-pages/package_esp32_index_cn.json 接着更新板子列表 1 arduino-cli core update-index 这里我们可以先不下载。 配置 VSCode与环境 安装Arduino Maker Workshop插件,然后打开一个文件夹用于工作目录。 插件安装完成后在侧边栏点击插件,点击Maker Workshop Home,即可打开配置栏。 首先进入Boards Manager,点击Not Installed标签,搜索esp32,然后点最右边的倒三角展开,版本选择x.x.x-cn,这个才是加速的版本 然后点击版本旁边的下载按钮开始下载。 安装完成后进入Board Selection,选择ESP32S3 Dev Module即可。 接着点击Board Configuration配置板子 Uplaod Speed最好别选921600,太高板子可能承受不住,512000我的可以正常刷入,实际根据板子体质决定。 Flash Mode根据实际情况来决定,部分板子可能支持OPI,可以通过下面命令查看(错误的配置会导致板子无法启动): 提示esptool找不到则是没安装esptool,如何安装esptool请自行Google 1 esptool flash-id 然后回输出Flash Memory Information,在Flash type set in eFuse一栏中查看,我的板子是quad (4 data lines) 所以我选择QIO 80MHz,如果你的Flash支持8 data lines则可以选择OPI Flash Size是多少选多少,我的是N16R8,所以我选择的是16MB Partition Scheme我选的是16M Flash(2MB APP/12.5MB FATFS),自己根据实际情况选择 PSRAM一般情况下,ESP32-S3-WROOM-1-N4R8/N8R8/N16R8是支持Octal SPI的,可以选择OPI PSRAM 该文中有详细表:https://blog.csdn.net/tianizimark/article/details/125264464 Arduino Run On和Events Run On是否要同一个核心看你自己,不同核心的话可能会出现多线程竟态问题(存疑),如果工程简单的话都跑在一个核心上没问题 剩下的全部默认即可 编译烧录 点击Compile和Upload就行。 飘红问题 有时候安装第三方库include后会飘红,这里只需要编译(Compile)一下,就会自动补齐配置 如果还是有问题,按住Ctrl+Shift+P打开VSCode的命令栏,找到编辑配置(UI),指定一下g++位置,包含路径中添加库文件夹即可 例如我的是C:\Users\GoodBoyboy\AppData\Local\Arduino15\libraries/** 参考 https://docs.arduino.cc/arduino-cli/getting-started/ https://blog.csdn.net/ZHOU_YONG915/article/details/130118049 https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html https://blog.csdn.net/tianizimark/article/details/125264464 https://blog.csdn.net/tianizimark/article/details/124663902

2025/8/20
articleCard.readMore

EPS32-S3刷入MicroPython

本教程适合乐鑫官方ESP32-S3模组,非官方模组可能不适用 下载固件 Tips:根据 Commit 6201e77,现在MicroPython已经支持自动计算flash大小 进入MicroPython的ESP32 S3的固件下载页面https://micropython.org/download/ESP32_GENERIC_S3/ 在Firmware (Support for Octal-SPIRAM)下方找到最新的一个固件,点击[.bin]下载bin格式的固件。 安装刷入工具 这里使用乐鑫的官方刷入工具esptool 没有python环境请先安装python环境 1 pip install esptool 安装esptool时会自动配置好环境变量 刷入固件 将EPS32-S3接入计算机,建议使用USB to COM口,即插即用。 一般来说esptool会自动识别COM口,不需要在命令中手动指定;Linux环境下可能需要将命令中的esptool替换为esptool.py 首先擦除flash: 1 esptool erase_flash 然后刷入固件: 1 esptool --baud 460800 write_flash 0 你的固件.bin 如果中途刷入失败可以尝试降低或移除--baud 460800(一般不会) 使用MicroPython 刷完将板子重接接入计算机,使用各种交互式终端连接对应的COM口即可 可以使用下面代码查看内存和闪存情况: 查看内存: 1 2 import micropython micropython.mem_info() 查看闪存: 1 2 3 import esp esp.flash_size() #或者esp.flash_size()/1024/1024 自行编译固件(可选) 本地编译 需要良好的网络环境 这里需要使用到IDF 目前,MicroPython支持IDF v5.2、v5.2.2、v5.3、v5.4、v5.4.1 和 v5.4.2 环境最好为Linux,Windows下则需要使用WSL 首先Clone拉取IDF 1 git clone -b v5.4.2 --recursive https://github.com/espressif/esp-idf 然后安装IDF: 1 2 3 cd esp-idf ./install.sh esp32 source export.sh #用于设置环境变量,每个新会话都需要重新设置 然后回到上一级目录,拉取MicroPython 1 git clone --recursive https://github.com/micropython/micropython 构建交叉编译器 1 2 cd micropython make -C mpy-cross 构建MicroPython 1 2 3 cd ports/esp32 make submodules make BOARD=ESP32_GENERIC_S3 BOARD_VARIANT=SPIRAM_OCT 构建完成后可以在build-ESP32_GENERIC_S3-SPIRAM_OCT文件夹中找到firmware.bin,这就是最终的固件。 GitHub Action 编译 可以使用GitHub Action来编译固件,避免网络环境问题。 https://github.com/GoodBoyboy666/ESP32-S3-N16R8-Action 可以自行fork修改 参考 https://github.com/micropython/micropython/tree/master/ports/esp32 https://github.com/micropython/micropython/tree/master/ports/esp32/boards/ESP32_GENERIC_S3 https://micropython.org/download/ESP32_GENERIC_S3/ http://micropython.com.cn/en/latet/esp32/quickref.html#installing-micropython https://github.com/espressif/esp-idf/blob/master/README_CN.md https://www.bilibili.com/video/BV1s24y137cK?vd_source=d70f12a573c31ee545ff78eaf31116ea

2025/8/18
articleCard.readMore

EPS32 S3刷入Picokeys

前言 Picokeys是一个开源的安全密钥固件,支持在Raspberry Pico (RP2040, RP2350) 和 ESP32-S3上运行 目前PicoKeys有三种功能:HSM、Fido2 Passkey、OpenPGP & PIV 刷入固件 一般EPS32 S3开发板有两个Type-c口,一个是USB直通,一个是USB to COM 使用USB直通需要进入特殊的bootloader模式才能刷入 使用USB to COM则可以直接刷入(需要安装对应芯片的驱动,我的板子上是CH343) 进入bootloader模式方法: 插入计算机,按住Boot按键后再点按RST按键,最后松开Boot按键。 然后进入Picokeys的ESP32 Flasher(https://www.picokeys.com/esp32-flasher/) 这里需要使用Edge或者Chrome浏览器,Firefox不支持 选择想要刷入的固件,点击Connect,在浏览器弹出的窗口中选择Jtagxxxx或者Usb single serial 修补 刷完固件后还需要使用Pico Commissioner来修补VIDPID 修补是在非bootloader模式下进行的 拔出EPS32再插入(如果之前选择插入的是USB to COM,则可能需要插至USB直通) 进入Pico Commissioner(https://www.picokeys.com/pico-commissioner/) 选择一个Vendor,其他设置自己自定义,然后在Options下方勾选Initialize 最后点击Commission via WebUSB,选择Picokeys进行修补 Tips:如果点击后没反应可以尝试使用手机的Edge或者Chrome试试

2025/8/18
articleCard.readMore

Debian13安装Nvidia驱动

检查硬件 1 lspci -vnn | grep -i VGA 检查输出内容中是否含有Nvidia字样的设备。 修改源 NVIDIA驱动属于闭源软件,需要启用non-free源 编辑source.list文件 1 sudo nano /etc/apt/sources.list 在每个源后面加上contrib和non-free,保存后退出。 然后更新软件包列表 1 sudo apt update 安装驱动 1 sudo apt install linux-headers-$(uname -r) nvidia-driver firmware-misc-nonfree 安装时会自动禁用nouveau 导入签名公钥 因为修改了内核模块,将无法通过secure boot启动,需要手动导入密钥至bios,如果主板未打开secure boot则可以跳过本节。 安装时已自动生成了密钥并对模块进行了签名,这里只需要导入签名公钥即可 1 sudo mokutil --import /var/lib/dkms/mok.pub 一般是这个路径,安装日志中会显示文件位置 然后会要求你输入密码,该密码仅用于临时验证,不需要太复杂 重启机器 1 sudo reboot 如果导入了签名公钥,那么在启动时会出现MOK management界面,选择Enroll MOK->Continue,然后输入刚刚使用mokutil导入公钥时输入的密码。 检查驱动 进入终端输入nvidia-smi,如果输出了显卡详细信息以及GPU进程,则代表驱动安装成功。 食用方法 启动程序时设置环境变量__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia即可。

2025/8/11
articleCard.readMore

现在小孩都这么早熟么。。。

今年下午去医院取上午抽血的检查报告时,我在那里等电梯,一个小屁孩跑过来让我跟着他。我一脸懵逼的看着他,我问他干什么他也不说,就让我跟着他。 因为取结果只有我一个人,而且这会医院下午才上班,人还不是很多,所以我下意识的想着他会不会想拐我,于是站在那里没动。 不过转念一想,一个还没我一半高的小屁孩怎么拐我,看着他一直催,我又怕是真的有什么事情,他妈妈需要帮忙什么的(因为旁边就是儿科) 于是我就慢慢的跟在他后面。 我又想万一不止他一个呢?例如利用小孩让人放松警惕把我带到偏僻地方,然后出来几个大汉把我绑走什么的,或者未成年犯罪?而且看他那不怀好意的眼神(真的就是那种赤裸裸不怀好意的眼神),我又有点不想管了。 不过还没等我想完,他就已经往儿科里去了,我想那个地方怎么都不能发生绑架这种事吧,想看看他到底搞什么鬼,便和他一起进了儿科 然后令我无语的地方来了 他把我带到儿科护士值班的地方,指着那里的一个护士说让我嫁给她?????? 什么跟什么??? 然后那个护士对着我和那个小孩笑。。。 哪家小屁孩又放出来没人管了 我当即转身就准备走,懒得和这种小屁孩计较,虽然我不急但是我的时间也不是让这种人随意浪费的 然后那个小屁孩还拉着我的手在那里继续说让我嫁给她。。。 我无语的把手抽出来,对着他说我有自己的事情,然后就没理他自己走了。 真的是什么跟什么,这么屁大的孩子知道结婚的意义是什么么,也不知道是在哪学的这些。。。

2025/8/4
articleCard.readMore

《动物迷城》二周目通关!

《动物迷城》二周目通关!非常好玩而且有意义的一款国产CRPG游戏😄 “逆境或许能将人毁灭,但别让它轻易把你我打败”

2025/7/29
articleCard.readMore

Bing索引终于恢复了

前言 差不多半年前,不知道发生了什么,博客在Bing的索引直线下降,直到完全没有。 URL检查是能索引的,但是就是死活不索引。。。 后面看到有人也有这个情况,是需要向客服提交工单解决,于是大概一个月前我也向客服提了工单,中间陆陆续续收到几封客服邮件,今天收到客服邮件说已经解决了该问题 上线Webmaster Tools看了一下,确实印象数上升了 去bing搜了一下site:goodboyboy.top,也确实是能看见博客了 后记 长达半年的丢失索引终于解决了😂,后面只能等排名慢慢恢复了

2025/7/22
articleCard.readMore

家里蹲大学开始招生!

正文 继上文《家里蹲大学即将建校,欢迎各位莘莘学子来本校深造(doge》,今日家里蹲大学正式建校(doge 我们将为每位同学免费分发本校的教育邮箱,教育邮箱申请方式如下: 向me#goodboyboy.top(#换成@)发送邮件进行申请,申请内容需要包含以下内容: 用户名(仅允许包含A-Z,a-z,0-9) 为防止被用于发送非法邮件,此邮箱使用CloudFlare邮件转发功能,只允许收信不允许发信,转发目的地为发送申请邮件的邮箱地址。 申请通过后将会向转发目的地邮箱发送一封验证邮件,请及时完整验证避免影响收信。 另外,禁止滥用教育邮箱,否则我们有权利收回教育邮箱 题外话 可以去薅薅管的不严的一些学生优惠,像GitHub Education这种特别严的就不要想了 另外因为使用邮件的是转发功能,所以收信可能会慢一些,国内的某些邮箱甚至可能收不到信,可以提前用自己的另一个邮箱测试一下。

2025/7/21
articleCard.readMore

家里蹲大学即将建校,欢迎各位莘莘学子来本校深造(doge

标题是开玩笑的(doge 最近注册了一个edu.kg后缀的域名,打算当一把校长(doge,看看能不能过那些教育邮箱验证 这类野鸡域名可能难过,不过可以试试,反正注册一个也没多少钱,实在不行就拿来自己玩了 目前正在等待全球NS记录同步,到时候整个官网和域名邮箱,欢迎有意愿的同学们来本校深造,我们会给每位学子分发自己的教育邮箱(doge

2025/7/21
articleCard.readMore

骑车去兜风~

var meting_api='https://meting.furwolf.com/api?server=:server&type=:type&id=:id&r=:r'; 前言 最近在学校都是三点一线,早上去寝室刷牙,白天在实验室,晚上去寝室洗个澡,然后回实验室睡觉。 感觉有点乏味,是时候出去散散心了 骑车 打算骑车去散散心,刚好最近新增了另一家共享电动车,电动车设计的更合理,比原来那几家多了手机槽,喇叭也是电喇叭,还有转向灯 原来那几家共享电动车真的可以划为单车范围了😂 电喇叭什么的其实都还好,我特别喜欢那个手机槽 以前用手机导航,只能用耳机连接手机,然后把手机揣口袋里,听着导航播报 高德有时候播报又特别抽象,时不时来一个“右前方”,还特别喜欢和右转一起播报,有时候我都走上直行道了,到路口给我说要右转,有时候我都右转了,给我说偏离路线。。。 现在听到“您已偏离路线,正在为您重新规划”这句话我就ptsd 而新的共享电动车,我就可以把手机放在手机槽里,看着导航开了😁 就在我解锁后,发现运营区域居然扩大了!!! 原来只能在河的西侧,现在把几乎是整个城市都包围了起来,我的活动范围大大扩张!!! 这不美美骑一下,直接高德导航到河东的运营区边缘😄 散心 虽然湖南温度晚上也高,但是开着电动车吹着风还是很舒服的 过桥时发现对面山头风景还不错,随手拍了一张 听着歌吹着风,看看风景,还是很不错的😄 给大家分享一下骑车时听的歌,都超好听(加载播放器可能需要一点时间,部分歌要VIP就通过歌名分享) VIP歌曲: 歌曲名:面会菜,歌手名:林生祥,专辑名:大佛普拉斯 电影配乐 歌曲名:空白,歌手名:王舜禾,专辑名:伍六七之暗影宿命 原声大碟 歌曲名:时光盲盒,歌手名:ChiliChill,专辑名:2021哔哩哔哩拜年纪 歌曲名:虫儿飞,风儿吹,歌手名:小蓝背心,专辑名:百啭千声 歌曲名:天命 The Chosen One (feat.William Wu),歌手名:且听东方/Ferkingge,专辑名:天命 The Chosen One (feat.William Wu) 歌曲名:武侯祠,歌手名:闫东炜,专辑名:天朝元素project 歌曲名:Black Myth: Wukong Main Theme (COVER版),歌手名:Diego Mitre,专辑名:Black Myth: Wukong Main Theme (Cover) 歌曲名:以梦为马,歌手名:熊猫堂ProducePandas,专辑名:大惊小怪 歌曲名:淋雨一直走,歌手名:张韶涵,专辑名:有形的翅膀 返回 返回的时候不打算原路返回,打算围着运营区边缘绕着回去。 相较于河东这边的窄公路,还是绕城的大路骑起来更爽😁 下面是过另一座桥时拍的夜晚风景 中途在一个立交桥下还遇到了卖臭豆腐的小摊(走不动路了) 停下来买了份臭豆腐,不过当时时间有点晚了,要赶着回寝室洗澡(十一点半要关门),就直接挂在车上 开了一会后发现车摇来摇去容易撒,只好停在一个垃圾桶旁边吃(偷感超重😂) 最难蚌的是,我选的这个地方明明很隐蔽,为啥还有人会路过??? 我当时是背着那几个人的,我明显看到他们几个路过我的时候还专门看了我几眼。。。怕不是被看成乞丐了🤣 吃完赶紧润,抓紧时间赶路,最后成功到达宿舍。 整个行程花了大概两个半消失,开了33公里,花了15.5,性价比还是蛮高的 后记 以后有空还可以多转转,活动范围变广了,又有新的区域可以探索了😀

2025/7/17
articleCard.readMore

OAuth2授权码、客户端凭证、PKCE、设备码授权流程详解

前言 本教程将结合Gemini 2.5 Pro来为各位进行讲解 OAuth 2.0 是一个行业标准的授权框架,它允许第三方应用程序在不获取用户凭证(如用户名和密码)的情况下,有限度地访问用户在某一网站上存储的私有资源。 OAuth 2.0 中的四个核心角色: 资源所有者 (Resource Owner):通常是终端用户,拥有受保护资源的最终控制权。 客户端 (Client):希望访问资源所有者受保护资源的第三方应用程序。 授权服务器 (Authorization Server):负责验证资源所有者的身份,并在获得资源所有者授权后,向客户端颁发访问令牌(Access Token)。 资源服务器 (Resource Server):存储受保护资源,并接受和验证访问令牌,以允许客户端访问资源。 授权码模式 (Authorization Code Grant) 这是功能最完善、安全性最高的授权模式,因为需要保持client_secret非公开,推荐用于传统的 Web 应用(即拥有后端的应用)。 下文的客户端包括客户端的前端与后端,资源所有者一般为用户 sequenceDiagram participant ResourceOwner as 资源所有者 participant Client as 客户端 participant AuthServer as 授权服务器 participant ResourceServer as 资源服务器 ResourceOwner->>Client: 1. 访问客户端 Client-->>ResourceOwner: 2. 返回授权链接 ResourceOwner->>AuthServer: 3. 通过链接访问授权页面 AuthServer-->>ResourceOwner: 4. 展示授权界面 ResourceOwner->>AuthServer: 5. 确认授权 AuthServer->>Client: 6. 重定向回调(携带code) Client->>AuthServer: 7. 发送code + client_secret请求token AuthServer-->>Client: 8. 返回access_token Client->>ResourceServer: 9. 携带access_token请求资源 ResourceServer-->>Client: 10. 返回受保护资源 note left of ResourceOwner: 用户操作流 note right of AuthServer: 安全校验: note right of AuthServer: - 验证code有效性<br/>- 验证client_secret<br/>- 检查重定向URI 发起授权请求 当用户使用客户端时,客户端需要发起授权请求。 该请求实际由用户发起,客户端负责构建一个包含client_id、redirect_uri、response_type 和scope等参数的URL授权请求链接: Base URL:授权服务器的授权Endpoint,例如https://xxx.com/api/authorize response_type:code client_id:客户端ID redirect_uri:回调URL scope:资源(权限)范围,例如openid、email、profile等 state:随机字符,可选(推荐),用于防止跨站请求伪造(CSRF)攻击 例如: 1 GET /authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=SCOPE 确认授权 用户定向到授权链接,如果此时用户未登录,则会要求用户进行登录 登录完成后,授权服务器会展示本次授权请求相关信息,例如客户端的详情,请求的资源内容等 如果用户确认授权,则重定向至redirect_uri(上文示例中的CALLBACK_URL内容)并在URL参数中携带本次确认授权的 AUTHORIZATION_CODE Tips:正常情况下,授权服务器会对redirect_uri的内容进行校验,确保当前授权链接中的redirect_uri内容与客户端在授权服务器注册时预留的redirect_uri内容一致,否则重定向至钓鱼链接会导致本次确认授权的 AUTHORIZATION_CODE 泄露 客户端请求access_token 客户端通过redirect_uri拿到本次授权的 AUTHORIZATION_CODE 后,使用 AUTHORIZATION_CODE 和 client_secret 向授权服务器请求 access_token 如果客户端启用了 state ,还会校验当前回调链接中的 state 值是否和当初授权链接中的 state 值一致,确保 AUTHORIZATION_CODE 仅用于本次授权请求会话,防止跨站请求伪造(CSRF)攻击 Base URL:授权服务器的令牌Endpoint,例如https://xxx.com/api/token 客户端向Base URL发送POST请求,请求体Content-Type为application/x-www-form-urlencoded,请求体内容如下: grant_type:authorization_code code:上文的 AUTHORIZATION_CODE redirect_uri:回调URL client_id:客户端ID client_secret:客户端Secret client_secret不可直接暴露给客户端前端,必须由安全的客户端后端使用,否则任何人(包括攻击者)只需要拿到 AUTHORIZATION_CODE 即可向授权服务器发起access_token请求 客户端请求资源 当授权服务器返回access_token后,使用access_token向资源服务器请求所需要的资源 资源服务器对access_token进行一系列校验,确认身份后返回客户端所需要的资源 客户端凭证模式 (Client Credentials Grant) 该模式适用于没有特定用户参与的场景,即客户端以自己的名义访问资源(例如,机器对机器的通信)。 因为没有用户参与,因此这里客户端可以直接向令牌Endpoint请求access_token 该模式需要具体参考授权服务器的文档,例如authentik身份识别基于服务账户,身份验证基于应用密码令牌(client_secret),因此还需要传递服务账户的username和password,这里仅示例通用情况。 客户端请求access_token Base URL:授权服务器的令牌Endpoint,例如https://xxx.com/api/token 客户端向Base URL发送POST请求,请求体Content-Type为application/x-www-form-urlencoded,请求体内容如下: grant_type:client_credentials scope:权限范围,具体参考授权服务器文档 请求头: Authorization:Basic ${将 client_id:client_secret 进行 Base64 编码后的字符串} 当然也可以将客户端凭证放在请求体中: grant_type:client_credentials scope:权限范围,具体参考授权服务器文档 client_id:客户端ID client_secret:客户端Secret 客户端请求资源 当授权服务器返回access_token后,客户端使用access_token向资源服务器请求所需要的资源 资源服务器对access_token进行一系列校验,确认身份后返回客户端所需要的资源 授权码 + PKCE (Proof Key for Code Exchange)模式 PKCE 是对授权码模式的增强,现在被认为是所有类型客户端(包括原生应用、单页应用和传统 Web 应用)的最佳实践。 核心思想:在授权请求时,客户端生成一个随机的验证器(code_verifier),并将其哈希值(code_challenge)发送给授权服务器。当客户端用授权码交换访问令牌时,必须同时提供原始的 code_verifier。授权服务器会验证 code_verifier 和 code_challenge 是否匹配,从而确保即使授权码被截获,攻击者也无法在没有 code_verifier 的情况下冒用。 sequenceDiagram participant C as 客户端 (App) participant U as 资源所有者 participant AS as 授权服务器 participant RS as 资源服务器 Note over C: 1. 生成 code_verifier, <br/> 计算 code_challenge C->>U: 2. 发起授权请求 (携带 code_challenge) U->>AS: 用户浏览器重定向至授权服务器 AS-->>U: 要求用户登录并授权 U->>AS: 同意授权 AS-->>C: 4. 重定向并返回授权码 (code) Note over C: 截获此处的 code 也无用,<br/>因为攻击者没有 code_verifier C->>AS: 5. 交换令牌请求 <br/>(携带 code 和 code_verifier) Note over AS: 6. 验证 code_verifier 和 <br/> 之前存储的 code_challenge 是否匹配 AS-->>C: 验证通过,返回 Access Token C->>RS: 7. 使用 Access Token 请求资源 RS-->>C: 返回受保护的资源 发起授权请求 客户端首先生成一个随机的 code_verifier,将这个 code_verifier 存储在本地(例如,App 的内存或浏览器的 sessionStorage 中),根据 code_verifier 计算出对应的 code_challenge。 code_challenge由code_verifier先按照code_challenge_method进行计算,然后进行Base64URL编码 然后构建授权请求链接: Base URL:授权服务器的授权Endpoint,例如https://xxx.com/api/authorize response_type:code client_id:客户端ID redirect_uri:回调URL scope:资源(权限)范围,例如openid、email、profile等 state:随机字符,可选(推荐),用于防止跨站请求伪造(CSRF)攻击 code_challenge:第一步通过code_verifier计算出的code_challenge code_challenge_method:S256(具体看授权服务器的支持类型) 授权服务器收到请求后,会存储 code_challenge 并将其与即将生成的授权码关联起来。 确认授权 用户在授权服务器的页面上登录,并同意授权给客户端。 然后重定向至redirect_uri,并附带code与state内容 客户端请求access_token 与标准的授权码模式相比,请求体中必须额外包含第一步生成的code_verifier Base URL:授权服务器的令牌Endpoint,例如https://xxx.com/api/token 客户端向Base URL发送POST请求,请求体Content-Type为application/x-www-form-urlencoded,请求体内容如下: grant_type:authorization_code code:上文的返回的code redirect_uri:回调URL client_id:客户端ID code_verifier:第一步生成的code_verifier 收到请求后,授权服务器计算code_verifier在使用算法(S256)后是否与开始提供的code_challenge一致,一致则下发access_token。 客户端请求资源 当授权服务器返回access_token后,客户端使用access_token向资源服务器请求所需要的资源 资源服务器对access_token进行一系列校验,确认身份后返回客户端所需要的资源。 设备码流程 (Device Code Flow) 设备码流程(Device Code Flow)非常巧妙地解决了那些没有浏览器或输入不方便的设备(比如智能电视、游戏机、命令行工具、树莓派等)如何进行 OAuth 2.0 授权的问题。 整个流程的核心思想是:在输入受限的设备上发起授权,然后在另一个功能齐全的设备(如手机或电脑)上完成授权确认。 sequenceDiagram participant TV as 客户端设备 (智能电视) participant User as 用户 participant AS as 授权服务器 participant Phone as 授权设备 (手机) TV->>AS: 请求设备码和用户码 AS-->>TV: 返回 device_code, user_code 等 TV->>User: 显示 user_code 和验证网址 User->>Phone: 在手机上访问网址 Phone->>AS: 提交 user_code 并登录授权 Note over TV, AS: 设备在后台持续轮询, 直到用户授权成功 AS-->>TV: (轮询成功后) 返回 Access Token TV->>User: 显示登录成功 设备发起授权请求 设备向授权服务器的一个特定端点——设备授权端点(Device Authorization Endpoint)发起POST请求, Base URL:授权服务器的设备授权Endpoint,例如https://xxx.com/api/device_authorization 请求体Content-Type为application/x-www-form-urlencoded,请求体内容如下: client_id:客户端ID scope:资源(权限)范围,例如openid、email、profile等 授权服务器将返回临时凭证 示例: 1 2 3 4 5 6 7 8 { "device_code": "GmRhmhtR4Q1d74VklbV_g9s-265aKqrC", "user_code": "WDJB-MJHT", "verification_uri": "https://example.com/activate", "verification_uri_complete": "https://example.com/activate?user_code=WDJB-MJHT", "expires_in": 1800, "interval": 5 } device_code:设备代码,是保存在设备上的代码 verification_uri:显示给最终用户以输入代码的 URL verification_uri_complete:与上述相同的 URL,不同之处在于代码已被预填充,用户无需再输入代码 user_code:最终用户输入的原始代码 expires_in:此令牌将在多少秒后过期 interval:设备应多久检查一次令牌状态的时间间隔(以秒为单位) 设备端显示信息并开始轮询 设备开始向用户展示verification_uri与user_code并引导用户访问该网址,填写代码 感觉和HMCL的登录MC正版账户流程很像啊 然后设备开始轮询,按照建议的interval,持续向授权服务器的令牌Endpoint发起POST请求。 请求体Content-Type同样为application/x-www-form-urlencoded,请求体内容如下: grant_type:urn:ietf:params:oauth:grant-type:device_code(设备码流程专用) device_code:上文的device_code client_id:客户端ID 需要注意的是,部分授权服务器返回的device_code过于抽象(说的就是你authentik),可能会内嵌部分需要转义的字符,因此返回的device_code内容是已经加了\转义符的,但是该转义符不属于device_code内容,如果设备对于发出请求的内容自带转义将会使\也被提交,导致出现invalid_grant错误! 确认授权 用户访问展示的verification_uri,并填入user_code,在授权页面确认授权 当然也可以直接展示verification_uri_complete 设备获取access_token 在用户同意授权后,下一次轮询将获得授权服务器下发的access_token 设备请求资源 当授权服务器返回access_token后,设备使用access_token向资源服务器请求所需要的资源 资源服务器对access_token进行一系列校验,确认身份后返回客户端所需要的资源。 总结 隐式授权模式和密码凭证模式这两个不安全的模式就不写了。 授权模式适用场景安全性推荐度 授权码 + PKCE所有类型的应用,特别是公共客户端(SPA、移动应用)和机密客户端(Web应用)非常高强烈推荐 授权码模式有后端的 Web 应用(机密客户端)高推荐 客户端凭证模式机器对机器(M2M)通信,无用户参与高推荐 设备码流程无浏览器或输入受限的设备中高特定场景推荐 隐式授权模式已废弃低不推荐 密码凭证模式已废弃,仅用于高度信任的遗留系统非常低不推荐 后记 暑假意外的高产😁

2025/7/15
articleCard.readMore

基于OIDC实现Authentik与阿里云RAM角色的联合身份认证

前言 OIDC的教程来了😁 在阿里云,OIDC一般不用于登录WEB控制台,而是用于程序化访问,例如CLI、SDK或自定义应用程序,工作流程完全由API驱动,不过可以通过构造免登录URL来进行登录。 OIDC 复杂很多,这里仅按照最基础的能完成授权的流程写,具体项目请根据具体实际情况设置。 部分Authentik操作这里不详细写了,可以去看看之前写的阿里云角色SSO对接authentik进行单点登录 创建 Authentik Provider 在Authentik的控制台侧边栏Applications->Providers中创建provider,类型选OAuth2/OpenID Provider 名称:自填 授权流程:自选或者自定义 客户端 ID和客户端 Secret自动生成,记不记无所谓,后面也看得到 重定向 URI/Origin(正则):这里因为并不需要使用到重定向,填个http://localhost就行 签名密钥:这里用Authentik默认的或者自己生成都行,生成操作看之前的文章 在高级协议设置里 作用域:同样是先不用管,待会再回来映射 然后点击完成创建。 创建 Authentik Application 下方有一处勘误,已修改 其实OIDC创不创建都无所谓,没啥用,不是授权入口,只是创建一个provider不会显示警告而已 这里可以通过给application授权指定组来实现权限控制(当然你也可以自己写策略),非该组内用户访问会出现Request has been denied. 如果不做权限控制,任何人都可以通过构造认证链接发起请求 在同级菜单下的Applications中点击创建,名称、slug随便取,用不到 提供程序选择刚刚创建的provider即可 创建身份提供商 打开阿里云控制台,进入RAM 访问控制 找到集成管理->SSO管理 选择角色SSO,OIDC 身份提供商名称:顾名思义,按照下面灰色字要求填就行 然后回到Authentik,点击创建的provider,可以在右边看到一系列URL以及左边的客户端 ID 这里我们需要客户端 ID以及OpenID 配置颁发者的值 回到阿里云,颁发者 URL填写OpenID 配置颁发者的值,客户端 ID就填客户端 ID的值 填完后会有个获取指纹按钮,这个获取的是你OIDC Endpoint的SSL证书的CA的指纹,正式环境请仔细对比 完成创建即可 创建角色 和前面那篇文章一样,想要扮演什么角色就创建什么角色,角色创建这里不重复写了,详情参考前面的文章,只是在身份提供商类型选项那里从SAML换成OIDC即可 然后选择刚刚创建的身份提供商 完成创建后会自动生成好信任策略,当然如果你有自定义的验证需求,可以自定义,这里就采用默认 添加映射 阿里云要求Id Token中包含aud和iss aud:客户端ID的值 iss:颁发者 URL(OpenID 配置颁发者)的值 来到Authentik的Customization->Property Mappings,创建两个map用来映射这俩值,类型都选择Scope Mapping 名称:自填 作用域名称:aud或者iss 表达式:python语言,我这里为了简单就直接返回了这俩值,自己可以根据官方文档表达式章节自定义怎么返回 1 2 #xxx为返回的值 return "xxxxxxxxxxx" 然后回到provider,编辑一下,在作用域中将自定义的两个map添加进去,默认的openid和email什么的可以留着 小插曲:大致流程 从Authentik获取Id Token,用Id Token在阿里云拿到STS Token,然后用STS Token换SigninToken,用SigninToken进行登录 获取Id Token 这里就是标准的OAuth2流程,本教程采用Authorization code授权流程,其他流程请自行探索 首先构造授权请求页面URL 在provider中可以看到授权 URL 这个就是Base Url 给Base Url拼接下面几个参数 下方有一处勘误,已修改 response_type:固定值code(为Authorization code授权流程的固定值) client_id:顾名思义,客户端ID redirect_uri:和创建provider时填的保持一致,上文填的是http://localhost这里就拼这个值 state:~~防重放攻击的,可选,~~防止跨站请求伪造攻击,自己去查阅相关资料 拼接完成后访问该url即进入授权页面,完成登录后页面将重定向到redirect_uri所填的值 这里就是http://localhost,后面会接code和state,这里我们需要使用code来换取Id Token 这里就需要使用POST请求,请求地址在provider中可以看到,名叫令牌 URL 然后向该地址POST如下参数 Tips:Content-Type 必须是 application/x-www-form-urlencoded grant_type:该流程中为authorization_code code:上面的code值 redirect_uri:同理,之前填的redirect_uri值 client_id:客户端ID client_secret:客户端 Secret(编辑一下provider就看得到了) 发起POST请求后,会返回一个json,包含access_token,和id_token 正常情况下我们会需要使用access_token,而id_token是给本地客户端用的,但是这里阿里云正是需要鉴别用户身份,因此需要使用Id Token而不是access_token 获取STS Token 这里暂时没找到阿里云的Endpoint,貌似只用用阿里云的SDK请求? 文档->AssumeRoleWithOIDC - OIDC角色SSO时获取扮演角色的临时身份凭证 文档里面有个调试按钮,可以方便进行请求 OIDCProviderArn和RoleArn文档也写了在哪看,找不到也可以去看看之前的那篇文章 OIDCToken就是上文的Id Token RoleSessionName用来让阿里云记录是谁在访问,用于日志审计,可以填用户邮箱之类的 然后就是发起调用 返回示例阿里云文档里有,这里就不展示了 获取SigninToken 这里同样是构造URL Base Url为:https://signin.aliyun.com/federation 拼接下面参数: Action:固定值GetSigninToken AccessKeyId、AccessKeySecret、SecurityToken:上文返回的json里有 发起Get请求后就会返回一个包含SigninToken的json 拼接免登录URL Base URL同样是https://signin.aliyun.com/federation 拼接下面参数: Action:固定值Login LoginUrl:登录失效跳转的地址 Destination:实际要访问的目标页面 SigninToken:上文json中 Tips:LoginUrl、Destination 和 SigninToken 都需要使用 encodeURL 进行编码处理 访问该拼接的URL即可进入Web控制台 后记 一般获取到STS Token后就可以对阿里云资源进行操作了,只是说要登录的话需要多一步获取SigninToken 终于是写完了,累死我了。。。

2025/7/14
articleCard.readMore

GitHub release以及Git Commit常用模板

前言 最近发现Lobe Chat的Commit和Release内容都很美观,找Gemini要了个模板过来。 Git Commit 模板 结构: 1 2 3 4 5 <emoji> <type>(<scope>): <subject> <BLANK LINE> <body> <BLANK LINE> <footer> 常用Emoji与类型: Emoji类型(type)描述示例 ✨feat新增功能 (A new feature)✨ feat(api): 新增用户注册接口 🐛fix修复 Bug (A bug fix)🐛 fix(payment): 修复微信支付回调失败的问题 📝docs仅修改文档 (Documentation only changes)📝 docs(readme): 更新项目介绍和安装指南 💄style代码格式调整,不影响代码逻辑 (A code style update)💄 style(lint): 调整 ESLint 规则并格式化所有 .js 文件 ♻️refactor代码重构,既不是修复 bug 也不是新增功能♻️ refactor(user): 重构用户认证模块,提升性能 ⚡️perf提升性能的改动 (A code change that improves performance)⚡️ perf(image): 优化图片加载速度,采用懒加载 ✅test增加或修改测试 (Adding missing tests or correcting existing tests)✅ test(utils):为日期格式化函数增加单元测试 🔨build影响构建系统或外部依赖的更改🔨 build(deps): 升级 webpack 到 v5 ⚙️ci更改 CI 配置文件和脚本⚙️ ci(github): 优化 GitHub Actions 的构建流程 🧹chore其他不修改 src 或 test 文件的更改🧹 chore(release): 准备 v1.2.0 版本发布 ⏪️revert回滚一个之前的 commit⏪️ revert(feat): 回滚上次的用户权限更新 🚀deploy部署相关🚀 deploy(prod): 部署 v2.0.0 到生产环境 🚧wip工作进行中 (Work in progress)🚧 wip(search): 搜索功能初步实现,请勿合并 🎉release发布新版本🎉 release: v3.0.0 ⬆️upgrade升级依赖 (Upgrade dependencies)⬆️ upgrade(react): 升级 React 到 v18 ⬇️downgrade降级依赖 (Downgrade dependencies)⬇️ downgrade(lodash): 降级 lodash 解决兼容性问题 示例 一个简单的修复: 1 🐛 fix(header): 修复移动端视图下导航栏溢出的问题 一个包含详细描述的新功能: 1 2 3 4 5 6 ✨ feat(profile): 新增用户个人资料编辑页面 - 用户现在可以上传头像、修改昵称和个人简介。 - 增加了输入验证,确保数据的有效性。 Closes #123 Release 模板 结构: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 # 🎉 Release v[版本号] - [发布日期] [这里可以写一段简短的发布介绍,比如 "我们很高兴地宣布 XX 版本的发布!本次更新带来了期待已久的新功能并修复了若干重要问题。"] ## ✨ 新功能 (Features) - 新增用户注册功能,现在可以通过邮箱或手机号注册 (#101) - 增加了全新的仪表盘页面,提供数据可视化图表 (#105) - 支持通过 OAuth2 与 Google 账户集成登录 (#112) ## 🐛 Bug 修复 (Bug Fixes) - 修复了在 Safari 浏览器中日期选择器无法弹出的问题 (#98) - 解决了特定场景下可能导致内存泄漏的 Bug (#102) - 修正了 API 文档中一个错误的参数示例 (#108) ## ♻️ 重构与优化 (Refactoring & Performance) - 重构了支付模块,提升了代码可维护性 (#95) - 优化了首页的加载性能,首次渲染时间减少 30% (#100) - 升级了数据库查询逻辑,响应速度更快 (#110) ## 📝 文档 (Documentation) - 更新了 `README.md` 文件,包含了最新的安装和使用指南 (#97) - 增加了贡献指南 `CONTRIBUTING.md` (#99) ## 🔨 构建与依赖 (Build & Dependencies) - 将项目依赖的 Node.js 版本升级至 v20 (#115) - 引入了 `Vite` 作为新的构建工具,大幅提升开发服务器启动速度 (#120) --- ### ❤️ 贡献者 (Contributors) 感谢以下所有为这个版本做出贡献的开发者! [@username1](https://github.com/username1) [@username2](https://github.com/username2) ... ---

2025/7/13
articleCard.readMore

实验室生活

前言 今年暑假不想回去,一是一开始有个市政府实习(虽然最后被鸽了),二是路费太贵,坐动车即使是学生票来回一次也要300多(绿皮太折磨人了,不坐),三是回家其实也不知道干什么,高中的同学基本上只有几个有联系了,四是听我一个实验室的室友留校就在原寝室,不用搬寝,所以这个暑假打算留校。 申请 原则上是学校是不允许留校的,但是实验室的同学因为经常有比赛,所以可以申请留校 借着这个机会,我也申请留校(doge 变故 不知道学校哪根筋抽了,今年暑假留校要搬寝室。。。 我又不习惯和不认识的人住一起(虽然后来是自己拉人组寝室) 最主要的是,东西实在是懒得搬,特别是湖南这几天的天气,要是搬个被子怕不是要热死,而且开学还要换回来 有几个留校的同学已经跑路回家了😂 想了一下,还是七月底再跑路吧 ,毕竟刚开始留校就跑路还是不怎么好(doge 安排 睡是不可能一起睡的,我就把衣服裤子和洗漱用品打包扔到了新寝室,日常用的电子设备和要看的书扔到了实验室。 刚好有个研究生学长暑假要回去了,他的那台显示器借给我用,我就不用搬显示器了 平常白天蹭实验室空调(寝室开空调还蛮贵的),在实验室学习,晚上在新寝室洗个澡,然后回实验室睡觉 第二天早上回新寝室洗脸刷牙一下,再润回实验室。 实验室有个不知道是谁的折叠床,虽然睡起来没有寝室的床舒服,但也勉强能过日子。 以前听闻ACM组有同学在实验室起来就写算法,写累了就睡,睡完起来继续写,一天起居都在实验室,现在自己也是过上差不多的这种日子了😂(不过没这么抽象罢了) 后记 还是在家里舒服(泪目

2025/7/13
articleCard.readMore

阿里云角色SSO对接authentik进行单点登录

前言 好久没写一篇正经的教程文章了,今天来写如何让authentik对接阿里云的角色SSO,实现单点登录指定角色 本质上任何支持SAML的第三方IAM都能完成,只不过大同小异,本教程采用authentik进行示例。 本教程结合Gemini 2.5 Pro+Deep Research完成,截稿时间:2025-07-11 正文 战略价值 下面为Gemini套话,可以跳过不看 将 Authentik 作为身份提供商(IdP)与阿里云进行集成,不仅仅是技术上的对接,更是一项具有深远战略意义的举措,旨在构建一个现代化、安全且高效的身份管理体系。其核心价值主要体现在以下几个方面: 集中式身份管理:将 Authentik 作为唯一的身份“真理之源”(Single Source of Truth),企业可以在一个统一的平台上管理所有用户的身份、凭证及其生命周期 。这意味着无需在阿里云 RAM 中创建和维护大量的独立 IAM 用户及其静态密码或访问密钥,极大地简化了管理复杂性。 增强的安全态势:通过联合登录,用户不再需要长期有效的阿里云访问凭证。取而代之的是基于会话的、有时效性的临时安全令牌(STS Token)。这种模式消除了静态凭证泄露的风险,显著减少了攻击面,是云安全最佳实践的核心要求 。 简化的用户访问流程:用户只需登录一次 Authentik 门户,即可通过单击应用图标无缝跳转至阿里云管理控制台,无需再次输入密码 。这不仅提升了用户体验,也提高了开发和运维人员的工作效率。 统一的审计与合规:所有认证请求都将集中在 Authentik 进行记录和处理。这为安全审计提供了单一、全面的日志来源,有助于企业满足各类严格的合规性要求,如 SOX、PCI-DSS 等 。 登录流程 虽然也是套话,但是建议了解一下 完整的 IdP 发起流程如下: 用户发起登录(IdP-Initiated):用户首先登录到其个人 Authentik 应用仪表板,然后点击代表阿里云控制台的应用程序图标 。 生成 SAML 断言:Authentik 接收到点击事件后,会生成一个经过数字签名的 SAML 断言(SAML Assertion)。这是一个 XML 格式的文档,其中包含了用户的身份信息(如用户名或邮箱)以及一系列特殊的属性,这些属性是阿里云用来确定用户应扮演何种角色的关键 。 浏览器重定向与 POST 请求:Authentik 将包含 SAML 断言的表单返回给用户的浏览器,并通过 JavaScript 自动提交此表单。浏览器向阿里云的断言消费服务(Assertion Consumer Service, ACS)端点发送一个 HTTP POST 请求,请求体中即为 SAML 断言 。 验证断言:阿里云的 SSO 服务接收到该 POST 请求后,首先会验证 SAML 断言的数字签名。它会使用预先配置好的、来自 Authentik 的 IdP 元数据中的公钥进行验签,以确保断言的完整性和来源可信性 。 解析属性:验签成功后,SSO 服务会解析 SAML 断言的内容,重点提取两个自定义的关键属性:Role 和 RoleSessionName 。 交换 STS 令牌:SSO 服务根据 Role 属性中指定的 RAM 角色 ARN 和 IdP ARN,调用阿里云的安全令牌服务(Security Token Service, STS),代表该用户请求扮演指定的 RAM 角色 。 创建控制台会话:STS 验证请求合法性后,会颁发一个包含临时访问凭证(AccessKey ID, SecretAccessKey, 和 SecurityToken)的 STS 令牌。阿里云 SSO 服务利用这个 STS 令牌生成一个预签名的、具有时效性的控制台登录 URL 。 重定向至控制台:最后,SSO 服务将此预签名 URL 返回给浏览器,浏览器自动跳转。用户最终无缝登录到阿里云管理控制台,并拥有了所扮演角色定义的全部权限。 创建签名证书 首先创建一个证书用于给SAML断言签名(当然你也可以用Authentik默认的证书,如果打算使用默认证书就可以跳过这节) 在侧边栏找到System->Certificates,点击生成按钮 常用名自己填就行,关键是私钥算法一定要选择RSA,千万不能选择ECDSA(大坑),因为阿里云不支持ECDSA签名验证,选择ECDSA会造成NoPermission.NotTrusted 创建完成进入下一节 创建Provider 这里我们先创建Provider 在侧边栏找到Application->Providers 点击创建按钮,选择SAML Provider类型 名称:自填 授权流程:默认的两个哪个都行,explicit的需要手动确认授权,implicit为静默授权 ACS URL:为阿里云固定链接 https://signin.aliyun.com/saml-role/sso(国际站可能不同)来源:进行角色SSO时企业IdP的SAML配置_访问控制(RAM)-阿里云帮助中心 颁发者( EntityID):为阿里云固定内容:urn:alibaba:cloudcomputing,来源:进行角色SSO时企业IdP的SAML配置_访问控制(RAM)-阿里云帮助中心 服务提供程序绑定:为固定选项POST Audience:为阿里云固定内容:urn:alibaba:cloudcomputing,来源:角色SSO的SAML响应_访问控制(RAM)-阿里云帮助中心 然后展开下面的高级协议设置,签名证书选择上一节创建的RSA证书,勾选签名断言 属性映射待会再设置 创建阿里云身份提供商 来到阿里云的访问控制RAM控制台https://ram.console.aliyun.com/ 在侧边栏找到集成管理->SSO管理 选择角色SSO->SAML,选择创建身份提供商 身份提供商名称:自填,但是后面有参数会用到 元数据文档:在authentik中刚刚创建的Provider的总览里,有个元数据下载按钮,下载该xml文档然后上传即可 创建完成后点击刚刚创建的身份提供商,找到ARN信息,复制备用,下文用Idp ARN代替该值 创建阿里云角色 既然要扮演角色肯定要先创建角色 在侧边栏找到身份管理->角色 点击创建角色,信任主体类型选择身份提供商 提示如果需要信任身份提供商,切换编辑器到自定义模式后配置。,点击旁边的切换编辑器 这里我们采用可视化编辑即可 效果:选择允许 主体:选择身份提供商 操作:勾选sts:AssumeRole(旁边的SetSourceIdentity不知道有什么用) 条件:用于实现更复杂的限制策略,可以自己设置(我没设置) 点击确定添加即可 然后赋予角色权限 点击刚刚添加的角色,在权限管理内新增授权,具体要什么权限自己定,然后记录ARN,后面备用,下文用角色 ARN代替 Tips:这里信任策略阿里云已经正常生成好了,不用自己填写或者修改了 创建Authentik的属性映射 这里我们需要映射Role和RoleSessionName两个属性到SAML断言中 Role:让阿里云知道是扮演哪个角色以及来自哪个身份提供商 RoleSessionName:让阿里云知道是哪个用户(IAM的用户)进行了本次扮演(登录),用来作为登录用户信息的一部分显示在控制台上和操作审计日志 在Authentik侧边栏中找到Customization->Property Mappings 选择创建,两个都是SAML Provider Property Mapping类型的 先创建Role 名称:自填 SAML 属性名称:为阿里云固定内容:https://www.aliyun.com/SAML-Role/Attributes/Role 表达式:python写 表达式这里返回的内容会出现在断言中,而该部分内容组如下: 1 角色ARN,IdpARN 简单的表达式直接返回这个内容即可,当然你可以写更复杂的表达式,具体请参考Authentik官方文档中表达式一节 1 return "角色 ARN,Idp ARN" 然后创建RoleSessionName 名称:自填 SAML 属性名称:为阿里云固定内容:https://www.aliyun.com/SAML-Role/Attributes/RoleSessionName 表达式:python写 这里需要返回一个唯一标识用户的值,如员工ID、Email地址等,取值要求为:长度不少于2个字符且不超过64个字符,只能是英文字母、数字和特殊字符-_.@= 我采用的是用户的Email 1 return request.user.email 这里变量由Authentik提供,其他变量请看Authentik官方文档 还有个可选的SessionDuration,表示会话有效期(我懒得弄没弄) 名称:自填 SAML 属性名称:为阿里云固定内容:https://www.aliyun.com/SAML-Role/Attributes/SessionDuration 表达式:python写,同理直接return或者自己写复杂的表达式 取值要求为整数,单位为秒,最小值为900,最大值不能超过Role元素所代表的角色的最大会话时间。 有兴趣可以看看角色SSO的SAML响应_访问控制(RAM)-阿里云帮助中心 进行属性映射 回到Authentik中,找到我们之前创建的Provider,点一下进入总览,编辑一下 在高级协议设置中,找到属性映射,把预置的没用属性全部移除,把刚刚创建的两个属性移到右边,保存 然后重新下载元数据,在阿里云SSO管理里更新一下元数据文档(存疑,建议还是更新一下) 创建Application(应用) 在Authentik创建一个应用 名称:自填 Slug:自填 提供程序:选择我们之前创建的Provider 剩下的看自己喜好 权限设置 默认创建的应用是所有用户都能访问的,如果不做权限控制,那么所有用户都能进你的阿里云😂 这里我们点击创建的应用,选择策略/组/用户绑定 然后点击绑定已存在的 策略/组/用户(这里推荐先创建一个组,然后把能够使用该应用的用户拉到这个组里,然后赋予这个组使用这个应用的权限,方便管理) 看自己选择是策略,组还是用户 策略的话要自己写策略,灵活性更高 Tips:默认注册流程中注册的用户是外部用户,无法进入Authentik仪表盘,可以去改一下注册流程 测试 进入Authentik主界面,点击应用,跳转到阿里云,右上角看到角色登录,即成功登录。 后记 折腾死我了,不过,Gemini 2.5Pro+Deep Research是真的好用😋

2025/7/11
articleCard.readMore

白嫖一年Gemini Pro

前言 Google先前发起~~大学生白嫖计划~~ Google One AI Pro 教育学生优惠,面向巴西、印度尼西亚、日本、英国和美国大学生,时间截止于2025年6月30日 链接:https://gemini.google/students/ 白嫖 但是~~~经过很多人测试,国内教育邮箱也能通过,而且不限制卡bin(国内万事达/VISA卡也行) 不过之前由于账号地区问题,一直卡在港区没转到日区,导致出现此账号无法订阅 Google AI Pro 然而今天晚上刷小黑盒突然又看到这个贴子(冥冥之中有感应?),打算再试一下 诶?地区转过来了??? 然后就是一套验证邮箱流程,成功白嫖😄 可以说是最后一天还剩三个小时的时候赶上车了

2025/6/30
articleCard.readMore

突然发现自己可能乳糖不耐受?

正文 今天早上起来去考试,怕考试途中饿于是吃了点酱香饼和一杯豆浆垫垫肚子。 十一点考试完回到寝室,去寝室楼下买了一升纯牛奶,然后边打游戏边喝,没一会一升牛奶就被我喝完。 然后就悲剧了,肚子一直咕噜咕噜叫,去了趟厕所,刚蹲下就拉。。。还是纯水的那种,大便没多少,全是水😂 之前也出现过这种情况,以为是空腹喝牛奶的问题,毕竟之前空腹喝牛奶也是这样拉肚子,但是要是先吃了饭再喝就没问题 而且小时候喝牛奶也没出现这类情况 后面想了一下,可能是轻微乳糖不耐受(?) 吃了饭牛奶在胃里存留的时间比较长,消化的好一点,空腹的情况下可能一下就进肠道了 以前喝牛奶我一般一次都只喝200ml-500ml,上了大学后可支配资金多了,喜欢一次喝一升(逃) 哎,终究是没逃过乳糖不耐受,之前看网上说国内乳糖不耐受的人很多,看来自己也是其中之一了😂

2025/6/30
articleCard.readMore

Android第三方Passkey管理器探索

前言 相信经常在国际互联网冲浪的友友对Passkey一定不陌生,Google,GitHub等网站均支持Passkey一键登录,国内手机厂商也有推行Passkey的趋势(?) 众所周知,国内很多手机厂商在以前都是管生不管养。基本上手机发售一年以后就别想着升级了,现在各大手机厂商稍微好一些。 然而,虽然系统是更新了,但是部分功能只有在新机型上才做适配,而本文的主角Passkey就是其中之一。 博主手中的OPPO Find X5虽然已经更新到ColorOS 15,但是Passkey功能仍然无法使用,仅在OPPO Find X6及其以上的机型才支持。 虽然可以通过插入Canokey等物理安全硬件来实现Passkey登录,但是我日常外出一般是不带这些安全硬件的(怕丢)。 并且我之前也吐槽过,既然通过Google的GMS都可以让我手机使用Passkey,为啥OPPO不支持。。。 探索过程 偶然间,看到一篇文章,说在Android 14中引入了Credential Manager API,该API可以用于集成密码管理工具,让密码管理工具来管理凭据,其中就提到了Passkey https://developer.android.com/identity/sign-in/credential-provider?hl=zh-cn 在此之前Android的默认Passkey实现是在Google的GMS中,并且也是对接的Google的凭据管理器 当时看到这篇文章时博主手机系统还是ColorOS 14,系统为Android 14,但是在网上下载了一个测试Demo软件,发现并不支持Credential Manager API,应该是厂商还没适配好 毕竟当时我也吐槽过,OPPO自家的Passkey实现与凭据管理器只在系统自带的浏览器上能调用,在Edge和Chrome中均调用的Google的Passkey实现与凭据管理器。 但是Google的Passkey实现会将Passkey上传到云端,并不是保存在手机本地,因此对其安全性表示怀疑(虽然声称是使用屏锁进行端到端加密) 没办法,可以说手机的Passkey功能基本用不了(包括一些Fido的2FA),如果实在要用Passkey,只能临时拿Canokey捅手机“屁股”或者用NFC(Canokey Canary已解决NFC感应问题),调用Google的Passkey实现来通过认证。 Tips:这个实现可以调用不同的凭据来源(保存密码的实现),Google的Passkey实现可以通过USB和NFC读取我手中的Canokey硬件,而OPPO的Passkey实现NFC读不了,USB处于半残状态(现在已经是完全残废的情况了) 这个方案一直在用,直到ColorOS 15最近的一次更新,我发现现在无论是OPPO自家的浏览器,还是Edge,Chrome甚至是Firefox,都会调用OPPO自家的Passkey实现 这就严重影响我的使用体验,因为OPPO自家的Passkey实现只会调用自家的凭据管理器(密码本),而且对于外置硬件也是半残状态(Google的至少我还能用Canokey硬件) 但令我没想到的是,我在手机设置里找到了一个新页面“登录信息自动填充服务”,这个页面有个“密码管理服务”,包含“密码本”(OPPO 用于保存密码的软件,包含存储Passkey),“Authenticator”(微软的),“Google” 然后默认是打开了密码本的,Google的没有打开。 我想,这会不会就是问题所在,于是我把密码本的关了,把Google的打开,发现Edge可以用Google的Passkey实现了 看到剩下微软的那个authenticator,突然想到会不会更新后现在已经支持使用第三方凭据管理器了? Tips:微软的authenticator已经宣布即将停止对密码管理功能的支持 于是下了个Bitwarden,发现确实新增了该选项 众所又周知,Bitwarden是可以自托管的,而且还有vaultwarden这种第三方项目来降低托管压力,所以临时先在本地服务器上建了个,尝试了一下,发现可以完美使用Passkey的 现状 原先情况: Edge,Chrome->Android Passkey接口-> Google Passkey实现(默认)->Google密码管理器/外置硬件 OPPO浏览器->OPPO Passkey实现(第三方)->密码本(不支持)/外置硬件(残废) 现在: Edge,Chrome-> Android Passkey接口->OPPO Passkey实现(默认)->Credential Manager API(在设置中选择)->密码本/Google/Bitwarden/外置硬件(残废) Tips:OPPO浏览器仍然不支持Credential Manager。另外如果仅开启Google,则会调用到Google Passkey的实现 并且由于调用的是OPPO的Passkey接口,现在还支持跨设备使用(手机扫描电脑上的Fido二维码在电脑上进行登录或注册) 因为该功能需要使用服务器,所以原先Google的Passkey就用不了(可能因为安全设计原因,挂了代理也会在最后一步失败) 后记 主要是Bitwarden在断网情况下无法新增,读取Passkey好像也有点问题?感觉这点比keepass就差点 不过这也主要看客户端实现咯,像PC上的KeepassXC就不支持在无网情况下使用已连接过的远程的密码库,而在Android上的客户端Keepass2Android就支持无网使用已连接过的远程密码库 虽然现在Keepass本身已经支持保存Passkey,但是Keepass2Android目前还不支持使用Credential Manager API来保存(悲) 目前打算先用着Bitwarden,希望Keepass2Android作者能尽快更新这个功能吧 另外由于选择Bitwarden则会调用OPPO Passkey的实现,所以我的Canokey完全用不了,只能在要使用的时候再切换成Google的了(悲)

2025/6/28
articleCard.readMore

多线程我太爱你了,你个“大可爱”

又是大早上被多线程折磨的一天 先简单讲一下功能框架吧 flowchart TD A(用户发起查询操作) --> B{查询cache} B -- 有 --> C[返回cache内容给用户] B -- 没有 --> D[请求remote] D --> E[remote返回内容] E --> F[内容返回给用户] E --> G[remote内容写入cache] 简单来说就是从两个数据源获取信息,先查cache,cache没有再查remote,remote得到数据后返回给用户并写入cache 然后就出问题了,具体问题表现为出现重复插入情况 经过排查,发现remote请求返回的数据正常,但是到写入Room数据库的时候出现插入数量正确,但是部分item重复插入,部分item丢失的情况 问了一下ChatGPT,在给出的几个可能性中锁定到多线程并发写入导致的竞态和异常。 不过虽然该保存数据的函数确实是suspend的异步函数,但是我在函数入口明明打了断点,断点只触发一次,不存在多线程的问题 把代码贴上来问了问ChatGPT,结果给我整绷不住了: 不是,也没人给我说呀,怎么kotlin的foreach和for还有这大坑等着我的。。。 然而把foreach改成for后还是一样的问题,继续追问 在众多的可能性中,锁定协程作用域问题 指出确保你的suspend fun saveServiceList()不是在多个协程并发调用!否则即使方法内部串行,外部还是会并发插入,导致竞态。 但是之前说过,我打的断点明明只触发一次,怎么会出现外部多次调用呢? 没办法,按照ChatGPT说的老老实实打Log看输出,在dao的insert方法前后插Log,发现确实是出现竟态问题。 然后ChatGPT再次指出确保整个suspend方法不在多个协程并发调用,提出用Mutex锁包裹整个suspend方法 结果发现居然没问题了??? 所以还真是被多个协程调用了???我怎么不知道??? 虽然用锁临时解决了该问题,但这个操作本质是不应该被多个协程调用的,说明代码本身有问题 本着不想写屎山的心态,继续问 然后ChatGPT指出: 所以断点在并发协程中并不适用来判断调用次数 我:多线程你太可爱了 给suspend方法第一行插个Log,发现确实日志打印了两遍 然后就是顺藤摸瓜,给所有调用点全部插Log,最后发现在ViewModel中的起始loadService函数被调用了两次 定位到View中发现在UI里有两处调用,一看代码就发现问题了。 一个来自ViewModel中的loadServiceState在View中监听,当State由Idle变为Loading后触发loadService,加载数据 然后一个手动刷新按钮,首先清除数据,然后更改loadServiceState的State为Loading,最后手动调用loadService 因此当手动刷新时,就会出现loadServiceState改变而触发的loadService以及按钮自身调用的loadService。。。 代码一多自己都忘记原来写的逻辑了,又是被多线程折腾的一天

2025/6/26
articleCard.readMore

[公告]现评论区已支持使用emoji表情符号😊

正文 现博客评论区已支持使用emoji表情符号😊 因历史原因,服务器数据库字符集采用utf8mb3编码,最大支持3字节字符,导致不支持4字节的emoji,以及大部分Unicode字符集。 为跟上时代发展(?),启动数据库更新计划。 目前已将数据库字符集由原来的utf8mb3调整为utf8mb4,排序规则由utf8mb3_general_ci调整为utf8mb4_unicode_ci。

2025/6/23
articleCard.readMore

Debian GRUB踩坑记——out of memory

起因 偶然在网上刷到一个很好看的grub引导界面,才发现原来grub也能设置主题,而且这种设置只是更改grub配置文件,不操作引导程序的二进制文件,因而不会影响Secure Boot 于是找了一个超好看的主题给grub安排上 结果就是: Error: out of memory Press any key to continue… 然后进不去系统了。。。 折腾 解决方案可以直接去下一节 首先出的问题是选择Debian引导后加载initramfs时提示内存耗尽 然后按下任意键提示:Kernel Panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) 因为我启用了luks全盘加密,所以这种情况第一反应是引导找不到加密分区了 问了一下ChatGPT,给出的方案是GRUB配置错误或者initramfs损坏 但是在此之前我都能正常启动系统,而且我也只是对GRUB进行了更改,没有动initramfs,所以排除initramfs损坏 然后我给ChatGPT拍了一下GRUB中Debian引导的引导脚本,ChatGPT说是initramfs中没有包含LVM支持,但同样的,我根本没动initramfs,而且之前也能正常开机 给ChatGPT说了一下,并附上了引导脚本,然后给出了修改引导脚本的方法,这里就开始鬼畜起来了 让我给内核文件路径和initramfs文件路径加上/boot前缀,说如果/boot是独立分区,不加前缀是找不到内核和initramfs的(开始胡言乱语起来了) 按照它说的,加了前缀之后引导直接找不到内核文件了。。。 给ChatGPT反映了一下该情况,ChatGPT马上改口,说不要加/boot前缀。。。 自己又怀疑了一下是不是存储介质的问题,毕竟是U盘,插到电脑上当作系统盘温度超高(把我的USB Hub连带着一起烫),没测过温度,反正手放在上面超过半秒就烫得受不了 引导到windows下用dg扫了一下,除了几十个严重,其他大多数都是良好,这种结果的话那就没什么参考价值了 没办法,只能拿另一个U盘装个Live CD来抢救 然后就是不知道自己是那根筋没对,把Live CD刻在DVD上折磨自己😅 好不容易把iso刻录完成,启动系统启动了十多分钟。。。 启动完成后解锁分区,挂载分区,update了一下grub(以为是grub损坏了) reboot,很好,问题依旧 不想再浪费时间启动DVD了,重新拿个U盘把Live CD刻了上去。。。 继续深问,然后说最稳妥的启动配置是不加前缀的那个,那不就和原来一样的了,变成最开始的问题😂 然后ChatGPT继续复盘,又说是initrd.img缺少luks和lvm模块。。。 我都说了快八百遍了没动initramfs 算了,选择怀疑一下自己,进Live CD挂载好原来的分区,执行update-initramfs -u重新生成一下 不出所料,还是一样的问题。。。 最后想着,看看把grub配置恢复成没安装主题的时候会怎么办(丝毫没怀疑是主题的问题) 注释掉主题配置行,执行update-grub 诶👆🙄,引导成功,进入系统 虽然成功进入系统,但是还是没搞懂原因,于是把解决方案给ChatGPT说了一下,又开始了胡言乱语。。。 说是主题会替换grub.cfg模板,阻断加密检测的执行云云 但我实际对比了一下启用主题和没启用主题下生成的grub.cfg的区别,就单纯多了一个加载主题资源的代码而已 而且整个主题都是我手动cp装的,根本没执行主题自带的安装脚本 后面继续胡言乱语,说要加入DRUB_ENABLE_CRYPTODISK=y来解锁Boot分区,但是我tm根本没加密boot分区啊啊啊。。。 启用luks全盘加密下根本不能加密boot分区,不然谁给你解密分区。。。 然后我给它“善意”的提醒了一下,马上又改口不用加。。。 还是得靠自己,控制变量法下找了另一个评级最高的主题试了一下,发现居然可以成功引导 间接证明ChatGPT前面一大堆都是废话。。。 突然灵光一现想着会不会有什么内存限制? 出问题的那个主题有13MB大小,没问题的那个主题只有0.7MB大小,并且最开始也是out of memory,或许根本不是什么找不到加密分区,一开始方向就错了!!! 问了一下ChatGPT,发现确实是有内存限制,不过不同设备限制不同 以防万一,去AI Studio顺便问了问Gemini,也是肯定有相关限制,但是具体限制多少以及怎么解除限制说不出来 Gemini留的链接: https://www.ibm.com/support/pages/system-boot-ends-grub-out-memory-oom https://forum.endeavouros.com/t/grub-out-of-memory/30967/38 https://www.kubuntuforums.net/forum/currently-supported-releases/kubuntu-22-04-jammy-jellyfish/post-installation-ax/666407-fatal-out-of-memory-error-on-boot-grub-efi-problem 梳理了一下,得到了最终的结果 结果 由于使用了较大的主题,导致GRUB out of memory,然后由于out of memory,导致luks模块和lvm模块没加载进内存,因此无法解密加密分区,出现Kernel Panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) 当主题换成较小的主题时未触发out of memory,正常加载模块,解密加密分区,进入系统。 回想起来,设置那个有问题的主题的时候,第一次分辨率配置格式没配置正确,导致引导界面分辨率超低,但也使得没有out of memory,成功引导进入系统,第二次正确配置成2560x1440的2k分辨率后就出现out of memory了(高分辨率特别占memory)。 具体memory大小我也不知道怎么查,可能是128MB?256MB?还是512MB? 不过这个memory会加载系统内核,initrd,还有各种模块,我看网上有些人的initrd文件大小高达100MB+,正常引导不设置主题都会出现out of memory😂

2025/6/19
articleCard.readMore

为Debian KDE更换显示字体为思源黑体

前言 因为感觉默认的note sans字体有点瘦和细,显示屏上看着有点难受,打算换个养眼一点的字体,感觉思源黑体不错,免费商用,不用担心版权问题,而且字体也好看。 下载 思源字体repo:https://github.com/adobe-fonts/source-han-sans 在下载向导页面可以看到多种字体类型:https://github.com/adobe-fonts/source-han-sans/tree/release 因为是用于系统显示,因此这里推荐使用包括了五种语言的字体,不推荐使用语言特定(Language-specific)和区域特定(Region-specific)的字体,避免出现字体不一致的情况。 至于是下载非VF字体还是VF字体可以自己根据实际情况决定 可变字体(Variable Font,VF)相较于传统字体支持无限可调字重(100-900,任意粗细),但目前系统设置里貌似不能调整,只能选择预设的字重 为了方便在设计时使用可变字体(不想装一个非VF一个VF),干脆直接装了VF版 旧版Debian建议选TTF,较新的Debian可以选OTF 带有HW为使用半宽字形表示 ASCII,可以理解为等宽字体(用于写代码等格式要求严格的使用场景) Variable OTF:https://github.com/adobe-fonts/source-han-sans/raw/release/Variable/OTC/SourceHanSans-VF.otf.ttc Variable HW OTF:https://github.com/adobe-fonts/source-han-sans/raw/release/Variable/OTC/SourceHanSansHW-VF.otf.ttc Variable TTF:https://github.com/adobe-fonts/source-han-sans/raw/release/Variable/OTC/SourceHanSans-VF.ttf.ttc Variable HW TTF:https://github.com/adobe-fonts/source-han-sans/raw/release/Variable/OTC/SourceHanSansHW-VF.ttf.ttc 如果是装非VF字体,可以选择super OTC,包含了五种语言和七种字重 Super OTC:https://github.com/adobe-fonts/source-han-sans/releases/download/2.004R/SourceHanSans.ttc.zip 同样的,这只能在较新的系统上使用,较旧的系统只能一个一个装。 Static OTCs:https://github.com/adobe-fonts/source-han-sans/releases/download/2.004R/SourceHanSansOTC.zip 安装 字体下载完成后右键安装字体即可 目前在5.27.5的KDE中,KFontView仍然无法在Wayland下正常使用,请直接右键安装字体。

2025/6/13
articleCard.readMore

Debian补全办公常用字体

前言 linux上WPS教育版用起来还不错,比libreOFFICE好用,但是缺少部分常用字体(楷体,黑体等) 因为版权问题,Debian并没有内置这些字体,需要自行安装,建议仅个人使用,商业使用请先获得版权方授权。 因为我一般只用于课设、团学内部文档、比赛等等,没有商业用途,所以就直接装了 安装 从网上或者windows下(C:\Windows\Fonts\)复制 windows对应的字体表:https://learn.microsoft.com/en-us/typography/fonts/windows_10_font_list 一般常用的有: 楷体(simkai.ttf) 黑体(simhei.ttf) 仿宋(simfang.ttf) 仿宋_G2312 宋体(WPS还是Linux已自带) 方正小标宋简 微软雅黑(msyh.ttc) 方正小标宋简以及仿宋_G2312可以从https://life.scnu.edu.cn/a/20220309/5508.html下载 字体下载完后,和windows安装字体一样,把字体文件移动到/usr/local/share/fonts/windows目录下即可 不推荐移动到/usr/share/fonts目录下,该目录受系统包管理器维护,在系统更新时可能被覆盖 这里为了方便管理,可以创建一个文件夹来集中这些有版权风险的字体(雾 1 sudo mkdir /usr/local/share/fonts/windows 然后将这些字体全部移动到这个文件夹下 1 sudo mv 字体文件路径 /usr/local/share/fonts/windows 然后就可以在wps上看到字体了。 除了中文的字体,还有Arial,Times New Roman等英文字体,这里采用软件包的方式安装 首先启用contrib和non-free软件源 1 sudo apt-add-repository contrib non-free 然后安装字体 1 sudo apt install ttf-mscorefonts-installer 安装完后即可在WPS上看到相关字体

2025/6/11
articleCard.readMore

Debian12 KDE Edge输入法问题

前言 因为部分网页可能Chromium内核渲染更好,而且也打算把两个主流内核都补齐,Chrome因为墙的原因数据同步比较麻烦,于是打算用Edge。 输入法问题 已知在最新版Edge(microsoft-edge-stable_137.0.3296.68-1_amd64)上出现输入法问题 具体表现为fcitx5在英文状态下无法输入任何内容,而在非英文状态下则正常输入。 ubuntu ask上也有人提了相关问题:https://askubuntu.com/questions/1550458/cannot-type-in-ms-edge-and-chrome-with-fcitx5-english-under-x11-fcitx5-chinese 该问题说包含Chrome浏览器,但是经过我测试Chrome浏览器没有出现该情况,我使用的是Wayland,可能是在x11上chrome也表现不佳? 目前在Debian的官方应用商店上也有相关评论: 该问题应该不是个例 解决方案 降级Edge浏览器,经过测试,136.0.3240.92-1版本没有该问题,其他版本未知 历史版本下载地址:https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-stable/ 后记 虽然该问题对中文用户影响较小,但我平时输入英文还是更喜欢切换到英文输入法,用中文输入法输入中文感觉好别扭。 Debian安装deb包需要sudo,因此一般不会像windows上那样偷偷自动更新(微软怕不是对自动更新有什么执念)

2025/6/11
articleCard.readMore

Debian KDE Plasma 5调整外接显示器亮度

KDE Plasma 6.2已支持调整DDC/CI协议的显示器 但是Debian12用的是Plasma 5.27.5,还不支持这个功能(悲 找到了一个好用的小插件,特地来分享记录一下 该插件可以像作为小组件集成到Plasma中,就像windows下的Twinkle Tray一样。 项目地址:https://github.com/davidhi7/ddcci-plasmoid 安装ddcutil ddcutil为实际控制显示器的工具,用于和显示器通信 1 sudo apt install ddcutil 安装完成后检测显示器,看是否能正常识别到显示器 1 sudo ddcutil detect 正常情况下应该能读取到类似下面的信息(这是我的显示器信息) 1 2 3 4 5 6 7 8 9 10 11 Display 1 I2C bus: /dev/i2c-32 DRM connector: card0-DP-1 EDID synopsis: Mfg id: AOC - UNK Model: Q27G2S Product code: 9986 (0x2702) Serial number: xxxxxxxxxxxxx Binary serial number: xxxx (0xxxxxxxxx) Manufacture year: 2024, Week: 8 VCP version: 2.2 如果返回的是下面的信息,说明没有加载内核模块(Debian一般都会出现这种情况) 1 2 No /dev/i2c devices exist. ddcutil requires module i2c-dev. 这里加载一下内核模块就行 1 sudo modprobe i2c-dev 验证是否加载成功 1 ls /dev/i2c-* 正常加载会显示/dev/i2c-0等设备文件 如果正确加载我们将配置写入模块列表确保开机自动加载 1 echo "i2c-dev" | sudo tee /etc/modules-load.d/i2c-dev.conf 然后更新initramfs 1 sudo update-initramfs -u 调整亮度 先尝试查询亮度 1 sudo ddcutil getvcp 10 设置亮度为50%(按自己喜欢来,只是做测试) 1 ddcutil getvcp 10 50 如果发现显示器可以正常响应亮度修改则进入下一步 安装backend 这个是小组件的后端,一个python包,可以通过pip或者pipx安装 pip: 1 pip install --user ddcci-plasmoid-backend pipx: 1 pipx install ddcci-plasmoid-backend 安装小组件 下载小组建https://store.kde.org/p/2015475 下载后解压,然后安装 1 kpackagetool5 --install plasmoid 如果您在上一步中使用了pipx来安装backend,则小部件设置Backend executable command必须设置为~/.local/bin/ddcci_plasmoid_backend(前面不带 python3 或其他任何内容)。 显示小组件 系统托盘的箭头->配置系统托盘->项目->找到显示器亮度 并根据您的偏好设置可见性 如果使用pipx安装的backend,则点击小组件后点击右上角的设置按钮,按照前面的提示后端命令行的内容,最后应用即可。 Enjoy!😘

2025/6/11
articleCard.readMore

Debian开玩Minecraft

前言 虽然装Debian确实是为了外出方便省电,但还是需要劳逸结合的(逃 官方启动器 在官方下载页面https://www.minecraft.net/zh-hans/download找到Debian 版及基于 Debian 的版本,下载deb包然后安装即可。 界面区别和windows版不大 LabyMod 第三方PvP客户端LabyMod也支持Debian,在下载页面https://www.labymod.net/en/download选择other,找到Debian下载deb包安装即可。 界面和windows版区别也不大 HMCL 第三方MC客户端由java编写,同时也适配了Linux系统,在下载页面https://hmcl.huangyuhui.net/download/下载.jar文件 需要注意的是,稳定版貌似提供的是一个sh脚本文件,但是我运行出了问题,下面开发版则是可以直接下载到.jar 另外系统需要先安装java环境才能启动HMCL,不过如果先安装了官方启动器,官方启动器会将环境配置好,因此直接使用命令就能启动 1 java -jar HMCL jar文件的位置 界面和windows版没有区别。 后继 虽然没装独显驱动,但是不打光影用核显跑还是没问题的,180fps跑满

2025/6/10
articleCard.readMore

Debian KDE使用指纹传感器验证身份

安装依赖 Debian默认没有安装指纹依赖包,需要手动安装 1 sudo apt install fprintd libpam-fprintd fprintd库用于让系统支持指纹传感器 libpam-fprintd库用于配置系统身份认证方式 安装指纹传感器驱动 一般传感器厂商会提供deb包或者使用通用驱动 1 sudo apt install deb驱动包文件路径 添加指纹 在设置->用户中,点击用户后会有一栏配置指纹身份认证,点击后即可添加指纹。 当然也可以通过fprintd-enroll命令来添加指纹。 添加身份认证方式 输入下面命令进入pam管理页面 1 sudo pam-auth-update 使用方向键以及空格键勾选Fingerprint authentication,回车完成设置。 修改登陆逻辑(KDE桌面需要) 因为登陆界面认证时会将密码用于解锁系统密码库,指纹认证没有提供密码,导致卡登陆界面(无论指纹或者密码是否正确)。 可能是KDE的SDDM逻辑问题,即使注释掉解锁密码库,在登陆界面指纹验证失败后也会导致卡登陆界面(不会再次调起指纹并且输入正确密码无效),指纹认证成功则不会卡。 整个逻辑就像并没有为多认证方式进行设计,或者根本没有考虑指纹登陆的情况一样(虽然设置界面确实是写了暂不支持使用指纹登陆系统)。 不过系统终端以及锁屏界面均可以通过指纹或密码的方式完成身份验证。 因此这里需要手动取消掉登陆界面的指纹功能来避免卡登陆。 1 sudo nano /etc/pam.d/sddm 找到@include common-auth一行注释掉,该行引用的配置包括了指纹认证的逻辑 因为common-auth被很多pam配置引用,因此我们这里不动它,而是为sddm手动构建一个auth流程。 在被注释的common-auth一行下添加下面内容 1 2 3 auth [success=1 default=ignore] pam_unix.so nullok try_first_pass auth requisite pam_deny.so auth required pam_permit.so 内容其实就是未开启指纹认证的时候common-auth的文件内容。 这样在开机登陆时使用原先的密码进行登陆,在终端和锁屏界面,或者其他需要身份认证的地方都可以使用指纹和密码进行认证。

2025/6/9
articleCard.readMore

浅尝Debian KDE桌面

前言 最近入手了一个128G的闪迪U盘,读速400MB/s,写入实测80MB/s,4k写入3MB/s,刚好了解到KDE桌面还不错,打算把他用来装Debian玩玩。 系统安装 和普通的Debian安装没什么不同,不过这次用的是在线安装的小镜像。 事实证明就应该下完整镜像,莫名其妙补了一堆用不到的软件,花了40多分钟。。。 因为是装在U盘里,并且有可能有移动需求,从数据安全的角度来说我还是倾向于使用全盘加密 虽然之前在另一个U盘里装Debian启用全盘加密后导致性能大跌,但这次U盘性能好一些,应该没有什么大问题(确信 主要是不启用全盘加密真的不敢存放隐私数据(U盘丢失概率比笔记本大多了) 密码设置没有太复杂,没有使用密码库软件生成的密码,这只是为了防丢,不是为了对抗,没必要设置太复杂的密码麻烦自己。 进入桌面 不得不说,linux启动就是比windows快,1s引导,2s进桌面,开机总共3s 进入桌面后,KDE给我的感觉是非常好看,简约但不失优雅。 桌面各种动画都有,最关键的是,比windows那奇怪的动画流畅多了 和GNOME的简约过头相比,KDE可以高度自定义桌面,虽然网上有些人说设置太多导致眼花缭乱,但我个人觉得刚刚好,默认的设置也完全够用 GNOME改个什么设置还要装插件,麻烦的要命。。。 不同的主题,感觉都很好看 软件安装 这个没得说,虽然有discover这类应用商店,但对国内用户不友好,下载慢而且很多软件没有(例如QQ,微信等) 目前一般有两种方式,使用deb包或者appimage包。 deb包需要使用命令安装 1 sudo apt install deb包文件位置 appimage包则双击就能直接用了 我优先使用deb包安装,没有deb包就下appimage包 装一些国产软件有一些小坑,例如qq音乐要在快捷方式参数加上--no-sandbox才能启动,微信要手动安装一个依赖才能启动,感觉QQ反而是支持的最好的了(QQ NT跨平台能力太强了),虽然我讨厌塞浏览器内核这种行为,但在跨平台方面确实效果不错。 浏览器我选择librewolf,安装稍微复杂一点,不过也没有多大困难 其实像这些有deb包的都很好安装,依赖什么的都会自动补全,appimage更是想删就删,像mac那样直接拖进废纸篓(右键删除)就完成卸载了。 最难安装的还是需要自行编译的软件,不过这类软件如果是小白用户一般也很难遇到咯 由于我有智能卡的需求,需要安装opensc来让某些APP(例如vera crypt、thunderbird)支持读取智能卡,官方release只有msi和exe,linux用户需要自己编译(泪目了🥹) 不过好在官方编译文档比较完善,编译也就make一下 软件代替 部分软件在linux上没有,只能找找平替的了 android studio,IDEA之类的倒是有Debian版 但是像affinity,Git Extension之类的就暂时没找到完美的平替 自带的GIMP虽然说功能强大,但是那个界面真的一言难尽,有一种10年左右的UI风格(个人觉得) 兼容性问题 这方面感觉Debian做的已经很不错了,开箱即用,目前没有手动打任何驱动(NVIDIA驱动不打算打),WIFI,蓝牙,显示器,触控板都能正常使用,而且居然还能调节键盘背光亮度,系统上暂时没有出现兼容性问题(Deepin之前登陆页面出现兼容性问题,屏幕发绿,不知道现在解决了没) 目前暂时不打算打NVIDIA驱动,装这个系统本来就是打算用作外出没带充电器时延长笔记本使用时间用的 更多的其实还是软件上的兼容性问题,QQ的桌面通知可以正常显示,但是没有通知铃声,而tg却能正常触发铃声。 另外QQ的截图功能用不了,这个貌似是Wayland的问题,之后再看看怎么解决吧。 QQ音乐的桌面歌词不能置顶显示,以至于开了和没开一样,系统的音乐控制控制不了QQ音乐 ,只能在QQ音乐界面切换音乐。 thunderbird的OpenPGP加密邮件不支持外置智能卡,并且通过opensc读取智能卡可以读取到证书,但是发邮件时就读取不了,虽然内置的KMail支持系统的OpenPGP实现,但是界面没thunderbird好看(doge 续航 实测在打开QQ音乐,librewolf浏览器,vscode,刷新率60Hz,重启机器3次情况下,持续使用2个小时仍有48%的电量 电池健康度为76%,电池初始容量为90Wh。 平常windows 11下3小时刚好用完。 综合下来延长续航效果还是有的。 打印机问题 linux下只要不要我碰驱动一切好说,一旦碰驱动就一言难尽。 实验室的那台打印机用不了,系统自带的驱动虽然已经识别出来了打印机型号,但是打印测试页失败。 之前打算给这台打印机弄个cups结果也是打印失败。 去hp官网下载驱动安装失败,有些包找不到,应该是Debian 12太新了。

2025/6/7
articleCard.readMore

暑假任务安排

正文 今年暑假打算留校不回去了,暂且安排一下暑假的任务免得忘记 Fast Auth可能要暑假才能继续开工,最近没动力写(逃 任务 [ ] Fast Auth开发 [ ] 工大助手开发 [ ] Mojang API重构 [ ] Minecraft Launcher Library重构 [ ] 市政府实习 [ ] 中级软件工程师备考 [ ] 教资备考

2025/6/2
articleCard.readMore

突然发现自己身体还是蛮脆弱的😂

正文 今天和学弟在一个郊区的公园散步,结果在过一个拱桥的时候,往下走那个阶梯的时候一脚踩空,直接摔了个狗吃屎(悲) 两脚的膝盖直接撞在阶梯拐角那,疼死我了。。。 不过当时穿的长裤,看不到伤情,继续和学弟逛了一会。 晚上回去感觉越来越痛,每动一下裤子布料就磨一下那个地方。回到寝室,看了一下,不出意外的青了😢,还有一片被磕出来的红斑,腿肚子上的红斑更长。 普通的一摔就磕成这样,果然人的身体还是不耐造(悲

2025/5/31
articleCard.readMore

完成GitHub学生认证

前言 前几天发现GitHub Pro好像可以免费使用GitHub Copilot,然后学生认证的话可以免费使用GitHub Pro计划,打算认证一下尝尝咸淡。 认证 感觉认证也不麻烦(前提是你是真学生),教育邮箱+学生证照片+允许网站定位就可以了。完全不需要像网上那种搞个学信网学籍证明,还整个英文版的什么的。 我学生证上面的章都模糊的看不清是哪个学校了😂,只隐隐约约看得出是个章 不过从approve状态转变为Coupon applied花了实实在在的三天(说最长要72小时还真用了72小时啊😂) GitHub Copilot 美美使用GitHub Copilot Copilot Pro除了GPT-4.5和o3用不了,其他都可以用(GPT-4.5的定价属实是有的癫了) 每个月有300次的高级请求 请求乘数如下: 模型高级请求 基本模型(当前为 GPT-4.1)0(付费用户),1(Copilot Free) 高级 GPT-4.11 GPT-4o1 GPT-4.550 Claude 3.5 Sonnet1 Claude 3.7 Sonnet1 Claude 3.7 Sonnet Thinking1.25 Gemini 2.0 Flash0.25 Gemini 2.5 Pro1 o110 o35 o3-mini0.33 o4-mini0.33 也就是说一个月差不多可以免费使用300次Claude 3.7 Sonnet模型,使用基本模型的话(目前是GPT-4.1)则是无限次😎

2025/5/31
articleCard.readMore

[公告]音乐馆重新上线

正文 时隔一年,音乐馆功能重新上线😁,目前部分分类还在施工,不过不影响使用。 打开方式: https://blog.goodboyboy.top/music/ 或者在顶部菜单里找到页面->音乐馆

2025/5/26
articleCard.readMore

[活动]送你一张明信片(长期有效)

前言 最近加入了Postcrossing平台,看到《方寸间、自有天地:一张明信片的故事》,感受到了明信片的魅力,因此特开一文用于明信片交换,如果有喜欢明信片的朋友,可以通过下发方式将指定格式的信息发送给我哦,收到信息后我会尽快安排邮寄😁~ 目前已发布的明信片样式可以在这篇文章里找到《博客明信片v2版发布啦,喜欢就快来申请一张吧!》 信息内容格式 收信人(必填,用于填写收信人一栏) 地址(必填,用于填写收信地址) 邮编(必填) 联系电话(选填,送达后邮递员可能会通过该手机号联系您) 网名(选填,用于写信内容) Email(选填,用于告知投递情况) 博客网址(选填,用于展示您的博客信息) 明信片样式(选填,默认随机样式) 信息发送方式 Email 将上方信息通过Email发送至me#goodboyboy.top(将#换成@) 因为包含隐私信息,建议发送邮件前使用博主公开的 OpenPGP 公钥对其进行加密(公钥指纹: 7B49 3862 FEFB 5A10 B875 4458 391C C423 AEFF 3B06),已和博主有邮件往来的也可以使用博主的 S/MIME 证书对其进行加密。 评论区留言 将上方信息在留言至本篇文章底部评论区 评论为公开内容,请务必在留言前使用博主的OpenPGP公钥对其进行加密,否则不建议使用该方式。 信息保存 提供的信息仅用于发送明信片,在投递后预计会保留一周左右,用于补寄等特殊情况。 参与展示 序号参与人/博主状态 1晚夜已寄出

2025/5/20
articleCard.readMore

加入Postcrossing!

What is Postcrossing? 这个项目的目标是让任何人都能从世界各地发送和接收明信片! 这个想法很简单: 每发送一张明信片,你就会从世界某个随机的明信片交换者那里收到一张明信片 。 The goal of this project is to allow anyone to send and receive postcards from all over the world! The idea is simple: for each postcard you send, you will receive one back from a random postcrosser from somewhere in the world. Why 简单来说,因为像它的创始人一样,有很多人喜欢收到真实的邮件。 从世界各地收到明信片(很多地方你可能之前从未听说过!)可以把你的邮箱变成一个惊喜的盒子——难道还有人不喜欢这样吗? Simply because, like its founder, there are lots of people who like to receive real mail. Receiving postcards from different places in the world (many of which you probably have never heard of!) can turn your mailbox into a box of surprises — and who wouldn’t like that? 正文 无意间看到扶苏的一篇文章《方寸间、自有天地:一张明信片的故事》,了解到了Postcrossing这个平台。 再加之昨天收到了晚夜的BQSL,想着一张明信片能够飘洋过海,成功到达对方的手里,是一件多么幸运和有趣的事 即使是国内之间的快递,都不能保证百分百能够到达目的地,更别说是普通的平信,还是国际邮件。我觉得,这是一件非常有意义的事情。 后记 申请了Postcrossing第一次的Postcard,目的地是是Russian,预计会在这几天发出😄

2025/5/20
articleCard.readMore

今天收到了BQSL卡片

BLOG QSL CARD ,中文名:博客QSL卡片,为防止与业余无线电中的QSL卡片混为一谈,所以将博客QSL卡片简称为BQSL卡片,实质为一种明信片,尺寸大小:140mm x 90mm 作用:可用于友站之间呼唤或者供游客申领,能够加强站点与来访游客或者友站的联系 设计规范:背面应注明日期时间,即访问博客的日期时间;写上HTTP状态码;IS代表网站速度,用时间代替速度,即用户打开网站到加载全部完成的时间;NAME代表网站名称;URL代表网站地址。 详情->《[BQSL]BLOG QSL卡片详解及免费申领(附预览图》 收信 今天终于收到了博主晚夜的BQSL卡 看了一下邮戳,4月29号博主所在市的邮戳,5月3号本市的邮戳,然而5月19号也就是今天我才收到我们学校收发室取信的短信通知。。。 算了懒得喷了 小插曲 我申请时填写的是我网名GoodBoyboy,一进收发室那个负责人问我叫什么名字 我:??? 有点小尴尬哈,不过我急中生智,先讲了我自己的名字,看到他正在一个本子上找我的名字,于是赶忙走过去找名字 因为其他都是中文,一下就看到了我的英文 负责人:咿呀,还是英文名,下次写你自己的名字咯 我:(尴尬) 然后负责人说让我签名,他帮我找 我就把GoodBoyboy签上了,负责人找到后看了下签名 负责人:哎呀,你签你的汉名咯 我:(尴尬)(默默签下名字)(赶紧逃离) 下次再也不填网名了,真的要命😿 卡片 很好看的卡片 后记 之后填到具体邮筒+使用中文化名得了,还以为邮路不通只能发不能收呢

2025/5/19
articleCard.readMore

好用的CloudFlare Tunnel,使我大脑旋转

致谢 CloudFlare真的是赛博活佛😍 起因 无聊看了几篇写CF Tunnel的文章,发现用来内外穿透效果还不错?(仅对于Web网站,游戏联机就不要想了) 虽然自己也有服务器可以搭建Frp进行穿透,但一个是配置麻烦,更改配置还需要重启Frpc,另外也不想什么都通过主服务器穿透,万一哪天主服务器被打进黑洞了超麻烦(阿里云的鸡一摸就死,服务还没死呢就给你扔黑洞去了) 更何况有些服务自带的验证安全程度还达不到我的要求,例如cockpit,仅仅是验证主机的Linux账户密码(为了防止自己锁自己,宿舍服务器密码没有用keepass生成),1panel还好,我用keepass,但是为了方便没开2fa。 这就导致离开寝室局域网后将无法控制我的宿舍服务器,只能使用部分映射出来的服务。 前几天就因为宿舍忘记交电费意外断电后我忘记启动kvm上挂的实验环境,导致实验室的学弟用不了虚拟机,当时在实验室也没办法远程启动,只能等我回寝室后登录后台启动 play CF Tunnel现在被归类到Zero Trust内 目前我用过的就3个大块:Gateway,Access,网络 Gateway管理的就是原来的warp以及warp+,不过现在大陆已经用不了了 CF Tunnel则在网络内 安装客户端与创建隧道 首先需要安装cloudflared,这里为了方便管理我选择的docker安装 按照官方给出的含token的命令创建好容器后,将容器网络改为host方便访问主机内各个服务 建议一个隧道对应一个主机,一个主机只需要创建一个隧道就行 创建公共主机名 这里就是配置具体服务(有点类似配置反向代理) 子域配置的是公共访问地址,访问内网服务就访问该地址 类型配置的是内网具体是什么服务,cloudflare如何访问你的内网服务,和设置CDN差不多,只是说CDN的话如果设置HTTP,那cf到你源服务器的流量就真的是完全未加密,而Tunnel默认是完全加密的 Tips:一个常见小坑是,源服务开启HTTPS并开启HTTP自动跳转HTTPS,但CF后端地址却填写的HTTP协议,这会造成无限跳转,无论用户访问是HTTPS还是HTTP。因为当流量从cf流向源服务器时进入的是80端口,源服务器会返回一个301跳转指向HTTPS,客户端收到301并继续跳转访问,然后流量继续从cf流向源服务器的80端口,无限循环。 一个大坑:如果你是用的docker部署,后端协议选的HTTPS的话,下发的配置默认不会带originServerName,导致cf会把源服务器返回的证书SNI与你填写的后端地址进行对比。一般来说本地服务都会填写127.0.0.1或者localhost,这就会导致remote error: tls: internal error",解决方案一个是开启noTLSVerify,不要验证TLS证书,一个是填写源服务器名称,而且我发现填写一次后哪怕之后删掉,也不会出现这个问题了。 创建完后就可以愉快的使用公共主机名地址访问了 添加Access 用来管理访问策略 策略 可以先创建一些策略,例如ban掉海外ip,仅允许自己的邮箱访问等等 应用程序 这里就对应之前的公共主机名,一个公共主机名可以对应一个应用程序(虽然设置上可以对应多个) 应用程序名称爱怎么填怎么填 公共主机名内容和服务公共主机名一致 然后在策略一栏选择自己创建的策略 登录方法默认的只有向邮箱发送一次性OTP代码的选项,不过支持很多第三方OAuth,例如github,google等,还支持自定义openid connect,可以对接我自己部署的zitadel 不过自从隔壁博主的casdoor出现 CVE-2025-4210 Casdoor <=v1.811.0 - Unauthenticated SCIM Operations导致 【警告】我们注意到moeworld.tech发生了邮件盗用现象后,我就有点不敢用自托管的统一身份认证了,在这种可能会危及到内网安全的重要服务上还是算了,邮件OTP也不是不行 目前市面上常见的IAM基本上都会有角色控制功能,不仅仅提供IdP的功能,也就是说应用开发可以完全不需要自己独立的账户权限管理功能而依赖IAM,但一旦IAM出现问题那就不好玩了 创建完应用程序后在Tunnel内的公共主机名进行绑定即可 后记 这下内网一些私密服务可以通过Tunnel+Access暴露出来了🥰 至于网站访问速度,虽然延迟有点高,但至少可以正常访问,不像CF的CDN,正常访问都是挑战

2025/5/18
articleCard.readMore

新注册furwolf.com域名

感谢<blog.zrf.me>博主的详细教程《超低价注册.COM域名:Google Workspace土耳其区仅需14元/年详细教程》,成功使用75拉里(约等于13.93 人民币)拿下furwolf.com域名 稍后部分服务可能会迁移至新域名上

2025/5/17
articleCard.readMore

恭喜邮储万事达储蓄卡加入卡包大家庭

展示 前言 因为之前开的那张中信的万事达卡不能自动兑汇(不如不开。。。),最近又听说邮储的万事达卡现在支持线上支付了(强制3ds),去app一看确实没有“仅支持线下场景使用”的提示,于是打算办一张,不方便购汇的时候可以用。 办理 一开始是去的校门口的支行,虽然不报什么希望,但是实在是懒得走那么远 结果可想而知——没有,不知道 好吧,直接去分行 但令我大吃一惊的是,分行居然也不知道有万事达储蓄卡 最后只能网申一张然后线下激活了 花了25块钱工本费用来网申 不过返了30微信立减金,但是我来回一趟花了5块路费,不亏不赚吧。。。 激活 前天晚上拿到卡,昨天课比较多,今天去激活 来到大厅,签了一张法律告知书 然后就取号排队了 不得不说,虽然现在断卡行动还在继续,但是比前几年正常多了 现在你只要是本地+有正当理由+分行,几乎都是100%下卡,至于是一类还是二类要看银行,邮储貌似一般都只开二类 分行一般开的限额也高一些 去年我去招商开户都还需要我的学生证或者学信网,今天开只需要一张身份证即可 小插曲 一开始还没开成功,绑微信绑不上,手机银行也绑不上 按理说手机银行怎么都可以绑上的,而且这卡好像都是23年9月出的,也不可能是系统没更新的原因 柜员小姐姐又查了一下账户,没看到新的那张卡 又在自助机上弄了一下,这次用的开通定制卡的方式,成功开卡 可能是之前用的开银联卡的方式所以开失败了 还有个小插曲就是绑定手机银行出现问题 微信是绑上去了,但是手机银行绑的时候要人脸验证,结果死活验证不过去,自助机上绑定的话需要拿原来的卡过来才行 不过好在最后还是人脸验证过了 最后 最后小姐姐还负责的提醒我一定要合规使用银行卡,不要出借。 后记 目前人民币支付可以正常交易,外币还没试,有空试一试,感受一下新万事达的自动兑汇(doge

2025/5/17
articleCard.readMore

服务器负载异常排查

正文 最近感觉博客访问奇慢,前几天还只是博客打开有点慢,以为是CDN的问题便没怎么管,结果今天发现评论系统也间歇性加载不出来了,发现事情并不简单。 排查 先登录了1panel面板,发现资源使用率高达170%,CPU也有70%(一般来说只会在20%左右) 马上切换到进程管理,发现openresty占用80%的CPU,第一反应是被打了 先暂时重启OpenResty,然后在查看网站日志,但看下来日志大小都都挺正常 然后就翻到了我们的老伙计Gitea,每一天的access.log高达10M+ 第一反应就是又被哪个傻逼AI爬虫给到处爬(之前就有先例,直接把服务器爬死机),但我明明记得已经在CF开了拦截所有AI爬虫,并且把海外IP全ban了。 翻了下日志,发现请求内容全是Runner的请求,没有看到AI爬虫的 暂时有点摸不清问题所在 在监控面板看了下服务器最近的资源占用率情况 结果不看不知道,一看吓一跳 平常没怎么看监控面板,而且这玩意图太有迷惑性了,不仔细看还以为是普通的请求造成的,把时间区间拉小一看,还以为是看心电图呢。。。 最难蚌的是最低资源使用率基本都是80%+ 打算往前面翻翻,结果发现从有日志的时间开始(监控日志保存30天)就一直是这样了。。。 好吧属实是疏忽了,平常看这玩意运行挺稳的就没怎么管,push博客也是全自动Hook,没怎么登录服务器(doge 本来还想打算继续看看,突然反应又慢了起来 这次进程管理都进不去了,只好登ssh,开htop看看是哪个玩意在作妖 htop一看,redis。。。 再看了看那170%的IO,懂了 把redis暂时关掉,看了下磁盘IO监控,果不其然,也是心电图 这里大概已经知道是怎么回事了,不过还有一个点想不通,然后想到了Gitea的Runner 看了一下日志,每两分钟Runner会请求一次Gitea,虽然不知道为什么这会造成Gitea发癫,但结果应该大差不差了 结论 Runner每两分钟请求一次Gitea Gitea发癫 Redis被Gitea冲爆 Redis把内存占满 Debian疯狂向swap写 swap疯狂读写磁盘 众所周知,阿里云的轻量磁盘IO垃圾的一匹 因此导致IO延迟爆炸 外加MySQL助攻 最终导致资源使用率最高到达1280%,平均300% 后记 暂时把redis和gitea关掉后服务器已经正常,因此问题就是出在Gitea上 1panel装的redis貌似默认有持久化,重启还没用,重启完载入数据库IO更加爆炸,只能手动flush,打个FLUSHALL命令花了21s才清空完数据库,不知道Gitea给redis塞了些什么玩意。。。 这个Runner还是我部署在华为云上的一个白嫖小鸡,当时感觉没啥可以部署的就部署了这玩意玩玩,结果给我整这一出 整个事件下来最让我绷不住的,是这个状态持续了好几个月,直到今天才明显感知出来,平常无论是Frp映射,还是发布博客,都没有任何感觉🤣,也是辛苦这台机了,2h2g,挂了一堆容器,还要塞个Gitea和MySQL

2025/5/12
articleCard.readMore

土区Apple Music发车记

前言 这篇文章算是半教程半记录吧 我同学昨天突然给我说想开am土区的家庭组,稍微查了一下,土区家庭组一个月59拉里,折合人民币11.02元(当前时间),一个车可以坐6个人(包括车主),平均下来一个人一个月1.83,这不比QQ音乐的8块钱香?于是开始狠狠查教程 注册土区账户 苹果在这方面貌似管的不严,非土区IP+非土区电话都可以注册,只要国家一栏选土耳其就行 但是貌似是25年后苹果收紧注册后,网站上注册apple id一直注册不了(不仅是土区,国区也是) 后面在Linux do上面找到一个偏方,实测有效(但是貌似会根据ip确定国家地区,不过后面可以改) 原文在这里->《注册美区/土区 Apple ID 方法(避开网页提示此时无法创建你的账户)》 简单来说就是利用在iPhone设置里进入APP Tab,找到邮件这个软件,使用邮件这个app创建账户,可以完美解决问题 该方法注册的账户地区应该是根据ip自动选择的 然后在https://account.apple.com/中更改国家地区为土耳其,账单地址找个土耳其地址生成器就行了 充值 账户创建完成后就是入金 和大多数厂商一样,土区锁卡,只能绑定土耳其本地信用卡,但是兑换码渠道目前没有收紧(微软你看看人家),因此入金还是蛮简单的 进入https://oyunfor.com/,在oyunfor里面买app store充值卡,应该是土耳其本地网站,全部用拉里结算,所以虽然说一次购买手续费要2.99%,但买一张25拉里的充值卡也就0.6的手续费,折合人民币0.11元,这点手续费我还是给得起的(doge oyunfor支持银联支付,但是那个支付方式的网站貌似要魔法才能访问,不过无所谓,反正我有外币卡(都是老顾客了doge) 先买了一张25的,怕apple搞事导致充不了 用同学的iPhone登录app store,然而app store这里卡半天,商店区域一直没切换到土区,充值卡用不了,结果我同学东点点,西点点,商店缓存刷新了,切换地区成功😂 然后就是充值入金,成功充值25拉里 建立家庭组 第一次建立不知道要登录设置的apple id,以为是登录app store就行,结果车主变成我同学了(后面了解到乘坐人员貌似也能共享,不一定是要车主才能共享订阅(未验证)) 然后就是复杂的切换账户 先是他在网站上登录他的美区id(原来是一个国区一个美区),改成土耳其区 然后把他的apple id退出,登录我新注册的土区id,最后建立家庭组并邀请他的土区id 接着退出我的土区id,登录他的土区id,接受邀请 出现限制 登录他的土区id后,我直接在媒体登录那里改成了我的土区id,结果在apple music准备激活订阅时,提示 This device is linked to another Apple Account. You must transfer this device to your Apple Accoumt to join Apple Music. You will be unable to transfer this device again for 90 days. 可能是切换账户太频繁了,我怕把他的账号搞得登不回去他的国区id,暂时停止操作 安装黑苹果 此为下下策,黑苹果使用不当容易导致封号!有真设备建议使用真设备 想了一下,主要是设备只有一台,切换太不方便,突然想到还可以黑苹果 网上装黑苹果麻烦主要是为了体验,所以到处折腾兼容性,但我只是为了登个apple id用设置这个app(doge 所以完全没问题😁 打补丁 使用电脑上原本就装着VMware workstation 17 pro,这里只需要打个补丁就行 unlock工具->https://www.ittel.cn/archives/18529.html 这里最高支持到macos 14 下载镜像 然后就是找镜像,有iso和dmg的,iso的可以直接引导,dmg还需要制作启动盘 对于我的目的来说什么镜像都无所谓,反正只是用来登录apple id,你藏点小木马也没啥,都是虚拟机 我这里使用的是dmg的方法(因为iso的镜像是百度网盘) 镜像下载地址->https://hackintosh.club/d/10000080 没有任何网盘会员的可以使用天翼网盘,完全不限速,爽 制作启动盘(iso的可以跳过) 有刻录到U盘的方式,还有刻录到VHD(虚拟磁盘)的方式 U盘我倒是有,但是U盘性能太差了,比较赶时间(当时已经是凌晨快1点了),于是使用VHD的方式 教程地址->https://zhuanlan.zhihu.com/p/130692555 这里说一下,虚拟磁盘格式建议选VHDX,动态大小,不会浪费过多磁盘空间,并且是本机使用,不用考虑其他机器的兼容性 这里用的balenaEtcher需要是旧版本才能把文件刻录到虚拟磁盘上,教程评论区有,我这里单独搬出来一下:https://www.filehorse.com/download-etcher/37508/download/ 这里不得不说,两张固态之间文件复制就是快(dmg文件在D盘,虚拟vhdx文件在C盘,两个硬盘没有分区),烧录速度最高达到1000多MB/s,跑满我的致钛7100 plus的速度,一个13.5G的镜像十几秒就烧录完了 创建虚拟机 我在下载镜像的时候就创建了,这里记录一下 打好补丁后创建虚拟机,我留了60G给黑苹果,我只是用个设置app,并不是打算真的体验它,所以不会装其他任何软件,60G完全够 CPU,内存什么的按照自己情况分,分的越多越丝滑咯 硬盘上等烧录好后我们添加硬盘直接添加物理磁盘,磁盘就选择那块虚拟磁盘,挂载整个磁盘即可,如果是iso的镜像直接挂载iso镜像即可 如果选择物理磁盘提示没有权限(哪怕点击下一步时已经给了管理员权限),先关闭VMware,然后右键快捷方式以管理员启动即可 然后就是注入三码(否则无法登录apple id) 找到虚拟机配置文件(后缀为vmx的文件),用记事本打开,加入类似如下的信息 1 2 3 4 5 6 7 board-id.reflectHost = "FALSE" board-id = "Mac-xxxxxxxxxxx" hw.model.reflectHost = "FALSE" hw.model = "MacBook Air" serialNumber.reflectHost = "FALSE" serialNumber = "xxxxxxx" smbios.reflectHost = "FALSE" board-id.reflectHost可能配置文件里已经设置了,如果重复了记得删除原来的设置 这里的序列码可以用变色龙生成或者在网上找找有没有现成的 最后网卡选择桥接模式(NAT貌似会影响apple id的登录) 开机安装 准备就绪后开机安装系统 进入安装界面后先用磁盘工具抹除60G的VMware虚拟磁盘,然后正常安装系统到这块磁盘上 经过一段时间的等待和多次重启后,进入系统 登录 安装完成后登录apple id,如果没有异常情况应该是能够成功登录的。 开启订阅 进入音乐app,登录apple id,然后选择订阅 这里又出现一个小插曲 订阅可以免费试用一个月,然而当我点击的时候,提示我需要添加支付方式 上网查了一下,有两种说法,一种说是被风控了,要重新注册账号,另一种是说账户余额不足以抵扣下一月 我看了一下,确实账户里只有25拉里不足以抵扣59.99拉里 为了保险起见,我又买了25拉里冲了进去,这样即使翻车了也只损失50拉里, 点击试用个人39.99拉里一个月的选项,成功试用没有提示添加支付方式😆 迁移歌单 这个环节还行,有专门的工具,目前先迁移了500首,有108首迁移失败😢,希望只是歌名匹配问题,这样还可以手动添加(都是我喜欢的歌啊啊啊) 安装VMware tool(可选) 如果闲的没事还可以装个VMware tool,这样至少屏幕帧率以及窗口大小正常 后记 打算先爽几天个人订阅,等同学订阅要过期了再订阅家庭版

2025/5/10
articleCard.readMore

日期进度条的小插件JavaScript版

前言 今天逛博客发现了一个很好看的日期进度条的小插件,但是是PHP版的,对于Hexo这类静态博客用不了 不过从原理上来说获取时间其实并不是必须要服务器计算,因此按理应该可以转换为纯JavaScript 文章原文->《写了个日期进度条的小插件》 偷了个懒,直接让AI来转 代码版权归 李锋镝的博客 所有 代码 index.html 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Countdown Timer</title> <link rel="stylesheet" href="styles.css"> </head> <body> <div class="countdown-container"> <div class="countdown-item" id="countdown-day"> <span class="countdown-text"></span> <div class="countdown-progress-bar"> <div class="progress-bar-fill"> <span class="countdown-percentage"></span> </div> </div> </div> <div class="countdown-item" id="countdown-week"> <span class="countdown-text"></span> <div class="countdown-progress-bar"> <div class="progress-bar-fill"> <span class="countdown-percentage"></span> </div> </div> </div> <div class="countdown-item" id="countdown-month"> <span class="countdown-text"></span> <div class="countdown-progress-bar"> <div class="progress-bar-fill"> <span class="countdown-percentage"></span> </div> </div> </div> <div class="countdown-item" id="countdown-year"> <span class="countdown-text"></span> <div class="countdown-progress-bar"> <div class="progress-bar-fill"> <span class="countdown-percentage"></span> </div> </div> </div> </div> <script src="script.js"></script> </body> </html> styles.css 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 .countdown-container { background-color: #FAF9F6; border-radius: 12px; padding: 10px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08); max-width: 500px; margin: 20px auto; } .countdown-item { height: 50px; position: relative; max-height: 80px; overflow: hidden; margin-bottom: 15px; } .countdown-text { color: #868e96; font-size: 0.95em; } .countdown-progress-bar { height: 16px; background-color: #E8E8E8; border-radius: 8px; overflow: hidden; position: relative; margin-top: 3px; } .progress-bar-fill { height: 100%; border-radius: 8px; transition: width 1s ease; position: relative; background-image: repeating-linear-gradient( -45deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2) 10px, transparent 10px, transparent 20px ); background-size: 28px 28px; animation: spiral-move 1s linear infinite; } .countdown-percentage { font-size: 0.8em; color: #fff; position: absolute; top: 50%; right: 5px; transform: translateY(-50%); z-index: 2; } @keyframes spiral-move { 0% { background-position: 0 0; } 100% { background-position: 28px 0; } } script.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 // 配置项 const config = { types: ['day', 'week', 'month', 'year'], // 默认显示所有类型 colorMap: { 'day': '#B5EAD7', 'week': '#FFDAC1', 'month': '#E2F0CB', 'year': '#FFB7B2' } }; // 更新倒计时显示 function updateCountdown() { const now = new Date(); config.types.forEach(type => { let elapsed = 0; let total = 0; let text = ""; switch (type) { case "day": elapsed = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds(); total = 24 * 3600; text = `今天已过去 ${Math.floor(elapsed / 3600)} 小时 ${Math.floor((elapsed % 3600) / 60)} 分钟`; break; case "week": elapsed = (now.getDay() * 24 * 3600) + (now.getHours() * 3600) + (now.getMinutes() * 60) + now.getSeconds(); total = 7 * 24 * 3600; text = `本周已过去 ${Math.floor(elapsed / (24 * 3600))} 天 ${Math.floor((elapsed % (24 * 3600)) / 3600)} 小时 ${Math.floor((elapsed % 3600) / 60)} 分钟`; break; case "month": const year = now.getFullYear(); const month = now.getMonth() + 1; const daysInMonth = new Date(year, month, 0).getDate(); elapsed = ((now.getDate() - 1) * 24 * 3600) + (now.getHours() * 3600) + (now.getMinutes() * 60) + now.getSeconds(); total = daysInMonth * 24 * 3600; text = `本月已过去 ${Math.floor(elapsed / (24 * 3600))} 天 ${Math.floor((elapsed % (24 * 3600)) / 3600)} 小时 ${Math.floor((elapsed % 3600) / 60)} 分钟`; break; case "year": const startOfYear = new Date(now.getFullYear(), 0, 1); const diff = now - startOfYear; elapsed = Math.floor(diff / 1000); const isLeapYear = (new Date(now.getFullYear(), 1, 29).getDate() === 29); total = (isLeapYear ? 366 : 365) * 24 * 3600; text = `今年已过去 ${Math.floor(elapsed / (24 * 3600))} 天 ${Math.floor((elapsed % (24 * 3600)) / 3600)} 小时 ${Math.floor((elapsed % 3600) / 60)} 分钟`; break; } const percentage = Math.round((elapsed / total) * 100); const item = document.getElementById(`countdown-${type}`); if (item) { const textElement = item.querySelector(".countdown-text"); const percentageElement = item.querySelector(".countdown-percentage"); const progressBarElement = item.querySelector(".progress-bar-fill"); textElement.textContent = text; percentageElement.textContent = `${percentage}%`; progressBarElement.style.width = `${percentage}%`; progressBarElement.style.backgroundColor = config.colorMap[type]; } }); } // 初始化 document.addEventListener("DOMContentLoaded", () => { // 可以根据需要动态隐藏某些类型的倒计时 // 例如,如果只想显示day和week: // config.types = ['day', 'week']; updateCountdown(); setInterval(updateCountdown, 1000 * 60); // 每分钟更新一次 }); 测试 本地测试下来应该没什么问题

2025/5/8
articleCard.readMore

Fast Auth——2FA APP开发进度汇报

开发进度 由于临近期末,需要推进即将截止的两个课设以及进行期末复习,开发进度暂时放缓。 目前开发进度已达到70%左右(整个项目进度),已实现核心功能 已开发功能 [x] TOTP [ ] HTOP [ ] STEAM令牌 [x] 使用Android密钥库加密存储Secret [ ] 导出 [ ] 明文导出 [ ] 端到端迁移 [ ] NFC迁移 [ ] QR迁移 界面 该演示图不代表最终效果(不过也大差不差了doge) 亮色模式下 [{"url":"https://pic.goodboyboy.top/imgs/2025/05/08/5g0v2.webp","alt":"首页","title":""},{"url":"https://pic.goodboyboy.top/imgs/2025/05/08/7h6t7.webp","alt":"设置页面","title":""}] 暗色模式下 [{"url":"https://pic.goodboyboy.top/imgs/2025/05/08/5pakd.webp","alt":"首页","title":""},{"url":"https://pic.goodboyboy.top/imgs/2025/05/08/5oz36.webp","alt":"设置页面","title":""}] 存储加密逻辑 MasterKey存储 sequenceDiagram participant App as 客户端/应用 participant AndroidKeystore as Android密钥库系统 participant MasterKey as MasterKey App->>AndroidKeystore: 1. 创建密钥库(KeyStore) App->>MasterKey: 2. 生成MasterKey App->>AndroidKeystore: 3.发起生物认证 AndroidKeystore->>MasterKey: 4. 使用AES-256加密MasterKey MasterKey-->>App: 返回加密后的MasterKey 加密Secret sequenceDiagram participant App as 客户端/应用 participant AndroidKeystore as Android密钥库系统 participant MasterKey as MasterKey participant OTPSecrete as OTPSecrete App->>AndroidKeystore: 1. 发起生物认证 AndroidKeystore->>MasterKey: 2. 解密MasterKey MasterKey->>OTPSecrete: 3.使用AES-256加密OTPSecrete OTPSecrete-->>App: 返回加密后的OTPSecrete 解密Secret sequenceDiagram participant App as 客户端/应用 participant AndroidKeystore as Android密钥库系统 participant MasterKey as MasterKey participant OTPSecrete as OTPSecrete App->>AndroidKeystore: 1. 发起生物认证 AndroidKeystore->>MasterKey: 2. 解密MasterKey MasterKey->>OTPSecrete: 3.使用解密OTPSecrete OTPSecrete-->>App: 返回解密后的OTPSecrete 后记 本来是打算走QR迁移的,突然灵光一闪想着NFC不也是可以传输数据,并且安全性比QR高很多。 查了一下,不同的NFC tag的容量不一样,但是如果使用Android NFC的点对点功能,NDEF Push Protocol对NDEF消息长度并没有严格的上限,Payload Length允许为4字节长度。 所以理论上可以传输232−12^{32}-1232−1字节的数据,约4GB(雾),不过貌似实际超过几十KB到上百KB的消息就容易出现传输失败。 到时候可以做个选项,要是数据少的可以直接通过NFC传完,要是数据多的话就通过NFC交换AES加密密钥 如果有闲心的话弄个ECC也不是不行,用QR或者NFC传递公钥,不过目前Android密钥库貌似只支持ECDSA,并且没有像OpenPGP那样支持使用基于Curve25519的加密功能(我宁可用国密也不想用ECDSA),到时候找找有没有什么靠谱的实现 另外暂时没有找到好的图标来源,到时候再看吧 自己做软件的好处之一就是想加什么功能自己加就完事了(doge

2025/5/7
articleCard.readMore

五一单日长沙游

小电车 最近租小电车租上瘾了,刚好发现青桔的有一天无限次骑,只要7块钱,于是果断下单。 不得不说,长沙的红灯是真的长 显示99秒不是要等99秒,而是它最多只能显示到99秒😂 不过有了小电车后确实方便很多,比起五一在地铁里人挤人,我还是更喜欢骑车兜风 就是当天太阳有点大,没做防晒,结果有点晒伤了😢 CityWalk 说实在的,逛了这么多次,觉得长沙没啥好逛的,感觉能逛的都逛完了。 去湖师大转转,发现还有好几个小孩子在拍照 尽管知道五一广场这边人多,但就是忍不住想来凑凑热闹(逃 哎,果然是众众众众众众众众众众众众众众众众众众众众 湘江 人太多了还是来看湘江吧。。。 本来打算去橘子洲,突然想起这玩意好像要预约,一看结果全预约满了😢 那就只能在对岸看了,不过对岸也舒服很多,人数比较适中,我猜橘子洲里肯定也是众众众众众众众众众众 晚上 晚上逛街发现一家“超级零食很忙” 看起来确实是“超级”,比平常门店大很多 返程 拍一张晚上的长沙站

2025/5/4
articleCard.readMore

预定一个2FA APP

设备Root后会显著降低密钥安全性,建议在非Root设备上使用。 开发进度-> Fast Auth——2FA APP开发进度汇报 需求 导出需求 目前市面上很多2FA验证器都不支持导出2FA,虽然说不可导出可以增加安全性,但是我个人觉得既然是2FA了,可以牺牲一点安全性提高一下便利性。 美观 很多2FA确实很好看,但不支持导出,例如authy,很多支持导出的又不好看 代码安全性 部分商业APP不开源,无法进行代码审计 数据安全性 TOTP/HOTP Secret预计使用AES256进行加密(有条件情况下使用X25519),加密密钥由Android密钥库保存,每次解锁需要强制生物验证。 数据迁移助手之类的软件一般不会迁移Android密钥库密钥,因此迁移的数据库数据将无法正常解密,需要手动导出2FA Secret至新设备! 预计功能 支持TOTP/HOTP(后期支持Steam令牌) 支持明文导出 支持双设备迁移 全离线加密存储 外观设计 使用Jetpack Compose设计,采用Material主题,支持动态取色,暗色模式。 系统要求 系统版本:Android 9+ 不可Root(不会检测) 开源 预计采用GPLv3协议进行开源

2025/4/30
articleCard.readMore

OpenID Connect认证流程

本文使用最常用的Authorization Code的Code认证方式,IdP采用ZITADEL 首先依赖方会要求提供discovery endpoint,具体地址由IdP提供,一般为{idp_domain}/.well-known/openid-configuration,该链接包含认证所需的很多endpoint。 然后在授权服务器注册应用,注册完成后会得到Client ID与Client Secret,注册时填写的callback地址由依赖方提供,用于用户完成授权后客户端调用callback地址传递授权码。 认证流程 重定向授权页面 由依赖方在登录页面发起授权请求 1 2 3 4 5 6 {idp_domain}/oauth/v2/authorize ?response_type=code &client_id=CLIENT_ID &redirect_uri=REDIRECT_URI &scope=SCOPE &state=STATE 其中CLIENT_ID为注册应用时获得的Client ID,REDIRECT_URI为注册应用时填写的callback地址,SCOPE为授权范围,具体范围需要参考IdP,在discovery endpoint中scopes_supported也可以看到可以申请的授权范围,STATE为由依赖方产生的随机值,用于防止CSRF攻击,在授权完成调用callback地址时会附加在链接中,便于依赖方校对。 重定向callback地址 用户授权完成后重定向至callback url 1 2 3 {callback_url} ?code=CODE &state=STATE 其中CODE为授权码,STATE与上文一致 请求Access Token 依赖方后端收到授权码,校验state后向token endpoint请求Access Token 后端构建Authorization头: 1 Authorization: "Basic " + base64( formUrlEncode(client_id) + ":" + formUrlEncode(client_secret) ) 1 2 3 4 5 6 7 8 9 {idp_domain}/oauth/v2/token Content-Type: application/x-www-form-urlencoded Authorization: Basic xxxxxxxxxxxxxxxxxxxxx grant_type=authorization_code code=CODE redirect_uri=REDIRECT_URI scope=SCOPE CODE,REDIRECT_URI,SCOPE同上文一致 返回如下Json: 1 2 3 4 5 6 { "access_token": "XXXXXXXXXXXXXXXXXXXXXXXX", "token_type": "Bearer", "expires_in": 43199, "id_token": "JWT" } ZITADELrefresh_token需要在scope内添加offline_access,否则返回内容不会有refresh_token ZITADELid_token默认不会含有email,profile之类的信息(即使scope已声明),需要在应用设置,令牌设置内勾选在 ID Token 中包含用户信息,或者直接向userinfo_endpoint请求用户信息。 获取用户信息 后端向userinfo_endpoint请求用户信息 1 2 {idp_domain}/oidc/v1/userinfo Authorization: Bearer ACCESS_TOKEN 其中ACCESS_TOKEN为上文拿到的Access Token 返回如下Json 不同IdP可能不一样 1 2 3 4 5 6 7 8 9 10 11 { "sub": "000000000000000000", "name": "xxxxxxxxxxxxxxx", "given_name": "xxxxxxxxxxxxx", "family_name": "xxxxxxxxxxxxxx", "locale": "zh", "updated_at": 0000000000000000, "preferred_username": "xxxx@xxxxxxxxxxxxxx", "email": "xxxxxxxxxx@xxxxxxxxx.xxxxx", "email_verified": true }

2025/4/25
articleCard.readMore