普通视图

发现新文章,点击刷新页面。
昨天以前土豆不好吃

我的欧洲生活 – 挪威篇

2025年11月17日 18:26

离开西班牙后,我便和朋友 Rainman 前往挪威奥斯陆。

欢迎来到这个世界上最民主、最自由、森林最多、空气最清新的国家——挪威。
这里的自来水好喝到可以直接装瓶卖,但物价也高得能让你的钱包瞬间清醒。

物价的话,换种说法,别的欧洲国家,哪怕是欧洲第一经济体德国,上厕所也只需要1欧元;而挪威,需要20 NOK,大概2欧元;再换一种说法,挪威遍地都是百万富翁;再换一种说法,挪威人周末来瑞典超市扫货。

一下飞机,就能感受到挪威的空气和别处不一样——格外清新,像是刚穿过森林过滤过的一样。大概是因为这里的森林实在太多了吧。


奥斯陆这趟行程我们没做太多计划,只顺路去了歌剧院——那是一座既宏伟又亲切的建筑,适合散步、遛娃、拍照打卡。
很遗憾,这次我又没拍好😂。只在另一侧拍了水。
虽然我不会游泳,但我总是喜欢靠近水的地方——海边、河边、湖边都好。
毕竟,全世界的水,终会在某处重逢。 我的欧洲生活 - 挪威篇


从歌剧院往前走,就能到蒙克美术馆。
这座馆的氛围有点特别——也许不太适合未成年人参观。
毕竟,那幅全世界都认识的抽象画《呐喊(The Scream)》就在这里。我当时就这个表情嗷😱😱😱😱😱😱😱
我的欧洲生活 - 挪威篇


挪威是一个挺特别的国家。

几百年前,当丹麦还是北欧霸主时,挪威只是它的一部分;后来瑞典崛起,挪威又成了瑞典的附庸。

到了二战时期,因为拥有丰富的自然资源,挪威又被纳粹德国占领。

战后,欧洲国家纷纷抱团取暖,陆续加入欧盟,但挪威却选择了另一条路。

也许是被统治惯了,不想再被别人管,挪威没有加入欧盟,却是北约的创始成员国。

为了与欧盟国家保持良好关系,它又加入了欧洲经济区(EEA),并签署了申根协定。

挪威使用自己的货币——挪威克朗(NOK),汇率和瑞典克朗(SEK)差不多。

顺便说一句:欧盟、申根区、欧洲经济区、欧元区、北约——这五个其实完全是不同的概念。


诺贝尔奖虽然由瑞典人阿尔弗雷德·诺贝尔创立,但有趣的是,六个奖项中唯一的“例外”——和平奖,却是在挪威奥斯陆颁发的。

我的欧洲生活 - 挪威篇

这背后其实有点历史原因:

诺贝尔在世时,挪威和瑞典还属于同一个王国,但挪威有自己的议会。

于是他在遗嘱里特地写明——和平奖由挪威方面负责颁发。

大概是想在两个“兄弟国家”之间保持点平衡吧。

所以啊,如果哪年你对和平奖不满意,别去喷瑞典人——这锅得挪威背😂。

我的欧洲生活 - 挪威篇


挪威不仅风景壮丽,钱包也异常厚实。

这个人口不到六百万的国家,凭借北海丰富的石油和天然气资源——尤其是在俄罗斯入侵乌克兰之后——彻底赢麻了。

欧洲急需新的能源供应,而挪威成了最大的赢家:石油、天然气出口暴涨,财政收入创下历史新高,更别提那个神话一般的“主权基金”了。

不过,挪威的财富可不止来自地下。

海里的“金子”——三文鱼,同样为这个国家赚得盆满钵满。

挪威是世界上最大的三文鱼出口国之一,海水寒冷而纯净,养出的鱼肉鲜嫩肥美。

哪怕在瑞典超市里,最常见的也是来自邻国挪威的三文鱼。
北海既养活了挪威人,也喂饱了半个欧洲。


挪威还有另一座让人神往的城市——特罗姆瑟(Tromsø),被称作“北极光之城”。

可惜这两年来,我一直没能踏上那片极光闪烁的天空。

也许,留一点遗憾,才算是真正的旅途吧。

 


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/europe-no.html

我的欧洲生活 – 西班牙篇

2025年10月23日 12:23

2023年是我在瑞典工作的公司成立十周年的周年庆,于是公司组织去西班牙玩。

西班牙位于欧洲西南端的伊比利亚半岛,与另一颗“牙”——葡萄牙,共享这片伸向大西洋的陆地。整个半岛的大部分领土属于西班牙,只有西侧一小角是葡萄牙的国土。

作为一个君主立宪制国家,西班牙的首都兼最大城市是马德里。按照我以往出行的习惯,我通常都会选择去一个陌生国家的首都探索一番。但这次例外——公司安排的是前往巴塞罗那旁边的小镇:锡切斯(Sitges)。

啊,对了,如果你也计划去巴塞罗那,可得小心:世界上其实有两个巴塞罗那。一个在西班牙,另一个在遥远的委内瑞拉。好消息是——它们都讲西班牙语 😂。

至于锡切斯,我对它的印象已经有些模糊了。只依稀记得那是一座面朝地中海的小镇,阳光明媚、海风温柔,街道两旁的行道树几乎全是高大的棕榈树。这可比我们那灰不溜秋的北欧好上几百倍了。

我的欧洲生活 - 西班牙篇

从锡切斯可以坐火车去巴塞罗那。正好那个时候朋友 @Rainman也来欧洲玩,就正好去巴塞罗那接他。

巴塞罗那的话,一般游记会推荐大家去圣家堂、哥伦布雕像。很明显我对这两个地方没什么兴趣,都没有认真拍照。


对我来说,比起巴塞罗那的建筑或雕像,我更感兴趣的,其实是西班牙的历史与政治制度。

在中学的历史课上,我们都学过那段波澜壮阔的时代——葡萄牙与西班牙先后开启了大航海时代,扬帆远航,把世界的版图从欧洲扩展到南美、非洲与亚洲。那时的西班牙,堪称世界的中心。

然而,荣耀并没有持续太久。16世纪末,西班牙与英国为争夺海上霸权开战,无敌舰队被英国海军全歼。自那之后,世界的主角转变成了那个日不落的帝国——英国。

时间快进到二十世纪。第二次世界大战之后,西班牙在弗朗哥的统治下成为了一个独裁国家。直到1975年,弗朗哥去世,胡安·卡洛斯登上王位。

这个名字在西班牙历史上颇具争议,但不可否认的是,他是推动西班牙走向民主化的关键人物。在他的带领下,西班牙没有经历流血革命,也没有社会崩溃——国家就这样平稳地完成了从独裁到民主的转型。

这样的历史转折,或许比任何一座教堂或雕像,都更让我对这个国家心生敬意。

更有趣的是,巴塞罗那所在的加泰罗尼亚省,是西班牙最富裕、最具活力的地区之一。这里拥有相当高的自治权,不仅是经济大省,也是文化上极富个性的地方。

走在加泰罗尼亚的街头,你会发现国旗的展示方式也很有意思——有的阳台上同时挂着西班牙国旗和加泰罗尼亚旗,有的干脆只留下那面红黄条纹、带星的独立旗帜。那是一种无声却坚定的表达。

我的欧洲生活 - 西班牙篇

甚至,连互联网世界都能看到他们的身影——“.cat” 这个顶级域名,正是专属于加泰罗尼亚(Cataluña)语文化的。爱猫人士以为是“猫咪专属域名”,实际上这是加泰罗尼亚人为自己语言争取到的数字象征。

要知道,加泰罗尼亚语和西班牙语是有一些差别的。在加泰罗尼亚,官方文件、街道标识、学校教育大多都用加泰罗尼亚语;当然了,那边的人都会说西班牙语。作为旅游大国,大部分人也可以说简单的英语。

这片土地上,关于“独立”的讨论几乎从未停歇。几年前就闹过一次,但是在全民公投后,结果最终未能成功。

或许许多人心里仍有想要独立的想法,但一想到独立后意味着要重新申请加入欧盟、欧元区乃至申根区——那就又成了一个现实的问题。理想与现实之间,始终隔着难以逾越的烦恼。


西班牙之行虽然短暂,但每当想起它的历史与文化,都会觉得,这个国家的故事,远比我当时走过的街道要辽阔。

 


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/europe-es.html

我的欧洲生活 – 德国篇

2025年10月18日 12:40

说起德国,真不得不佩服这个国家——
不仅是欧洲第一大经济体,连自家的超市都能开遍整个欧洲。

而真正来到柏林之后,我才发现,那些在电影《气球》中看到的分裂与逃亡、理想与恐惧,在现实中都变成了可以触摸的历史与秩序。那种震撼,是屏幕前永远体会不到的。


德国是一个拥有许多冷战遗迹的国家。

二战结束后,作为战败国的德国被迫接受《波茨坦协定》的内容。苏联、英国、法国和美国分别占领了德国的一部分,最终形成了两个国家:德意志民主共和国(东德)和德意志联邦共和国(西德)。

动画《间谍过家家》中虚构的两个国家——东国与西国,其实正是在影射冷战时期的东德与西德。
连首都的名字都只是比柏林多了一个字母 t,而剧中那些建筑风格、人物设定、甚至秘密组织的细节,都在处处暗示那个分裂而紧张的时代。

柏林作为纳粹德国的首都,在战争结束后,也被一分为二。很多读者如果不了解历史或地理,可能会以为柏林正好位于德国的中间,东西各占一半,就像切西瓜一样。

但实际上,整个柏林都位于东德境内,其中的西柏林完全被东德包围,成为飞地。

我的欧洲生活 - 德国篇

战争结束后不久,西方盟国与苏联之间的关系迅速恶化,冷战由此开始。苏联试图封锁柏林,但西方国家通过大规模空运建立了“空中走廊”,成功维持了西柏林的物资供应。最终,苏联被迫解除封锁。

然而,随着时间的推移以及东西方关系的进一步恶化,一道分隔东西柏林的高墙逐渐出现——这就是后来闻名世界的“柏林墙”。

80年代后期,随着全球民主化浪潮的兴起,东欧各国政局剧烈动荡。柏林墙也在这一历史洪流中应声倒塌,象征着东西方长期对立的终结。几个月后,东西德实现统一,重新成为一个完整的国家。

而在随后的几年里,苏联也走向解体,冷战正式落下帷幕。那段被铁幕、意识形态和对峙分割的时代,终于成为了历史。


因此我说,德国是一个拥有着很多冷战遗迹的国家。如果你对历史、尤其是二战、冷战历史比较感兴趣,那么德国是一定要去的地方。

去柏林的话,同样也是坐飞机比较划算,第一次没有坐过夜火车。抵达的机场是勃兰登堡机场。BER机场不是德国最大的机场,最大的是FRA法兰克福机场,FRA在整个欧洲也算是欧洲数一数二的大机场了

作为游客,建议根据停留时间购买 Berlin WelcomeCard,并依行程选择 AB 或 ABC 区域。这张卡几乎可以搭乘所有公共交通工具:
S-Bahn(市郊铁路)、U-Bahn(地铁)、Tram(有轨电车)和 Bus(公交车)都包含在内,出行非常方便,去部分景点甚至还有折扣

我的欧洲生活 - 德国篇

德国人十分松弛,就是这么一张纸,需要在机器上插进去打个卡带上时间戳,否则会被认为是逃票。同样,没安检。

我的欧洲生活 - 德国篇


第一个需要去的景点是,勃兰登堡门。

这座宏伟的大门见证了柏林从辉煌到分裂、再到统一的整个历史。冷战时期,它正好矗立在东西柏林的交界线上,成为铁幕最具象征意义的地标之一。

我的欧洲生活 - 德国篇

1987年,美国总统里根就在这里发表了那句震撼世界的呼喊——“Mr. Gorbachev, tear down this wall!”。

那声音回荡在柏林墙的两侧,也仿佛宣告着分裂时代即将结束,柏林墙终将被历史推倒。

另一场同样载入史册的演讲,则是肯尼迪在1963年发表的“我是柏林人(Ich bin ein Berliner)”

在冷战时期的西方世界,关于柏林最著名、最具象征意义的两场演讲,正是这两场。


第二个景点是,国会大厦。

德国国会大厦 Reichstag 可以免费参观,有中文的audio guide,需要先使用护照预约,安检比较严格。

我的欧洲生活 - 德国篇

前方那个圆顶的建筑就是国会大厦。

在参观过程中,可以了解到德国的政治体制、德国的建筑与历史变迁等内容。顶楼可以俯瞰柏林


去柏林,一定要去柏林墙。在市中心的波茨坦广场,可以看到柏林墙的一部分。

波茨坦广场在二战前,是柏林最繁华的商业中心。随着冷战拉开帷幕,这里被柏林墙分隔,变成了无人区。

我的欧洲生活 - 德国篇

波茨坦广场往另一边走,会有犹太人屠杀纪念碑

我的欧洲生活 - 德国篇

柏林墙虽然在城市很多地方都有遗址,但是最出名的还是东区画廊。在柏林墙倒塌之后,有许多艺术家来此作画,其中最知名的就是《兄弟之吻》

我的欧洲生活 - 德国篇

穿越墙壁的汽车,可能是在象征着东德人们追求自由、不屈不挠的翻墙精神

我的欧洲生活 - 德国篇

在东区画廊附近,我遇见了一群穿着反光衣的人,他们用胶水把自己的手粘在马路上

我的欧洲生活 - 德国篇

刚开始我还不懂,不知道他们在做什么,德语我也看不懂。

德国警察还挺细心的,给抗议者的手除胶,然后带走。有一个维持秩序的女警察甚至还用英文提醒我靠边走,别离马路中心太近

后来问了同事才知道他们是德国的气候抗议活动。


继续往另一边走,可以去参观查理检查站。查理检查站是冷战时期东西柏林之间最重要的边境通道之一,在很多电影游戏中都可以看到。

