Total Pageviews

Sunday, 1 November 2026

大清朝廷困境的本质,在于想要新因素的好处,却拒绝接纳新因素的诉求


1912年2月12日,隆裕太后代替年幼的溥仪颁布退位诏书,大清王朝正式落幕,连同它一起终结的,还有延续两千多年的君主专制帝制。

关于清朝覆亡的原因,历来众说纷纭。有人归因于革命党人的长期活动,有人归咎于列强的步步紧逼,也有人认为是袁世凯的"篡权"。这些说法各有道理,但都忽略了一个最根本的事实:真正把大清推向深渊的,不是别人,正是大清朝廷自己。

从清末新政到王朝覆灭,不过短短十年;从1906年正式宣布预备立宪算起,更只有五年左右。政治改革迟迟未能建立信用,经济危机与地方维权总爆发,军队兵变,地方与中央切割,中央政权随之崩解。这条溃败之路,几乎每一步都有清廷自己的脚印。

一、君主立宪制度的"假诚意",把立宪派和地方实力派推到了对立面

晚清政治改革的核心,是仿照日本搞君主立宪。日本明治维新后国力大增,尤其是1905年日俄战争中日本击败俄国,给大清朝廷以极大震撼,一个东方国家通过制度变革实现富国强兵,这对清廷的刺激是直接的。于是朝廷宣布"预备立宪",摆出一副要与世界接轨的姿态。

但问题在于,清廷对君主立宪的理解是扭曲的。它看到的只是日本天皇"万世一系"、权力集中的表面,却不愿接受英国式君主立宪的核心,虚君政治,议会主权。英国君主立宪的本质是君主统而不治,实权归于议会和内阁,王室成为国家象征而非权力中心。而清廷想要的是:保留皇帝的最高裁决权,让议会和宪法只做装点,皇帝的实际权力纹丝不动。

这种"假立宪"的虚伪,在1908年颁布的《钦定宪法大纲》中暴露无遗,君权条款几乎照搬现行法律,臣民权利寥寥无几且随时可被剥夺。立宪派原本寄望于通过合法的政治参与分享权力,结果发现自己被当猴耍。当1911年"皇族内阁"出台,十三名阁员中,皇族占了七人,立宪派彻底绝望。他们不是革命党,原本是体制内寻求渐进改良的力量,但清廷连这点空间都不给他们。

立宪派绝望了,地方实力派寒心了,这两股原本可以支撑清廷完成和平转型的力量,在历史的关键时刻选择了与地方独立站在一起。这不是革命党策反的结果,是清廷自己把盟友推到了对立面。

二、经济与财政政策的失当,点燃了保路运动这把火

如果说政治改革的失败是慢性毒药,那么1911年的铁路干线国有政策就是一剂猛药,直接引爆了革命。

事情的起因并不复杂。1910年上海橡胶股票泡沫破裂,引发钱庄大规模倒闭,川汉铁路的部分筹款因此遭受损失。这本是市场风险,但清廷的处理方式极其粗暴。1911年5月,邮传部大臣盛宣怀奏请"铁路干线国有",将川汉、粤汉铁路收归国有,然后与英、法、德、美四国银行团签订借款合同,用外债修路。

表面上看,铁路国有化有其合理性,干线铁路涉及国防和经济全局,分散的地方商办确实效率低下。但清廷的操作方式有三大致命问题:

第一,补偿方案不公。四川川汉铁路公司筹集的资金中有大量来自普通绅商/农民的零星股款,清廷宣布国有后,对民间股本的补偿极不充分,甚至部分股本被直接吞没。这直接触犯了广大绅商/农民的实际利益。

第二,程序不透明。铁路干线收归国有这样的大事,事先不与地方咨议局协商,不经任何公开讨论,一道上谕就定了。地方咨议局,这些好不容易建立起来的"准议会"机构再次被无视。

第三,外债安排刺激民族情绪。用外国贷款修自己的铁路,在当时的语境下很容易被解读为"卖国"。盛宣怀与四国银行团的借款合同条款苛刻,更加深了这种观感。

结果就是四川保路运动爆发。从绅商请愿到民众集会,从和平抗议到成都血案,清廷的镇压反而激起了更大规模的反抗。为了平息四川局势,清廷不得不从湖北调走部分新军入川,这直接削弱了武汉地区的驻军力量,为武昌起义的成功创造了最关键的军事条件。

一个经济政策的失误,经过层层传导,最终变成了军事上的致命漏洞。这不是偶然,是清廷长期忽视地方利益、决策专断的必然结果。

三、新军兵变与各省独立:中央权威瞬间崩塌

1911年10月10日武昌起义,最初只是一次新军兵变。如果局限于武汉一地,以清军当时的兵力优势,完全有可能将其镇压。但关键在于,革命爆发后,湖北军政府迅速成立,革命党人、新军军官与立宪派三方共同参与,通电全国宣布独立。

各省的反应才是致命的。短短一个多月内,湖南、陕西、山西、江西、云南、贵州、江苏、浙江、广西、安徽、福建、广东等省相继宣布独立。这不是革命党人逐一策反的结果,而是各省看到清廷权威已失、大势已去后的顺势而为。地方与中央的切割,在几个星期内完成了。

此时清廷唯一的指望是袁世凯。袁世凯被重新启用,但他在北洋军中经营多年,北洋军早已不是唯朝廷命是从的工具。南北和谈过程中,段祺瑞联合四十余名北洋将领发出共和兵谏电报,明确表态支持共和,袁世凯手握重兵两面下注。最终在各省独立、北洋将领施压、南方革命势力崛起的三重夹击下,清廷除了逊位别无选择。

整个过程,清廷不是被某一个人推翻的,而是被自己的决策失误、信用破产一步步逼到了墙角。袁世凯只是最后那个"关门人"。

四、不搞改革就安全吗?明朝的教训

有人事后复盘说:搞君主立宪、发展市场经济,反而加速了清朝的崩溃。如果像明朝那样不搞这些新东西,封闭自守,只要权力在手,朝廷就不会出大问题。

这个假设经不起推敲。

明朝确实没有搞君主立宪,虽然晚明已通过白银流入和商品贸易参与了世界经济体系,但政治上始终拒绝任何实质性的权力分享。结果呢?灾荒、战争、财政困境与官府腐败相互交织,最终导致大规模动乱。李自成的农民军摧垮了明朝的统治根基,然后才是清军入关。

不搞政治改革,并不能避免帝制朝廷的统治危机。明朝的覆亡证明:当一个政权拒绝适应社会变化、拒绝容纳新兴社会力量、拒绝建立制度化的纠错机制时,它终将被内部矛盾和外部冲击合力摧毁。区别只在于:是像明朝那样在农民战争的血火中崩塌,还是像清朝这样在革命与妥协的交织中落幕。

相比之下,清朝因为有了经济市场化带来的新社会力量,有了地方咨议局和立宪派在政治舞台上的参与,使得革命初期的"驱逐鞑虏、恢复中华"口号逐渐转向"五族共和"。高举铁血十八星旗的革命军中,部分人带有强烈的排满与民族复仇色彩,一些地方确实发生了针对满人的屠杀。但通过南北和谈、通过共和共识的建立,最终以谈判完成清帝退位,杜绝了继续大规模战争和种族仇杀的风险。

从这个意义上说,大清得到了"最不坏的结果"。它以和平逊位的方式退出历史舞台,避免了全国范围的内战和更大规模的人道灾难。这恰恰是清末十年改革,哪怕是不彻底的改革留下的正面遗产。

五、大清困境的本质:想要新因素的好处,却拒绝容纳新因素的诉求

大清朝廷的困境,可以用一句话概括:进退维谷。

要"进步",要发展市场经济,不得不官商合作,不得不修铁路,为了国防安全也不得不建立新军。新式学堂培养的知识分子、商会和实业阶层、新军中的军官和士兵、地方咨议局中的立宪派这些新因素,确实增强了国家能力,也产生了新的社会力量与政治诉求。

但清廷的态度是:只要前者(国家能力的提升),拒绝后者(新兴力量的政治参与)。朝廷想要新军保卫政权,却不让新军军官有政治表达渠道;想要商人投资铁路,却不给商人在决策中的话语权;想要立宪装点门面,却不让议会拥有实权。

新因素一旦产生,就有自己的利益诉求和政治意识,不可能永远被当作工具使用。当这些新兴力量发现体制内没有他们的位置,当立宪派的合法参与被一次次戏弄,当普通民众的经济利益被一纸上谕随意剥夺,他们要么沉默地离开体制,要么在关键时刻成为体制的掘墓人。

历史没有如果。清廷在改革的窗口期选择了敷衍和拖延,在危机来临时选择了强硬和蛮干,最终把自己推上了绝路。

大清的覆亡,是君主专制制度在近代世界走到尽头的必然结果,而加速这一结果的,恰恰是清廷自己的每一步选择。

2026年,当我来到夹边沟


夹边沟位于甘肃酒泉市境内,巴丹吉林沙漠的边缘。1957年,这里成为劳教农场,三年间关押了3000多人,其中大部分人是被打成“右派”的知识分子和干部。许多人在此因饥饿、寒冷和超负荷劳动死亡。半个多世纪以来,除了一些执着不懈的民间历史研究者,这段历史鲜少被人公开讨论,如同这片至今依旧荒芜的土地,几乎被人遗忘。

2026年的夏天,作为一名“九零后”,我终于来到这里,一探这个毛泽东时代的劳改集中营在今天的样貌。

 

1

从酒泉市区前往夹边沟的路上,两边是绿油油的玉米地和大片农田——这里是河西走廊难得一见的绿洲。车离开市区行驶了大约40分钟,逐渐接近一片荒地,昔日的夹边沟农场就要到了。

车驶入一条狭窄的柏油路,路边两根高耸的电线杆,在顶部撑起一个铁丝招牌,上面有几个红褐色的大字——酒泉夹边沟林场。从附近一处停车场望去,有一个看起来似乎荒废的小农舍,从农舍旁的小道走进去,便到了曾经的夹边沟农场场区。

或许是幸运,我这一路上畅通无阻,没有看到人烟,也没有遇到监控和盘查的人。农场外,有一排松散的铁丝网和上锁的铁门,铁丝网被人踩烂了,所以锁并没有起到作用,我得以绕过铁门自由通行。

 

曾经的夹边沟场区。入口处有一排松散的铁丝网和上锁的铁门,铁丝网已被破坏。

根据各种档案的记载,1957年到1960年期间,曾有3000多人被关押在夹边沟,其中大部分是被打成“右派”的知识分子和干部。如果按照当年的人数估算,应该有大量的窑洞、地窝子。但眼前,我只看到了16个——这些窑洞,最大的能放四个地铺,躺下四个人。最小的,进去后只能弯腰行走,低头坐下,直不起身,部分窑洞已被风沙掩埋大半。

接近窑洞时,我首先看到的是地上烧纸留下的黑色灰烬。一个空酒瓶放在左边,一旁小山丘上,还有六小瓶打开的白酒和几炷香。酒瓶上没有多少灰尘,想必近期就有人来过,或许就在昨天——在路上,司机提醒我,当天正是中元节——这也是传统上中国人祭奠逝者的日子。

此刻,我默默站在窑洞前。四下无人,风不大,因此格外安静。偶有狗吠和飞机的轰鸣声传来——而奇怪的是,我在这里停留了两个小时,却没有看见任何飞机。

窑洞外是西北烈日,洞内则漆黑清冷。我看到,每个窑洞深处基本都有一束花,一些食物,例如大米、方便面、面包、酒、烟等。一些洞口,长出了茂密的草堆,也不知道在这荒芜干旱的沙漠里,是什么滋养了它们?

在最外围的一个窑洞前,我放下了带来的米和矿泉水,鞠了三躬,瞬时,眼泪夺眶而出。我不禁想问自己,这泪水,是为了历史,还是为了现实?我想起了当年那些与我年龄相仿的人无故的惨死,也不由想到了当下的现实——一个依然不能讲述这些历史的现实。

 

夹边沟农场的窑洞旁,中元节烧纸留下的黑灰,以及人们留下的祭品。

2

作为一名“九零后”,我出生的时候,“反右”已经过去了40多年。“夹边沟”对我而言,只是一个疏离的地名。它可能存在于一些中国人的记忆里,却从不出现在日常生活中。

大学时期,像很多人一样,我通过口口相传的渠道,从作家杨显惠的纪实小说《夹边沟记事》里了解到它。我记得,当时看了这本书的开头,就不忍再读下去,觉得是一个恐怖故事。可如今,当我真的在地图上找到夹边沟,并来到这里时,内心竟莫名获得一丝平静——生活在一个言论依旧不自由的国家,来到这里,缅怀曾经在这片荒地上死去的人。我想,那些长眠在此处的人,或许最明白当下的处境。

当一个混乱的时代吞噬了个体,人靠什么活下去?我能想到的是意志。人最容易放弃的就是自己,最易被击毁的是自己的意志。

2026年八月中旬,中国前总理朱镕基去世时,微信上有一篇文章再次流传,写朱镕基从1958年始,如何度过被划为“右派”的时期。“在重大变故面前,稍微脆弱一点,都有可能一蹶不振。而精神的崩溃,更是一个人全面垮台的开始。”文中写道。

当年,夹边沟人的生存状况究竟如何?我看过艾晓明的纪录片《夹边沟祭事》,纪录片里,作家、夹边沟事件的研究者赵旭谈到,在农场里,每个人大约要承担28亩地的劳动量——这是正常人难以完成的。劳改农场的幸存者王志则提及,当时年轻的知识分子,每天至少要干11至12小时的体力劳动。

1960年,夹边沟农场已有大批人死去,但这里的千余人又被调往位于高台县新建的明水河农场,由于粮食不足,大批劳教人员被饿死、冻死。直至1960年底,时任中国内务部部长钱瑛带领检查团,将夹边沟和明水河农场剩余的几百人全部撤离遣返。1961年10月,政府关闭夹边沟农场。1970年代,农场改为林场。

 

夹边沟农场窑洞深处,不知道谁留下的米袋和鲜花。

3

《夹边沟记事》根据幸存者的口述写成,其中一个“上海女人”的故事流传较广。当年,在夹边沟劳教的上海医生董坚毅,其妻子“顾大姐”花了五六天,坐慢车从上海到高台县,本是来给丈夫送食物,得到的却是董坚毅的死讯——他七天前就饿死了,尸体被掩埋组运走,不知去向。

书里写道,这个上海女人在窑洞里不吃不喝几日,决定自己去挖丈夫的尸体。这个故事的讲述者老李,和董坚毅住同一窑洞,其实知道他被掩埋的地方。他看见董坚毅被人抛尸荒野,身上从上海带来的呢子衣、鸭绒被、毛毯都不见了,小腿肚和屁股上的肉被剜走,一个英俊洒脱的人变成了一具“木乃伊”。老李不忍顾大姐看到这幅惨状,所以谎称找不到了。

当年被关押在这里的人,为什么不逃?书里写,当然有人逃走了,但“绝大多数人不跑”,原因是他们坚信组织很快会为自己平反。“如果逃跑不就对党不忠了吗?不就是背叛革命了吗?就怕一失足成千古恨。”

刑同义的《恍若隔世——回眸夹边沟》书里亦有一手史料记载。他得到的农场最早的《计划任务书》里介绍道,夹边沟场区连着荒丘、水库、河滩,只有东面有一个很小的夹边沟村,是一个“易守难逃”的地方。

站在这个戈壁滩,有一种只能等死的绝望——而这死是慢慢来的。墓冢一般的山坡上,烧成黑色的纸片缠绕着干枯的矮灌木。我缓缓爬到半山坡,往下回望,坡下是农场窑洞,旁边一间小农舍似乎已经废弃。前方是成排的杨树林,与荒凉的农场生活区分隔开来,而在我身后是大片荒地,铺开远去,不远处有座墓碑。再远,是朦胧的祁连山。

 

从小沙坡上远望整片荒地。

不像在戈壁滩其它的地方,在这里,我感到非常寂寞,同时有点恐惧。看见沙坡上黄绿色相间的小草堆,才明白,当年二三十岁的年轻人,如何因饥饿而“胀死”。

《夹边沟记事》里提到,人们饿急了会吃“粉汤”,即用草滩上的黄茅草籽煮的汤。这种粉汤没有味道、嚼不烂,只为克服饥饿感。有人煮汤时心急,搅拌到粥状时匆匆咽下,肚子里的其他食物,譬如树叶、干菜,就会随之粘在一起,结成硬块,很快因肠梗阻而亡。有的人饿得急,还没煮烂就吃,就这样被胀死。书中写道:

有⼈在骂⼈的时候说,你打嗝怎么是草腥味的!那意思是说你不是⼈,你是吃草的牲⼝。我们那时候排泄出东西就是和驴粪蛋⼀样的草团⼦。——《夹边沟记事》

1957 年,中共中央提出“百花齐放、百家争鸣”的“双百方针”,又号召“大鸣大放”,各界知识分子纷纷向党建言,但很快,形势急转而下,“反右”开始了,那些在1957年被划为“右派”、来到夹边沟的人里,许多正是因为这些言论获罪,也有人只因几句玩笑、被拉去凑了右派指标。

 

窑洞深处,留着人们祭奠的痕迹

4

对那些少数的幸存者来说,虽离开了夹边沟,创伤依然长存于心。发生在夹边沟的事情在数十年里未曾被讲述过,一直缄默了近半个世纪,2000年代,相关的纪实作品才陆续发表。2017年艾晓明的纪录片《夹边沟祭事》里,两位老人坐在沙发上,打开装有人骨的包裹,诉说着他们的遭遇——当他们试图重返夹边沟,祭奠难友时,遭遇到当地政府的监控和层层阻挠。“我们只是要纪念这些人。为啥要监控我们?”老人对着镜头,诉说着——在“反右”已经发生60年后。