我的欧洲生活 - 德国篇

牌子上写着醒目的大字:“YOU ARE LEAVING THE AMERICAN SECTOR.”

哨岗另一侧堆着沙袋墙,常有人放下鲜花,或许是在纪念那些为自由倒下的人。

这里还能遇见穿军装的“美军”或“苏军”,其实是街头演员,合影后打赏几欧元就行。


德国人的英文虽不如荷兰人流利,但日常沟通起来问题不大。和朋友买Pixel手机的时候,可以感受到那个店员在努力憋英文出来😂虽然场景既认真又有些搞笑,但是 Guten Tag, danke!

柏林是一座让历史与现实并行的城市——喜欢冷战故事的人,一定要来。

 


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/europe-de.html

我的欧洲生活 – 荷兰篇

2025年10月17日 14:26
注意:本文可能包含部分不适合未成年人的内容。如果你未满足所在国家的法定成年年龄,请酌情阅读。

走,我们去河南玩!

如果你认识一位荷兰朋友,你最好不要说他来自Holland(荷兰),而要说 Netherlands(尼德兰)。

荷兰是一个位于西欧的低地之国,北荷兰省和南荷兰省是整个荷兰的经济中心。这两个省份是荷兰12个省之一。如下图所示:

我的欧洲生活 - 荷兰篇

这就像你用英格兰泛指英国一样,是以偏概全的说法。所以还是用尼德兰更好,当然除非你在明确指明荷兰省,那么没问题。

提到荷兰,我的第一印象是17世纪荷兰海上贸易非常发达,荷兰在全球贸易和海上运输方面的主导地位,因此被称作“海上马车夫”。

由于海运非常发达,荷兰流传着“飞翔的荷兰人”的海上传说。这个传说讲述了一艘无法靠岸、永远在海上漂泊的幽灵船,象征着荷兰丰富的海洋历史和航海传统。

电影《加勒比海盗》里有“飞翔的荷兰人”的说法,应该就是从这里来的。并且很显然荷兰皇家航空很喜欢这句话

我的欧洲生活 - 荷兰篇

可千万别说荷兰什么破国家,500年前是小渔村,人家四五百年前正是要崛起的时候,也就是荷兰的黄金时代

在思科实习的时候,我的同事移民到了荷兰鹿特丹。正好在2023年的时候我有8天年假,于是就约了一下。

虽然之前有过出国的经历,但是坐飞机还是第一次。由于是申根区之内的旅行,和国内旅行没差别,带上护照和居留卡,拿着电子登机牌就可以。

 

下飞机的时候,走无申报通道就行。一下飞机,我就觉得这阿姆斯特丹这地方混杂着英式美式英语,火车上的乞丐都是先说英文再说荷兰语。我的英文还没有一个乞丐说得好😂

从斯基浦机场到阿姆斯特丹市中心要做NL的火车,黄蓝配色非常亮眼。荷兰的火车有闸机,不像瑞典那样直接上就行。机器上买纸质票贵1欧元。

我的欧洲生活 - 荷兰篇

手机App可以扫码,节约1欧元。Sprinter站站停很慢,IC Direct是直达车快很多

我的欧洲生活 - 荷兰篇

 

众所周知,荷兰常被认为是一个“黄赌毒全合法”的国家。

但实际上,这种说法存在不少误解。

黄色(性产业):

在阿姆斯特丹著名的红灯区 De Wallen(德瓦伦),性产业是公开且受政府监管的合法区域,已成为城市的一部分文化景观。

不过在其他城市,这类区域并不常见,大多规模较小或管理更为严格。

下图就是经过德瓦伦时拍的一张照片,走过的时候,一股尿骚味,感觉很差劲……

我的欧洲生活 - 荷兰篇

赌博:

荷兰的赌博业同样合法化且受政府监管。

大型赌场(如 Holland Casino)主要集中在阿姆斯特丹、鹿特丹、海牙等大城市,小城市则往往只有线上或少量实体赌场可供娱乐。

毒品:

所有硬性毒品(如可卡因、冰毒等)在荷兰依然严格禁止。

只有软性毒品(如大麻)在政府许可的 coffee shop 内可少量购买与使用。

这类 coffee shop 在阿姆斯特丹最为常见,而在其他城市数量有限,比如我在鹿特丹就没找到。

当然了,你要是敢把这类东西带出荷兰,那就是严重的非法行为。

走在阿姆斯特丹的街道上, 如果你闻到了一股呛鼻子的味道,那别想了,就是大麻。

同时,阿姆斯特丹市政府也在提醒, 不能从贩子手上买毒品,那是非法的。

我的欧洲生活 - 荷兰篇

来都来了, 在荷兰,想要吸蘑菇,其实一般是迷幻蘑菇/松露,满大街都能看到标志是个🍄的店铺(主要在阿姆斯特丹)

我的欧洲生活 - 荷兰篇

价格根据不同的品牌和店铺,从十几欧元到二十多都有。饿着肚子吃了,很难吃,并且一点效果都没有。手机的字没有变3D,我也没有变超人,很失望,很贵,很饿,很绝望。


在荷兰市中心闲逛的时候看到了一辆坦克

我的欧洲生活 - 荷兰篇

阅读旁边的文字说明了解到这是在俄罗斯入侵乌克兰期间被机会的俄军坦克。我的运气比较好,这辆坦克恰巧是最后几天展出。

牌子上写到,它象征着欧洲民主的脆弱性和韧性。……自乌克兰战争爆发以来,再次证明民主并非理所当然,而是一个易受威胁的脆弱结构。

我的欧洲生活 - 荷兰篇

如果你要去荷兰玩,一定要记得提前订票,著名的景点包括:安妮指甲、梵高博物馆、荷兰国立博物馆、尼莫科学博物馆等等。

第一次的时候我没去上,但是第二次的时候都去上啦。那些在初中美术书上看到的作品,比如梵高自画像,向日葵等等,都看到咯。

这里就是众多自画像之一

我的欧洲生活 - 荷兰篇

久负盛名的向日葵

我的欧洲生活 - 荷兰篇

国立博物馆之倒牛奶的女仆,也是非常出名的

我的欧洲生活 - 荷兰篇


除了阿姆斯特丹外,鹿特丹也是非常值得玩、且非常特殊的一个城市。从阿姆斯特丹去鹿特丹,坐NL的火车就行了

一般来说,欧洲的城市往往都是教堂,护城河,公园,美术馆,满大街都可以看到各种老旧风格的建筑。

但是鹿特丹这个城市不一样。我第一次听说鹿特丹,还是在游戏《战地5》里,这张地图被DICE宣传了很多遍

我的欧洲生活 - 荷兰篇

在二战期间,德国迅速占领了荷兰。为了迫使荷兰尽快投降并为后续攻入其他国家做准备,德国空军对鹿特丹进行了大规模轰炸。鹿特丹作为欧洲第一大港,对于德国具有重要的战略意义。此次轰炸几乎将这座城市夷为平地。仅仅用了五天时间,荷兰便被迫宣布投降。

战后,各国政府纷纷主导城市重建工作,大多选择在原有的基础上按照原来的样子重建。然而,鹿特丹则与众不同。由于这座城市几乎被夷为平地,市政府开始全面自我革新,兴建了各种奇形怪状的建筑。这种独特的重建方式使鹿特丹成为了现代建筑的标志性城市。

比如这个,是鹿特丹中央车站

我的欧洲生活 - 荷兰篇

下面这个黄色的叫做方块屋

我的欧洲生活 - 荷兰篇

威廉姆斯大厦(Willemswerf Building),成龙在电影《我是谁》里跳下来的那个大厦。方块屋在电影里也有。

我的欧洲生活 - 荷兰篇

电影里的场景

我的欧洲生活 - 荷兰篇

走在鹿特丹的大街上,到处都有这种奇奇怪怪的建筑,仿佛建筑师是在玩MineCraft一样

我的欧洲生活 - 荷兰篇

我的欧洲生活 - 荷兰篇

鹿特丹比较有名的地方是Markthal 一个大商场。外形很奇特,像马蹄一样。内部包含一个大型食品市场、商店和餐馆,外部是公寓。

我的欧洲生活 - 荷兰篇

其独特的设计和内部的彩绘天顶,其实非常漂亮,使其成为鹿特丹著名的地标和旅游景点之一。

我的欧洲生活 - 荷兰篇

我在这个商场里买到了煎饼果子,味道很不错,非常满足。

我的欧洲生活 - 荷兰篇

鹿特丹的整体风貌与传统的欧洲城市截然不同,充分展现了其作为国际都市的奇葩魅力。连CoCo奶茶店也在这里开设了分店😂,就是价格十分感人 我的欧洲生活 - 荷兰篇


荷兰另一个值得去的城市是海牙荷兰语Den Haag / 英文The Hague

从鹿特丹乘坐火车可以继续前往海牙。虽然阿姆斯特丹是荷兰的首都,但实际上海牙才是政治中心。荷兰政府、驻外使馆、国际法院和军事法庭等重要机构都设在海牙。

我的欧洲生活 - 荷兰篇

著名的《戴珍珠耳环的少女》原作也收藏在海牙的毛里茨皇家美术馆(Mauritshuis)。这幅画是约翰内斯·维米尔的杰作,可以说是这个美术馆的镇馆之宝了。 我的欧洲生活 - 荷兰篇

海牙海牙,自然有海(北海,北大西洋的一部分)

我的欧洲生活 - 荷兰篇

海牙也要有牙🦷🦷🦷🦷就在这里🦷🦷🦷🦷👈

非常不幸的是,晚上想返回位于阿姆斯特丹机场附近的酒店时,荷兰铁路系统出了问题。最终,我被迫在莱顿下车,不得不打了一辆Uber回到酒店,花费了50欧元。幸运的是,后来荷兰铁路公司报销了这笔费用。

我的欧洲生活 - 荷兰篇


第三次去荷兰的时候,去了尼莫科学博物馆,这个有一点像上海科技馆,比较适合小孩子玩。那些复杂的物理概念,哪怕是中文我都看不懂,在这里就只能当作重新读高中了。

博物馆旁边有一条河,河对面的建筑是Booking在荷兰的总部。要是能到这里上班就好了。

我的欧洲生活 - 荷兰篇

想起来了之前在VMware接触到的第一位同事(面试官),后来就跳槽去了Booking


综合体验下来,我觉得荷兰更好一些。天气舒服,美食也多,而且感觉他们的英文水平甚至比瑞典人还要好。

当然了,我听说在荷兰和比利时交界处有个神奇的小镇——巴勒-纳骚(Baarle-Nassau)。那儿有栋著名的房子,只要一脚跨过去,就从荷兰到了比利时。整座小镇被国界线切得七零八落,连咖啡馆和民居都一半属荷兰、一半属比利时,地上还画着白色的边界线,特别有趣。

我的欧洲生活 - 荷兰篇

可惜我既没车、又没欧盟驾照,还不太敢开……

所以没去成那地方,没能像视频里那些人一样,一脚在荷兰、一脚在比利时,轻松实现“跨国旅行”😆


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/europe-nl.html

我的欧洲生活 – 丹麦篇

2025年10月16日 11:21

理论上讲,丹麦才是我来到的第一个国家。当时的航班是从上海浦东出发的SK998,落地丹麦哥本哈根卡斯楚普机场。

在瑞典呆了几个月,尽管哥本哈根就在边上,直到2023年3月底我才去玩。

想从马尔默去哥本哈根,最经济实惠的办法便是坐火车。从马尔默的Triangeln 车站坐车到København H(哥本哈根市中心),在Skånetrafiken app上买票就行。

马尔默和哥本哈根之间隔着海,两座城市之间通过Øresund Bridge连接起来。

我的欧洲生活 - 丹麦篇

从飞机上俯瞰, Øresund Bridge的结构极为独特:从瑞典的马尔默一侧出发,桥梁在海平面上延伸,上层是公路、下层是铁路。

跨过海面后,桥梁抵达一座名为 Peberholm(佩贝霍尔姆) 的人工岛。这座岛是为了连接大桥与海底隧道而专门修建的,同时也成为自然生态的实验场。

过了岛后,大桥直接一头扎向海底。

值得一提的是,Peberholm 人工岛并不对公众开放,只有少数科研人员获准登岛,用于观察自然生态的自我演化过程。由于人类干预极少,这座岛如今已成为鸟类、昆虫和植物的天然天堂。

坐火车很容易,算好时间到车站,一如既往没有安检也没有检票,找到öresundståg 上车就行。因为穷买不起一等座,所以找二等座非预留的位置坐下就行。错过的话也没关系,再等几分钟就有了。 我的欧洲生活 - 丹麦篇

到达哥本哈根市中心大概要30-40分钟,期间可能会有人来查票。作为守法好公民上一定要买票的,逃票罚款好几千。

哥本哈根有一条护城河,护城河可能是大部分欧洲城市的特点之一,所以可以租大船小船。我一个单身狗就没必要这么善待自己了

我的欧洲生活 - 丹麦篇

在丹麦购物有一点好处,那就是不用计算汇率,因为DKK和CNY几乎是一比一的;相反,在瑞典购物也有一点好处,所有的商品在我的眼中全部都是7折!

丹麦也几乎是无现金社会了,但是部分地方仍然可以使用现金。我把我从国内带过去的DKK花掉了,得到的克朗的硬币长这样

我的欧洲生活 - 丹麦篇

丹麦的整体收入和消费要比瑞典高一些。具体来说就是,大家的数字是一样,但是单位一个是DKK一个是SEK😂

下面这张图片是坐火车时拍到的彩色房子,很漂亮

我的欧洲生活 - 丹麦篇

哥本哈根,或者说整个丹麦吧,最著名的景点一定是小美人鱼了。可惜直到一年多之后,我才去看过。因为要办英国签证,需要到哥本哈根提交指纹和照片,于是就顺路去了