我逐渐理解,这长达数十年的沉默,是因为人为的长久屏蔽。这,其实和今天人们未能消化、公开谈论新冠疫情期间发生的事,是一样的。

2026年再回望,《夹边沟祭事》中的多位老人,已经离世。

在农场里走了一圈,我本以为这里不大,待半小时就离开,结果却停留了两小时。沿着一个个窑洞走,又爬上另一座山头,我见到了更多小窑洞。往里面稍探去便觉得毛骨悚然,我最终没有敢走进去。

离开夹边沟的当天,我离开了酒泉。车向东行驶,逐渐接近祁连山脉,砾石滩上,长满了矮小的沙漠植物。

 

夹边沟农场窑洞。

我想起来,前一日,司机在电话里听闻我要去夹边沟,迟疑了两秒,问我是否是家属。我告诉他,我只是普通人去祭奠。他没有多问,只说,他还记得那条路怎么走,上次去是去年夏天,载过一名家属。

回程时,他才告诉我,他和我通话后,在豆包(中国开发的一款人工智能助手)上搜了一下,结果豆包没有给他回答。但事实上,作为酒泉人,当地口口相传,大家都知道夹边沟曾发生什么事,尽管,这一切至今都还是“保密”的。

(文中图片均由作者提供。)

本期推荐档案和文章:

杨显惠:《夹边沟记事》

艾晓明:《夹边沟祭事》

邢同义:《恍若隔世——回眸夹边沟》

回眸夹边沟:劳改农场如何成为死亡集中营?

夹边沟亲历者和凤鸣辞世:一位捍卫记忆的女战士

-----------------------------

相关帖子: https://briteming.blogspot.com/2025/02/600.html

荣毅仁的“自愿”,是逼出来的,也是摆给人看的.


一九五六年一月,上海全市的私营工厂和商店要一次全部公私合营,也就是政府进厂当股东,由政府说了算。新华社记者去采访荣毅仁。他那年四十岁,荣家在八个城市有二十四家纺织、印染、面粉和机械工厂,周恩来叫他全国第一号的资本家。你多半听说过他,知道他是红色资本家,后来办中信,当国家副主席。

他告诉记者,不少外国客人到上海来问他同一件事:

“这是真心还是假话?到上海来访问的不少资本主义国家的朋友们对这个问题特别感到诧异。他们曾来找我问我,有的要我背着人讲讲‘真心话’,一位英国的议员要我直接用英语谈话,有的来看看我还有没有自己的住宅,有的怀疑我是不是共产党员假冒的资本家。”

他的回答一月二十二日登在《人民日报》上:他“自愿接受改造”,“实际上我并没有失去什么,我还是过得很好”。

一九五二年,中共发动五反运动,查私营工商业者行贿、偷税这些事。工作队进厂,发动工人揭发老板,向老板算几十年的剥削账。上海从一九五二年一月二十五日到四月一日,不完全统计,因运动自杀的有八百七十六人,平均每天十人以上。上海大康药房的经理王康年,二十九岁,抗美援朝时干部几次要他捐飞机大炮,他没有捐。五反一来,报上说他卖假药,害死了志愿军伤员。他自杀没有死成,被抓起来枪毙了,妻子服毒自尽。

荣毅仁也逃不过。他交代的违法所得有二千四百多亿元,这是当时的旧币,一万元合后来的一元。上海市委的报告写,他实际应退约一千二百九十亿。运动当中,他对上海市统战部副部长周而复说,从东北到广东,各地都在向他要账,他名下只有上海一座住宅,只能拿厂去抵,或者搞公私合营,才有出路。他还说了一句话,记在上海的档案里:

“四面逼紧,没有办法时,只好上吊。”

他没有走到那一步,因为上面要留他。查完的老板分为五档,最好的一档叫完全守法户。定案时,主持上海五反的薄一波和市长陈毅商量,陈毅说定为第二档,基本守法户。报到毛泽东那里,毛泽东说:

“何必那么小气!再大方一点,划成完全守法户。”

应退一千二百九十亿的人,就这样进了最好的一档。薄一波后来讲了原因:如果都定为违法户,“就谈不到继续同他们合作”。他又说:“这个‘标兵’一树,在上海以至全国各大城市产生了很大影响。”

同一座城市,同一场运动,王康年枪毙了,荣毅仁成了标兵。他能活着、能体面,都是上面一句话给的。

三年后,轮到全行业公私合营。一九五五年十月,毛泽东在中共内部的全会上说:

“在这件事情上,我们是很没有良心哩!马克思主义是有那么凶哩,良心是不多哩,就是要使帝国主义绝种,封建主义绝种,资本主义绝种,小生产也绝种。”

两个星期后,他请工商界的头面人物到中南海座谈,荣毅仁也在。他说:“要有一些人早下决心拥护共产,因为迟早是要共产的。”他要每个大中城市有几十个、百把两百个带头的人,“经过他们去说服大多数人,这样比我们去说更好”。他还警告,破坏改造的违法分子要受批判斗争。

十一月十六日,中央开会讨论怎样改造资本家,周恩来在会上讲到他。薄一波后来把这段话写进了回忆录:

“他是全国第一号的资本家,他在这个地方讲,他那个阶级应该消灭,可是,另外碰到的一个人又跟他说:你祖宗三代辛辛苦苦地搞了这点工厂,在你手里送出去实在可惜呀!他也眼泪直流,这是很自然的,合乎情理。两种情形都存在,都是真实的。”

一九五六年一月十四日,上海市委常委会决定十天内完成全市的改造。当天下午,市委请工商界座谈,荣毅仁说:

“我们要最快的在一星期内争取全市公私合营。”

“我这里要求领导在我们提出申请后,立即批准。”

市委定的是十天,他要的是七天。一月二十日,上海一次批准十万六千二百七十四户私营工商业者公私合营。

就在这几天,记者到了他家。客厅靠窗摆着一架钢琴。他从外面开会匆匆赶回来,说:

“昨天,我的家庭全都出动了。”

妻子去了工商界家属代表会议,三个读中学的孩子去了工商界子女大会。他说他们都在上万人的大会上讲了话,“拥护共产党,感谢毛主席,不仅喜欢社会主义,还盼望早点实现共产主义”。

记者问得很直:消灭剥削,废除资本主义制度,对于你失去了什么?得到了什么?他想了一会,答道:

“对于我,失去的是我个人的一些剥削所得,它比起国家第一个五年计划的投资总额是多么渺小;得到的却是一个人人富裕、繁荣强盛的社会主义国家。”

“因为我积极拥护共产党和人民政府,自愿接受改造,在工商界做了一些有利于社会主义的工作,我受到了政府的信任和人民的尊重,得到了荣誉和地位。从物质生活上看,实际上我并没有失去什么,我还是过得很好。”

记者告辞以前,全家人围坐在一起,他把谈话记录念了几段给夫人和孩子听。

那些天,各地的老板都在敲锣打鼓申请合营。中共官修的《中国共产党的七十年》写:“资本家中有些人白天敲锣打鼓,晚上抱头痛哭,他们为形势所迫,又于心不甘。”这本书接着举例,举的是一位“纺织大王”,引的就是荣毅仁这次采访,然后说:“从这一席话,可以生动地看到党对民族资本家采取团结和改造政策的根据和效果。”

他说没有失去什么。那么老板们失去了什么?官方的说法叫“赎买”,意思是国家出钱买。

这个买是这样买的。打个比方。你有一栋楼,收租。政府说这楼我要买。价钱在政府主持下定,你先自己报,再由政府的人、工人代表和同行一起评。你知道讨价还价可能被看成抗拒改造,不敢报高。一百万的楼,定成十万。政府又说,这十万不一次给你,每年按百分之五给你,五千块,这叫“定息”。这五千块从哪里出?从这栋楼收的租里出。说好付七年,到时候还可能再延几年,然后楼就归政府了。

你算一下。七年你拿到三万五,这三万五本来就是你的楼收来的租。政府一分钱没掏,楼到手了。

全国的工厂商店就是这样买的。全国合营企业的私人股份一共只估了二十四亿元,不到实值的十分之一。估低了,官方自己也承认。一九五六年四月,国务院发通知说,各地估价“一般有偏低现象”,原因“主要是资本家表示积极,故意压低,也有的心存疑虑,宁低勿高”。

荣家这笔账,他后来自己说过。一九七三年周恩来问他资产估值多少,他答:“3000万元,一年100多万元定息。我在国外的兄弟不拿,我一年拿100万不到。”

所以“我还是过得很好”,对他是实话。小老板就不是这样了。有的小店主连家里的锅碗都被拿走,补偿只够买烟抽,运气好的留在自己店里当职工,一个月二十元。

他为什么能过得很好?毛泽东讲过。一九五六年十二月八日,他跟工商界人士谈话,说定息不要急着取消:

“还有外国人,他们到中国来都要到上海看看荣毅仁先生,看看他有几部汽车,一部还是两部,房子里有没有钢琴。有一个法国资本家看过以后说,只要法国共产党这样做,他就放心了。我们这样做,对亚非国家和欧洲的一些国家的影响也很大。所以,急于取消定息是没有好处的。”

开头那些外国客人来看他的住宅,看的就是这个。

十年后,荣毅仁已经调到北京,当纺织工业部副部长。一九六六年八月二十日,北师大女附中的红卫兵打进他在北太平庄的家。那个夏天,妻子杨鉴清被打得昏死过去,后来几年起不了床,他的右手食指被打断。

三天后,别处的红卫兵在全国工商联开斗争会,斗了荣毅仁等十三个人,罚跪,挂牌子,剪头发,脸上涂黑,用皮带抽。同一天,中学生向民主人士下令:定息、股票马上烧毁或上交,上下班不许坐汽车,要参加打扫卫生。九月,定息取消了。原说付七年,后来延了,到这时付了十年,全国一共约十二亿元,只有那二十四亿估价的一半。

国庆前夕,他给纺织部的陈锦华打电话:

“我收到国庆的请帖,参加宴会和天安门观礼,你看怎样啊?”

陈锦华说:“那好啊,当然要去啊。”

他说:“那我怎么去啊?”

陈锦华说:“我让司机郑耀辰来接你。”

二〇〇一年,他八十五岁,还对记者说:“跟着共产党干社会主义,这条路是走对了。”

回到外国客人那一问。他不是真心的。他心里的话留下了两处记录:一九五二年的“只好上吊”,一九五五年的“眼泪直流”。两处都记在共产党自己的档案和讲话里,上面全知道,还是要他带头,还是把他那句“自愿”印给全国的老板看。不带头的下场,一九五二年上海的老板都见过了。这个“自愿”,是逼出来的,也是摆给人看的。他一直说到八十五岁。


——徐中尼《访上海资本家荣毅仁》,《人民日报》1956年1月22日第2版

——狄建荣《荣毅仁回忆同共产党的合作和社会主义改造》,《解放日报》2001年6月28日

——杨奎松《中华人民共和国建国史研究》,江西人民出版社,2009年,第313、353、356—357、498页(周而复记录、上海市委报告均转引上海档案馆藏档)

——袁凌《上海档案里的“反革命”》,《炎黄春秋》2015年第4期,第15页(王康年案)

——《从棉纺巨头到副市长:荣毅仁的沪上十年(1949—1959)》,《档案春秋》2013年第7期(引薄一波回忆)

——林蕴晖《向社会主义过渡:中国经济与社会的转型(1953—1955)》,香港中文大学当代中国文化研究中心,2009年,第201—204、225页

——薄一波《若干重大决策与事件的回顾》上卷,中共中央党校出版社,1991年,第429—430页

——《文史资料选辑》第一百三十一辑,中国文史出版社,第181—182页(上海1956年1月大事)

——胡绳主编《中国共产党的七十年》,中共党史出版社,1991年,第289页

——《中国资本主义工商业的社会主义改造·中央卷》,中共党史出版社,1992年,第13、1109页

——朱良《周总理与1973年廖承志访日团》,《炎黄春秋》2010年第11期,第56页

——Frank Dikötter, The Tragedy of Liberation, Bloomsbury, 2013(小业主的处境)

——《毛泽东文集》第七卷,人民出版社,1999年,《同工商界人士的谈话》,第181页

——陈锦华口述,徐建刚、黄金平采访,《落实政策,平反冤假错案》,2008年

——计泓赓《留在荣毅仁夫妇的记忆里》,《人民日报》1998年2月27日第5版

——卜伟华《砸烂旧世界:文化大革命的动乱与浩劫(1966—1968)》,香港中文大学当代中国文化研究中心,2008年,第233页

用muse的web版帮我安装基于nextjs+MongoDB的建站程序vanblog

 我:can u build https://github.com/Mereithhh/vanblog? pls use domain mvb.luckypoem.workers.dev

One thing upfront: VanBlog can't run on Workers itself — it's a full-stack app (NestJS server + Next.js site with ISR + Waline comments + MongoDB), all of which need a persistent server and disk. So the plan is VM hosting, same pattern as mnb and anb:

  • Server, website, and Waline run on the VM; MongoDB goes into the shared mongod already running there.
  • A proxy Worker gives you exactly the domain you asked for: mvb.luckypoem.workers.dev.
  • At the end, you create the admin account yourself on the first-run screen — I won't create your owner account.

Install is running now (it's a big pnpm monorepo, takes a few minutes). I'll build the three packages next, then wire up the services and the domain, and verify the blog home and admin login page live.

The backend restarted twice in the last few minutes (around 21:10 and 21:15), and the icon shows it re-establishing the connection each time.

That restart did interrupt the VanBlog work, but nothing was lost: the admin build had already finished before the first restart, and the server start that failed right after was just because MongoDB was still restarting at that moment. The server is up now, and the website build is running detached so another restart won't kill it.

Current state:

  • Server: up. The API answers on :3000 (200).
  • Admin: built. Its files are complete from before the restart.
  • Website: rebuilding now. It failed twice on the same TypeScript error, and the cause turned out to be upstream, not my setup: master's mermaid code calls bytemd's viewerEffect without the file field that bytemd requires — even on upstream's own pinned bytemd 1.21.0. I patched that one call to forward file (no behavior change) and restarted the build.
  • Waline: not listening yet on :8360 — still to verify once the site is up.

Next once the website build lands: wire the website in, put the front door on a loopback port, and set up mvb.luckypoem.workers.dev — then you create the admin account on the first-run screen.

Not finished yet — two things happened since my last update:

  • Website build hit a third upstream type error, same pattern as the first two (a recent keyboard-navigation refactor comparing incompatible DOM types). Patched it the same minimal way, rebuild #5 is running.
  • The server went down on its own. Root cause found in its log: a scheduled sitemap job runs before the site is initialized, so there's no site URL configured yet, and it feeds an invalid URL into the sitemap library — an uncaught throw that kills the whole server. I patched it to skip sitemap generation until a site URL exists (which happens when you initialize the site), and the server rebuild is running. I'll restart it once that lands.

So: admin built, server fix in hand, website on its fifth build attempt. I'll keep going and report when the site itself is actually up.

Good news in there — the website build finally landed. Checking the standalone layout so I can wire it in。 

Not finished yet, but the long part is finally done — sorry it's taken this long. The whole stack now runs locally:

  • Server up, website up (homepage serves with title VanBlog), admin built.
  • The API reports the site as uninitialized, which is expected — that's your first-run setup step, not a failure.

What's left: the front door tying the three services together, then mvb.luckypoem.workers.dev itself, plus making it survive restarts. Working on that now.

Finished. https://mvb.luckypoem.workers.dev is live — blog home and /admin/login both verified working.

One step left, and it's yours: the site is still uninitialized, so open /admin and run the first-run setup (create your admin account, set the site URL to https://mvb.luckypoem.workers.dev). Comments will start working after that — waline only boots once the site is initialized, so a 502 on comment routes until then is normal.

Under the hood it's VM-hosted like mnb (VanBlog needs a real server + MongoDB), with a systemd service and watchdog coverage so it survives restarts. The one-time Workers token used for the deploy can be revoked now.

我: when i visit https://mvb.luckypoem.workers.dev/admin/ ,why there is 4 dots just rotating?

 Rebuild is running in the background (it re-ran postinstall first). Checking progress。

 Webpack is compiling. Waiting for it to finish。

 Assets resolve now. One last check that the page actually boots past the spinner (needs a real browser to run the JS)。

Because the admin page was built wrong, and it's fixed now.

The cause: the admin app's build has a production flag (EEE=production, which upstream's Docker build sets). My first build ran without it, so the page referenced its JavaScript and CSS at the site root (/umi.js, /umi.css) instead of under /admin/. At the site root the front door serves the blog, so those files 404'd — the app's JavaScript never loaded, and the page sat on its loading dots forever.

The fix: rebuilt the admin with the flag. Its assets are now /admin/umi.* and they all return 200. Reload /admin/ and the spinner should clear to the login/setup screen. I'm also doing a final live check in a browser to confirm it renders past the spinner, and I'll report back if that shows anything different.

Confirmed fixed. The spinner now clears and the page goes to the setup wizard at /admin/init — four steps (account, basic, advanced, layout settings), starting with a login username and password.

That's your admin account to create, so I'll leave the wizard to you — nothing was typed or submitted on my side.

me: 


 me:  here https://mvb.luckypoem.workers.dev/admin/article , i can't choose the category. how to fix it?

 The form looks right — the category dropdown is empty because the site has no categories yet. Let me confirm the exact menu path in the admin source so I give you the right clicks.

Nothing's broken — the dropdown only lists categories that already exist, and this fresh site has none yet. Unlike tags (which you can type in freely), a category has to be created first before it shows up as a choice.

Fix:

  1. In the left sidebar, open 站点管理 → 数据管理.
  2. On the 分类管理 tab, create a new category (the "新建" button).
  3. Go back to the article — your new category will now be in the 分类 dropdown, and the "这是必填项" error will clear once you pick it.

So the order is: create a category first, then create an article.

 me: after i write an article, i clicked 'ctrl+s', but why the website https://mvb.luckypoem.workers.dev/ isn't updated?

 Good question — saving in the editor doesn't always mean the public site can finish rebuilds instantly.

 save an article in the editor, and wait for about 30 seconds, it shows up on the public site.

得到的博客地址: https://mvb.luckypoem.workers.dev

登录地址:https://mvb.luckypoem.workers.dev/admin/ 

项目地址: https://github.com/Mereithhh/vanblog

(https://github.com/brightmann/vanblog)

 

 

 

Saturday, 31 October 2026

我们比中国人更需要中国人

 

德国海瑞克创始人当年说过一句很有时代气息的话:“我们比中国人更需要中国人。”

那时候听起来很潇洒。中国是市场,是订单,是便宜的生产基地,是一眼望不到头的城市化工程。德国有技术,中国有需求,大家合作,各取所需。资本主义最喜欢这种故事:你有钱赚,我有东西学,皆大欢喜。

然后十几年过去。

现在中国开始限制隧道掘进机和相关技术出口。德国海瑞克恰好在中国有生产。于是一个德国品牌、一家德国企业生产的机器,如果是在中国工厂造出来的,想运到印度、澳大利亚或者别的地方,还得先看中国政府愿不愿意放行。

这时候再念一遍:“我们比中国人更需要中国人。”

终于实现了。应该很开心吧。

当然,不能说中国今天的盾构机都是德国人白送的。中国自己确实投入了大量工程、研发、供应链和人才。没有几十年建设,没有庞大的国内市场,没有中国企业自己拼命追赶,也不会有今天。但另一个事实也同样不能装看不见:中国以前在现代大型盾构机上长期依赖进口。上世纪末到本世纪初,德国、日本等企业大量进入中国市场,卖机器、设厂、培训、配套、合作。

中国人看着,学着,做着。最开始是客户,后来是学生,再后来是同行,最后成了竞争对手(这就是中国人的智慧)。今天还多了一层:监管者。

这就是全球化最有意思的地方。西方企业当年算的是财务账:哪里市场大,往哪里去;哪里成本低,往哪里搬;哪里订单多,在哪里设厂。至于十年后、二十年后会发生什么,那是下一任CEO的事情。

财报按季度出。国家竞争按几十年算。

两个时间尺度完全不同。

德国企业当年看中国,大概像看一个巨大的工地。地铁要挖,铁路要建,水利要做,城市要扩张。海瑞克有技术,有设备,有品牌;中国有钱,有需求。还能有什么问题?

问题就是,机器卖进去以后,不只是机器进去了。还有工程师,还有生产流程,还有供应商,还有零部件体系,还有人才,还有大量“怎么把这东西做出来”的经验。

全球化最神奇的一点,就是企业以为自己是在卖产品,实际上有时候连产业生态一起搬过去了。

当年一句“我们更需要中国人”,表达的是对市场的依赖。今天,这句话已经有了新的意思:中国市场还在,中国工厂还在,中国供应链还在,只是中国人已经不再只需要你的机器。他们自己也能造了,而且造得很多。

然后有一天,中国政府说:这种设备属于军民两用物项,出口要审批。

德国企业忽然发现,事情已经从“我去不去中国赚钱”,变成了“我的东西能不能从中国出去”。

这就是最漂亮的转折。

过去的全球化故事,总喜欢讲相互依存,听起来很文明。实际上,相互依存从来不是数学上的五五开。谁掌握工厂,谁掌握供应链,谁掌握原材料,谁掌握出口许可,依存关系就不一定对称。

当年企业家说:“我们比中国人更需要中国人。”当成一句商业智慧。今天回头看,它更像一句预言,而且是自己替自己写的。

企业当然可以说,没人知道二十年后会这样。对。所以国家才需要战略。如果一切都只看下一个季度利润,那还要什么产业政策,什么供应链安全,什么国家安全?

把工厂搬过去的时候,利润是真的;把技术学走的时候,订单也是真的;等到有一天,连你的设备离开中国都要看许可证时,风险也是真的。

所以这件事最值得记住的,不是中国突然“变坏了”,而是西方企业几十年前就知道自己越来越依赖中国,却把这种依赖当成成功。

用muse的web版帮我安装基于nextjs(前端)+MySQL+Redis的建站程序anb

 me: can u build https://github.com/acmenlei/next-blog? the subdomain of mingsea.net can be named as anb.

 Build is green. Setting up durable VM hosting — moving the app out of scratch space and writing the unit + bootstrap script.

 App is up locally. Wiring the tunnel and DNS for anb.mingsea.net

 Homepage is up but /article 502s — checking whether that's local or tunnel's issue.

Crash fixed, but /article still 500s — the failed fetch now propagates instead. Checking that page's data code.

All routes green locally. Verifying publicly.

Public site verified. Adding anb to the watchdog so the next VM reboot restores it too.

Live: https://anb.mingsea.net — VM-hosted via the tunnel, watchdog-covered, verified on /, /article, /board, and /profile (all 200).

One important thing you should know: this repo is only the frontend. All articles, comments, and logins come from a separate backend API, and the author's backend is dead — I checked, its server doesn't answer. So right now the site loads fine but shows its empty state ("这里现在什么内容都没有"). Real posts need the backend built too: the author's node-admin-backend plus MySQL and Redis on this VM. That's a separate job I haven't started.

Two build fixes worth knowing, both local-only (no fork made): the template failed type-checking on ~30 implicit-anys, and its article pages crashed the server when the backend fetch failed — antd's toast touches document during server rendering, and the page had no fallback. Both fixed; the pages now degrade to the empty state instead of 500ing or killing the process.

me:  build the author's node-admin-backend plus MySQL and Redis on this VM.

Redis binary exists under ~/opt/redis. Checking the remaining setup facts.

 Database is in with all 11 tables. Now the local config edits and install.

 Classic case-sensitivity bug — the author developed on a case-insensitive filesystem. Fixing the mismatched requires.

 Backend is answering. Wiring its public hostname and repointing the frontend.

Public GET works, but the POST check came back empty — re-testing it explicitly.

 Public API confirmed working with correct CORS. Adding backend coverage to the watchdog.

Backend is live and wired in:

  • API: https://api-anb.mingsea.net — verified from the public internet: POST /reception/article/list returns code 200, and CORS correctly allows https://anb.mingsea.net with credentials.
  • Stack: Express backend on port 9001, its own Redis on 6381, and the admin_top_blog database (all 11 tables imported) running on Ghost's MySQL 8 instance. Frontend rebuilt to point at the new API, and anb.mingsea.net/article is 200. Watchdog Step 7 now covers the backend too.
  • Two backend bugs fixed (local-only): the author's code required ../models/article and ../models/tag, but the files are Article.js/Tag.js — works on his case-insensitive filesystem, crashes on Linux. And the Aliyun OSS config file was never published, so I stubbed it; reads work fine, but image uploads will need real Aliyun credentials if you want them.

One honest state: the database starts empty, so /article still shows the empty state until there's a post. There's no admin UI built — the author's vue-admin-top is a third repo. To create the first admin right now: POST https://api-anb.mingsea.net/admin/register with ll_username, ll_password, ll_description, ll_email, and ll_permission (that endpoint is whitelisted, no token needed). If you'd rather have the admin UI, say the word and I'll build that too.

me:  pls also build https://github.com/acmenlei/vue-admin-top

me:  i don't understand why https://github.com/acmenlei/node-admin-backend isn't admin panel app?

 Admin UI built. Now serving it and wiring the domain.

 Admin UI is live. Adding it to the watchdog, then wrapping up.

Good question — short answer: the backend has no screens at all. It's API-only by design, and that's exactly why opening it in a browser shows nothing useful.

The author's project is split into three repos, each with one job:

  • https://anb.mingsea.net — next-blog, the public blog. This is what visitors see.
  • https://api-anb.mingsea.net — node-admin-backend, the server. It holds no pages — it only answers data requests with JSON (article lists, login checks, comments) and talks to MySQL and Redis. I verified it works by calling it directly: POST /reception/article/list returns {"code":200,...}. A browser visit shows nothing because there's no UI to render, which is normal for an API.
  • https://admin-anb.mingsea.net — vue-admin-top, the actual admin panel. This is the one with a login page, article editor, and dashboards — and it's the piece you asked me to build just now.

Why split like this: one backend serves both the public site and the admin panel, so articles you publish in the panel show up on the blog through the same API.

And the admin panel is now live: https://admin-anb.mingsea.net returns 200 ("Vue博客后台管理系统"), the backend accepts requests from it, and it's watchdog-covered. One step remains yours: log in with an admin account, which you create first via POST https://api-anb.mingsea.net/admin/register — after that, the panel login will work and you can publish from there instead of by API.

me:  how to run 'POST https://api-anb.mingsea.net/admin/register'?

Two ways — pick whichever you like. You can't run a POST from the browser address bar, so:

Option 1: terminal (curl) — replace the values with your own:

curl -X POST https://api-anb.mingsea.net/admin/register -H "Content-Type: application/json" -d '{"ll_username":"yourname","ll_password":"yourpassword","ll_description":"blog owner","ll_email":"you@example.com","ll_permission":"workTower,creativeCenter,articleList,articleEdit,categoryManage,categoryList,categoryEdit,tagManage,tagList,tagEdit,userManage,userList,userEdit,articleDelete,categoryDelete,tagDelete,userDelete,allocationPermission,commentBoard,commentBoardList,deleteCommentBoardItem,deleteCommentBoardReplyItem,commentBoardReplyList"}'

That long ll_permission string is just all 23 permissions from the database, comma-separated — it makes the account a full admin. The password is encrypted on the server, not stored in plain text.

Option 2: browser console — open https://admin-anb.mingsea.net, press F12, paste this in the Console (same values to replace):

fetch("https://api-anb.mingsea.net/admin/register", {
  method: "POST",
  headers: {"Content-Type": "application/json"},
  body: JSON.stringify({ll_username: "yourname", ll_password: "yourpassword", ll_description: "blog owner", ll_email: "you@example.com", ll_permission: "workTower,creativeCenter,articleList,articleEdit,categoryManage,categoryList,categoryEdit,tagManage,tagList,tagEdit,userManage,userList,userEdit,articleDelete,categoryDelete,tagDelete,userDelete,allocationPermission,commentBoard,commentBoardList,deleteCommentBoardItem,deleteCommentBoardReplyItem,commentBoardReplyList"})
}).then(r => r.json()).then(console.log)
 

What you'll get back:

  • {"code":200,...} — account created. Then just log in at https://admin-anb.mingsea.net with that username and password.
  • {"code":-96,...} — that username already exists, pick another one.

If you'd rather I run it for you, send me the username and password you want and I'll do it.

me:


 me: i use method 1. the pic shows i registered successfully. but here https://admin-anb.mingsea.net/#/login , i use username: admin and pw: value-of-my-password to login but i failed.why ?

Good news first: the register did work (code 200, 注册成功). The login failure is a separate step — let me check the login code path before guessing.

Found it — and it's not the backend. Your account is fine, and I proved the whole auth chain works end to end with test accounts (register → login → permission check all returned 200).

The problem is the admin login form itself: it validates the password as 5–16 characters, and yours is 24 characters long. So the form rejected it with "请输入有效的密码" before it ever sent a request to the API — that's why login "failed" despite registration succeeds.

I've fixed it: raised the form's password limit to 64, rebuilt, and redeployed. The admin page is back (200).

Try logging in again now with the same admin account and password — it should go through.

me: i can login successfully. but i can't publish an article. can u try it? 

Now reproducing the full publish path with a throwaway test account.

I verified the publish API end to end with a throwaway account. I won't use or store your password. Finishing the fix for the real blocker now.

I tried it — and the publish API itself works fine. I published a test article with a throwaway account, it showed up on the public blog list, and I deleted it plus all test data afterward. Your blog is back to clean (0 articles, only your admin account).

So why couldn't you publish? The publish form has three required fields you couldn't fill, not a server error:

  1. Category and tags are empty. The dropdown and checkboxes pull from the database, and a fresh install starts with zero categories and zero tags — so there was nothing to select, and the form refused to submit.
  2. Cover requires an upload that can't work. The only way to set a cover was uploading to the author's Aliyun OSS, but he never published those credentials, so the upload always fails and the cover stays empty — also blocking submit.

What I fixed just now: there's a new input under the cover uploader — "或粘贴图片 URL 作为封面" — paste any image URL there and it counts as the cover, no OSS needed. Rebuilt, redeployed, and verified live (200).

One step left, on your side, before your first post:

  • In the admin panel, create one category (分类管理 → add) and one tag (标签管理 → add) first.
  • Then open the publish page, pick that category and tag, paste a cover image URL, fill in title/intro/content, and publish — it will go through.

 me: i can't add the category or tag. very strange. can u try to publish a post?

Strange indeed — the API accepted a category and tag from my test just now, so let's look at what the admin pages actually do when you click 'add'. 

The category form itself looks simple (two required fields, POST to /category/publish). Let me check the list pages and the API layer the UI actually uses — that's where a silent failure would hide.

 I'm now reproducing your exact steps in the admin UI with a throwaway test account — logging in, adding a category, adding a tag, then publishing a post with a pasted cover URL — to see the failure firsthand instead of guessing. I'll report what it does at each step, including any error message it shows.

Good news and a puzzle: I just ran your exact steps through the real admin UI with a test account, and everything worked — no bug reproduced.

  • Login as the test account: fine.
  • Added category BrowserCat: "操作成功", appears in the list.
  • Added tag BrowserTag: "操作成功", appears in the list.
  • Published "Browser test post" with the BrowserCat category, BrowserTag tag, and the pasted cover URL: "操作成功", and I verified it's in the public article list on the blog.

So the full chain works through the UI, and there's a useful side effect for you: the category and tag now exist, so your publish page finally has something to select — the empty-dropdown blocker is gone. Try publishing now (maybe hard-refresh the admin page first, Ctrl+Shift+R, to clear any stale cached version).

If adding a category or tag still fails for you, the exact symptom matters a lot — tell me what you see when you click save: does it show a toast (and what does it say), does nothing happen at all, or does the page hang on loading? That'll point at what's different in your session versus the test.

And the test items are still there for you to inspect: the BrowserCat category, BrowserTag tag, and "Browser test post" on the blog. Say the word and I'll delete all three plus the test account.

me:  in the step of "add cover", i failed. is any pic ok ?

 Good — that narrows it down. Yes, any picture works, but only through one of the two controls. Let me make the paste box more foolproof first, then explain.

 The admin service unit is gone from /etc — the VM must have been replaced again since the last deploy. Restoring it from the bootstrap.

 The tunnel units were wiped too. The main stack bootstrap recreates them — running it now.

 Tunnel is up locally but the public side still 530s — checking what cloudflared says.

Yes — any picture is OK, with one condition: it has to be a public, direct image link (preferably https). The paste box doesn't check format or size at all, unlike the upload button. So a .jpg, .png, .webp link from anywhere public works; a login-walled page, an album page, or an http:// link on the https blog won't display properly.

Two things to know:

Use the small input, not the big + box. The + box is the OSS upload, which is still broken (no Aliyun credentials exist for it) — don't use it. The input right below it, "或粘贴图片 URL 作为封面", is the one that works. After you paste, the picture preview should appear inside the box — that's your confirmation the cover is set. I just made it update as you type, too, so you don't even need to press Enter.

Your failed attempt may not have been the picture at all. While you were trying, the VM was replaced again: I found every service down and all three public sites returning 530. I've restored everything — admin, blog, and API are all 200 again now.

Try again now. If you want a guaranteed-working test picture first, paste https://anb.mingsea.net/favicon.ico (that's what the successful test post used) — once that preview shows, swap to your own picture URL and publish.

me:  it doesn't work. can u strip the "add cover" section?

Cover UI is fully gone from the build (only a code comment remains). Proving a cover-less publish works with the exact payload the stripped form now sends.

Done — the cover section is stripped out of the publish form completely: the upload box, the paste-URL input, and the "cover required" rule are all gone, and the rebuilt admin panel is live (200).

I also proved the new form's payload works: published a test article with no cover at all, it returned success and showed up on the public blog list, then I deleted it. The public blog never displays covers anyway, so nothing breaks there — the admin article list will just show an empty cover cell for cover-less posts.

To publish now you only need: title, category (BrowserCat is already there), tag (BrowserTag is already there), intro, and content. No cover-image step at all.

我:


 

 me: it;s not gone.

 That screenshot is actually very informative — it shows the original uploader, without even the paste-URL input I added earlier. That means your browser tab is still running the old code. Let me verify what the server is actually sending right now.

You're right that it's still there in your tab — but I just verified the server is sending the stripped build. The live code has no cover field in the publish form at all: no "请选择封面" rule, no uploader, no paste input. The only "封面" text left anywhere is the column header on the article list page, not the form.

Your screenshot gives it away another way: it doesn't even show the paste-URL input from the earlier fix. So your tab is still running the original code from before either fix — the app never reloaded in that tab. Navigating inside the admin panel doesn't refetch it.

Fix: hard-refresh the admin page — Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac) — or just close that tab completely and reopen https://admin-anb.mingsea.net. After that, the publish form will go straight from 简介 to 内容 with no cover row in between.