我的欧洲生活 - 丹麦篇

当然少不了买一张明信片然后寄回去!

我的欧洲生活 - 丹麦篇

“小美人鱼”雕像无疑是最具有国际知名度的丹麦地标。如果有幸的话,在2010年上海世博会期间,可能你也见过小美人鱼的原貌。丹麦政府把原版的小美人鱼(不是复制品)借给了上海世博会,展出结束后又回到了港口。

小美人鱼所在的那个港口,也特别美

我的欧洲生活 - 丹麦篇

蓝天大海,走在去小美人鱼的路上,还有着跑步的人跟你喊,Walk on the right!

说到丹麦,很多人可能没什么特别的印象,只觉得那是个欧洲北部的小国。
但其实,丹麦的经济实力相当强,历史地位也不容小觑。维京人听说过吧?

几百年前,在“卡尔玛联盟”时期,丹麦可是北欧的真正霸主。后来随着瑞典的崛起、挪威的分离,丹麦逐渐衰落,甚至还被英国人“偷家”——1807年,哥本哈根战役中,英军直接把丹麦的舰队烧了个干净。

网上有个很有名的段子:

1807年,丹麦海军在哥本哈根战败后,为了重建舰队,种下了9万棵橡树;

2007年,皇家林业局的继任部门——丹麦自然局,郑重通知国防部:

“为了重建海军而种下的那9万棵橡树,已经准备就绪了。”

这事儿听起来像玩笑,但其实差不多是真的 😂。

还有一个趣闻,说在瑞典的斯科讷省(Skåne)——也就是历史上丹麦失去的领土——有一群人每年都会在特定的一天拿着铁锹挖地,象征性地想“把斯科讷从瑞典挖开来”,重新“并回丹麦”。
(斯科讷的瑞典语确实带着一股浓浓的丹麦味。)

TIL There is a group in Sweden that every year gather to dig so that a part of Sweden called Skåne wont be attached to Sweden and can be sent to Denmark or Germany
byu/korkad intodayilearned

现在的丹麦,人均GDP大概有70000多美元。非常恐怖了

乐高听说过吧?把塑料块卖给了全世界的丹麦公司。马士基,那个船运集装箱的也是丹麦的公司。

我的欧洲生活 - 丹麦篇

糖尿病护理公司诺和诺德也是丹麦公司,这公司市值高到可怕,从市值的角可以说是富可敌国(丹麦),从年销售额的角度可以说是丹麦经济的中流砥柱。他们还出了几款减肥药,并且在中国获批了。当然了这是处方药……

说起来诺和诺德,我高中时期坐在我前面的女孩子,她的爸爸好像就是在这里工作。因为她送我的一个笔记本,上面是 Novo Nordisk的标志……

丹麦应该是我去过的最多的外国了,因为每次要去其他国家,我都要去哥本哈根凯斯楚普机场坐飞机。CPH机场离市中心很近,火车只要20分钟左右即可到达,而且靠近欧洲中部,是北欧五国最大的机场。

小提示,丹麦在丹麦语中的拼写是Danmark,在英文中的拼写是Denmark。分不清不要怪我哦。