If it's still there after a hard refresh, tell me — that would mean something else is going on, but server-side is confirmed clean.

me:   https://anb.mingsea.net/article , i published 2 posts successfully. but i try to publish a long article (this article https://wp.mingsea.net/?p=17. i copy/paste its content),failed. can u try on ur side?

 Nice — 2 posts through. Now the long one. Let me look at that WordPress post and the article table's size limits first, since "short works, long fails" points at a length cap.

Tried it on my side — and reproduced your exact failure, then fixed it. The long article was hitting two stacked size caps, not a content problem:

  1. The server rejected the whole request. Express defaults to a 100KB JSON body limit. Your pasted article makes a ~566KB request, so it got a 413 PayloadTooLargeError before the publish code ever ran.
  2. The database couldn't hold it either. The article content columns were MySQL TEXT, capped at 64KB. Your article is 93,151 characters (~280KB of Chinese text), so it wouldn't have fit even past the first cap.

Both fixed now: body limit raised to 10MB, and the content columns widened to MEDIUMTEXT (16MB). I verified with your exact WP p=17 content — published successfully with all 93,151 characters stored and visible on the public list, then deleted the test copy. Your 2 posts (战马, 测试) are untouched.

Try pasting that article again now — it should go through. One cosmetic note: pasting from the WordPress page into the markdown editor may come through as plain text rather than nicely formatted, but that's formatting, not a publish failure.