直到如今,我家里还有一个在丹麦便利店买的饮料剩下的瓶子。拿回去还能换1 DKK。只是很可惜我再也去不了了:-(

 


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/europe-dk.html

我的欧洲生活 – 瑞典篇

2025年10月15日 10:47

众所周知,2022年的春天上海因为新冠疫情而封城2个月,那期间我萌生起了出国的想法并付诸行动,朋友内推后,我拿到了瑞典一家小公司的offer。

6月1日封城结束,我便开始了办护照等手续。因为我还要带猫猫走,因此还一起办了猫的各种手续。

10月初收到了移民局的信,于是便订了当年感恩节,也就是11月24日的机票。

北欧航空只允许带 8kg的猫包上飞机,我做了最坏的只能带一只猫走的打算。非常庆幸的是并没有人称重,于是我成功的给两只猫都带走啦。

我的欧洲生活 - 瑞典篇

那是我第一次出国,也是第一次坐这种 3-4-3的宽体机,第一次到的国家是丹麦而不是瑞典,因为我要去的城市马尔默就在丹麦首都哥本哈根边上,去哥本哈根比去斯德哥尔摩更方便。

 

当时在哥本哈根机场还是很慌的,第一次出国谁不慌啊……

一路上猫崽子们还是挺争气的,没尿没拉也没叫,最终在当地时间早上6点多到了临时的住处。

我的欧洲生活 - 瑞典篇

当天晚上他们就适应了新的家,甚至还学会了跳高捉迷藏。猫猫们可能不知道自己飞到了地球的另一边。也许,在他们眼中有我的地方就是家。

我的欧洲生活 - 瑞典篇

在我走后没几天,中国就爆发了白纸运动,三年的清零政策就这样一瞬间解除了。那时候我还在嘲讽自己,再忍忍就好了,干嘛跑得这么快。

整个欧洲的纬度其实是比较高的,北欧的纬度就更高了,因此在冬天环境会比较恶劣。气温倒是没中国北方零下十几度那么冷,但是每天都是阴天还会下雨,日照时间大概只有六七个小时。

有的时候也会下雪,但是一般不会存住很久,不像中国北方那样。基本上就是像下图这样,对于没看过雪的南方孩子来说应该还挺让人激动的:

我的欧洲生活 - 瑞典篇

虽然我躲过了11月-12月中国的新冠感染潮,但是……躲得了初一躲不了十五啊。在1月中,大概是在看房途中,我不小心中招了

瑞典的租房和国内是完全不一样的。像我第一次去看房是找了这样的独栋别墅

我的欧洲生活 - 瑞典篇

真的很好看!房东出租一层,大概有个80平方米的使用面积吧。瑞典没有类似国内的公摊面积的说法。

瑞典的房屋租赁和国内不一样,基本上可以分为一手房和二手房。所谓的一手房,就是像政府机构、或者是政府机构认可的房地产公司租房,这种房子基本上是想住多久就住多久的,签的合同会写着 Until further notice;另外一种二手房,就是向二房东租房。

我的欧洲生活 - 瑞典篇

所有的一手房,搬进去的时候都是家徒四壁:只有厨房和卫生间是装修好的,其他地方,甚至连个灯都没有。搬走的时候也要把你的灯、家具都带走。

中招后, 头一点晚上感觉不太对劲,立刻去超市买了体温计,搭配我之前12月底准备的布洛芬。

然后去请了假。瑞典法律规定,病假第一天无薪,第二天开始每天都能够拿到80%的薪资。通常前14天是不需要任何证明的,雇主也基本不会拒绝病假申请。

我的老板还是很体贴的,知道我病了后还问我要不要帮忙买东西,毕竟他也知道我一个人才来一个多月,人生地不熟,也没有人照顾……

还是要说,生病的时候真的好难受。前三天里我就在家里躺着,发着高烧,连拿手机的力气都没有。那时候真的好无助……第四天的时候开始逐渐好转,但还是咳嗽。

痊愈之后就陷入了很深的抑郁之中,国内在过春节,我一个人还要上班,工作也不会做,找不到房子,为什么要带两只猫过来啊,没有他们我早就跑回国了吧。

事情出现转机是在2023年的1月底,运气爆棚的我竟然只排队40多天就租到了一手房。

更厉害的是竟然是新房。

我的欧洲生活 - 瑞典篇

左侧的那个树杈后面,四层阳台,就是我家啦。进电梯要按4,但是门牌号是1302而不是1402哈哈哈哈哈奇怪的瑞典人。

在中国,房屋的朝向一般是以坐北朝南为佳。瑞典不是这样的。

瑞典维度非常高,因此到了夏天日照时间会很长,更具体的说太阳会在落山的时候落得很慢,如果再靠北甚至能看到极昼。

因为这个原因,瑞典的房子以朝向西为佳。这样在夏天,到了晚上八九点阳光也可以洒满屋子。

晚霞则是更美了,下面这张照片拍摄于5月30日9点57分。

我的欧洲生活 - 瑞典篇

这个房子房间面积大概58平方米,足够我一个人和两只猫使用了。

我的欧洲生活 - 瑞典篇

家具要么捡二手,要么去宜家买。成年人的乐高确实名不虚传,我还把龙骨装反了,直到一年之后才发现😂

我的欧洲生活 - 瑞典篇

随着春天的到来,我心情也开始逐渐变好了,没有之前那么想急切回国的想法了,主要也是回不去,海关只让一人带一只猫😂。两只小崽子越来越喜欢在门前的走廊上打滚和跑来跑去

我的欧洲生活 - 瑞典篇

在过了4月1日之后,我有了上一年度的年假。瑞典法律规定每年有25天年假,我恰巧工作了4个月,于是我有8天年假。虽然不多,但是我决定要出去旅游见见世面……

瑞典的铁路运营公司叫SJ,他们的高铁外面是这样,挺丑的,而且慢

我的欧洲生活 - 瑞典篇

里面是这样,很干净整洁。而且有Wi-Fi

我的欧洲生活 - 瑞典篇

没有安检,没有检票,可能有查票的工作人员。乘客要做的事情就是,看准自己的车上去对号入座就行。

我打算跑去斯德哥尔摩玩一玩,早上坐高铁,然后夜里坐夜间火车,第二天一早就回来连酒店都不用预定。斯德哥尔摩中央车站还是很漂亮的

我的欧洲生活 - 瑞典篇

我去了瓦萨博物馆,还去了诺贝尔博物馆。这个世界上最高荣誉的奖项诺贝尔奖,便是根据瑞典发明家阿尔弗雷德·诺贝尔的遗愿所颁发的奖项。所以在瑞典也能看到很多诺贝尔街、诺贝尔广场之类的地名。

我的欧洲生活 - 瑞典篇

这应该是路过的一家喜来登酒店。

北欧五国分别为 丹麦🇩🇰、瑞典🇸🇪、挪威🇳🇴、芬兰🇫🇮、冰岛🇮🇸,从国旗中可以看到他们都是统一的北欧十字,丹麦瑞典和挪威在历史上有着更为紧密的联系。

还路过了一条小巷子,树上开着很漂亮的樱花,背后是精美的公寓楼。

我的欧洲生活 - 瑞典篇

这一趟首都之行,我觉得最大的收获是……认识了一个会说中文的瑞典人,走在路上的时候他说“你好”叫住了我。谁说瑞典人都是社恐的?毫不夸张,这家伙中文说得比我还还标准……

后来了解到,他是芬兰人,小时候就搬到了瑞典。后来对中国文化有一些兴趣,自学中文,还去中国学了几年的中文。也就是说,这家伙,会芬兰语,瑞典语、挪威语、丹麦语、英语、中文……是很厉害了。(瑞典语、丹麦语、挪威语有比较高的互通性)

夏天的时候瑞典超市会卖一些应季水果,比如樱桃🍒

我的欧洲生活 - 瑞典篇

在德国超市lidl里每年夏天都有售卖,价格确实很感人,37人民币一斤,确实让我感动的痛哭流涕。后来在另一家本土超市 ICA 发现了价格便宜还好吃的樱桃,甚至都不用自己分拣。
我的欧洲生活 - 瑞典篇

瑞典的治安算是比较好的了,你可以放心大胆的把你的背包背在身后,甚至可以把手机放在身后的背包里或是裤子的后兜里。虽然这么说,自行车是很容易被偷的:

我的欧洲生活 - 瑞典篇

我的同事们都丢过很多辆自行车,毕竟偷了你的手机,没法销赃,偷了自行车很容易就卖二手。

要说起瑞典最知名的品牌,那么一定是IKEA – 宜家了

很荣幸,我去的第一家宜家就是原厂的。在国内的时候我没去过宜家

我的欧洲生活 - 瑞典篇

这个只是一家商场里的小宜家,只卖一些日用品。

大的宜家要坐车去,还挺远,和国内的那种宜家基本差不多了。

我的欧洲生活 - 瑞典篇

那种在网上看到的精心装修布置的样板房自然也有

我的欧洲生活 - 瑞典篇

瑞典还有一些其他知名的品牌,比如沃尔沃,H&M,爱立信,Spotify,Electrolux(一个卖冰箱等白色家电的公司,国内可能不太常见)

和宜家一样,我也是出国之后才去了H&M,那当然无论身在何处都要支持我老婆的事业了!

我的欧洲生活 - 瑞典篇

直到来到了瑞典,我才知道,原来我之前最喜欢的游戏《战地4》其开发商 DICE 就是瑞典的公司;2021年最佳游戏《双人成行》也是瑞典公司。甚至连像素游戏《我的世界》也是瑞典的……

在瑞典的生活,虽然很……可以说无聊吧,但是胜在悠闲自在。虽然这么说,一个人还是太孤独了,最终于2024年底我决定回国。文化、饮食,和可能的爱情,是我回国的三大原因。

现在想想,这两年多的海外旅居我还是有些事情后悔没去做,比如说……

  1. 我应该继续缴纳国内的社保(灵活就业),为可能回国的事情准备一个退路
  2. 应该趁这机会随便读一个硕士,这样就算纯正的海归,像我这种工作后回来的其实什么都不算

转念一想,也许我不应该回来。没了工作,没了爱情,没了退路。如果我没回国,只要再坚持坚持,就可以拿到永居甚至是入籍了。

但是人生就是这样,没有回头路,塞翁失马焉知非福。但是,无论如何,我想说,这一趟,不亏。这两年半的旅居生活是我的目前人生中的闪光点。


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/europe-se.html

绿联 AX900 CM763 无线网卡 (AIC8800 芯片) 在 Ubuntu上的驱动

2025年9月20日 09:43

由于最近要做一些libvips的性能优化,必须要在一台物理机器且带NVIDIA GPU的机器上运行。Windows上用libvips很痛苦,于是就给自己家的二手捡垃圾PC装上了 Ubuntu 24.04

安装好之后,自然是没有无线网卡驱动的,我的无线网卡是捡垃圾的UGREEN AX900 CM763,还好从官网搜到了驱动 https://www.lulian.cn/download/154.html

本来以为,编译驱动什么的就是个10分钟的小事情,make && make install && modeprobe xxxx

但是这次没想到,一晚上都没了……🫠

编译驱动

根据官方的pdf说明

bash install_setup.sh
cd drivers/aic8800
make
make install

modprobe cfg80211
modprobe aic_load_fw
modprobe aic8800_fdrv

理论上就应该能看到无线设备了!

但是……2025年,我猜你很可能遇到……

could not insert 'aic_load_fw': Key was rejected by service

说明你启用了安全启动,要么禁用安全启动(小心你的Windows可能启动不了),要么自己创建一份key 然后 enroll

给驱动增加签名

由于开启了安全启动,内核默认会拒绝加载没有签名的ko文件,所以我们要自己创建key,导入,然后去签名ko

创建MOK

sudo apt install mokutil
openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv -outform DER -out MOK.der -nodes -days 36500 -subj "/CN=WiFi/"
mokutil --import MOK.der

之后会让你输入个密码,然后reboot重启系统,会看到这样类似蓝色的界面

绿联 AX900 CM763 无线网卡 (AIC8800 芯片) 在 Ubuntu上的驱动

Enroll后,继续启动进入系统,输入 mokutil --list-enrolled 看是否能够看到创建的issuer信息,如果能够看到,那就可以了

编译模块并签名

总之无论如何,肯定已经编译出来了两个ko文件,并且key已经enroll了

那么就要签名,让内核能够加载这个模块,要注意两个内核模块都要签名哦,路径自己改改

sudo /usr/src/linux-headers-$(uname -r)/scripts/sign-file sha256 \
~/Desktop/MOK.priv \
~/Desktop/MOK.der \
aic8800_fdrv/aic8800_fdrv.ko

sudo /usr/src/linux-headers-$(uname -r)/scripts/sign-file sha256 \
~/Desktop/MOK.priv \
~/Desktop/MOK.der \
aic_load_fw/aic_load_fw.ko

签名成功,可以用modinfo确认一下

modinfo aic8800_fdrv/aic8800_fdrv.ko | grep signer
modinfo aic_load_fw/aic_load_fw.ko | grep signer

有输出内容,能够看到签名信息,就说明正确了

此时就可以make install了,一定不要忘记了这步

加载内核模块

modprobe cfg80211
modprobe aic_load_fw
modprobe aic8800_fdrv

然后lsmod看看呢?

lsmod | grep aic
aic8800_fdrv 696320 0
cfg80211 1433600 1 aic8800_fdrv
aic_load_fw 90112 1 aic8800_fdrv

然而你ifconfig还是可能,看不到无线网卡,插拔也看不到😂

dmesg 说firmware failed to open

看看dmesg

dmesg | grep -i aic

你会惊喜的发现

[ 1.233597] usb 5-2: Product: AIC Wlan
[ 1.233599] usb 5-2: Manufacturer: aicsemi
[ 3.930973] aic_load_fw: loading out-of-tree module taints kernel.
[ 3.932153] aic_bluetooth_mod_init
[ 3.932158] AICWFDBG(LOGINFO) aicwf_prealloc_init enter
[ 3.937597] AICWFDBG(LOGINFO) pre alloc rxbuff list len: 1000
[ 3.937638] AICWFDBG(LOGINFO) aicwf_usb_probe vid:0xA69C pid:0x8D80 icl:0x0 isc:0x0 ipr:0x0
[ 3.937644] AICWFDBG(LOGINFO) aicloadfw_chipmatch USE AIC8800D80
[ 3.937648] Aic high speed USB device detected
[ 3.938438] aic_load_firmware :firmware path = /lib/firmware/aic8800D80/fmacfw_8800d80_u02.bin
[ 3.938734] aic_load_firmware: fmacfw_8800d80_u02.bin file failed to open
[ 3.938742] aicwf_bus_deinit
[ 4.439093] usb_err:<aicwf_usb_rx_submit_all_urb,234>: bus is not up=0
[ 4.439118] usb_err:<aicwf_usb_rx_submit_all_urb,234>: bus is not up=0

又惊喜又意外是不是,还需要一个firmware,还好他的压缩包里有,全给复制进去

file aic8800_linux_driver/fw/aic8800D80/fmacfw_8800d80_u02.bin
# 输出为 aic8800_linux_driver/fw/aic8800D80/fmacfw_8800d80_u02.bin: data

sudo mkdir -p /lib/firmware/aic8800D80
cp aic8800_linux_driver/fw/aic8800D80/* /lib/firmware/aic8800D80/

重新加载内核模块,这次应该可以了吧?

sudo modprobe -r aic8800_fdrv
sudo modprobe -r aic_load_fw
sudo modprobe aic_load_fw
sudo modprobe aic8800_fdrv

dmesg wrong size of firmware file

尽管上一步我们已经modeprobe了,但是dmesg中看到……

[ 591.951845] usbcore: registered new interface driver aic8800_fdrv
[ 0.458630] Spectre V2 : Enabling Speculation Barrier for firmware calls
[ 0.640535] ACPI: [Firmware Bug]: BIOS _OSI(Linux) query ignored
[ 0.667872] acpi PNP0A08:00: [Firmware Info]: ECAM [mem 0xf0000000-0xf7ffffff] for domain 0000 [bus 00-7f] only partially covers this bridge
[ 3.621969] systemd[1]: Mounting snap-firmware\x2dupdater-167.mount - Mount unit for firmware-updater, revision 167...
[ 3.636882] systemd[1]: Mounted snap-firmware\x2dupdater-167.mount - Mount unit for firmware-updater, revision 167.
[ 3.938438] aic_load_firmware :firmware path = /lib/firmware/aic8800D80/fmacfw_8800d80_u02.bin
[ 3.938734] aic_load_firmware: fmacfw_8800d80_u02.bin file failed to open
[ 3.938740] wrong size of firmware file
[ 4.334041] nouveau 0000:0b:00.0: pmu: firmware unavailable

啊这?虽然我不懂代码,但是看起来这些的没问题啊,<=0 嘛
绿联 AX900 CM763 无线网卡 (AIC8800 芯片) 在 Ubuntu上的驱动

更新下 initramfs 应该就好了

sudo update-initramfs -u

然后重启系统,然后继续……

sudo modprobe -r aic8800_fdrv
sudo modprobe -r aic_load_fw

sudo modprobe aic_load_fw
sudo modprobe aic8800_fdrv

继续重新加载内核模块

但是此时,好像Wi-Fi还没出啦啊?

更改设备模式

lsusb可以看到

Bus 005 Device 003: ID a69c:5724 aicsemi Aic MSC

然后如果你细心打开过文件管理器,或者 df 了一次,还会发现

/dev/sdb1 3808 3195 613 84% /media/benny/UGREENs

甚至是如果你仔细看了上面的dmesg high speed USB device detected

[ 3.937644] AICWFDBG(LOGINFO) aicloadfw_chipmatch USE AIC8800D80
[ 3.937648] Aic high speed USB device detected
[ 3.938438] aic_load_firmware :firmware path = /lib/firmware/aic8800D80/fmacfw_8800d80_u02.bin
[ 3.938734] aic_load_firmware: fmacfw_8800d80_u02.bin file failed to open

系统把这个识别成了U盘😂 记得在Windows下使用也是直接多了个U盘,里面有个setup.exe

只不过这个设备不是用的 usb_modeswitch,而是udev,并且其实在install_setup.sh中已经提示了

驱动源代码中搜索可以看到如下内容

KERNEL=="sd*", ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="5721", SYMLINK+="aicudisk", RUN+="/usr/bin/eject /dev/%k"
KERNEL=="sd*", ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="5723", SYMLINK+="tendaudisk", RUN+="/usr/bin/eject /dev/%k"
KERNEL=="sd*", ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="5724", SYMLINK+="ugreenudisk", RUN+="/usr/bin/eject /dev/%k"

那么创建一个文件 /etc/udev/rules.d/99-aic-udisk.rules

加入如下内容(或者你想加了上面三行,也行)

KERNEL=="sd*", ATTRS{idVendor}=="a69c", ATTRS{idProduct}=="5724", SYMLINK+="ugreenudisk", RUN+="/usr/bin/eject /dev/%k"

然后运行如下两条命令

sudo udevadm control --reload-rules
sudo udevadm trigger

重新插拔网卡,并lsusb

root@vips:~# lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 001 Device 003: ID 05e3:0608 Genesys Logic, Inc. Hub
Bus 001 Device 004: ID 1c4f:0043 SiGma Micro USB Keyboard
Bus 001 Device 005: ID 10c4:0005 Silicon Labs USB OPTICAL MOUSE
Bus 001 Device 011: ID 368b:8d88 AICSemi AIC 8800D80

wlx6c1ff7557a1b: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether 6c:1f:f7:aa:aa:aa  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

哦吼,出现了 🙃真不容易,不愧是开源拖拉机😂 赶紧备份一下整个分区……🫠

但是你以为这就完了吗?没有……

固定内核版本

由于不是dkms,如果某一天一个apt upgrade,然后升级了内核,那么就会发现,咦,我Wi-Fi呢?所以我们要固定一下内核版本,避免出现这种问题

sudo apt-mark hold linux-image-$(uname -r) \
linux-headers-$(uname -r) \
linux-modules-$(uname -r) \
linux-modules-extra-$(uname -r)

🤣 怎么感觉好像工作量全部在配置环境上了

🤔 要是使用 Windows + VM + 直通GPU会不会好一点?总之😂 太痛苦了,没想到竟然被一个无线网卡硬控了4个小时


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/aic8800-ubuntu.html

把 Cloudflare WARP 转换为 http 代理

2024年11月3日 20:53
这篇文章在 2025年05月27日17:43:28 更新了哦~

Cloduflare WARP真的非常好用,并且支持代理模式。启用这个模式之后,它会在本机监听一个socks端口,应用程序可以配置到这个端口来使用代理。

对于服务器来说,一般使用 warp-cli

warp-cli proxy port 60606
warp-cli mode proxy

之后你就可以 curl

https_proxy=socks5://127.0.0.1:60606 http_proxy=socks5://127.0.0.1:60606 curl ipv4.win
IP: 104.28.157.116 CLOUDFLARE.COM CLOUDFLARE.COM

然而,Go的程序不支持 socks 代理,要手动加transport我可没那个功夫去加。

好消息是,Go默认是尊重环境变量http_proxy的。那么就要想办法给socks代理转换为http代理

这事很简单嘛!用pproxy就好了,别的不会,这个肯定很会!

pproxy -v -l http://127.0.0.1:8118 -r socks5://127.0.0.1:60606

https_proxy=http://127.0.0.1:8118 http_proxy=http://127.0.0.1:8118 curl ipv4.win
curl: (52) Empty reply from server

# pproxy logs
Serving on ipv? 127.0.0.1:8118 by http
http 127.0.0.1:45012 -> socks5 127.0.0.1:60606 -> ipv4.win:80
Unknown remote protocol from 127.0.0.1

把 Cloudflare WARP 转换为 http 代理

怎么报错了呢🤨

可能是 pproxy的问题,那么用gost

gost -L http://127.0.0.1:8118 -F socks5://127.0.0.1:60606
2024/11/03 12:32:43 route.go:700: http://127.0.0.1:8118 on 127.0.0.1:8118
2024/11/03 12:32:46 http.go:162: [http] 127.0.0.1:33284 -> http://127.0.0.1:8118 -> ipv4.win:80
2024/11/03 12:32:46 http.go:257: [route] 127.0.0.1:33284 -> http://127.0.0.1:8118 -> 1@socks5://127.0.0.1:60606 -> ipv4.win:80
2024/11/03 12:32:46 http.go:280: [http] 127.0.0.1:33284 -> 127.0.0.1:8118 : unexpected EOF

把 Cloudflare WARP 转换为 http 代理

那……Privoxy

forward-socks5 / 127.0.0.1:60606 .

也不行!

甚至直接在 Firefox里设置socks5代理,也不行🤨

把 Cloudflare WARP 转换为 http 代理

任何网站都打不开

把 Cloudflare WARP 转换为 http 代理

偶然取消 DNS请求的勾选,就成功了……突然恍然大悟,WARP可能不支持远程解析DNS

 

那么要么用回 socks4

pproxy -v -l http://127.0.0.1:8118 -r socks4://127.0.0.1:60606
gost -L http://127.0.0.1:8118 -F socks4://127.0.0.1:60606

要么给加上DNS的支持

gost -L "http://127.0.0.1:8118?dns=1.1.1.1" -F socks5://127.0.0.1:60606

人生中宝贵的几个小时就这么没了。

 

需要注意:warp-cli的proxy mode 不支持超过10秒的连接

Proxy mode has a timeout limit of 10 seconds for requests. If a request goes above the 10 second limit, Cloudflare will drop the connection.

遇到这种情况,要么不用proxy mode, 要么在 gost或pproxy那边绕过,如

gost -L http://127.0.0.1:8118 -F socks4://127.0.0.1:60606?bypass=api.openai.com

文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/warp-http-proxy.html

用 Azure Container Apps 运行PHP网站

2024年10月12日 15:10

既然是容器,那么也可以跑数据库的吧?那只要再加上一个PHP,就可以跑 WordPress、Typecho之类的应用了!

在 Azure上,用 Container Apps也是可以做到serverless PHP应用的,具体来说就是:

一个pod里开两个容器(不太推荐这种方式),一个是php-apache,一个是MySQL,通过挂载 Azure Files作为volumes来做数据存储。

为什么想这么玩,是因为:

  1. 优化大陆访问,Azure的亚洲机房对大陆访客很友好,因此不能用 Cloudflare
  2. 不想自己管理SSL证书,因此不能用vm
  3. managed database太贵了用不起
  4. FrontDoor也太贵了,每个月要$35的保护费🥹

使用这种方案的好处是,在访问量不是很大的情况下,成本应该可以忽略不计,甚至可以在无访问时缩放到0副本,真正“无服务器” 😂

创建资源组

创建资源组,创建一个vnet,因为存储不想公开给所有人,也方便以后和其他虚拟机内网互通。

创建容器应用

创建的时候,Container Apps Environment 选择已有的虚拟网络

用 Azure Container Apps 运行PHP网站

配置镜像,这个镜像是我自己构建的,基于 php:8.3-apache,支持MySQL、PostgreSQL、SQLite,添加了 mod_rewrite,足够给WordPress和Typecho用

用 Azure Container Apps 运行PHP网站

创建存储

Primary Service选择 Azure Files

用 Azure Container Apps 运行PHP网站

创建 File Shares

用 Azure Container Apps 运行PHP网站

我创建了两个,一个是数据库的,一个是网站文件的

用 Azure Container Apps 运行PHP网站

连接 volume到容器

在创建完存储后,去复制一下access key

用 Azure Container Apps 运行PHP网站

在容器环境中添加 SMB

用 Azure Container Apps 运行PHP网站

依次输入信息,这里没有补全,不要输入错了哦

用 Azure Container Apps 运行PHP网站

在容器环境中创建完之后,就可以到容器应用中添加啦

用 Azure Container Apps 运行PHP网站

这里需要注意,数据库的卷可能需要如下额外挂载参数

uid=999,gid=999,nobrl,mfsymlinks,cache=none

容器应用volume

编辑容器,添加volume就可以了

用 Azure Container Apps 运行PHP网站

然后添加一个数据库的sidecar

用 Azure Container Apps 运行PHP网站

如果需要配置环境变量,也可以一起配置了

用 Azure Container Apps 运行PHP网站

最终结果是这样的两个容器,在一个pod里,我这种穷人自然只能选择最低配置啦

用 Azure Container Apps 运行PHP网站

存储安全

默认存储是可以公网访问的(需要用户名密码),为了更安全,我们可以配置为只有某几个虚拟网络可以访问

用 Azure Container Apps 运行PHP网站

配置应用

可能需要进入 console,执行一下创建数据库之类的操作,这点就自行发挥了。

最终结果

在不使用的时候,容器可以缩为0(可以配置为最低1副本)

用 Azure Container Apps 运行PHP网站

在有请求的时候,真的能访问耶,而且还可以自动扩容!

用 Azure Container Apps 运行PHP网站

总结

正常的应用不应该这样配置的,两个容器在同一个pod内耦合也是不理想的。

如果想要利用 Azure的优良线路,正常来说应该选择如下方法之一

  1. Azure VM,自己配置SSL证书,域名解析到这个IP,最经典的操作
  2. Azure FrontDoor + 任意VM,SSL证书可以由FrontDoor管理
  3. Azure应用网关 + 任意VM,解析域名到网关的IP,配置SSL证书,然后上传证书给应用网关
  4. Azure Load Balancer + 任意VM,证书需要在VM上配置好,因为LB是4层的
  5. Container Apps运行PHP,用托管数据库,Container Apps支持托管SSL
  6. Container Apps运行PHP,通过VM运行数据库,两者网络之间创建对等连接
  7. Static Web Apps,只能给静态站点用
  8. Web App + Database,完全托管,价格很感人
  9. Azure Kubernetes Service ,这个我可能不太懂,但是应该可行……

最后……

PHP用什么跑不好要用这个,真是只有真正的赛博精神病才能够想出来这种操作🤡


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/aca-php.html

使用 Cloudflare Worker获取图片元数据

2024年9月28日 17:56
这篇文章在 2024年10月05日23:31:04 更新了哦~

WebP Cloud 提供一个接口,可以用于获取图片的元数据,比如长宽、大小、色彩空间以及blurhash。这部分计算,尤其是blurhash其实还是略有压力的,我们就想能不能把这部分功能放到回源请求上。

对于 Azure Function来说,这必然不是问题,因为他几乎就是一个完整的NodeJS环境,可以用sharp的;但是对于Workers,由于他是V8,只支持NodeJS最基本的一些API,最多带上Wasm,这样用sharp就变成了几乎不可能的事情,因为要调用libvips。

最终经过了我的艰苦探索,发现一个名为 @cf-wasm/photon 的库,可以用来获取图片基础信息。首先需要 import

import { PhotonImage, SamplingFilter, resize } from '@cf-wasm/photon';

用起来也还行,首先我们需要通过 fetch获取图片,得到一个response,可以从这里拿到 ArrayBuffer

const response = await fetch(url)
const buffer = await response.arrayBuffer();

然后PhotonImage 需要Uint8Array,那先转换一下

const inputBytes = new Uint8Array(buffer);

然后加载图片

const inputImage = PhotonImage.new_from_byteslice(inputBytes);

宽高可以用 inputImage.get_width()inputImage.get_height()

色彩空间可以用 inputImage.get_image_data().colorSpace

文件大小直接 buffer.length就行

计算 blurhash建议先调整图片大小,毕竟 Worker有执行时间限制

const resized = resize(inputImage, 32, 32, SamplingFilter.Nearest);

然后计算

const blur = encode(resized.get_raw_pixels(), resized.get_width(), resized.get_height(), 4, 4);

至于图片格式,那只能靠magic header了,比如使用如下ChatGPT给我的神奇代码

function getImageFormatFromArrayBuffer(arrayBuffer) {
	const uint8Array = new Uint8Array(arrayBuffer);

	// Check for PNG (first 8 bytes: 89 50 4E 47 0D 0A 1A 0A)
	if (
		uint8Array[0] === 0x89 &&
		uint8Array[1] === 0x50 &&
		uint8Array[2] === 0x4e &&
		uint8Array[3] === 0x47 &&
		uint8Array[4] === 0x0d &&
		uint8Array[5] === 0x0a &&
		uint8Array[6] === 0x1a &&
		uint8Array[7] === 0x0a
	) {
		return 'png';
	}

	// Check for JPEG (first 3 bytes: FF D8 FF)
	if (uint8Array[0] === 0xff && uint8Array[1] === 0xd8 && uint8Array[2] === 0xff) {
		return 'jpeg';
	}

	// Check for GIF (first 6 bytes: GIF87a or GIF89a)
	if (
		uint8Array[0] === 0x47 &&
		uint8Array[1] === 0x49 &&
		uint8Array[2] === 0x46 &&
		uint8Array[3] === 0x38 &&
		(uint8Array[4] === 0x37 || uint8Array[4] === 0x39) &&
		uint8Array[5] === 0x61
	) {
		return 'gif';
	}

	// Check for BMP (first 2 bytes: 42 4D)
	if (uint8Array[0] === 0x42 && uint8Array[1] === 0x4d) {
		return 'bmp';
	}

	// Check for WebP (first 4 bytes: 52 49 46 46 and "WEBP" in bytes 8-11)
	if (
		uint8Array[0] === 0x52 &&
		uint8Array[1] === 0x49 &&
		uint8Array[2] === 0x46 &&
		uint8Array[3] === 0x46 &&
		uint8Array[8] === 0x57 &&
		uint8Array[9] === 0x45 &&
		uint8Array[10] === 0x42 &&
		uint8Array[11] === 0x50
	) {
		return 'webp';
	}

	return 'Unknown format';
}

部署

npm install @cf-wasm/photon
npm install blurhash
wrangler deploy 

就可以了。wrangler会自动打包,把依赖和wasm也一起上传上去

Azure Function

如果你使用的是Azure Function,那么事情就简单多了,直接安装并使用 sharp 就行。需要注意的一点是,Azure Function可以选择运行的环境是Linux还是Windows。所以本地也要安装好正确的sharp然后才可以部署。

npm install --cpu=x64 --os=linux sharp

详情可以参考Cross-platform

最终结果

需要注意 Workers 有内存限制,小心图片太大直接报错

原来是打算把 Worker和Function 用同一套代码库的,但是由于 Worker的限制,即使不同情况下使用不同的import,Worker还是没法兼容 Function🫠

所以只能分开两个分支了。

使用 Cloudflare Worker获取图片元数据

Cloudflare Workers 太弱智了,害我失去了人生中宝贵的三个小时使用 Cloudflare Worker获取图片元数据


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/worker-image-metadata.html

使用 InfluxDB 存储QPS数据

2024年9月27日 21:16

我跑了,换到clickhouse存了,性能更好,详见 https://dmesg.app/one-api-clickhouse.html

一个月之前做了一个 Open AI的接口站,用 locust 压力测试了一下,发现在 1C 2G 的 Hetzner Cloud 上,QPS 只能有大概20左右的样子

使用 InfluxDB 存储QPS数据

后来花了一晚上时间,一边看电视一边优化,成功把 QPS 提升到了几百,短期内应该足够用了。

在做完这个优化之后,我就一直在想那么我在正常情况下遇到的 QPS 究竟有多少?

我的应用 one-api 是用 Go 写的,是 Gin,那应该只有这几个问题:

  1. 何时加入 QPS 计算代码:通过中间件就可以了
  2. 何时写入数据库,攒够一定数量批量写入为最佳
  3. Grafana 展示数据

写入数据到 InfluxDB

写入数据很简单,我用的是 InfluxDB 1.8 所以也要使用旧版本的库,也不需要 tag 所以 nil 即可

import "github.com/influxdata/influxdb/client/v2"
 
func writeInflux() {
    c, _ := client.NewHTTPClient(client.HTTPConfig{
       Addr:     "https://host",
       Username: "user",
       Password: "pass",
    })

    bp, _ := client.NewBatchPoints(client.BatchPointsConfig{
       Database:  "burn",
       Precision: "ns",
    })

    fields := map[string]interface{}{"value": 1}
    pt, _ := client.NewPoint("qps", nil, fields, time.Now())
    bp.AddPoint(pt)

    _ = c.Write(bp)
    _ = c.Close()
}

但是每个请求都写一次这个成本太高了。不如攒够几百个一起写,这样最经济,节约IO嘛。

展示数据

这部分就非常简单了。这样就可以查询 QPM

SELECT COUNT(value) FROM "qps" GROUP BY time(1m)

QPS是这样的

SELECT COUNT(value) FROM "qps" GROUP BY time(1s)

那么现在问题只剩下三个了,定时器、积攒数据、消费数据。消息队列之类的办法当然可以,但是可能有点小题大做了。

定时器

一种办法是用 time.NewTicker

func QPSTicker() {
    logger.SysLog("Starting QPS ticker...")
    var ticker = time.NewTicker(time.Second * 10)
    for {
       select {
       case <-ticker.C:
          writeInflux()
       }
    }
}

另外一种就是很传统的开一个死循环,然后在循环里 sleep 非常简单

func SleepTicker() {
    for {
       time.Sleep(10 * time.Minute)
       // do something
    }
}

在启动web服务器之前 go QPSTicker 即可

收集方法1:追加时间戳到 slice 中

var DataPoints []time.Time

var mutex = sync.Mutex{}

func QPS() func(c *gin.Context) {
    return func(c *gin.Context) {
       mutex.Lock()
       config.DataPoints = append(config.DataPoints, time.Now())
       mutex.Unlock()
       c.Next()
    }
}

需要注意的是,由于我们可能会在竞争状态下写数据,所以要用一个互斥锁

消费数据

func writeInflux() {
    const batchSize = 500
    if len(config.DataPoints) < batchSize {
       return
    }
    //....
    for _, v := range config.DataPoints[:batchSize] {
       pt, _ := client.NewPoint("qps", nil, fields, v)
       bp.AddPoint(pt)
    }
    // ....
    config.DataPoints = config.DataPoints[batchSize:]
}

收集方法2: channel

方法1确实简单,但是需要互斥锁,而且处理完还需要改变数据,可能内存消耗会比较大。如果能用channel 那么就更好了。也很简单!

var DataPoints = make(chan time.Time, 1000)

在中间件中,使用非阻塞的 select 去给channel里送数据

func QPS() func(c *gin.Context) {
    return func(c *gin.Context) {
       select {
       case config.DataPoints <- time.Now():

       default:
          logger.SysError("Channel is full!!!")
       }
       c.Next()
    }
}

如果直接

config.DataPoints <- time.Now()
c.Next()

那么channel满了整个服务也就卡住了,因此要用 select,丢数据总比卡住强

消费数据

也没啥差别,就是循环不一样,并且不用考虑修改数据

for i := 0; i < batchSize; i++ {
    v := <-config.DataPoints
    pt, _ := client.NewPoint("qps", nil, fields, v)
    bp.AddPoint(pt)
}

最终结果


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/influxdb-qps.html

one-api/new-api性能优化:使用 ClickHouse 作为日志系统

2024年9月21日 20:36

one-api/new-api是一款开源的OpenAI 接口管理 & 分发系统,支持OpenAI、Anthropic、Gemini等多种模型。

这套系统,从使用者的角度来看,用起来倒还好,UI很简洁;从开发者的角度来看,作者挺不容易的要用Go来处理各种奇奇怪怪的序列化问题,一旦请求数量过多并发就无法提升,除此之外也有很多奇奇怪怪的地方啦……


我的OpenAI接口转发站 「头顶冒火」 就是在这个基础之上搭建的。只不过经过了我的大量修改,比如加个数据看板这种前端的功能

one-api/new-api性能优化:使用 ClickHouse 作为日志系统

以及默默的看不见的后台优化。经过我的一番调教之后,目前在Hetzner 2C 2G的机器上已经能达到几百的QPS了。当然实际使用,还得看真正的API能够提供多少TPM和RPM。


在高并发的情况下,本来数据库的压力就比较大,如果还开启了日志记录功能,那么数据库就会被大量的日志写入直接撑爆;另外,即使在非高负载的情况下,在日志条目比较多的时候,查看每一页日志的请求速度也会很慢。

优化并发最简单、并且大概率好用的办法之一是用更高配置的机器。但是一般来说,提升到一定配置之后,再提升基本没帮助,也就是陷入边际效应递减。而且对于我这种穷人,自然只能靠变异优化了。

本文将介绍如何使用 ClickHouse替换原有的日志系统,避免在高并发的时候清空恶化并降低性能。至于其他方面的优化,等以后再说~

one-api的数据库

one-api默认支持三种数据库,SQLite,MySQL和PostgreSQL。

  • SQLite:嵌入式的数据库,只有一个文件。在高并发下,即使允许多线程读写,其性能也不会好到哪里去。
  • MySQL:知名的RDBMS
  • PostgreSQL:PostgreSQL应该有更丰富的功能和特性。

如果你实在懒得自己优化,直接换成 PostgreSQL+高配置的机器吧,PostgreSQL的性能应该会好过MySQL一些。

one-api的日志系统:写入

One-api的普通数据和日志默认情况下均记录在同一个数据库之中,其Go Struct Model 如下

type Log struct {
	Id               int    `json:"id"`
	UserId           int    `json:"user_id" gorm:"index"`
	CreatedAt        int64  `json:"created_at" gorm:"bigint;index:idx_created_at_type"`
	Type             int    `json:"type" gorm:"index:idx_created_at_type"`
	Content          string `json:"content"`
	Username         string `json:"username" gorm:"index:index_username_model_name,priority:2;default:''"`
	TokenName        string `json:"token_name" gorm:"index;default:''"`
	ModelName        string `json:"model_name" gorm:"index;index:index_username_model_name,priority:1;default:''"`
	Quota            int    `json:"quota" gorm:"default:0"`
	PromptTokens     int    `json:"prompt_tokens" gorm:"default:0"`
	CompletionTokens int    `json:"completion_tokens" gorm:"default:0"`
	ChannelId        int    `json:"channel" gorm:"index"`
}

默认配置下,每一次请求,都会调用 RecordConsumeLog 写入日志,其代码非常简单

log := &Log{
	UserId:           userId,
	Username:         GetUsernameById(userId),
	CreatedAt:        helper.GetTimestamp(),
	Type:             LogTypeConsume,
	Content:          content,
	PromptTokens:     promptTokens,
	CompletionTokens: completionTokens,
	TokenName:        tokenName,
	ModelName:        modelName,
	Quota:            int(quota),
	ChannelId:        channelId,
}
err := LOG_DB.Create(log).Error

逐条写入日志

在请求数量比较多的情况下,这相当于是一条一条的执行 insert 语句。这种情况下主要有以下缺点:

  1. 网络延迟:每次操作都有往返的RTT、通信开销
  2. 事务开销:每次操作都要开启和关闭事务
  3. 负载:频繁插入会导致CPU和IO负载增加

即使你很有钱,用到了每个月几百几千美元配置和价格奇高的Managed database,1和2的问题还是无法避免

insert_many优化

使用 insert_many,积攒一定的数据之后一起插入,这是一种非常简单的优化思路。这种办法也有些缺点:

  1. 要等数据积攒到一定程度,会有一定的延迟
  2. 积攒数据到时候可能会有内存消耗过多的风险
  3. 可能会有更高的失败概率,一次性插入数据过多,可能会有 max_allowed_packet 之类的问题

优点很明显,一次性写入数据可比一条一条写要快上很多倍了。

one-api自带了一个类似的功能,环境变量BATCH_UPDATE_ENABLEDBATCH_UPDATE_INTERVAL 。不过截止到我写本文的时候,写日志的这部分功能不包含在batchUpdate内…而是在一个go routine内执行的…

更要命的是,这个batchUpdate从设计上来看,只能聚合数值类型的数据😂

func addNewRecord(type_ int, id int, value int64) {
	batchUpdateLocks[type_].Lock()
	defer batchUpdateLocks[type_].Unlock()
	if _, ok := batchUpdateStores[type_][id]; !ok {
		batchUpdateStores[type_][id] = value
	} else {
		batchUpdateStores[type_][id] += value
	}
}

改造一下当然可行,攒够1000条或到一定时间间隔一起写入数据库,难度不高,ChatGPT可以帮忙。

one-api的日志系统:读取

假如日志很少,那么读取日志并不会造成什么大问题。

但是如果数据量很大,并且还用户点击了下一页,甚至是最后一页进行了分页操作,那么 OFFSETLIMIT 会显著影响数据库的性能。

MySQL 在执行 OFFSET 时,必须从头开始扫描并丢弃前 OFFSET 条记录,然后返回 LIMIT 条记录,导致较大的时间开销,尤其是 OFFSET 数值较大时。

优化方法应该是有一些的,比如通过主键而不是OFFSET ,具体可以问问ChatGPT,我不是很愿意进行这个方向的研究😂

ClickHouse介绍

ClickHouse是yandex开发的一个开源列式数据库管理系统,专门为高性能的分析查询而设计,在处理大量数据时有出色的表现。

ClickHouse采用列式存储,不像MySQL等是行式存储,特别适合存储日志。在 WebP Cloud Services 中我们的统计信息来源便是 ClickHouse

one-api/new-api性能优化:使用 ClickHouse 作为日志系统

更重要的是,ClickHouse的查询语句和MySQL、PostgreSQL等几乎没什么差别,基本上改改就能用,学习曲线很平缓。

ClickHouse创建数据库表

根据上面的Go Struct和已有的数据结构,改改基本上就可以了。为了保持一致,created_at 就用数字时间戳了,虽然使用 datetime是更好的办法。

CREATE TABLE logs
(
    user_id           Nullable(Int64),
    created_at        Int64  DEFAULT toUnixTimestamp(now()),
    type              Nullable(Int64),
    content           Nullable(String),
    username          String DEFAULT '',
    token_name        String DEFAULT '',
    model_name        String DEFAULT '',
    quota             Int64  DEFAULT 0,
    prompt_tokens     Int64  DEFAULT 0,
    completion_tokens Int64  DEFAULT 0,
    ip                Nullable(String),
    user_agent        Nullable(String)
) ENGINE = MergeTree()
      ORDER BY (created_at, username, token_name, model_name);

连接到ClickHouse

使用官方的 github.com/ClickHouse/clickhouse-go

var ch, err = clickhouse.Open(&clickhouse.Options{
	Addr: []string{"clickhouse:9000"},
	Auth: clickhouse.Auth{
		Database: "openai",
		Username: "default",
		Password: "",
	},
})

写入数据

query := `INSERT INTO logs ( user_id, created_at, type, content, username, token_name, model_name,quota, prompt_tokens, completion_tokens, ip, user_agent)`
ch.Exec(ctx, query, 1, timestamp, ...)

批量写入数据

Clickhouse最厉害的地方之一是可以批量写入数据,官网上是这么说的

Generally, we recommend inserting data in fairly large batches of at least 1,000 rows at a time, and ideally between 10,000 to 100,000 rows.

你看他们多自信,让你攒够了再写,一次写几万没问题

创建batch

在Go里批量写入,要先创建batch

var batch, err = ch.PrepareBatch(ctx, `INSERT INTO logs ( user_id, created_at, type, content, username, token_name, model_name,
	quota, prompt_tokens, completion_tokens, ip, user_agent)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)

追加数据

然后往这个 batch里疯狂append数据,也就是在原来的RecordConsumeLog 中追加数据到这个batch

batch.Append(userId, helper.GetTimestamp(),
	LogTypeConsume, content, username, tokenName, modelName, quota, promptTokens, completionTokens, ip, ua,
)

提交

时间差不多了,或者数据足够了,提交

batch.Send()

定时器

定时器可以用ticker

func ClickHouseTicker() {
	var ticker = time.NewTicker(time.Second * 60)
	for {
		select {
		case <-ticker.C:
			FlushLog()
		}
	}
}

在 main里 go ClickHouseTicker()即可

清空batch

写完数据之后,然后清空batch,不能越写越多

batch=createBatch()

程序退出清理工作

最后,要捕获 SIGINT和SIGTERM信号,不能重启程序的丢日志啊,这个时候就要用到IIFE+goroutine了

sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
go func() {
	for {
		sig := <-sigChan
		switch sig {
		case syscall.SIGINT, syscall.SIGTERM:
			logger.SysLog("Cleaning up...")
			model.FlushLog()
			model.ConsolidateConsumeQuota()
			time.Sleep(time.Second * 2)
			syscall.Exit(0)
		}
	}
}()

读数据

查询语法基本和MySQL一样,ch.Query(ctx, query, params…)就行,order by,offset,limit,count等都有

删除日志

日志总得删掉,要不然磁盘早晚会被撑爆。用类似如下语句就行,最后的 max_execution_time 可选,避免日志真的太多删除也很费时间

DELETE FROM logs WHERE type = 2 AND created_at < timestamp SETTINGS max_execution_time=3600

数据分析

通过 Grafana + Clickhouse作为数据源,可以直接画图表。之前 QPS 的数据是写入到 influxdb的,一旦超过7天查询就很慢,现在我单独把本应写到 influxdb的数据写到了一个名为 stats 的ClickHouse表,只保存关键的信息,如请求时间,模型名称等。

比如看看大家用得最多的模型,最多的竟然是mini,然后是4o;但是消费是反过来的,因为4o的价格比较贵,mini比较便宜

one-api/new-api性能优化:使用 ClickHouse 作为日志系统

查询性能能提升

简单的执行三个查询,一共有354万的数据。主要测一下大量翻页,可以看出即使最差的select * 的情况下,ClickHouse大概也有4-5倍的性能提升。

查询 MySQL ClickHouse 比率
select count(*) from openai.logs; 2.73 0.002 1365.00
select user_id from openai.logs limit 3 offset 3000000; 4.19 0.022 190.45
select * from openai.logs limit 3 offset 3000000; 12.16 2.778 4.38
mysql> select count(*) from openai.logs;
+----------+
| count(*) |
+----------+
|  3540771 |
+----------+
1 row in set (2.73 sec)

mysql> select user_id from openai.logs limit 3 offset 3000000;
....
3 rows in set (4.19 sec)


mysql> select * from openai.logs limit 3 offset 3000000;
....                                                                                                                                                                                                                                                   |
3 rows in set (12.16 sec)



c387e15ec5c6 :) select count(*) from openai.logs;

SELECT count(*)
FROM openai.logs

Query id: 9382c843-927d-471f-8f6c-85a049d58294

   ┌─count()─┐
1. │ 3540771 │ -- 3.54 million
   └─────────┘

1 row in set. Elapsed: 0.002 sec.

c387e15ec5c6 :) select user_id from openai.logs limit 3 offset 3000000;

SELECT user_id
FROM openai.logs
LIMIT 3000000, 3

Query id: 66d0ea52-6b30-4a73-b313-fc45bdcbaaf2
....

3 rows in set. Elapsed: 0.022 sec. Processed 3.29 million rows, 29.64 MB (146.90 million rows/s., 1.32 GB/s.)
Peak memory usage: 4.26 MiB.

c387e15ec5c6 :) select * from openai.logs limit 3 offset 3000000;

SELECT *
FROM openai.logs
LIMIT 3000000, 3

Query id: 0faf62f4-5203-434a-8b3d-d1bcf032a8d0
....
3 rows in set. Elapsed: 2.778 sec. Processed 3.01 million rows, 9.26 GB (1.08 million rows/s., 3.33 GB/s.)
Peak memory usage: 258.79 MiB.

最后

那当然是要自我宣传一下啦!

🌟🌟🌟欢迎注册并使用 头顶冒火 OpenAI 接口转发站,稳定、快速、高并发,注册即赠送 $0.5 测试额度 🌟🌟🌟


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/one-api-clickhouse.html

Stripe 如何安全收款并避免盗刷与测卡

2024年8月3日 16:33

警告⚠️
在大多数国家和地区,盗刷信用卡都是犯罪行为,数额巨大甚至是重罪。请不要以身试法

使用Stripe进行收款是一件非常简单的事情,用起来非常舒适,不想写代码就无代码,会写代码还想自己设计前后端可以用 Stripe Elements。

当你把事情做好了,把你的在线服务上线了,这个时候就一定要考虑一个问题:盗刷与测卡(卡号测试、银行卡测试)。

盗刷很好理解,用偷来的信用卡资料去你的网站上购物;测卡则是用小额交易或者其他方式来确定盗窃来的信用卡是可用的,然后后续再大额诈骗中使用这些信用卡。

毫无疑问,只要是在线电子商务,就一定无法避免盗刷与测卡的。

一个非常重要的事情是,商户、卡组织与Stripe都有责任与义务阻止盗刷和测卡。

也许有人不理解,为什么商户也有责任与义务阻止盗刷和测卡?我就是把 Stripe 的一个Payment Link放到了网上,某个不怀好意的人看到了去盗刷,主动给我送钱,我为什么要负责?

切勿贪小便宜!真正的持卡人发现了异常的付款记录,他们会去找发卡行争议,发卡行就会找到 Stripe,商户就需要应对争议,这期间不仅要承担争议费,在争议比例过高时Stripe还会封号、甚至罚款。

Stripe 如何安全收款并避免盗刷与测卡

所以,遇到来源不明的欺诈付款,一定要全额退款,可不能贪小便宜。

不过如果对方是用支付宝、微信付款的那就不用管了,因为这俩应该是不支持争议的😂


前不久,我“惹毛”了一个黑产论坛。这个黑产论坛的人就开始盗刷+测卡。

Stripe 如何安全收款并避免盗刷与测卡

比如这种,人在新加坡用墨西哥的卡🤨,再考虑到我的用户的大部分都是中国人,所以看起来怎么都很像盗刷。

后来他们盗刷不成还恼羞成怒开始DoS我了,峰值竟然有5500万。

至于我是怎么“惹毛”这群人的,以后也许可以写一篇来说说这件事,我觉得这其中的乐趣会比听相声还搞笑🤣,不笑我倒贴你100块。如果你还是想先了解一下,那么看这个Twitter就够了


本文的主题是,已经使用了Stripe,如何防患于未然,避免盗刷与测卡?假如在遭遇盗刷,需要立刻做什么事情?

Radar

Stripe Radar 是Stripe提供的一个反欺诈工具,基于强大的机器学习实现,并且可以自定义许多规则。比如ChatGPT不允许中国的卡付款,类似这样的规则,就可以Radar的规则实现。

Radar不收月费,而是从每笔交易里抽成,Stripe还是很良心而且很会赚钱的。

在遭遇盗刷的时候,立刻要做的事情就是开启 Radar,并且根据自己的情况调整灵敏度。同时,审核所有已经通过的付款,如果怀疑是盗刷的,要立刻全额退款,全额退款无法争议。在处理完之后,联系 Stripe客户表明自己的情况,要求他们提供协助和建议。

以新加坡为例,Radar风控团队版每笔交易 0.1新币,机器学习版每笔0.08新币。强烈建议开启团队版。

开启了Radar之后,我们可以在 Risk Control里调整Radar的灵敏度

Stripe 如何安全收款并避免盗刷与测卡

根据自己的情况调整即可,比如我在面临大量盗刷的时候,把灵敏度调到了50,意味着所有风险分数高于50的付款都会被拒绝。

3DS

在某些地方,你可能会看到 Stripe 2D 的说法。这里的2D指的是在线刷卡的时候只要输入卡号、有效期、CVV并且无需进行身份验证(通常是短信或者App推送)。对应的需要验证的则是3DS

在开启了 Radar之后,就可以强制开启3DS了,这样对于盗刷的情况基本可以拦截一大部分,毕竟他们没有卡主的手机。

Radar – Rules- Authenticate Rules,三个默认的3DS规则全部打开

Stripe 如何安全收款并避免盗刷与测卡

自定义阻止规则

每个人的业务都不同,比如我的这个业务下,美国人不太可能来用,更别提那些非常小众东南亚、加勒比海和非洲国家了。因此对于我来说,我只要允许中国、最多再包含港澳台的银行卡就可以了。

那么我们就可以添加一个这样的自定义阻止规则,表达式这么写,NOT要写最前面而不是写IN之前

NOT (:card_country: IN ('CN','HK','MO','TW'))

不想接受预付费卡,那么可以这样写

:card_funding: = 'prepaid'

只想接受中港澳台,允许任何国家的Apple Pay、Google Pay(这俩安全系数很高,不太可能是盗刷),那就这么写

Block if NOT (:card_country: IN ('CN','HK','MO','TW')) AND NOT(:digital_wallet: IN ('android_pay','apple_pay'))

3DS通过就接受,无所谓发卡国,可以这么写

NOT :is_3d_secure_authenticated:

某人在某黑产论坛:真不敢c cn的卡

在Stripe平台上,我们能做的事情差不多只有这么多了。为了防止测卡,我们还需要在自己的集成方式上下点功夫。

测卡的方式

测卡一般分两种,一种是授权,类似你在某些网站如Cloudflare绑定你的信用卡,并不会真正的扣款,App上一般能看到记录,但是每月账单上未必会有;另外一种则是付款,一般是小额付款,确认卡片有效准备来波大的。

Stripe 如何安全收款并避免盗刷与测卡

检测测卡

首先当然是要检测到测卡。显著增加的拒绝交易其实就可以用来识别测卡。

  • 尝试查看付款详情的时候,能看到Stripe会自动屏蔽测卡,如上面的这张截图
  • Developers的overview页面图表能发现很多失败

Stripe 如何安全收款并避免盗刷与测卡

  • 在 Developers – Logs 里查找402,也会发现大量记录

在发现被搞了之后,我们要赶紧采取措施。哪怕赶紧找到测卡的人的IP给屏蔽了也是办法。当然坏人没那么笨,简单的防火墙规则未必会管用的。

保护 API Key

Stripe的API Key分为两种,一种是Publishable Key可以公开的;另一种是Secret Key,有这个Key就可以访问Stripe的系统和全球金融网络,所以万万不能泄漏

遇到测卡建议无论是否泄漏 secret key,都去吊销生成一个新的,并且添加IP访问白名单

Stripe 如何安全收款并避免盗刷与测卡

优化集成方式

使用 Stripe有很多集成方式,使用 Stripe Payment Link、Stripe Checkout、Stripe Elements是推荐的方式

Stripe 如何安全收款并避免盗刷与测卡

用这种集成方式, Radar能够自动捕获到更多的信息,更容易判断盗刷测卡。

其他的方式可能就需要自己手动提供信息了。

添加captcha

在请求创建 PaymentIntent之前可以加上一个验证码,比如 Cloudflare的turnstile就是非常好的选择。 这样对于大部分的垃圾请求都可以过滤了。

需要注意,turnstile是需要客户端+服务端实现的。只有客户端是不完整的。

频率限制

一般来说,单个IP创建 PaymentIntent 不应该太频繁,可以加上频率限制降低被刷概率。

登录会话

登录了才能购买,类似这种可以降低被刷成本

自定义规则

根据自己的业务情况,使用不同的规则来屏蔽测卡。甚至还可以根据IP地理位置和发卡国组合,就看想象力了

战果

这是屏蔽的付款,看这个时间这么接近,人在美国用着泰国、甚至一些奇怪的小国家的卡,风险系数非常高

Stripe 如何安全收款并避免盗刷与测卡

大概拦截了3000欧,恐怖啊😂 最开始规则配置的不太正确,估计误杀了一部分

Stripe 如何安全收款并避免盗刷与测卡

总结

  1. Stripe的文档还是挺不错的,SDK用起来也非常舒服。无论是stripe.js还是python bindings,写满了 type annotations,想用错都难
  2. Stripe的客服也很专业,响应非常迅速
  3. 坏人太多啦

文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/stripe-fraud.html

救命!我的 Azure Front Door WAF 规则不生效怎么办!

2024年7月17日 13:41
这篇文章在 2024年09月27日21:17:41 更新了哦~

最近在用 Front Door加速网站访问,想添加一个这样的规则:只允许 /v1下的路径和特定国家的IP,其他全部禁止。

这其实很简单的!只要创建两条 WAF规则,一个禁止全部优先级低,一个允许特定路径,优先级高。

100优先级的允许规则

救命!我的 Azure Front Door WAF 规则不生效怎么办!

看起来一点问题都没有吧,非常简单容易理解

500 优先级阻止规则

救命!我的 Azure Front Door WAF 规则不生效怎么办!

也很好理解,阻止全部

没用!

配置下来就是不生效,所有的请求都被阻止了。给阻止规则停用,就全部能访问。

azure-cli里看下WAF的配置,避免他们前端有bug

{
    "rules": [
        {
            "action": "Allow",
            "enabledState": "Enabled",
            "groupBy": [],
            "matchConditions": [
                {
                    "matchValue": [
                        "/v1"
                    ],
                    "matchVariable": "RequestUri",
                    "negateCondition": false,
                    "operator": "BeginsWith",
                    "transforms": []
                },
                {
                    "matchValue": [
                        "CN"
                    ],
                    "matchVariable": "SocketAddr",
                    "negateCondition": false,
                    "operator": "GeoMatch",
                    "transforms": []
                }
            ],
            "name": "Allow",
            "priority": 100,
            "rateLimitDurationInMinutes": 1,
            "rateLimitThreshold": 100,
            "ruleType": "MatchRule"
        },
        {
            "action": "Block",
            "enabledState": "Enabled",
            "groupBy": [],
            "matchConditions": [
                {
                    "matchValue": [],
                    "matchVariable": "RequestUri",
                    "negateCondition": false,
                    "operator": "Any",
                    "transforms": []
                }
            ],
            "name": "Deny",
            "priority": 500,
            "rateLimitDurationInMinutes": 1,
            "rateLimitThreshold": 100,
            "ruleType": "MatchRule"
        }
    ]
}

从结果来看,完全没问题。给规则从 begins with 改成 contains又可以,但是那也意味着https://cn-test.burn.hair/login?id=v1也会允许,而这不太行

救命!我的 Azure Front Door WAF 规则不生效怎么办!

看看 log

没办法了,开 log 试试看吧,不行就联系 Azure Support

先要去 Log Analytics workspaces 里开一个记录日志的奇怪的东西

救命!我的 Azure Front Door WAF 规则不生效怎么办!

然后去 Diagnostic settings 里 把这个东西和 Front Door、Workspaces关联起来

救命!我的 Azure Front Door WAF 规则不生效怎么办!

然后去log页面运行这样的一个查询,绝了连看日志都得学习查询语法😓

AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK" and Category == "FrontdoorWebApplicationFirewallLog"

然后就能看到 WAF的日志了

救命!我的 Azure Front Door WAF 规则不生效怎么办!

什么??

WTF?谁家 requestUri这么写啊 还带端口的,怪不得我配置的 begins with /v1 不好用,但是contains就行……

救命!我的 Azure Front Door WAF 规则不生效怎么办!

火速去 WAF 里,改一下规则,给matches value改成完整的路径就好了

https://cn-test.burn.hair:443/v1

救命!我的 Azure Front Door WAF 规则不生效怎么办!

😢 问题解决了,开的各种东西可以去 Resource Group 里删除了


「不愧是 Azure,从定价,到文档,到使用,没一个符合直觉」

—— Nova Kowk


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/azure-waf.html

警惕USDT-TRC20 数字货币诈骗

2024年4月30日 14:21

最近看到一种很有趣的数字货币诈骗方法,有人有意无意在群里、论坛或者其他公开的地方,泄漏自己的助记词,往往是Tron这个链,然后这个钱包里有大概几十到几百USDT,比如下图这样

警惕USDT-TRC20 数字货币诈骗

一般来说这种钱包TRX会比较少,多一点的也有,其实没关系的。

众所周知,进行 USDT 转账是需要 TRX做燃料的。有些人可能就想到了……

  1. 既然有助记词,那么就可以导入到自己的钱包里
  2. 从别的地方转进来几十 TRX做gas
  3. 然后把这几百USDT转走,白嫖成功

事情哪有那么简单!

偷取燃料费

当你把 TRX 转进来之后,还没等这笔钱热乎,就会被瞬间转走!

比如在上图中我们看到的这个例子,他的地址是 TAvwgoX6zbExfotsgP351QxxCoHGH8mmyb ,通过查询 tronscan我们可以查询一个地址的所有交易记录,我这里用不同颜色的做了标记:

警惕USDT-TRC20 数字货币诈骗

我们可以很清楚地看到,受害者向 mmyb的地址转账,慢则5分钟,快则几十秒,这笔TRX就会被转到 Mh4J 的地址。截止到目前为止,这个地址已经骗了$90了。

我还有另外一个例子,也是一样的手段

警惕USDT-TRC20 数字货币诈骗

拼手速?

假如你也会写点代码,你可能在想,哎不就是拼手速嘛!转点TRX进去,然后瞬间再转走 USDT,全程代码操作延迟1秒,那不就白嫖成功了嘛!

有这种想法不得不说非常聪明,然而骗子也早就想到了这一点,骗子也不想偷鸡不成蚀把米。如果你注意看 tronscan地址下的提示信息:

警惕USDT-TRC20 数字货币诈骗

The current account’s "Owner Permission" is authorized to:TDtwL9uggJ1qBhWW83z6q7ssNdhi12356L

The current account’s "Active Permission" is authorized to:TDtwL9uggJ1qBhWW83z6q7ssNdhi12356LView Account Permission

Tron的一个特性之一是,可以把自己地址的权限转移给另一个地址。权限本身是独立的、唯一归属的,要么归你要么归我。

通常来说我们使用这个地址对应的私钥签名的,但是把权限转移给另外一个地址之后,就可以用那个地址的私钥签名了。

在这个例子中,这个蜜罐地址 mmyb 的全部权限都被转移给了另外一个 356L的地址,mmyb的私钥因为缺失权限,是无法签名交易的。

更进一步,Tron还可以设置mult-sig(多重签名),需要多个私钥+权重计算才可以完成某个操作,有点像大家开会表决。

😂所以,即使你手速够快,也会发现自己没权限动这个资金。

当然了,如果你发现一个地址的权限没有被更改过,那可能确实是天上掉馅饼了。

我被骗了吗

哈!😏😏😏

警惕USDT-TRC20 数字货币诈骗

哈,你终于领悟了成为传奇的诀窍……拿上你的家伙,咱们走。


我可是传奇,想要骗我可没那么容易😉😉😉

参考阅读

https://www.exodus.com/support/en/articles/8598817-what-crypto-scams-should-i-watch-out-for#honeypot

 


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/usdt-trc20-scam.html

Telegram Login Widget 如何验证用户数据

2024年4月21日 22:05

使用Telegram 做OAuth是一件很容易的事情,毕竟整个 Telegram API 都相当开放。

需要注意的一件事情就是,我们需要验证用户的数据确实是来自于 Telegram而不是第三方伪造的。无论是在回调函数还是回调URL中,Telegram都会提供如下参数:
id, first_name, last_name, username, photo_url, auth_date, hash
根据官方文档的说法,验证数据分如下几步:

  1.  把这些字段按照字母顺序排序,组成一个字符串,每一个键值对之间用换行符隔开,如 'auth_date=<auth_date>\nfirst_name=<first_name>\nid=<id>\nusername=<username>'
  2. 计算 bot token 的 sha256
  3. 计算 HMAC_SHA256,明文是上面的这个字符串,密码是bot token 的sha256,然后计算这个结果的hex,比对与他提供的 hash是否相等

看起来很简单是不是,随便丢给 ChatGPT就能出结果。甚至官方还给了个PHP的代码

但是很抱歉的是,这样并不能给保证100%验证成功,甚至可能会完全无法验证。以下是我踩的两个坑

hash字段要排除

在组装字符串的时候,记得排除hash字段,要不然是不可能计算出正确的结果的。

值是空的字段要排除

在 Telegram 中,username、photo_url 等字段不是必须的。在计算时,这种类型的字段如果为空,那么不需要考虑

可怕的是,无论是官方文档,还是PHP示例代码,都没有提到值是空的字段不参与计算的事情。😢

示例代码

Go 的示例代码

type TelegramAuthData struct {
	Id        int    `json:"id"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
	Username  string `json:"username"`
	PhotoUrl  string `json:"photo_url"`
	AuthDate  int    `json:"auth_date"`
	Hash      string `json:"hash"`
}

func verifyTelegramAuthData(data TelegramAuthData) bool {
	// Extracting fields and preparing for sorting.
	fields := map[string]string{
		"auth_date":  strconv.Itoa(data.AuthDate),
		"first_name": data.FirstName,
		"last_name":  data.LastName,
		"id":         strconv.Itoa(data.Id),
		"photo_url":  data.PhotoUrl,
		"username":   data.Username,
	}

	// Sorting the fields to create the data-check-string.
	var keys []string
	for key := range fields {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	var dataCheckString string
	for _, key := range keys {
		if fields[key] != "" {
			dataCheckString += fmt.Sprintf("%s=%s\n", key, fields[key])
		}
	}
	dataCheckString = dataCheckString[:len(dataCheckString)-1] // Remove the last newline character

	// Calculate the SHA256 hash of the bot's token.
	hasher := sha256.New()
	hasher.Write([]byte(botToken))
	secretKey := hasher.Sum(nil)

	// Compute the HMAC-SHA-256 signature.
	hmacHasher := hmac.New(sha256.New, secretKey)
	hmacHasher.Write([]byte(dataCheckString))
	computedHash := hex.EncodeToString(hmacHasher.Sum(nil))

	// Compare the computed HMAC with the received hash.
	return computedHash == data.Hash
}

Python 版本

def verify_telegram_authdata(data: dict) -> bool:
    # Extracting fields and preparing for sorting
    fields = {
        "auth_date": data.get("auth_date"),
        "first_name": data.get("first_name"),
        "last_name": data.get("last_name"),
        "id": data.get("id"),
        "photo_url": data.get("photo_url"),
        "username": data.get("username"),
    }

    # Sorting the fields to create the data-check-string
    keys = sorted(fields)
    data_check_string = "\n".join(f"{key}={fields[key]}" for key in keys if fields[key])

    # Calculate the SHA256 hash of the bot's token
    hasher = hashlib.sha256()
    # bot token token
    hasher.update("token".encode("utf-8"))
    secret_key = hasher.digest()

    # Compute the HMAC-SHA-256 signature
    hmac_hasher = hmac.new(secret_key, data_check_string.encode("utf-8"), hashlib.sha256)
    computed_hash = hmac_hasher.hexdigest()

    # Compare the computed HMAC with the received hash
    return computed_hash == data.get("hash")


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/telegram-login-widget-verify-data.html

物理化 eSIM

2024年3月23日 17:34

众所周知,国行 iPhone 是不支持 eSIM的。那么有没有办法让国行 iPhone也能过试用 eSIM运营商呢?答案当然是有的啦。

国内有几个厂商在做这个,比如 ESTK.me, 5ber 国外还有一个 eSIM.me。使用起来不复杂,买他们的SIM卡,然后通过软件,通常是Android手机把eSIM Profile写到这张卡里,然后把这个卡插到手机里就可以了。

问题来了,我又没有 Android 手机,而且这些厂商往往都会通过软件手段限制写入。比如 5ber这里,按照下载次数收费的

物理化 eSIM

 

有没有别的办法呢?当然有的啦,读卡器

 

智能卡读卡器

去神奇的淘宝可以买到这种设备,价格不贵,大概20-30人民币

物理化 eSIM

 

eSIM card

可以去上面提到的那些厂商买,也有一些其他的厂商,可以自行搜索。

如果购买5ber的话,他们的官网在这里 https://esim.5ber.com/?language=en-US

使用邀请码 E6ARJX 可能会有优惠。另外,联想有一款 ThinkPad Thales eSIM card 亲测可用

写卡

我直接用了 EasyLPAC,图形界面方便操作。把卡片插进去,软件就应该能读到。500KB大概够写十几个了

物理化 eSIM

 

切换到 Profile选项卡,点Download 输入运营商给的信息,然后Enable,拔卡插手机即可

物理化 eSIM

 

写卡次数限制?不存在的,想写多少写多少,别超了就行。🤷

当然了每次要换卡也比较麻烦就是了……这个没办法,早日换支持 eSIM的设备就好了

 

相关资料


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/physical-esim.html

沉浸感

2024年2月3日 16:49

我很喜欢玩赛博朋克2077,在往日之影发布之前很久已经清完了所有的支线和委托。往日之影发布之后,我熬了两个晚上,玩完了全部四个分支。

随后我就出去旅游了。往日之影的结局是在是太让我感到无力了,那个时候我就坐在酒店的阳台上发呆。为什么会这样啊,我很难过。与其这样生活,真的不如再活半年,或者把身体留给强尼,至少这样不会失去朋友啊。

人们总是很容易把艺术作品中的情形代入到自己的生活中,或者反之。

那个时候我在日记里写道

这也像极了现在的我。我在一点点失去我的朋友,我找不到生活的目标,这样的生活,远远不如以前开心。可生活不是游戏,没办法轻松重新选择故事走向。每次重新选择,都要承担巨大的代价和后果。

在赛博朋克之后,我再也没有精力去如此品玩任何其他的游戏。可能是怕试错成本太高,很难找到符合我的喜好的游戏。Game Pass Ultimate过期之后也没有再续费,因为感觉自己完全找不到能玩的游戏。

在影视作品中,我有些时候也会有这种感觉。

最近白嫖了 Apple TV+,然后熬了三四个晚上看完了灵异女仆。 电视剧的节奏挺慢的,至少说在惊悚悬疑题材里这个节奏很慢,还好我没有追剧。

沉浸感

我很喜欢女主Leanne的人设和演员的演绎,Leanne只不过是童年破碎的受害者,她把自己对亲生母亲的期望全部投射到Dorothy身上。 男主一家人里如果有一个人能稍微正常点,那么结局会不会好一点? 只有厨师小弟对 Leanne 很好,没有理由也没有利益的羁绊,只是可惜那场约会永远也不会到来……

沉浸感

哎所以我总是很怕这样。一方面是怕自己陷得太深,另一方面是怕自己以后很难发现更好的文艺作品。

我毫不犹豫的熬夜,只是想把自己沉浸在这些作品所营造的背景和氛围之内,这样我就可以暂时忘记发生在现实生活中的种种烦恼与问题,这样我就可以忽略问题,像鸵鸟一样把头埋在沙子里。 然而一旦看完这部电影,玩通那个游戏,这样的沙子也就不复存在,也就只能面对现实和无奈。🤷

有时候可能看看爽剧,玩一玩那些无脑爽游戏(比如最近几年的使命召唤),只是娱乐放松而没有沉浸,这样可能会对我的心理状态有更好的帮助。沉浸感太强,有的时候可能也不是一件好事🫠


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/immersion.html

2023 年终总结

2023年12月31日 19:14
这篇文章在 2024年01月08日21:30:43 更新了哦~

很多年没有写年终总结了。有很多事情都是2022年年底的发生的,四舍五入也算2023年的吧!

博客

2023年一共写了16篇博文,从数量上看起来比较少,从质量上看则更是堪忧🙃️

2023 年终总结

这一年学习到了很多有用没用的知识。

在这之后,我的博客就停更了差不多半年左右。这半年里我和 Nova 还有 Tuki 开发并运营了 WebP Cloud Services。这个想法来自于3年前的WebP Server Go,区别是Cloud Services功能更多更强大,而且是托管的SaaS服务。

这半年里我也写了博文,只不过放在了 WebP Cloud Services Blog 上。为了增加受众,并且我们的目标是出海,我们还用 ChatGPT 翻译了英文版本的。

然后,我就又回到了🥔不好吃

这一年的写作到这里就结束了。

工作

换了一份工作,薪水比上份工作多一些。然而我觉得这份工作并没有上一份工作那么令我满意,主要集中在以下这几点吧:

  • 工作流程:上一份工作的公司刚刚被某芯片大厂收购,工作流程非常规范(因此也很慢),任何变更要通过 PR来,需要2个 review,Test Coverage大概有个70-80%左右
  • 技术栈的变化:前司是Python + Clarity + PostgreSQL,我还接手了一个 Java的Play framework 的东西,感谢老板对我的信任,我都没想到能弄明白🤣 现在则是 Nodejs + Angular+ Elm 。JavaScript 懂得都懂黑点无数,大家也乐此不疲的黑JS,但是无论怎么说JS还是发展非常迅速的一门语言,生态也挺不错。Elm那我只能😄啦,只提一点:标准库上次更新是2021年2月。
  • 氛围和企业文化:这个没什么可说的,都很棒,同事们都挺好的。也没有什么996的事,到点就走。但是不知为何我还是更喜欢前司😂

别的倒也没什么,俗话说钱难挣嘛,这个道理都懂……

旅游

从来不爱旅游的我,今年竟然破天荒的去了5个城市旅游。收获了什么忘记了,无论是「这个楼也太他妈牛逼了」还是「落霞与孤鹜齐飞」都不记得了。 我也没写xxx游记,只记得买了很多明信片😓

开源项目

  • YYeTs:基本没什么太大的变化,dependabot的commit比我还多。前段时间转手给别人之后我更是不管了
  • WebP Server Go:虽然有持续的更新,并且有一些 issue,但是主要都是 Nova 大佬在处理,我基本打酱油了
  • ytdlbot:更新了一大堆东西,每天跑 200G 流量。收获0.
2023 年终总结

总体来看,基本上可以说还是那些,没什么新的变化,“僵尸”状态可能是一个比较恰当的形容词。

游戏

电子阳痿

等了很久的《往日之影》发布了,熬夜两天玩完,后劲太大直接让我郁闷的好几天。假如我是V,我会怎样做出选择?接受治疗活下去,然后失去一切。真的是一切都变了,物是人非,曾经的V震天甚至还会被街边的小混混揍,而这只是短短的两年。

2023 年终总结

曾经的V可是这样的啊(这套服装是往日之影里那个法国黑客奥罗尔的复刻,还蛮好看的,我好喜欢。这把刀好像叫爪磨?)

与其这样生活,真的不如再活半年,或者把身体留给强尼。甚至,不如信了荒坂。

人呐难免会把艺术作品中的事情联想到自己身上,这个高塔结局说的不就是现在的我吗?

2023 年终总结

Steam Deck

买了个 Steam Deck,淘了个显示器键盘和1 TB SSD,然后装了个双系统。这玩意就是一个标准的x86机器,所以装双系统甚至三系统这种事情对于我来说自然不在话下。惊叹于 Windows 11,和Windows 10比起来还是变化了很多的。

装完系统我也没怎么用,看起来也电子阳痿了。

影视作品

看了挺多,大部分都是 Netflix上的

  • VIVANT: 堺雅人主演的电视剧,前几集挺好看,后面略差。堺雅人的英文不太好,可能不如中文吧
  • 石子和羽男 :有村架纯真的是太可爱了,我好爱她😍
  • V世代:黑袍纠察队的衍生电视剧,很不错
  • 上载新生第三季:略差了,但是比第二季好多了
  • 最后生还者:改编于同名游戏,那个被魔山捏爆脑子的王子还真是挺忙
  • 曼达洛人第三季:电视剧总体水平还不错,但是不知道为何我不太喜欢了,可能毕竟不是星战粉吧。主演还是上面那个王子
  • 阿妮亚传奇啊不,间谍过家家:我很少看动漫,但是这部动漫竟然坚持下来了
  • 赛博朋克 边缘行者:听说看完这部动漫的人都打开游戏锤爆亚当·重锤,我也不例外😂
  • 噬亡村:柳乐优弥和吉冈里帆主演的电视剧。吉冈妹子真的好美,不知道第二季什么时候,这都快一年了
  • 弥留之国的爱丽丝:第二季不太行……
  • 甜蜜家园第二季:更不行
  • 谁是被害者:台剧,还不错嗷
  • 模仿犯:也是台剧,不错
  • 罪后真相:台湾电影,还好,看预告片不错
  • 黑暗荣耀:宋慧乔主演的,不错
  • 黑镜第六季:琼糟透了、海之彼岸还不错,其他的一般般
  • 关注者:不知道讲了什么,全程被池田 エライザ吸引🤩
  • 初恋:这年头的纯爱剧,还真的能看进去(其中有一点点18🈲️镜头哈哈哈),主题曲和灵感均是宇多田光的 First Love,光妹真的浑身发光啊
  • 重启人生:假如我能重启人生,那么我要做的事情会不会是,在高考结束之后的那天,把上交的志愿改掉?

还有一堆乱七八糟的,不记得了想不起来了

音乐

新增了很多 OST,都是赛博朋克的,最喜欢的还是 Gate K9(往日之影)和 The Rebel Path(单挑荒坂塔)

剩下的歌曲基本都是日本流行乐,基本全是 uru,我最喜欢的可能是 「ドライフラワー 」吧,这种小小伤感的歌曲最让我喜欢了:

  • なんでもないよ
  • ドライフラワー
  • 無機質
  • 再会
  • ポジティ部入部
  • ランドマーク
  • 紙一重
  • 君の幸せを
  • セレナーデ

2023 年终总结

还是挺恩爱的。但是酸奶后背脱毛了😫,并且从昨天开始呕吐腹泻😫😫😫

2024的期待

猫咪身体健康就可以了,看起来已经恢复了,但是似乎饿瘦了🫠

2023 年终总结


文章版权归原作者所有丨本站默认采用CC-BY-NC-SA 4.0协议进行授权|
转载必须包含本声明,并以超链接形式注明原作者和本文原始地址:
https://dmesg.app/2023-end.html
❌
❌