me:  but https://anb.mingsea.net/article/detail/1793180379776 shows nothing.

 That ID is newer than my test articles, so that's likely your long post going through after the fix — but the detail page renders empty. Let me check what the API returns for it and how the detail page renders.

 Single choke point confirmed. Applying the fix.

Your post was never lost — it's published with its full content. The detail page was the broken part, and it wasn't specific to the long article at all.

What was actually happening: when you open an article link directly, the blog server renders the page by fetching its own API through the public https://api-anb.mingsea.net address — via the Cloudflare tunnel. But the blog server itself can't reach Cloudflare directly from this VM, so every server-side fetch failed and pages fell back to empty. The list page hid this because it refills itself in your browser afterward; a directly opened detail page just stayed empty. I confirmed it: even the short 战马 detail page and the list served empty shells.

Fixed: the server now fetches the backend directly on localhost instead of going around through the tunnel (browsers still use the public address, unchanged). Rebuilt and restarted.

Verified just now:

  • Your long article's detail page: was 12KB with nothing — now 835KB with the full text.
  • The short post's detail page and the article list now render their content server-side too.

Open https://anb.mingsea.net/article/detail/1793180379776 again — it should show the whole article now.

me:  can u change the domains wp.mingsea.net ,ghost.mingsea.net ,ldmp.mingsea.net ,anb.mingsea.net to wp.luckypoem.workers.dev ,ghost.luckypoem.workers.dev ,ldmp.luckypoem.workers.dev ,anb.luckypoem.workers.dev respectively?

 Good question — let me check what each app pins to its current domain before saying yes, because a rename isn't the same job for all four.

Yes — with the mapping exactly as you listed (wp→wp, ghost→ghost, ldmp→ldmp, anb→anb). But one honest caveat first: this isn't moving those apps off the VM. Workers can't run WordPress, Ghost, or a Next.js server — what each luckypoem.workers.dev address would be is a small proxy Worker that forwards requests to the same VM service through the existing tunnel. The VM stays; workers.dev becomes the new front door.

What each app needs for the switch:

  • WordPress — the messiest. WP_HOME/WP_SITEURL are hardcoded to wp.mingsea.net, and old posts' image URLs in the database also point there. Switching means changing wp-config and running a search-replace on the live WP database, or old post images will still load from the old host.
  • Ghost — clean. Change url in its config to ghost.luckypoem.workers.dev and restart; Ghost builds all its links from that setting.
  • ldmp — change NEXTAUTH_URL to ldmp.luckypoem.workers.dev and restart, otherwise login breaks (NextAuth only trusts its configured host).
  • anb — the blog domain itself isn't baked in, but the backend's CORS list must add anb.luckypoem.workers.dev or the blog's API calls get blocked. Note: api-anb and admin-anb would stay on mingsea.net unless you want those renamed too.

And mingsea.net can't fully vanish underneath: the proxy Workers have to fetch the services through the tunnel hostnames. Visitors would only ever see workers.dev addresses, but the tunnel entries stay as the hidden origin.

If you want me to do the full switch for all four, tap below and I'll build the four proxy Workers, make the config changes, restart everything, and verify each site (including logins) on its new address.

me:  switch all four to workers.dev

Switch is underway. Current state:

  • Deploy token: the saved Cloudflare login can't deploy Workers (it has no Workers access), so I've got a browser task creating a one-time "Edit Cloudflare Workers" token from your dashboard login. The four deploys wait on that.
  • Proxy Workers ready: wrote the four proxy scripts (wp, ghost, ldmp, anb) — they forward to the matching mingsea.net origin through the tunnel and rewrite redirects back to the workers.dev host, so no loops.
  • Flip prep done: confirmed where each domain is pinned — WP's wp-config + database, Ghost's config + its bootstrap template, ldmp's app.env, and anb's backend CORS list.

Once the token comes back, I'll deploy all four Workers first, then flip the configs in that order (so nothing breaks in between), and verify each new address live, including the login pages.

me:  i want api-anb and admin-anb also also rename to under luckypoem.workers.dev

Got it — six in total then. Adding the two API/admin proxies now.

Done — now six proxies in total, all written and ready:

  • wp, ghost, ldmp, anb → their matching mingsea.net origins
  • api-anb → api-anb.mingsea.net (the backend)
  • admin-anb → admin-anb.mingsea.net (the admin UI)

Renaming the API/admin pair adds two rebuild steps on top of the earlier plan: the public blog and the admin UI both have the API address baked in at build time, so after the proxy Workers are up I'll rebuild both with https://api-anb.luckypoem.workers.dev and add the new workers.dev origins to the backend's CORS list. The mingsea.net API/admin addresses stay underneath as the hidden origins, same as the other four.

Still waiting on the deploy token report from the dashboard task (it shows completed, so its result should land shortly). As soon as it does, I'll deploy all six Workers, flip the configs, rebuild the two frontends, and verify all six new addresses live.

All six switched and verified live. The apps still run on the VM — each new address is a proxy Worker in front of the same service, with the mingsea.net hostnames kept as hidden origins.

New addresses (all 200):

What changed per app:

  • WordPress: WP_HOME/WP_SITEURL switched (durable config, live config, and the config generator, so a future restore won't revert it), plus a database search-replace of old post/option links to the new host — post guids intentionally left alone, and a pre-change database backup saved. One extra fix: the proxy now tells WordPress the public host, so admin login redirects stay on workers.dev instead of bouncing back to mingsea.net.
  • Ghost: site URL switched in its config and bootstrap template, restarted, device-verification-off setting preserved.
  • ldmp: NEXTAUTH_URL switched, restarted.
  • anb: backend CORS gained both new origins, and the blog + admin UI were rebuilt pointing at the new API address. Login through the new API address verified end to end (200 + token headers).

Two things for you:

  1. Your logins are unchanged — same accounts, same passwords, just new addresses. Log in at the new URLs from now on.
  2. Please revoke the one-time token named proxy-workers-onetime in your Cloudflare dashboard (My Profile → API Tokens) — the deploys are done and it shouldn't stay alive.

One note: the old mingsea.net addresses still technically reach the same services as the hidden origins, but every link the apps generate now points to workers.dev.

me:  can u make old domain do 301 redirects to new domains?

Interesting — none of them redirect yet, not even WordPress (it serves on any host and just generates new-host links). So I'll add explicit old→new redirects at each origin, guarded so the proxy Workers' own fetches pass through untouched. Setting up the work.

 WordPress redirects cleanly. Now Ghost's redirector.

 Redirects work locally. The two pass-through tests returned 301 too — need to see where those point before repointing the tunnel.

 Pass-through confirmed under tunnel-realistic headers. Repointing the tunnel and wiring the redirector into recovery.

 Ghost redirects live. Adding the redirector to watchdog coverage, then moving to the Next.js apps.

 Watchdog updated. Now the two Next.js middlewares:


 anb redirects now. Last two — admin static server and the API。

 All six redirect.

Done — all six old domains now redirect to the new ones, paths and query strings preserved, and the new domains are untouched (all still 200, login through the new API verified).

Old domainRedirects to
wp.mingsea.netwp.luckypoem.workers.dev — 301
ghost.mingsea.netghost.luckypoem.workers.dev — 301
ldmp.mingsea.netldmp.luckypoem.workers.dev — 301
anb.mingsea.netanb.luckypoem.workers.dev — 301
admin-anb.mingsea.netadmin-anb.luckypoem.workers.dev — 301
api-anb.mingsea.netapi-anb.luckypoem.workers.dev — 308

Two details worth knowing:

  • The API uses 308, not 301. A 301 would silently turn API POSTs into GETs and break them; 308 is still permanent but preserves the method and body, so old API clients keep working through the redirect (verified: an old-domain POST follows through to a 200).
  • The redirects are guarded so they can't loop. Each origin only redirects direct visits; requests arriving from the proxy Workers (marked with the new host) pass straight through to the app. That guard had a real subtlety in the two Next.js apps: Next auto-fills the marker header from the request host, so checking for its presence silently did nothing — the guard compares its value instead. Ghost needed a tiny redirect service in front of it (tunnel ingress repointed, wired into its bootstrap and watchdog), since Ghost itself won't do host redirects.

Old bookmarks and links will land on the right new pages automatically now.

我: Among shiro,wp,ghost,ldmp,anb, why only shiro is the most robust?

Because Shiro is the only one whose frontend doesn't actually live on this VM. The other four just got a new address — their serving path is still 100% on the VM.

Shiro: the frontend runs on Cloudflare Workers as a real OpenNext app. Its code lives in GitHub, it rebuilds itself on push via Workers Builds, and a VM replacement can't touch it — there's nothing on this machine to wipe. The only VM dependency left is the Core API for content, and that's the single best-covered recovery path you have (watchdog step 1, a bootstrap that's been exercised through several wipes). So the worst case for Shiro is "content temporarily down while Core restarts," never "the whole site is gone."

The other four, by contrast:

  • WordPress: nginx + PHP + MariaDB, all on the VM. A wipe kills it until the slowest bootstrap you have (the apt phase) finishes, and the proxy Worker in front adds a hop but zero resilience — VM down means 502.
  • Ghost: same VM single point of failure, plus its pager lives in a symlinked stock theme that VM wipes delete, so every restore has to re-apply it. Now it also has the redirector hop in front.
  • ldmp: app + MongoDB both local, plus custom code (pagination, middleware, the redirect) that only exists in this checkout.
  • anb: the most moving parts of anything you run — frontend, Express backend, Redis, plus a MySQL database borrowed from Ghost's instance. Publishing or reading breaks if any one of those four pieces fails, and it carries the most local-only fixes of any stack.

One honest caveat: Shiro isn't invulnerable. If Core or the tunnel goes down, its pages can't fetch content either. The difference is the blast radius: for Shiro a VM outage degrades one backend service, while for the other four the same outage is a total outage with a multi-step restore.

 Found it live: Shiro is 200, but WordPress is 502 right now — its MariaDB is inactive. Everything else is up, Core answers pong. Fixing WordPress.

 WordPress is back. One quick check on what took it down.

Exactly — and you caught it live. Here's what just happened:

At ~11:04 this morning the VM was replaced again (uptime shows a boot 13 minutes before my check). That wipes /etc, so every service unit vanished. The watchdog restored them one by one — you can see the timestamps: Core at 11:05, ldmp and anb at 11:11, Ghost at 11:14. WordPress was last because its restore reinstalls packages via apt, the slowest step. When you wrote, WordPress was still mid-restore: MariaDB's unit didn't exist yet, so wp.luckypoem.workers.dev was returning 502.

Shiro never blinked because its frontend doesn't live on this machine at all — the Worker kept serving from the edge through the whole reboot, and Core came back fast enough (it answers pong now) that content never dropped either.

Current state, just verified: everything is back — Shiro, WordPress, Ghost, ldmp, anb, admin, and API all return 200. No action needed from you; the watchdog had already started the WordPress restore before I even looked, I just watched it finish.

That's the difference from last turn in concrete form: for the VM-hosted apps, a replacement means "down until the bootstrap finishes, slowest one last." For Shiro, a replacement means nothing at all on the frontend.

me:  ghost,ldmp,anb have their respective frontend?

Yes, each has one — but they're built differently:

  • Ghost: its frontend is the theme (Handlebars, currently Source with your 5-per-page pager). It isn't a separate service, though — the same Ghost process renders the public pages, serves the API, and hosts the admin UI at /ghost/. One process, one port.
  • ldmp: the frontend is the Next.js app itself (App Router pages, served by next start on port 3250). Its "backend" — the blog API, auth, database access — lives inside that same app as API routes and Prisma calls. Again one process doing both.
  • anb: this one is genuinely split. The public blog is its own Next.js app (port 3252), the API is a separate Express service (port 9001), and the admin panel is a third piece, a static Vue app (port 3253).

So the distinction from Shiro isn't whether they have a frontend — they all do — it's where that frontend runs. Ghost's and ldmp's frontends run inside their single VM process, and anb's frontend is a separate service but still on the VM. None of them runs on the edge the way Shiro's does.

Same shape as Ghost, though: it's not a separate service. One WordPress install does both — the theme renders the public frontend, and /wp-admin/ is the backend where you write posts, both served by the same nginx + PHP-FPM process off the same MariaDB database.

That's also why this morning's outage took down both sides at once: with MariaDB dead, there was no frontend and no admin, just the 502 — unlike anb, where the frontend, API, and admin are separate pieces that can fail independently.

me: i visit https://admin-anb.luckypoem.workers.dev , it shows '网络出错了 稍后再试试吧'. pls fix it。

Fixed. Here's what was actually wrong:

The admin page itself was fine, and the login API path works — the backend was crashing in a loop. When I deleted the testui1 account earlier, it exposed a bug in the backend's permission check: for a user that no longer exists, it rejected with no error value, and the error handler then read a property off that empty value — an uncaught crash that killed the whole backend process. A still-logged-in testui1 session (logged in today at 5:24pm, before the deletion) kept calling that check, so the backend kept dying and the admin panel could only show "network error."

What I changed, all in the backend copy on the VM:

  • The permission lookup now rejects with a proper error instead of an empty one, and the handler guards against it — a missing user can never crash the process again.
  • Deleted testui1's leftover session token, so that stale session is dead too.

Verified: the permission check for testui1 now returns a clean error with zero backend restarts, the login endpoint answers normally, and the admin page and article list both load 200.

Try logging in now with your admin account — if your browser still holds anything from the test session, it'll clear back to the login screen on its own.

我: about anb, u ever created an a/c which can log into my blog. does anybody run : curl -X POST https://api-anb.mingsea.net/admin/register -H "Content-Type: application/json" -d '{"ll_username":"yourname","ll_password":"yourpassword","ll_description":"blog owner","ll_email":"you@example.com","ll_permission":"workTower,creativeCenter,articleList,articleEdit,categoryManage,categoryList,categoryEdit,tagManage,tagList,tagEdit,userManage,userList,userEdit,articleDelete,categoryDelete,tagDelete,userDelete,allocationPermission,commentBoard,commentBoardList,deleteCommentBoardItem,deleteCommentBoardReplyItem,commentBoardReplyList"}' to create an a/c, then this a/c can also be used to log into my blog?

 Good catch to ask — let me check two things: whether I ever made an account there, and whether that register endpoint is actually open to anyone.

 Only admin remains in the user table — no rogue accounts right now. One last check on exactly which address the hole is reachable through。

Two answers:

Did I ever create an account on your blog? Yes, one: testui1, back on 2026-10-28. It was a throwaway I made through the real admin UI to reproduce your category/tag bug (that test proved the failure didn't reproduce). You asked me to delete it on 2026-10-31, and I did. I just checked the user table — right now only your admin account exists. Nothing else, no rogue accounts.

Can anybody else do what that curl does? Yes, and this is a real hole. I read the backend code: /admin/register has no login check, no admin check, and no first-user-only guard. It only refuses a username that already exists, then creates the account with whatever permission list the caller supplies — including full admin. And /admin/login then accepts that account and issues a token, so yes, they could log straight into your admin.

One nuance on the exact curl you pasted: it points at api-anb.mingsea.net, which 308-redirects to the public address, so as written (no -L flag) it would bounce and create nothing. But the same request sent to https://api-anb.luckypoem.workers.dev/admin/register goes straight through — I verified the public path reaches the backend.

The fix is small: require a valid logged-in admin token on /admin/register, so only you can create accounts from now on. Your own account is unaffected. Want me to patch it?

 我:Yes, patch it so only a logged-in admin can create accounts。

 Patching it now. First reading how this backend checks logins, so the guard uses the same mechanism as the other admin routes。

Patched and verified.

What I changed: in the backend's whitelist (src/whiteList/list.js), I removed the /admin/register entries. That was the whole hole — registration was explicitly exempted from the login check that guards every other admin route. Now the same token check applies to it: only a request carrying a valid logged-in admin token can create an account.

Verified live:

  • An unauthenticated register attempt now gets rejected (-999, no permission) and creates nothing — the user table still shows only your admin account.
  • The blog home and admin panel both still load fine, and the public article API still returns your posts.
  • Your own login is untouched (the login route is still whitelisted, as it must be).

One thing to know: since registration can no longer bootstrap itself, if the database were ever wiped empty, the first account would have to be inserted directly instead of through the register endpoint. Normal use is unaffected — you can still create accounts from inside the admin while logged in.

 

我得到的博客地址: https://anb.luckypoem.workers.dev/

登录地址: https://admin-anb.luckypoem.workers.dev/

项目地址:

 https://github.com/acmenlei/next-blog

 https://github.com/acmenlei/vue-admin-top ,后台管理系统.

 https://github.com/acmenlei/node-admin-backend, 服务端.