包含关键字 typecho 的文章

当 AI 长出身体,从能听会说到能看会动!Agora Convo AI World 拉斯维加斯之夜活动回顾

主笔:周森

审校:小炫

编辑:陈述

AI 不再仅仅是屏幕里的对话框,从能感知情绪的陪护机器人,到具备实时翻译能力的智能眼镜,AI 硬件化成为 CES 2026 呈现的重要趋势。

然而,在 AI 硬件热潮背后,行业也在迫切寻找一个答案:当 AI 试图长出「身体」,它需要怎样的底层架构与交互逻辑?

1 月 9 日晚,Agora(声网兄弟公司)联合 RiseLink(博通集成)在拉斯维加斯 The LOFT at Cabo Wabo Cantina 举办了 Convo AI World 论坛活动。

这场吸引了近 300 位全球科技精英参与的盛会,意在为这股 AI 硬件热潮指引风向。

两家企业不仅联合发布了基于 BK7259 芯片的 R2 全场景 AI 机器人开发套件,更首次系统性地提出了「物理 AI 的蓝图」。

△ 活动现场

具身 AI 的蓝图:从「工具」到「生命形态」

当前,行业正处于从文本模型、语音助手,迈向具备长期记忆、情绪理解与陪伴能力的 AI 伙伴的早期阶段。

Physical AI,本质上是具身智能(Embodied AI) 在消费级市场的落地呈现。AI 硬件不再是冰冷的电子零件,而是一种正在形成的数字生命形态。

由 Agora 与 RiseLink 联合提出的 Physical AI 蓝图,则试图为下一阶段的具身智能发展提供一套以体验为核心的设计方法论。

Tony Wang 在演讲中强调,Physical AI 的关键不在于堆砌硬件参数,而在于对话体验,即在复杂环境中理解语境、识别说话者并感知情绪的能力。

未来,AI 的核心语言将从单向的「指令」彻底转变为双向的「对话」,其商业模式也将从硬件单次销售,转向以订阅制为核心的长期服务。


△ 发言嘉宾:Tony Wang,Agora 联合创始人兼 CRO

张鹏飞博士进一步阐述道,Physical AI 时代的竞争已演变为协同效率的竞争。想要成为或持续保持第一,前提是与各自领域中已经处于领先位置的伙伴深度协作。

RiseLink 将通信、算力与功耗管理深度整合,配合 Agora 的 RTC 实时互动能力,构成了 Physical AI 的基础引擎:以低延迟保障交互的自然性,以高能效支撑长时间的在线陪伴。

△ 发言嘉宾:张鹏飞博士,RiseLink(博通集成) CEO

真实的 AI 堆栈:重构技术底层

当 AI 跨越数字边界、从云端软件形态进入物理硬件,底层的技术架构不应该只是「模型 + 数据 + 算力」,而需要从「原子」到「比特」实现闭环。

在论坛环节,嘉宾们探讨和回答了什么是「真实的 AI 堆栈」并达成共识:AI 是否好用,取决于设备能否通过物理感知快速理解语境并做出即时反应。

△ Panel: The Real AI Stack

圆桌主持人:Rin Yunis 博士,RiseLink 开发者体验负责人 (中)

圆桌嘉宾: (自左向右)

  • Max Fillin, WowCube CEO
  • Blake Margraff, Healthcare Technology 创始人
  • Amir Eitan, Nanit CPO
  • Lin Chen 博士, Wyze 首席科学家

在架构选择上,边缘(Edge)与云端(Cloud)的分工不再是二选一,而是基于延迟、隐私和成本的精密平衡 。对实时性和隐私敏感的能力更适合本地运行,而需要持续迭代、受成本约束的功能则更适合放在云端,工程实践应从验证出发,再逐步优化边云分配。

在消费级场景中,成本是最硬的约束条件。无论技术听起来多么具有颠覆性,如果缺乏可持续的单位经济模型(Unit Economics),产品终究无法走出实验室成为长期的生意。

同时,嘉宾们达成了一个感性却深刻的共识:AI 必须具备稳定的记忆和一致的行为 。一个今天热情、明天健忘的 AI 硬件,是无法真正建立起用户信任的。

△ 圆桌嘉宾:Max Fillin, WOWcube CEO(左)

这种信任的建立,在家庭与健康等强私密场景下尤为微妙。品牌的真实投入与清晰的价值传递,远比罗列一堆天衣无缝的安全技术术语更有效。 用户对 Physical AI 的接受度,往往并不取决于你背书了多少项加密协议,而取决于极其直观的交互体感,即:反馈要即时(低延迟)、过程要透明(可解释)、底线要有人守(人类参与)。

△ 圆桌嘉宾:Lin Chen 博士, Wyze 首席科学家

应用与具身落地:AI 硬件的场景爆发

Physical AI 最令人兴奋的特质在于它的多模态能力,以及在各个场景的迅速渗透。

△ WOWcube(左):将经典的 2x2 魔方形态与 24 个高分辨率屏幕相结合,通过扭转、倾斜和触觉交互,让玩家在立体的物理空间中体验沉浸式的游戏与应用。

△ Wyze(右上): 新款户外安防摄像头采用贴纸式安装方式固定在窗户上,可从室内进行户外录像

△ Nanit Pro(右下): 全功能婴儿监控系统,新增用于记录宝宝成长发育的功能

在医疗与健康领域,Physical AI 的价值在于它能实时处理复杂的生理信号,并以人类能理解、能接受的方式进行交互,从而在专业性与亲和力之间找到平衡。

Blake Margraff 指出,AI 在医疗中的落地绝非简单的自动化,而是要实现「自动化的患者监测与干预」。

△ 圆桌嘉宾:Blake Margraff,Healthcare Technology 创始人

Amir Eitan 则从育儿与家庭监测的角度补充道,真正的信任来自于 AI 能在特定场景下提供「可解释的反馈」。

△ 圆桌嘉宾:Amir Eitan,Nanit CPO

在 AI 陪伴的主题论坛中,各位嘉宾围绕 AI 陪伴产品在儿童与家庭场景中的实际落地展开话题。

△ Panel:Where AI Companionship Comes to Life

圆桌主持人:Patrick Ferriter,Agora 产品与市场高级副总裁(左下)

圆桌嘉宾:

  • 孙兆治,珞博智能 CEO(左上)
  • Angela Qian,灵宇宙 Luka AI 战略负责人 (右上)
  • Wayne Zhang, Dify Chief of Staff(右中)
  • Margo Wang,Lgenie &灵机一动 Agent 市场总监(右下)

稳定性和一致性是影响儿童用户对 AI 硬件接受度的关键因素。无论是故事内容、角色设定还是互动方式,一旦发生变化,都会显著影响使用体验。

低延迟是实时陪伴场景中的基本要求,是建立用户与产品情感连接的底线,响应过慢会直接削弱互动的自然感。

长期留存更具挑战性。吸引用户首次尝试与长期留存两者的差异性需要引起重视,长期留存更具挑战性,需要 AI 在持续使用中形成稳定的互动节奏和情感连接,而不仅是单次回应。

安全与责任方面需要引入多层防护思路,包括年龄匹配内容、实时干预机制、以及对儿童隐私的明确告知与限制。当 AI 承担陪伴角色时,如何在维持互动亲密性的同时设立清晰边界,仍是行业需要持续面对的问题。

△ Fuzozo 芙崽(左上):面向 Z 世代的 AI 养成系潮玩

△ Luka AI Cube(右上):灵宇宙小方机,儿童 AI 学伴

△ Lgenie (左下):小匠宠物陪伴小车 & 四足桌面机器人

△ 海马爸比(右下): AI 智能婴儿看护器

在产品演示环节,Diana Zhu 博士主持发布了 Choochoo AI 教育机器人。她提到,Choochoo 能够实现流畅的视觉与动作反馈,核心在于集成了 RiseLink 的高集成度 SoC 方案。该芯片在单颗硅片上整合了 Wi-Fi 连接、音视频处理与 AI 加速引擎,使得开发者能够绕过复杂的底层硬件调优,直接在 R2 套件上通过简单的 API 调用,实现原本需要高性能服务器才能支撑的「视觉-语言-动作」协同。

△ 发言嘉宾:Diana Zhu 博士,RiseLink 美国负责人

作为首款由 RiseLink 芯片与 Agora 对话式 AI 引擎深度驱动的教育机器人,Choochoo 不仅能听懂孩子的提问,更能通过视觉传感器「看」到周围的环境与孩子的动作,并做出相应的物理反馈。

△ Choochoo / 延伸阅读:对话式 AI 升级,不仅能看还能动

值得一提的是,作为 R2 全场景 AI 机器人开发套件标杆案例,陆吾智能旗下的桌面机器人「陆卡卡」也同步亮相。现场,陆卡卡展示了如何在紧凑的形态下实现高频、低延迟的 AI 交互。

△ 陆卡卡 / 延伸阅读:桌宠陆卡卡,一只「兵蚁」从二次元走进现实

在两款极具代表性的具身智能产品身上,我们看到,当 AI 拥有了强大的「大脑」(大模型)与灵敏的「身体」后,交互的边界已彻底被打破。两款产品的发布,共同定义了 AI 硬件的新高度,同时也标志着基于 Agora 与 RiseLink 合作的 AI 方案已经完全成熟。

在快闪分享环节,Joey Jiang 分享了打造 AI 原生硬件的最短路径,强调了模块化硬件对快速实现概念落地的意义。他指出,AI 原生硬件的开发不应再遵循「从零打样」的旧逻辑。通过 Seeed Studio 提供的模块化感知节点(如传感器、视觉模块)与 RiseLink 方案的即插即用式结合,硬件原型的验证周期可以从数月缩短至几周。这种「搭积木」式的开发模式,正是初创团队在 Physical AI 浪潮中抢占市场窗口期的最短路径。


△ 发言嘉宾:Joey Jiang,Seeed Studio 销售副总裁

Kim Jin 分享了打造糯宝 AI 机器人的背后故事。在研发背后,团队耗费大量精力对用户意图的深度理解。通过多模态感知,敏锐地捕捉视觉、触觉与语音背后的感性信息,实现拟人化的回复。这种交互不只是指令的执行,而是基于对用户意图的精准洞察,让机器人产生真实的「情感共鸣」。这标志着 Physical AI 真正跨越了工具属性,进化为懂得用户灵魂的情感伴侣。

△ Pophie (机器灵动) 产品负责人 Kim Jin

△ Maxevis(左):迈威儿童拍学机

△ Pophie 糯宝(右):桌面级情感陪伴机器人

隐私、授权与信任:环境式 AI 的底线

随着环境式 AI(Ambient AI)走向「始终在线」,隐私与信任已不再是合规问题,而是产品体验本身。用户真正担心的并非模型出错,而是设备在「不被察觉的情况下」收集和使用数据。

△ Panel:When AI Is Everywhere: Redefining Data Privacy, Consent, and Trust

圆桌主持人:Ramana Kapavarapu,Agora 首席信息安全官 (CISO) & IT 运营负责人(中)

圆桌嘉宾:(自左向右)

  • Diana Zhu 博士,RiseLink 美国负责人
  • Joe Tham,Ellie 海马爸比联合创始人
  • Gibran Mourani,MiniMax 全球客户经理
  • 卜峥,Kaamel AI 联合创始人兼 CEO

△ 成立于 2021 年底的 MiniMax 刚刚宣布港股上市,成为从成立到 IPO 用时最短的 AI 公司。大家首先向 MiniMax 的 Gibran Mourani 道贺。

围绕隐私实践,嘉宾们形成了一个明确共识:说到做到、做到可见。

透明性: 相比冗长的隐私条款,产品应在交互层面清晰呈现系统是否在监听、收集了什么数据,以及用户如何即时控制这些行为。透明性体现在硬件指示、软件状态和使用流程中,比如用物理指示灯直观地告诉用户系统是否在监听。

边缘保护: 通过边缘计算最小化数据流动,让原始语音和视觉数据停留在本地,是保护隐私的最有效路径。对多数场景而言,无需上传云端、本地处理并仅传递必要信号,既有助于隐私保护,也降低了系统暴露面。

响应机制: 谈及安全事件响应,需要成熟、结构化的应对机制,而非临时决策。快速隔离、明确影响、及时修复与复盘改进,比短期业务考量更重要。过往大型数据泄露案例反复证明,延迟或回避只会放大长期损失。

真正可规模化的信任,来自硬件与软件的一致设计以及可实时验证的控制能力。认证和合规是基础,但只有当系统行为与承诺持续一致,用户对「无处不在的 AI」才会产生长期接受度。

△ 活动现场

AI 具身化不可挡!

纵观整场活动,我们可以从三个层面理解这场关于 Physical AI 的深刻变革:

技术本质: 从「挂载」到「具身」。 AI 不再是硬件外挂的一个功能,而是通过专用芯片和实时通讯协议,深度融合进硬件的神经系统。

交互范式: 从「指令」到「共生」。 当 AI 能够理解语境、感知情绪并拥有长期记忆,它就从一个「好用的工具」进化为一个「理解你的物种」。对话不再是手段,而是其存在的形式。

商业本质: 从「买断」到「订阅」。 物理 AI 的核心价值在于其随时间不断进化的能力。厂商卖出的不再是零件,而是长期的服务与情感陪伴。

在 Agora 和 Riselink 两家公司和来自人工智能、芯片和硬件、AI 算法,以及数字健康、家居安防、AI 陪伴和教育等领域的数十家 AI 软硬件企业代表和顶尖专家的背书下,AI 将跳出单纯的数字世界,开始在物理世界中,真正长出它的身体。■

阅读更多 Voice Agent 学习笔记:了解最懂 AI 语音的头脑都在思考什么

uniappx服务端推送消息配置

一.前置条件

1.申请Dcloud 开发者账号(https://www.dcloud.io/
2.HBuilder安装
3.安装模拟器
4.uni-push 2.0 文档(https://uniapp.dcloud.net.cn/unipush-v2.html
5.创建一个uniappx项目
image.png

二.配置步骤

1.确认AppID

打开项目中 manifest.json 文件,确认AppId是否存在,若不存在则点击右侧 重新获取 按钮(此处可能需要登录Dcloud账号),会生成AppID
image.png

2.构建项目生成证书

点击 Hbuilder 菜单 运行 》 运行到手机或模拟器 》 制作自定义调试基座
image.png
点击打包,会出现打包校验提示,继续打包即可,随即会在控制台打印相关信息,此处等待时间可能较长,我们继续推进下一步,打包后台运行即可。
image.png

3.新建uniCloud

登录Dcloud 开发者中心,点击左侧uniCloud...
image.png
在新标签页中点击右上角新建服务空间,按提示完成即可,例子中建立一个叫uniapp-hello 的服务空间(取名仅作区分,无其他含义,视自己习惯命名即可);
image.png

4.创建应用信息推送

回到Dcloud开发者管理页面,点击左侧uni-push > 2.0(支持全段推送) > 应用信息
image.png
点击当前应用下拉框,选择我们需要推送的应用
image.png

选择平台视业务而定,此处示例仅勾选Android
image.png
点击选择Android包名,若包名不存在,则需等待上一步打包结束后刷新当前页面重新选择,
再添加云服务空间,选中上一步创建的空间即可,最后点击开通应用
image.png

5.创建云函数

在项目目录下的uniCloud 》coudfunctions 目录右键,选择新建云函数/云对象(若没有uniCloud目录可在项目根目录上右键,选择 创建uniCloud云开发环境 )
image.png
填写函数名点击创建即可
image.png
随后替换新建函数下的index.js 和 package.json 内容
image.png

index.js新内容如下(需替换第二行中自己的appId):


'use strict';
const uniPush = uniCloud.getPushManager({appId:"__UNI__XXXXX"}) 
exports.main = async (event, context) => {
    const body = JSON.parse(event.body);
    return await uniPush.sendMessage({
        "push_clientid": body.cid,     
        "title": body.title,    
        "content": body.content,
        "payload": body.data
    })
};
 

package.json新内容如下:

{
    "name": "photo_push",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "extensions": {
        "uni-cloud-push": {} 
    },
    "author": ""
}

6.增加uni-push 能力

在Hbuilder中打开manifest.json,勾选uni-push(消息推送)
image.png
在新建立的photo-push 目录上右键,选择 管理公共模块或扩展库依赖
image.png
选中统一推送服务,点击确定
image.png

7.增加扩展库依赖表

在database目录下增加以下依赖表文件(文件内容见结尾附件)
image.png
在 opendb-device.index.json 右键 》 初始化云数据库索引
剩余其他三个文件 右键 》 上传DB schema

8.上传部署云函数

随后在photo-push 目录点击 右键 》 上传部署,等待上传完成即可
image.png

9.云函数URL化

回到第三步创建发unicloud服务空间,点击 服务空间 名称进入详情页
在左侧 云函数/对象列表找到创建的云函数
image.png
点击函数名进入详情页,页面底部 云函数URL化编辑配置函数名 /sendMessage
注:每个服务空间内函数名不可重复,且URL路径不可出现重复部分
image.png

10.项目增加消息监听

在项目App.uvue中 onLaunch()生命函数中增加监听代码
image.png

uni.onPushMessage(res => {
                console.log("监听消息:", res)                
                if (res.type == "click") {
                    console.log("点击消息:" + res)
                }
                if (res.type == "receive") {
                    console.log("收到APP消息" + res.data);
                    // 创建本地通知栏消息
                    uni.createPushMessage({
                        title: res.data.title as string,
                        content: res.data.content as string,
                        payload: res.data.payload
                    })
                }
            }) 

11.启动项目

启动模拟器,此处以 网易Mumu模拟器 演示
Hbuilder 菜单点击 运行 》 运行到手机或模拟器 》 运行到Android App 基座
选择设备后运行,等待控制台编译完成
image.png
控制台选择 连接云端云函数
image.png

12.发起调用

使用云函数URL发起调用
image.png

效果如图
image.png
请求参数说明:

{
    "cid": ["02e3c939927d45df1028b274e493488c"], // 设备ID,长度不超过500
    "title": "绿12",
    "content": "收到消息12",
    "data": {    // data 为自定义业务参数,该字段可不传
        "type": "messageList"
    }
}

附件

●opendb-device.index.json

[
    {
        "IndexName": "index_device_id",
        "MgoKeySchema": {
            "MgoIndexKeys": [
                {
                    "Name": "device_id",
                    "Direction": "1"
                }
            ],
            "MgoIsUnique": true
        }
    }
]

●opendb-device.schema.json

{
    "bsonType": "object",
    "required": [],
    "permission": {
        "read": false,
        "create": true,
        "update": false,
        "delete": false
    },
    "properties": {
        "_id": {
            "description": "ID,系统自动生成"
        },
        "appid": {
            "bsonType": "string",
            "description": "DCloud appid"
        },
        "device_id": {
            "bsonType": "string",
            "description": "设备唯一标识"
        },
        "vendor": {
            "bsonType": "string",
            "description": "设备厂商"
        },
        "push_clientid": {
            "bsonType": "string",
            "description": "推送设备客户端标识"
        },
        "imei": {
            "bsonType": "string",
            "description": "国际移动设备识别码IMEI(International Mobile Equipment Identity)"
        },
        "oaid": {
            "bsonType": "string",
            "description": "移动智能设备标识公共服务平台提供的匿名设备标识符(OAID)"
        },
        "idfa": {
            "bsonType": "string",
            "description": "iOS平台配置应用使用广告标识(IDFA)"
        },
        "imsi": {
            "bsonType": "string",
            "description": "国际移动用户识别码(International Mobile Subscriber Identification Number)"
        },
        "model": {
            "bsonType": "string",
            "description": "设备型号"
        },
        "platform": {
            "bsonType": "string",
            "description": "平台类型"
        },
        "uni_platform": {
            "bsonType": "string",
            "description": "uni-app 运行平台,与条件编译平台相同。"
        },
        "os_name": {
            "bsonType": "string",
            "description": "ios|android|windows|mac|linux "
        },
        "os_version": {
            "bsonType": "string",
            "description": "操作系统版本号 "
        },
        "os_language": {
            "bsonType": "string",
            "description": "操作系统语言 "
        },
        "os_theme": {
            "bsonType": "string",
            "description": "操作系统主题 light|dark"
        },
        "pixel_ratio": {
            "bsonType": "string",
            "description": "设备像素比 "
        },
        "network_model": {
            "bsonType": "string",
            "description": "设备网络型号wifi\/3G\/4G\/"
        },
        "window_width": {
            "bsonType": "string",
            "description": "设备窗口宽度 "
        },
        "window_height": {
            "bsonType": "string",
            "description": "设备窗口高度"
        },
        "screen_width": {
            "bsonType": "string",
            "description": "设备屏幕宽度"
        },
        "screen_height": {
            "bsonType": "string",
            "description": "设备屏幕高度"
        },
        "rom_name": {
            "bsonType": "string",
            "description": "rom 名称"
        },
        "rom_version": {
            "bsonType": "string",
            "description": "rom 版本"
        },
        "location_latitude": {
            "bsonType": "double",
            "description": "纬度"
        },
        "location_longitude": {
            "bsonType": "double",
            "description": "经度"
        },
        "location_country": {
            "bsonType": "string",
            "description": "国家"
        },
        "location_province": {
            "bsonType": "string",
            "description": "省份"
        },
        "location_city": {
            "bsonType": "string",
            "description": "城市"
        },
        "create_date": {
            "bsonType": "timestamp",
            "description": "创建时间",
            "forceDefaultValue": {
                "$env": "now"
            }
        },
        "last_update_date": {
            "bsonType": "timestamp",
            "description": "最后一次修改时间",
            "forceDefaultValue": {
                "$env": "now"
            }
        }
    },
    "version": "0.0.1"
}

●opendb-tempdata.schema.json

{
    "bsonType": "object",
    "required": ["value", "expired"],
    "permission": {
        "read": false,
        "create": false,
        "update": false,
        "delete": false
    },
    "properties": {
        "_id": {
            "description": "ID,系统自动生成"
        },
        "value": {
            "description": "值"
        },
        "expired": {
            "description": "过期时间",
            "bsonType": "timestamp"
        }
    }
}

●uni-id-device.schema.json

{
    "bsonType": "object",
    "required": [
        "user_id"
    ],
    "properties": {
        "_id": {
            "description": "ID,系统自动生成"
        },
        "user_id": {
            "bsonType": "string",
            "description": "用户id,参考uni-id-users表"
        },
        "ua": {
            "bsonType": "string",
            "description": "userAgent"
        },
        "uuid": {
            "bsonType": "string",
            "description": "设备唯一标识(需要加密存储)"
        },
        "os_name": {
            "bsonType": "string",
            "description": "ios|android|windows|mac|linux "
        },
        "os_version": {
            "bsonType": "string",
            "description": "操作系统版本号 "
        },
        "os_language": {
            "bsonType": "string",
            "description": "操作系统语言 "
        },
        "os_theme": {
            "bsonType": "string",
            "description": "操作系统主题 light|dark"
        },
        "vendor": {
            "bsonType": "string",
            "description": "设备厂商"
        },
        "push_clientid": {
            "bsonType": "string",
            "description": "推送设备客户端标识"
        },
        "imei": {
            "bsonType": "string",
            "description": "国际移动设备识别码IMEI(International Mobile Equipment Identity)"
        },
        "oaid": {
            "bsonType": "string",
            "description": "移动智能设备标识公共服务平台提供的匿名设备标识符(OAID)"
        },
        "idfa": {
            "bsonType": "string",
            "description": "iOS平台配置应用使用广告标识(IDFA)"
        },
        "model": {
            "bsonType": "string",
            "description": "设备型号"
        },
        "platform": {
            "bsonType": "string",
            "description": "平台类型"
        },
        "create_date": {
            "bsonType": "timestamp",
            "description": "创建时间",
            "forceDefaultValue": {
                "$env": "now"
            }
        },
        "last_active_date": {
            "bsonType": "timestamp",
            "description": "最后登录时间"
        },
        "last_active_ip": {
            "bsonType": "string",
            "description": "最后登录IP"
        }
    },
    "version": "0.0.1"
}

摘要

随着 Anthropic 开源 skills 仓库,"Code Interpreter"(代码解释器)模式成为 Agent 开发的热门方向。许多开发者试图采取激进路线:赋予 LLM 联网和 Python 执行权限,让其现场编写代码来解决一切问题。但在构建企业级“智能文档分析 Agent”的实践中,我们发现这种“全托管”模式在稳定性、安全性和可控性上存在巨大隐患。本文将分享我们如何摒弃激进路线,采用 Java (确定性 ETL) + DSL 封装式 Skills + 实时渲染 的混合架构,在保留 LLM 灵活性的同时,确保系统的工业级稳定性。

一、 背景:当文档分析遇到“复杂生成”

在我们的“文档处理 Agent”项目中,基础的问答功能(RAG)已经解决得很好。但随着用户需求升级,我们面临了新的挑战:

用户场景

“这是 2024 和 2025 年的两份经营数据报表,请对比 DAU 和营收的同比增长率,并生成一个 Excel 表格给我。另外,把总结报告导出为 PDF。”

这类需求包含两个特征:

  1. 逻辑计算:需要精确算术(LLM 弱项)。

  2. 文件 IO:需要生成物理文件(LLM 无法直接做到)。

引入 Skills(让 LLM 调用 Python 代码)似乎是唯一解。但在具体落地时,我们走了一段弯路。

二、 弯路:激进的“纯 Skills”路线

起初,我们参考了开源社区做法,采用了 完全的 Code Interpreter 模式。我们将 requestspandasreportlab 等库的权限全部开放给 LLM,并在 Prompt 中告诉它:“你是一个 Python 专家,请自己写代码解决所有问题。”

这种“裸奔”模式在生产环境中遭遇了三次暴击:

  1. 输入端不可控:LLM 对非结构化数据(如无后缀 URL、加密 PDF)的处理极其脆弱,经常陷入报错死循环。

  2. 输出端崩坏:让 LLM 从零绘制 PDF/Word 是灾难。经常出现中文乱码、表格对不齐、使用了过期的库 API 等问题。

  3. 安全黑洞:数据流完全在沙箱内闭环,Java 主程序失去了对内容的控制权,无法拦截敏感词或违规数据。

三、 变革:Java 主控 + DSL Skills 的混合架构

为了解决上述问题,我们重构了架构。核心思想是:收回 LLM 的“底层操作权”,只保留其“逻辑调度权”。

我们制定了新的架构分工:Java 负责确定性的数据流转与安检,LLM 负责意图理解与代码组装,Python 沙箱 负责在受控环境下执行具体计算。

3.1 架构设计概览

我们将系统重新划分为四个逻辑层级:

  • ETL 层 (Java):负责下载、MIME 识别、OCR、敏感词检测。这是“确定性管道”。

  • Brain 层 (LLM):负责阅读纯文本,进行逻辑推理,并生成调用代码。

  • Skills 层 (Python Sandbox):提供高度封装的 SDK(DSL),而非裸库。

  • Delivery 层 (Java):负责将 Markdown/HTML 实时渲染为 PDF/Word。

3.2 输入侧:回归 Java 流水线 (ETL)

我们不再让 LLM 去下载和解析文件。所有输入文件,先经过 Java 的 DocPipeline。利用 Apache Tika 进行精准解析,并立即进行敏感词检测文本截断。这一步保证了喂给 LLM 的数据是干净、安全、标准化的纯文本

3.3 中间层:DSL 封装模式 (The Wrapper Pattern)

这是我们对 Skills 实践最大的改进。我们禁止 LLM 直接写 import pandas 进行底层操作,而是预置了一套高度封装的 DSL。

Python 端封装 (excel_tool.py):

import pandas as pdimport osdef create_excel(data_list, filename="report.xlsx", output_dir="/workspace"):    try:        df = pd.DataFrame(data_list)        save_path = os.path.join(output_dir, filename)        # 【封装价值体现】自动处理格式、列宽、引擎兼容性,屏蔽 LLM 的幻觉风险        with pd.ExcelWriter(save_path, engine='openpyxl') as writer:            df.to_excel(writer, index=False, sheet_name='Sheet1')                        # 自动调整列宽 (LLM 很难写对的工程细节)            worksheet = writer.sheets['Sheet1']            for idx, col in enumerate(df.columns):                max_len = max(df[col].astype(str).map(len).max(), len(str(col))) + 2                worksheet.column_dimensions[chr(65 + idx)].width = min(max_len, 50)                    return save_path    except Exception as e:        return f"Error: {str(e)}"
复制代码

Skill 说明书 (SKILL.md):

我们在 Prompt 中通过“接口契约”强行约束 LLM 的行为,明确了何时该写代码,何时该纯输出文本。

# File Generation Skill (Standardized)你拥有生成专业格式文件(Excel, Word, PDF)的能力。沙箱中已预装了封装好的 `excel_tool` 库。**核心决策树**:1. 如果是 **统计数据/表格** -> 必须生成 **Excel** -> **写 Python 代码**。2. 如果是 **分析报告/文档** -> 必须生成 **Word/PDF** -> **禁止写代码**,走渲染路径。---### 场景 1:生成 Excel (.xlsx)**规则**:禁止使用 `pandas` 底层 API,必须调用封装函数。**数据结构**:必须是【字典列表】,每个字典代表一行。**Python 调用示例**:```pythonimport excel_tool# 1. 准备数据 (从文档中提取)data = [    {'年份': '2024', 'DAU': 1000, '营收': '500万'},    {'年份': '2025', 'DAU': 1500, '营收': '800万'}]# 2. 调用封装函数 (自动处理样式、列宽)excel_tool.create_excel(data, filename='analysis.xlsx')```---### 场景 2:生成 Word / PDF (.docx / .pdf)**规则**:**严禁编写 Python 代码**(如 `reportlab``python-docx`)。**执行动作**:1. 请直接输出内容丰富、排版精美的 **Markdown** 文本。2. 在 Markdown 的**最后一行**,务必添加对应的动作标签,系统会自动将其渲染为文件。**输出示例**:# 2024 年度经营分析报告## 一、 数据概览本季度营收同比增长 20%...| 指标 | Q1 | Q2 || :--- | :--- | :--- || DAU | 100w | 120w |...(此处省略 2000 字内容) ...<<<ACTION:CONVERT|pdf>>>
复制代码

3.4 输出侧:渲染与交付的分离

对于不同类型的文件,我们采取了截然不同的交付策略:

  1. Excel(强结构化):走 Skills 路线。LLM 组装数据 -> 调用 excel_tool -> 沙箱生成物理文件。

  2. Word/PDF(富文本):走 渲染路线严禁 LLM 写代码生成。

  3. LLM 只输出高质量的 Markdown 并在末尾打上 <<>> 标签。

  4. Java 后端拦截该标签,利用 OpenHTMLtoPDFPandoc 将 Markdown 实时转换 为精美的 PDF/Word。

四、 硬核代码实现 (Spring AI)

以下是我们在 Spring AI 体系下实现这套混合架构的关键逻辑。

4.1 动态技能注入 (SkillManager)

我们实现了一个 SkillManager,支持按需加载技能。为了提升性能,我们设计了 Session 级的“防抖机制”,确保同一个会话中只需上传一次 Python 脚本,避免重复 IO。

@Servicepublic class SkillManager{    // 缓存技能脚本: 技能名 -> { 文件路径 -> 内容 }    private final Map<String, Map<String, String>> skillScripts = new ConcurrentHashMap<>();    // 防止重复注入的防抖 Set    private final Set<String> injectedSessions = ConcurrentHashMap.newKeySet();    /**     * 核心逻辑:根据需要的技能列表,动态注入脚本到沙箱     */    public void injectToSandbox(String sessionId, List<String> neededSkills) {        // 1. 防抖检查:如果该 Session 已注入,直接跳过,避免重复 IO        if (injectedSessions.contains(sessionId)) return;        // 2. 注入 Python 包结构 (__init__.py)        sandboxService.uploadFile(sessionId, "/workspace/skills/__init__.py", "");        // 3. 批量上传该技能所需的 DSL 脚本        for (String skillName : neededSkills) {            Map<String, String> scripts = skillScripts.get(skillName);            if (scripts != null) {                scripts.forEach((path, content) ->                     sandboxService.uploadFile(sessionId, path, content)                );            }        }        injectedSessions.add(sessionId);    }        // ... 省略加载 Resource 的代码 ...}
复制代码

4.2 业务调度与意图分流 (Handler)

串联 Java ETL、LLM 推理和最终的交付分流。

@Servicepublic class DocumentAnalysisRequestHandler{    public Flowable<Response> processStreamingRequest(Request req) {        // 1. 【Java ETL】确定性解析与安检        // 无论 URL 还是文件,先转为纯文本,并做敏感词过滤        List<ParseResult> parsedDocs = etlPipeline.process(req.getUrls());                // 2. 【技能注入】        List<String> neededSkills = List.of("file_generation");        skillManager.injectToSandbox(req.getSessionId(), neededSkills);        // 3. 【LLM 执行】Context Stuffing        String prompt = buildPrompt(parsedDocs, skillManager.getPrompts(neededSkills));                // 调用 LLM,挂载 ToolContext 以实现多租户隔离        Flowable<AgentOutput> agentFlow = chatClient.prompt()                .system(prompt)                .user(req.getUserInstruction())                .toolContext(Map.of("projectId", req.getSessionId()))                 .stream()                .content();        // 4. 【结果分流】        return agentFlow                .toList() // 收集完整回复                .flatMap(this::handlePostGenerationAction);    }    /**     * 核心分流逻辑:决定是返回沙箱文件(Excel) 还是 调用Java渲染(PDF)     */    private Single<AgentOutput> handlePostGenerationAction(List<String> rawChunks) {        String text = String.join("", rawChunks);        // 分支 A:检测到 Python 生成了 Excel (Skills 产物)        // 格式:[FILE_GENERATED: /workspace/report.xlsx]        if (FILE_GENERATED_PATTERN.matcher(text).find()) {            String path = extractPath(text);            return Single.just(new AgentOutput(path, OutputType.FILE));        }        // 分支 B:检测到转换指令 (渲染产物)        // 格式:<<<ACTION:CONVERT|pdf>>>        if (text.contains("<<<ACTION:CONVERT|pdf>>>")) {            // Java 侧实时渲染:Markdown -> PDF            // 优势:完美控制字体和样式,避免 Python 生成乱码            String pdfPath = docConverterService.convertAndSave(text, "pdf");            return Single.just(new AgentOutput(pdfPath, OutputType.FILE));        }        // 分支 C:普通文本        return Single.just(new AgentOutput(text, OutputType.TEXT));    }}
复制代码

4.3 拦截与交付 (SandboxTools)

在 Tool 执行层做最后一道防线:输出内容的二次安检

@Componentpublic class SandboxTools{    @Tool(name = "execute_command", description = "在沙箱中执行 Shell 命令")    public String executeCommand(ExecuteCommandRequest req, ToolContext context) {        String projectId = (String) context.getContext().get("projectId");                try {            // 1. 执行 Python 脚本            Map<String, Object> result = sandboxMcpService.executeCommand(projectId, req.command());            String stdout = (String) result.get("stdout");            // 2. 【关键】输出侧安检            // 防止 LLM 通过代码计算出违规内容,绕过输入侧检查            if (banwordService.hasBanWords(stdout)) {                log.warn("Banword detected in sandbox output!");                throw new BanwordException("敏感内容阻断");            }            // 3. 超长截断 (防止 LLM 上下文爆炸)            if (stdout.length() > MAX_TEXT_LENGTH) {                return stdout.substring(0, MAX_TEXT_LENGTH) + "\n[SYSTEM: TRUNCATED]";            }            return stdout;        } catch (Exception e) {            return "Execution Error: " + e.getMessage();        }    }}
复制代码

五、 总结

Skills 技术让 LLM 拥有了“手”,但这双手必须戴上“手套”。

通过这次架构演进,我们得出的核心经验是:

  1. 不要高估 LLM 的 Coding 能力:它是一个优秀的逻辑推理引擎,但在工程细节(排版、库依赖、环境配置)上非常糟糕。DSL 封装是必须的。

  2. 不要丢掉 Java 的确定性:解析、下载、格式转换、安全检查,这些传统代码擅长的领域,不要交给概率性的 LLM 去做。

  3. 架构分层

  4. Input: Java (Standardization & Security)

  5. Thinking: LLM (Reasoning)

  6. Action: Python (Calculation via DSL)

  7. Output: Java (Rendering & Delivery)

这种混合架构,既保留了 Agent 处理复杂动态需求的能力(如自定义计算涨跌幅),又守住了企业级应用对稳定性与合规性的底线。

在多账号运营、数据采集、跨境业务和隐私保护等场景中,代理IP的使用越来越普遍。很多人用过代理IP,却不清楚代理IP是否可以自己搭建、又该如何搭建。下面小编就为大家详细讲解下。
代理IP怎么搭建?从原理到实操完整说明

一、什么是代理IP?

代理IP本质上是一个“中住哪服务器”。当你的设备通过代理访问互联网时,目标网站看到的并不是你的真实IP,而是代理服务器的IP。

简单来说,代理IP的作用主要体现在:

隐藏真实IP,提升隐私安全性

降低账号或请求之间的关联风险

切换访问出口,模拟不同地区或网络环境

二、代理IP的常见搭建方式

从实用角度看,代理IP的搭建方式大致分为三种:

1.本地代理+远程转发(不推荐新手)

通过多层转发或端口映射实现代理访问,稳定性和安全性都比较依赖网络环境,一般不适合长期使用。

2.基于VPS自建代理

这是目前个人或小团队使用最多的方式。基本思路是:

购买一台海外或国内的VPS服务器

在服务器上部署代理服务程序

本地设备通过服务器进行网络访问

这种方式的优点就是:可控性强、IP独享。缺点:需要一定的服务器和运维基础。

3.利用云服务或云厂商网络

部分云厂商允许用户配置网络转发或自定义网关,也可以实现代理功能。

三、基于VPS搭建代理IP的基本流程

第一步:准备服务器资源

通常需要具备以下条件:

一台VPS(Linux 系统使用最多,如 CentOS、Ubuntu)

独立公网IP

SSH登录权限

服务器位置可以根据使用需求选择,比如访问海外平台可优先选择对应国家节点。

第二步:选择代理协议

不同协议适合不同使用场景,常见的有:

HTTP / HTTPS 代理:配置简单,适合网页访问

SOCKS5 代理:兼容性强,适合软件、浏览器和脚本

第三步:部署代理服务

在服务器上安装代理程序后,需要完成以下配置:

设置监听端口

配置用户名和密码

限制访问来源,防止被滥用

第四步:客户端连接与测试

在本地设备中填入:

服务器IP

代理端口

账号信息(如有)

然后访问IP查询网站,确认出口IP是否已成功切换。

image.png

1、初次调研

主要目的是让ERP软件提供商的实施顾问人员能够对企业各个部门的业务流程初步了解,能收集到各个部门业务流的所有单据,和各个部门人员认识,了解他们对ERP的认识和期望,以便制订工作计划。

2、系统培训

主要目的是让企业所有人员认识到什么是ERP,并在企业中应用ERP系统能给企业带来如何的效益,另外就是ERP软件各个系统的功能培训。

3、流程拟定

主要目的是实施顾问人员根据自己对该企业的了解结合自己或所在公司对企业所在行业的累积经验,结合ERP系统拟定出一个符合企业需求的业务流程,能在系统中得到合理的体现;

这是一个非常重要的阶段,一个企业的管理能否从此通过ERP得到提升,流程能否更完善,就需要这个流程拟定。

4、编码原则

主要目的是企业能在实施顾问人员的指导下,制定企业应用ERP的基本原则,其中包括物料的编码原则、供应商、客户的编码原则、产品结构(包括BOM架阶)的分阶建立等。

5、资料收集

主要目的是企业的人员在熟悉了各项编码原则的基础上,收集企业应用ERP管理所需要的基本资料,包括物料资料、供应商、客户、部门、人员等收集。

6、流程测试

主要目的是企业的人员测试流程拟定的合理性,并使用企业实际的业务流程来测试ERP系统的功能完善性,和操作的方便性。

7、期初导入

主要目的是搜集ERP系统上线的期初数据,并在实施顾问人员的指导下录入ERP系统,为企业正式应用ERP系统奠定夯实的基础。

8、上线辅导

主要目的是将企业的实际业务数据在ERP系统中处理,一般在系统上线的第一、二个月的时间里面,有必要的双轨模式进行,以防企业人员在上线期初操作不熟练所造成错误。

9、月结辅导

主要目的是在应用系统一个自然月后,通过ERP系统来跑出企业管理所需要的各种报表、检验报表的完善性,数据的准确性。

当然,一个企业中要成功实施一个ERP系统,单纯靠以上九个步骤是远远不够的,ERP的实施是一个非常规范的过程,所以,我们在这里将这个过程分作为两大块。

一、以实施文档全面贯穿实施过程

作为实施顾问人员,在实施的过程中,应将各种标准的实施文档提交给企业,以确保ERP实施项目的质量进行,也就是说,顾问与企业之间的工作与文档的制作息息相关,可见文档在实施进程中的重要性非同一般。

那么,文档到底对整个实施工作有怎样的作用呢?

首先,我们大致将ERP实施中的文档作为一个分类:

分阶段实施计划文档

分阶段目标设置文档

标准业务流程文档

标准编码、标准数据文档

标准参数设置文档

功能操作指南文档

这些文档将会伴随着ERP实施的各个阶段逐渐充实、完善。

也同时记载了整个实施的过程和成果。那好,现在我们来分析一下这些文档的价值所在:

书面化的文档有助于实施人员与企业人员明确了解各自的职责,信息互通,共同把握实施过程的节奏。

标准业务流程文档有助于双方明晰业务流程,有效配合业务流程的重组和优化。

标准编码、数据文档及标准参数设置文档是实施中不可缺少的基础资料,可有效减少重复工作,避免对正常工作的影响。

功能操作指南文档可帮助最终用户规范化操作,加强培训效果。

前面我们曾经提到,ERP的实施工作可能长达数年不定,在这个时间跨度中,企业在最初实施ERP时确定的ERP项目的人员,也许难免要发生一些变化,那么,在发生变化时,ERP实施文档就可以承担起指导双方快速工作的标准文档的作用。

还有,当实施完成后,企业的运行过程将是更漫长的过程,那么实施的标准文档就将成为企业实施信息化的公共载体,成为指导企业后续工作的航标,和企业在后续人员培训方面提供详尽的素材。

二、培训全面贯穿实施过程

在ERP实施的过程中,培训始终是作为一条主线的,具体来说,在系统实施过程中,培训对象包括以下四类:

企业领导层、核心小组(项目负责人)、技术小组、最终用户。

企业领导层培训:对高层的培训主要是ERP管理理念的培训,通常会由软件提供商安排较资深顾问师对企业领导层进行ERP管理思想的培训,使得企业领导层能够从总体上理解ERP系统的理念、流程和功能。

核心小组(包括项目负责人、部门经理)培训:对于这一类的培训内容包括ERP系统的管理思想概念、ERP系统的具体功能以及ERP系统各种报表的应用。

技术小组培训:技术小组的成员主要包括参与ERP系统及相关数据库和网络安装、设置及管理的信息部门成员。培训的主要目标是提供ERP系统的设计结构,各个模块的关联关系与数据库结构,系统问题处理等。

最终用户培训:培训目的是使用户了解ERP系统后新的业务前景、目标以及带来的好处,使用户能清楚地了解到ERP是什么,怎样通过它提高个人及整体的业务表现,使用户发觉其工作内容的变化及ERP将如何融入其日常工作。同时向用户提供从现状到未来迁移过程中通用的术语,指导用户如何使用ERP完成其工作。

ERP实施过程中的培训作为实施的一条主线,既体现了ERP实施很高的附加值,又充分体现了ERP实施过程中的知识转移。

把ERP从半成品到成品的过程实质就是知识转移的过程,其中包含企业的管理诊断,实施战略的选择,业务流程的设定,对企业需求的恰到好处的分析。

综上所述,企业信息化是一个长期的过程,在这个过程中,成熟完善的ERP系统是信息化成功的前提,严谨科学的实施方式是保证ERP成功上线的关键。

image.png
image.png
image.png

【声明】:以上所发文章仅供大家学习参考,请不要作商业用途;ERP系统的专业性很强,文中难免有错误,一旦发现,请联系我们及时更正;最后感谢图片内容的提供商:织信ERP,该厂商专注企业信息化系统管理10年余,坚持传播生产管理知识,自研低代码开发底座,基于B/S架构,可帮助企业快速构建生产管理所需的各项功能。

前言

在一个优秀的应用设计中,界面不仅仅是平铺直叙的展示,更需要有层级感。当用户点击删除按钮时,我们需要一个确认框来防止误触;当后台数据加载完成时,我们需要一个轻量的提示告诉用户 好了 ;当用户对某个晦涩的功能图标感到困惑时,我们需要一个气泡弹窗来解释它的含义。这些浮在主界面之上的交互层,我们统称为 覆盖物(Overlays)

在早期的开发中,很多工程师习惯直接使用系统原生的 AlertDialog,那种灰底黑字的弹窗虽然功能健全,但在如今这个颜值为王的时代,它打断了用户的情绪流,也破坏了应用的整体设计语言。

在鸿蒙 HarmonyOS 6 中,ArkUI 为我们提供了极其强大的弹窗定制能力。无论是转瞬即逝的 Toast,还是完全自定义的 CustomDialog,亦或是指向性明确的 Popup 气泡,我们都可以像搭积木一样,用声明式的代码构建出既美观又灵动的交互体验。

一、 轻量级反馈与上下文气泡

在进入复杂的弹窗之前,我们先解决最基础的反馈需求。当用户复制了一段文本,或者刷新列表成功时,我们不需要让用户进行任何操作,只需要给出一个朕已阅的信号。这就是 Toast。在 API 20 中,系统将这类交互统一收敛到了 promptAction 模块下。我们不再像以前那样去寻找 Window 实例,而是直接调用 promptAction.showToast。这个 API 非常纯粹,它接受一个显示时长、一条消息文本,以及一个可选的位置参数。但在实战中,建议尽量保持 Toast 的简洁,不要试图在里面塞入过多的文字。它应该像一阵风,来过,被看到,然后消失。

如果说 Toast 是全局的广播,那么 Popup 气泡就是点对点的悄悄话。CustomDialog 是一种模态交互,它会给背景加上遮罩,强迫用户聚焦。但有时候,我们并不想打断用户的操作流,只是想对界面上的某个元素做一点补充说明。比如一个帮助的小问号图标,或者一个“新功能”的引导提示。

这时候,ArkUI 提供的 bindPopup 属性是最优雅的选择。这意味着任何组件——一个按钮、一张图片甚至一段文字,都可以绑定一个气泡。系统会自动计算目标组件在屏幕上的位置,然后决定气泡是出现在上方、下方还是侧边,并自动生成一个小箭头指向目标。我们作为开发者,几乎不需要关心坐标计算的问题,只需要关注气泡里的内容构建即可。

@Entry
@Component
struct PopupExample {
  // 控制气泡显示的开关状态
  @State showPopup: boolean = false;

  // 定义气泡内部的 UI 结构
  @Builder
  PopupContent() {
    Column() {
      Text('功能说明')
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.White)
        .margin({ bottom: 4 })
      
      Text('这里是详细的补充文案,系统会自动根据位置计算箭头指向。')
        .fontSize(12)
        .fontColor('#E6E6E6')
    }
    .padding(12)
    .backgroundColor('#4D4D4D') // 气泡背景通常与文字反色
    .borderRadius(8)
  }

  build() {
    Column() {
      // 任何组件都可以绑定气泡,这里以一个问号图标为例
      SymbolGlyph($r('sys.symbol.questionmark_circle'))
        .fontSize(24)
        .fontColor($r('sys.color.ohos_id_color_text_secondary'))
        // 1. 点击切换状态
        .onClick(() => {
          this.showPopup = !this.showPopup;
        })
        // 2. 绑定气泡属性
        .bindPopup(this.showPopup, {
          builder: this.PopupContent,     // 指向内容构建器
          placement: Placement.Bottom,    // 优先显示位置(系统会自动调整)
          mask: false,                    // false 表示非模态,不阻断用户操作其他区域
          enableArrow: true,              // 显示指向目标的小箭头
          popupColor: '#4D4D4D',          // 气泡背景色(需与 Builder 背景一致或透明)
          onStateChange: (e) => {
            // 3. 状态同步:当点击空白处气泡消失时,同步更新 boolean 变量
            if (!e.isVisible) {
              this.showPopup = false;
            }
          }
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

二、 定制化核心:CustomDialog 与控制器模式

当业务逻辑变得复杂,比如需要用户领取优惠券、签署隐私协议或者选择复杂的筛选条件时,系统的标准弹窗就捉襟见肘了。这时候,CustomDialog(自定义弹窗)就是我们的救星。它的设计哲学非常有趣,采用了一种 控制器(Controller) 模式。我们需要定义两个部分:一个是弹窗本身的 UI 结构,另一个是控制它打开和关闭的遥控器。

首先,我们需要定义一个被 @CustomDialog 装饰器修饰的结构体。在这个结构体里,你可以使用任何 ArkUI 组件:Column、Row、Image 甚至 List。这意味你可以把弹窗做得像普通页面一样丰富多彩。紧接着,在父组件中,我们需要实例化一个 CustomDialogController。这个控制器是连接父子组件的纽带。在实例化时,我们需要传入 builder 参数,指向我们刚才定义的弹窗组件。

@Entry
@Component
struct HomePage {
  // 1. 实例化控制器:连接父组件与弹窗组件
  // 必须在 @Component 中作为成员变量定义
  dialogController: CustomDialogController | null = new CustomDialogController({
    builder: PrivacyAgreementDialog(), // 引用外部定义的 @CustomDialog 组件
    autoCancel: false,                 // 点击遮罩是否允许关闭(强制交互场景通常设为 false)
    alignment: DialogAlignment.Center, // 弹窗在屏幕中的对齐方式
    customStyle: true,                 // 是否完全自定义样式(去除系统默认的白色背景和圆角)
    offset: { dx: 0, dy: 0 },          // 相对对齐位置的偏移量
    maskColor: '#33000000',            // 自定义遮罩层颜色
  });

  // 推荐:在组件销毁时清理控制器,防止内存泄漏
  aboutToDisappear() {
    this.dialogController = null;
  }

  build() {
    Column() {
      Button('打开隐私协议')
        .fontSize(16)
        .onClick(() => {
          // 2. 通过控制器打开弹窗
          if (this.dialogController != null) {
            this.dialogController.open();
          }
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

这里有一个初学者常犯的错误,就是试图通过 @Prop 或 @Link 来直接同步父子组件的数据。虽然 CustomDialog 支持这些装饰器,但由于弹窗并不在常规的组件渲染树中,数据的响应式更新有时会存在滞后。最佳的实践是:在打开弹窗时传入初始数据,在关闭弹窗时通过回调函数返回结果。比如做一个“领取优惠券”的弹窗,我们在构建 CustomDialog 时定义一个 confirm 回调函数。当用户点击弹窗里的“立即领取”按钮时,我们调用这个回调,把结果传回给父组件,然后关闭弹窗。这种 事件驱动 的数据流向,比复杂的双向绑定更加稳健且易于追踪。

做出来和做得好看是两码事。默认的 CustomDialog 往往带有系统默认的圆角和白色背景,有时甚至会有默认的内边距。为了实现设计师眼中那种“全屏半透明”或者“底部异形弹窗”的效果,我们一定要善用 customStyle: true 这个配置项。一旦设置为 true,系统就会移除所有默认的弹窗样式,给你一张完全空白的画布。这时候,你需要在你的 @CustomDialog 组件内部,自己定义背景色、圆角和阴影。虽然麻烦了一点,但它赋予了你像素级的控制权。

三、 综合实战:构建营销活动弹窗体系

为了将上述知识点融会贯通,我们来构建一个真实的电商营销场景。这个页面包含一个模拟的“会员中心”,右上角有一个绑定了 bindPopup 的帮助图标,点击会展示活动规则;而在页面中心,有一个“领取大礼包”的按钮,点击会唤起一个完全自定义样式的 CustomDialog 优惠券弹窗。

在这个代码中,请仔细观察 CouponDialog 的定义,它是如何通过 controller 关闭自己的,以及父组件是如何通过 CustomDialogController 配置 customStyle: true 来移除系统默认背景的。这就是构建高颜值弹窗的标准模板。

TypeScript

import { promptAction } from '@kit.ArkUI';

@CustomDialog
struct CouponDialog {
  controller?: CustomDialogController;

  couponAmount: number = 0;
  onConfirm: () => void = () => {};

  build() {
    Column() {
      // 顶部装饰
      Stack({ alignContent: Alignment.Bottom }) {
        Column()
          .width('100%')
          .height('100%')
          .backgroundColor('#FF4040')
          .borderRadius({ topLeft: 16, topRight: 16 })

        Text(`¥${this.couponAmount}`)
          .fontSize(40)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.White)
          .margin({ bottom: 20 })
      }
      .width('100%')
      .height(120)

      // 内容
      Column({ space: 12 }) {
        Text('恭喜获得新人优惠券')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333')

        Text('全场通用,无门槛立减。有效期至 2026-12-31')
          .fontSize(14)
          .fontColor('#999')
          .textAlign(TextAlign.Center)
          .padding({ left: 20, right: 20 })
      }
      .padding({ top: 20, bottom: 20 })

      // 按钮
      Row() {
        Button('残忍拒绝')
          .backgroundColor('#F5F5F5')
          .fontColor('#666')
          .layoutWeight(1)
          .margin({ right: 10 })
          .onClick(() => {
            // 【修复点 2】调用时加上 '?' (可选链),防止空指针报错
            this.controller?.close();
          })

        Button('立即领取')
          .backgroundColor('#FF4040')
          .fontColor(Color.White)
          .layoutWeight(1)
          .onClick(() => {
            this.onConfirm();
            // 【修复点 3】同理,加上 '?'
            this.controller?.close();
          })
      }
      .width('100%')
      .padding({ left: 20, right: 20, bottom: 20 })
    }
    .width(300)
    .backgroundColor(Color.White)
    .borderRadius(16)
    .shadow({ radius: 10, color: '#33000000', offsetY: 5 })
  }
}


@Entry
@Component
struct DialogAndPopupPage {
  // 状态变量:控制气泡 (Popup) 的显示与隐藏
  @State isHelpPopupVisible: boolean = false;

  // 【核心】定义弹窗控制器
  // 必须在 build() 之外实例化
  // builder 参数指向上面定义的 @CustomDialog 组件
  private dialogController: CustomDialogController = new CustomDialogController({
    builder: CouponDialog({
      couponAmount: 100, // 向弹窗传递数据
      onConfirm: () => {
        // 定义弹窗确认后的逻辑
        this.handleCouponReceived();
      }
    }),
    autoCancel: true,                 // 允许点击遮罩关闭
    customStyle: true,                // 使用完全自定义样式(去除系统默认白底圆角)
    alignment: DialogAlignment.Center // 居中显示
  });

  // 模拟业务逻辑:领取成功后的 Toast 反馈
  handleCouponReceived() {
    promptAction.showToast({
      message: '领取成功!已存入卡包',
      duration: 2000,
      bottom: 100
    });
  }

  // 定义 Popup (气泡) 的内容构建器
  @Builder
  PopupBuilder() {
    Column() {
      Text('活动规则说明')
        .fontSize(14)
        .fontWeight(FontWeight.Bold)
        .fontColor(Color.White)
        .margin({ bottom: 8 })

      Text('1. 仅限新用户领取\n2. 每日限领一张\n3. 不可与其他活动叠加')
        .fontSize(12)
        .fontColor(Color.White)
        .lineHeight(18)
    }
    .padding(12)
    .width(200)
  }

  build() {
    Column() {
      // --- 顶部导航栏 ---
      Row() {
        Text('会员中心')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)

        Blank() // 撑开中间空间

        // 帮助图标 (绑定 Popup)
        Text('?')
          .fontSize(18)
          .fontColor(Color.White)
          .backgroundColor('#CCCCCC')
          .width(24)
          .height(24)
          .textAlign(TextAlign.Center)
          .borderRadius(12)
          // 【核心】绑定气泡
          .bindPopup(this.isHelpPopupVisible, {
            builder: this.PopupBuilder(), // 指向 Builder
            placement: Placement.BottomRight, // 气泡位置
            popupColor: '#4C4C4C',            // 气泡深色背景
            enableArrow: true,                // 显示箭头
            mask: false,                      // 非模态,不遮挡背景
            onStateChange: (e) => {
              // 状态同步:处理点击外部自动消失的情况
              if (!e.isVisible) {
                this.isHelpPopupVisible = false;
              }
            }
          })
          .onClick(() => {
            // 点击切换显示状态
            this.isHelpPopupVisible = !this.isHelpPopupVisible;
          })
      }
      .width('100%')
      .padding(20)

      // --- 页面主体内容 ---
      Column({ space: 30 }) {
        // 模拟大图占位
        Column()
          .width(200)
          .height(200)
          .backgroundColor('#E0E0E0')
          .borderRadius(100)
          .margin({ top: 50 })

        Text('超级会员大礼包')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)

        Text('包含 100 元无门槛优惠券')
          .fontSize(16)
          .fontColor('#666')

        // 【核心】触发弹窗的按钮
        Button('立即领取')
          .width('80%')
          .height(50)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .backgroundColor('#FF4040')
          .shadow({ radius: 10, color: '#4DFF4040', offsetY: 5 })
          .onClick(() => {
            // 打开自定义弹窗
            if (this.dialogController) {
              this.dialogController.open();
            }
          })
      }
      .width('100%')
      .layoutWeight(1) // 占据剩余高度
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F8F8F8')
  }
}

总结

弹窗和覆盖物是应用与用户沟通的第二语言。Toast 是轻声的耳语,CustomDialog 是正式的对话,而 Popup 则是贴心的便签。

在鸿蒙 HarmonyOS 6 开发中,掌握 @CustomDialogbindPopup 是构建高级 UI 的必修课。我们抛弃了系统的默认样式,通过 customStyle 获得了对画布的完全掌控权,让弹窗不再只是功能的载体,更是视觉设计的延伸。切记,不要滥用弹窗,每一次遮罩的出现都是对用户注意力的强行掠夺。

好的交互应该是克制的,只在真正需要的时候才优雅地浮现。

CRM系统是干什么用的?从零开始全面了解客户管理工具

最近发现,越来越多的企业决策者和管理层将关注点聚焦于几个关键命题:
“企业必须推动精细化运营,向管理要效益”
“客户资源是企业的核心战略资产,需进行系统性经营”
“亟需部署CRM系统,实现客户关系的数字化、智能化管理” 
这些听起来方向明确、势在必行,但真到了要建系统、要执行落地的时候,不少企业却陷入困惑:
CRM客户关系管理系统究竟是什么?与Excel表格、零散的客户记录工具有什么本质区别?怎么才能选择一套真正契合自身业务、投入产出比高的CRM解决方案? 
这篇文章,就从一个根本问题切入,——“CRM真正的价值到底是什么?”并结合国内领先的CRM厂商纷享销客的实践案例,剖析CRM背后的底层逻辑与实战价值。
让你不仅知道“要不要上 CRM”,更清楚“该怎么用好 CRM”。

一、CRM不是工具,而是一套以客户为中心的管理体系

1.1 CRM的定义与本质

CRM(Customer Relationship Management),即客户关系管理系统,核心目标并非简单地“记录客户信息”,
而是通过技术手段,构建一套覆盖从获客 → 成交 → 复购/续费的可追踪、可优化、可复制的管理机制。
根据Gartner在《2024年CRM市场指南》中的定义:“现代CRM系统已超越传统销售自动化范畴,演变为集营销获客、销售转化、客户服务与数据分析于一体的智能客户运营平台。”
这意味着,CRM的本质是一种企业级客户资产沉淀机制,而非仅限于前端销售人员使用的辅助工具。

1.2 与Excel、微信标签等“伪CRM”的本质差异

许多企业常误以为“有客户名单就是有CRM”。例如,使用Excel表格管理客户、依赖企业微信打标签、或让销售员在手机备忘录中记录跟进情况。
这些方式虽能短期满足基础需求,但在规模化、流程化、数据驱动层面存在致命短板:
• 信息孤岛严重:客户数据分散在不同员工终端,离职即流失;
• 过程不可见:管理者无法掌握销售推进的真实节奏与卡点;
• 决策无依据:缺乏结构化数据支撑,难以评估渠道效果、销售效能或客户价值。

二、CRM的核心模块:覆盖客户全生命周期(以纷享销客为例)

一个成熟的CRM系统通常包含五大核心功能模块,共同构成客户旅程的完整闭环:

2.1  营销与活动管理

说白了就是帮你把各个渠道来的客户线索“收好、分快、跟准”。
比如你在抖音、百度或者微信投广告,用户一留信息,系统自动抓进来,不漏掉;还能给线索打分,谁更可能成交就优先推给销售。办个线上直播或展会?报名、签到、后续跟进全在线搞定。
最实在的是,花多少钱、带来多少客户、最后成没成交,一笔账清清楚楚,不像以前“钱花了,效果靠猜”。
这样你就能知道哪条渠道真管用,下次把预算花在刀刃上。

2.2  线索管理

帮助企业把从各个地方来的潜在客户(比如官网留言、广告点击、展会名片)统一收进来,不乱不丢。
纷享销客会自动判断谁更可能买——比如有人反复看产品页,就打个高分,优先推给销售;没人跟进的线索还会自动“回收”,转给别人跟。
整个过程像流水线一样:先识别,再打标签,接着分人跟,最后看效果。市场和销售不再扯皮,线索也不再“休冬眠”,转化自然就上去了。

2.3  客户与联系人管理

其实就是帮你把“谁是客户、谁在对接”这件事理得明明白白,不用担心销售离职客户丢失的问题。
比如你公司卖设备给一家工厂,这家工厂就是“客户”,而采购经理老王、技术主管小李就是“联系人”。
系统会把这些信息全记下来——不光是电话微信,还有每次聊了啥、什么时候拜访过、买过什么产品,全都自动归到一起。
哪怕老王跳槽了,新来的销售也能一眼看懂:“哦,原来上次谈的是这个需求,现在该找小李了。”
而且客户还能分级,像A类重点客户,系统会提醒你定期跟进;
要是好久没动静,可能自动放回公海,让别人试试。
这样客户资源就真正变成了公司的资产,不是某个人的私有物。
说白了,就是让客户信息“看得清、跟得上、留得住”。

2.4 商机与销售漏斗管理

商机管理是CRM最核心的价值体现。纷享销客支持企业自定义销售阶段(“初步接洽→需求确认→方案演示→报价谈判→合同签署”),每个阶段设置关键动作与成功标准,形成可视化销售漏斗。
通过漏斗分析,管理者可清晰看到:
• 当前有多少商机处于各阶段?
• 哪个环节流失率最高?
• 下季度预计成交金额是否达标?
更实用的是,系统能自动防撞单、智能预测业绩,并将最佳销售实践固化进流程——新员工照着走就不会跑偏,老员工也能避免凭感觉跟进。最终实现从“靠人盯”到“靠流程驱动”,提升赢单率和预测准确性。

2.5 客户服务管理

纷享销客帮助企业把“售后”这件事做得又快又稳,让客户觉得你靠谱。
比如客户家的设备出问题了,他不用打电话干等,直接扫个码、在微信小程序里点一下,就能提个服务请求。
系统马上收到,自动分给离得最近、有空的工程师——就像打车软件派单一样。
工程师上门前,手机上能看到这台设备以前修过啥、配件用过哪些;
修完还能当场扫码让客户打分,满意不满意一目了然。
要是客户是VIP,系统还会优先安排专属客服,服务更快更贴心。
所有这些流程——从客户报修、派工、上门、用配件、收钱到评价——全在线上走,不靠Excel也不靠嘴记。
老板在后台还能看数据:哪个产品老坏?哪个工程师效率高?客户满意度掉没掉?一清二楚。
总之,就是让服务不乱、不拖、不丢事,客户省心,公司也省力。

2.6  报表与BI分析

BI分析就像给公司装了个“数据仪表盘”,销售做了多少单、客户从哪来、服务满不满意,一眼就能看清。一套好的CRM必须能将过程数据转化为决策洞察。典型报表包括:
• 销售业绩达成率
• 线索来源渠道ROI
• 商机阶段转化率
• 客户生命周期价值(LTV)
更实用的是,它能自动发现谁该复购了——比如客户买的软件快到期了,或者老在用某个功能,系统就会提醒销售!不用靠人脑记,也不用等客户主动找上门。整个过程简单直接:看数据、抓机会、促成交,让老客户不断带来新生意。
说到底,纷享销客CRM做的不是简单的“记客户电话”,而是帮企业把客户当成资产来经营。通过营销自动化 → 智能线索管理 → 标准化销售流程 → 全景客户视图 → 数据驱动服务与复购的完整链路,纷享销客真正实现了:
• 前端:精准获客、高效转化
• 中台:过程可视、协同高效
• 后端:体验保障、价值深耕

三、为什么企业需要CRM?三大角色视角下的真实价值

3.1 对老板:守住客户资产,降低经营风险

对企业主而言,最大的隐性成本不是软件采购费,而是“人走客户飞”。据麦肯锡调研,超过60%的企业客户资源高度依赖个别销售个人关系,一旦核心人员离职,客户流失率高达40%以上。
CRM系统通过强制数据录入与权限管控,确保所有客户互动记录沉淀在系统中。即使销售离职,客户仍属于公司资产,可无缝交接。此外,CRM提供的销售预测与现金流预判功能,让老板告别“拍脑袋定目标”,实现科学经营。

3.2 对销售:减负增效,专注高价值沟通

一线销售最怕“填表式CRM”。真正优秀的系统应成为销售的“智能助手”,而非负担。纷享销客通过以下设计提升销售体验:
• 移动端一键记录:通话后自动生成跟进日志;
• 智能提醒:自动提示“3天未联系的A类客户”;
• 话术库与模板:快速调用成功案例与标准应答;
• 任务自动化:商机推进到下一阶段时,自动创建待办事项。

3.3 对管理者:从“结果管控”转向“过程赋能”

传统管理依赖周报、月报和口头汇报,信息滞后且失真。CRM则实现全流程透明化:
• 可查看每位销售的日程安排、客户拜访轨迹、沟通频次;
• 可对比团队成员在相同阶段的转化效率;
• 可识别高绩效销售的行为模式,并复制推广。
纷享销客的团队协作空间支持跨部门协同(如销售+售前+交付),确保大客户项目高效推进,避免内部扯皮。

四、国产CRM崛起:纷享销客如何满足中国企业的独特需求?

在全球CRM市场,Salesforce长期占据主导地位。但在中国市场,企业对客户资产的精细化运营需求日益迫切——不仅要管好客户信息,更要实现从获客、转化到复购的全生命周期价值挖掘。而国内企业的需求,例如系统集成、审批流程、发票管理,使得国产CRM更具适配性。
纷享销客作为国内领先的智能型CRM厂商,深耕中国市场十余年,产品不仅贴合本土业务逻辑,更积极融合AI能力,打造“智能化+场景化”的新一代客户运营平台。其核心优势体现在以下五个方面:
• 深度集成微信生态:支持企业微信客户同步、聊天侧边栏、朋友圈素材库,实现私域流量无缝管理;
• 灵活审批流:可配置合同审批、折扣申请、回款确认等复杂流程,贴合国内企业内控要求;
• 业财一体化打通:与用友、金蝶等财务系统对接,打通“签约—开票—回款”链条;
• 垂直行业解决方案:针对制造业、医疗、快消、IT服务等垂直领域提供预置模板。
• AI驱动的智能销售助手:系统可以自动分析客户行为、预测成交概率,并在销售跟进中实时推荐话术、成功案例和下一步行动;同时支持自动生成会议纪要、识别商机风险、预警客户流失,帮助销售减少重复工作,专注高价值沟通。更重要的是,纷享销客采用PaaS平台架构,支持企业按需扩展模块(如CPQ报价、服务工单、BI分析),避免“一次性买断但用不起来”的陷阱。这种“用多少、配多少、智能多少”的弹性模式,尤其适合大中型企业。

五、如何选型CRM?七大关键评估维度

当您认识到CRM的价值并准备开始选择时,面对市场上数百款琳琅满目的产品可能会感到无从下手。面对市面上数十款CRM产品,如何为您的企业选择一款合适的CRM系统?建议从以下七个维度综合评估:

结语:CRM不是成本,而是增长基础设施

在数字经济时代,客户关系已成为企业最稀缺的战略资源。CRM系统不再是“可选项”,而是如同ERP、财务系统一样的企业数字基建。
它帮助企业回答三个根本问题:
• 我们到底有多少真实有效的客户?
• 销售团队每天在做什么?效率如何?
• 下个月、下个季度的业绩从哪里来?
总而言之,CRM系统远不止是一个记录客户信息的数据库软件。它是一种将“以客户为中心”的理念融入企业血脉的战略工具,是连接市场、销售和服务,驱动业务流程优化和决策智能化的核心引擎。希望本文能帮助您对CRM有一个清晰的认知。 

常见问题 (FAQ)

1、CRM系统和ERP系统有什么区别?
简单来说,CRM(客户关系管理)主要面向外部,关注的是与客户相关的活动,如市场、销售、服务,目标是增加收入和提升客户满意度。而ERP(企业资源计划)主要面向内部,管理的是企业的核心业务资源,如财务、库存、采购、生产等,目标是优化内部流程和降低成本。两者可以集成,共同构成企业数字化管理的核心。
2、CRM系统一定要全员使用吗?可以只给销售用吗?
A:不建议。CRM的价值在于打通“营销—销售—服务”全链路。若仅销售使用,将导致线索来源不清、售后脱节、数据断层。理想状态是市场、销售、客服、管理层均在系统中有角色和数据贡献。
3、纷享销客和国外CRM(如Salesforce)相比有什么优势?
A:纷享销客在微信生态集成、本地化审批流程、中文界面体验、实施成本及响应速度上更具优势。Salesforce虽功能强大,但对中国企业常见的“人情化流程”“多级审批”“业财一体”支持较弱,且本地化服务成本高。
4、CRM系统主要适用于哪些类型的企业?是不是只有大企业才需要?
答: CRM系统适用于所有有客户并希望与客户建立长期关系的企业,并非大企业专属。不同规模企业需求侧重不同:中小企业可使用CRM标准化销售过程、防止客户流失、提升人效;中大型企业则更侧重于跨部门协同、数据整合分析与生态连接。当前,许多云原生CRM(如纷享销客CRM)提供灵活订阅模式与标准化功能模块,大幅降低了中小企业的使用门槛与启动成本。
5、实施CRM系统最大的挑战是什么?如何规避失败风险?
答: 最大挑战往往来自组织与文化层面,而非技术本身,具体包括:员工抵触改变、使用率低下;业务流程与系统不匹配;数据质量差导致洞察失效。规避风险需:首先,确保高层推动与明确业务目标;其次,选择用户体验好、移动化程度高的系统,降低使用阻力;再次,结合业务痛点梳理优化流程,并进行分阶段上线与持续培训;最后,建立数据治理规范。选择像纷享销客这类重视用户体验与成功服务的厂商,也能获得重要的实施支持。

编者按: 英伟达财报的营收神话是否掩盖了其现金流恶化的现实?而在“循环融资”的质疑声中,OpenAI 与甲骨文等关键客户的供应链“去英伟达化”浪潮,又将如何重塑 AI 硬件的竞争格局?

我们今天为大家带来的这篇文章,作者的观点是:英伟达目前的高速增长依赖于激进的库存策略和宽松的信用条款,但其最大客户正通过定制芯片和直接采购关键组件来构建独立的供应链,这导致双方关系正从深度捆绑走向潜在的激烈竞争。

作者 | Philippe Oger

编译 | 岳扬

过去 48 小时,我完全沉浸在对英伟达 2026 财年第三季度财报[1]的深度研究中。如果你只看新闻标题,一切看起来都完美无缺:营收同比增长 62 %,达到 570 亿美元,黄仁勋还在大谈“AI 的良性循环”。

但我想弄清楚光鲜数据下的真实情况,于是深挖了资产负债表,并将其与围绕 OpenAI 和 Oracle 的所有新闻进行了交叉验证。 我并不是华尔街的专业分析师,但即便仅凭自己梳理线索(并借助了 Gemini 的帮助),我也开始看到这个所谓的“AI 联盟”出现了一些裂痕。就在英伟达创下业绩纪录的同时,他们最大的客户似乎正在悄悄武装自己,准备另起炉灶。

以下是我对硬件市场、OpenAI 与英伟达之间“亦敌亦友”的关系,以及包括迈克尔·贝瑞(Michael Burry)在内大家都在讨论的“循环融资(circular financing)”理论的一些看法。

01 英伟达财报:完美表象下的隐忧

表面看来,英伟达无疑是 AI 时代的绝对王者 —— 数据中心业务已占据公司总营收近九成,这一事实无可辩驳。然而,当我深入研读财报细节时,发现了三处值得警惕的“红色信号”

  • 现金流之谜:英伟达公布的净利润高达 319 亿美元,但我查阅现金流量表时发现,其经营活动产生的现金流仅为 238 亿美元。这意味着有 80 亿美元的利润尚未立即转化为现金。
  • 库存激增:我注意到,今年库存几乎翻倍,达到 198 亿美元。管理层解释称这是为“Blackwell”发布做准备,但在我看来,持有大约 120 天的库存量,会带来巨大的资金占用压力。
  • 应收账款周期拉长:我计算了其应收账款周转天数(DSO),发现已悄然攀升至约 53 天。在营收飙升的同时,英伟达却要等待近两个月才能回款,这暗示他们可能正在向企业客户提供极为宽松的信用条款,以维持增长飞轮的运转。

我的个人判断?英伟达正通过透支现金流来囤积库存,将全部赌注押在 Blackwell 架构[2]能在第四季度被市场瞬间消化。

02 拆解“资金空转”传闻的虚实

我想说清楚一点:接下来这部分内容并不是我最先发现的。最近财经新闻到处都在讨论这件事,而且如果你关注迈克尔·巴里(就是那位电影《大空头》里的“大空头”原型人物),你很可能已经看到他发推文警告所谓的“循环融资”和可疑的收入确认(Revenue Recognition)[3]行为。

我尝试自行理清这其中的关系,看看大家究竟在争论什么。巴里最近分享了一张图表,把这一系列交易描绘成一张交易“关系网”,其结构大致如下:

  • 环节一:英伟达承诺向 OpenAI 投资数十亿美元(这属于已被广泛报道的“千亿美元投资路线图”中的一部分)
  • 环节二:OpenAI 与甲骨文(Oracle)签署了一份高达 3000 亿美元的巨额云服务合同(即“星门计划”,Project Stargate),用于托管其人工智能模型。
  • 环节三:为履行该合约,甲骨文随即向英伟达下达价值 400 亿美元的 GB200 GPU 采购订单。

巴里的核心论点(也是据传美国司法部等监管机构介入调查的原因[4])在于:这套模式形同“资金空转”。这引发了一个尖锐的问题:如果英伟达停止向 OpenAI 投资,OpenAI 还有足够现金去和甲骨文(Oracle)签下那笔大单吗?而甲骨文又是否还会采购那些芯片? 如果答案是“不会”,那么部分营收数据的稳固性可能远不如表面看来那样坚实。

03 OpenAI 正在采取行动降低对英伟达的依赖

我近期一直在关注的另一个重大转变,是 OpenAI 的战略转向。他们曾是英伟达最耀眼的“模范客户”,如今却越来越像一个潜在的竞争对手。一方面,他们仍与 NVIDIA 保持紧密合作 —— 部署 10 吉瓦(gigawatts)的基础设施用于训练 GPT-6;但另一方面,他们似乎正在构建一条能彻底摆脱黄仁勋(Jensen Huang)掌控的供应链。

如果你有所留意,相关迹象其实已经相当明显。 “星门计划”(Project Stargate) 不仅仅是个数据中心,更是一项包含定制硬件在内的庞大基础设施计划。据多家媒体报道(例如此处[5]、此处[6]、此处[7],并在 Hacker News 上引发了激烈的讨论[8]),OpenAI 已直接从三星和 SK 海力士(全球两大 HBM 内存供应商)采购 DRAM 晶圆,绕开了英伟达的供应链。

此外,人才流向也透露出关键信号:OpenAI 已从数个行业巨头处挖走多名芯片人才,包括 2023 年招揽了谷歌前 TPU 负责人 Richard Ho,以及近期从苹果挖走的约 40 名硬件工程师。

结合 OpenAI 与博通(Broadcom)的合作[9],我推测其策略是:用英伟达 GPU 构建智能模型,但最终在自家的定制芯片上运行推理任务 —— 以此大幅削减高昂的运营成本,或押注类似谷歌 Edge TPU 的专用芯片(NPU)来处理推理负载。

但关键问题来了:OpenAI 打算用谁的钱来支持这项事业?而英伟达对其未来规划又究竟有多大影响力?

而且,所谓“英伟达向 OpenAI 投资 1000 亿美元”的说法,至今尚未得到官方证实(如此处[10]所述)。

04 甲骨文一个有趣的思路:收购 Groq

眼下所有人都在讨论推理成本问题(Inference costs) —— 也就是实际运行 ChatGPT 或其他大语言模型(LLM)的花销,远比训练它们更昂贵。我最近在关注 Groq 这家初创公司,他们明确宣称在推理任务上比英伟达更快、更便宜。其创始人乔纳森·罗斯(Jonathan Ross)[11]曾是谷歌 TPU 团队的负责人,甚至可以说是 TPU 概念的最初提出者。

但还有一层情况,我认为被大多数人忽视了:OpenAI 直接采购晶圆所引发的 HBM 短缺问题。

据我所知,目前英伟达最大的瓶颈之一就是 HBM(高带宽内存)。 HBM 由专业内存代工厂生产,而这些产线早已完全超负荷运转。然而,Groq 的架构依赖的是 SRAM(静态随机存储器)。 由于 SRAM 通常是在逻辑制程代工厂(比如台积电 TSMC)中与处理器本身一同制造的,理论上它不会遭遇与 HBM 相同的供应链紧张问题。

综合这些因素,我觉得甲骨文真该认真考虑一下收购 Groq。拿下 Groq 不仅意味着获得更快的芯片,更关键的是 —— 当其他芯片全都售罄时,Groq 的芯片可能仍然有货。这本质上是一种供应链对冲(supply chain hedge)。

对甲骨文的最大客户 OpenAI 而言,这也将带来巨大的优势:更快、更便宜的推理能力。

再结合此前的传闻:甲骨文出租英伟达芯片的利润率极其微薄[12],据传低至 14%,那这笔收购就显得更加合理。通过控股 Groq,甲骨文不仅能摆脱“英伟达税”(NVIDIA Tax),改善自身利润空间,还能彻底绕过 HBM 短缺的困局。

据 Groq 在 2025 年 9 月的最近一轮融资披露[13],其估值约为 69 亿美元。即便支付溢价,以甲骨文的财力也完全有能力完成这笔收购。

但问题是:英伟达会允许这事发生吗?

如果答案是否定的,那又说明了什么?是否意味着当前这套“循环融资(circular financing)”体系中存在某种利益交换 —— 比如,英伟达承诺向 OpenAI 投资 1000 亿美元,条件是甲骨文必须只能使用英伟达芯片?

05 Final Thoughts

进入 2026 年,观察英伟达、OpenAI 与甲骨文之间的博弈,这场三方角力正陷入彼此钳制的僵局。我无从得知英伟达是否事先知晓 OpenAI 与内存厂商之间的晶圆供应协议,亦或其中存在任何合谋?英伟达是否正在极力维持自己在“星门计划”(Stargate)中训练和推理环节的独家地位?而 OpenAI 又到底打算打造什么样的芯片?是类似 TPU/LPU 的架构?还是更偏向 Edge TPU 那样的边缘推理芯片?

迈克尔·巴里(Michael Burry)正在全面做空这套体系[14]。

至于我,只是个读财报的普通人,无力揣测市场走向。但我非常确定一点:AI 硬件市场比以往任何时候都更炽热,未来几个季度的风云变幻必将精彩绝伦。

免责声明:我偶尔会发表些真知灼见,但更多时候说的都是蠢话。阅读本文时请务必谨记这一点。

END

本期互动内容 🍻

❓如果“循环融资”属实,谁最可能成为这个链条中最先断裂的一环?

文中链接

[1]https://nvidianews.nvidia.com/

[2]https://www.nvidia.com/en-us/data-center/technologies/blackwe...

[3]https://www.investing.com/news/stock-market-news/michael-burr...

[4]https://m.economictimes.com/news/international/us/nvidia-reje...

[5]https://openai.com/index/samsung-and-sk-join-stargate/

[6]https://www.asiafinancial.com/samsung-sk-hynix-building-starg...

[7]https://www.kedglobal.com/artificial-intelligence/newsView/ke...

[8]https://news.ycombinator.com/item?id=46169224#46170844

[9]https://openai.com/index/openai-and-broadcom-announce-strateg...

[10]https://fortune.com/2025/12/02/nvidia-openai-deal-not-signed-...

[11]https://www.linkedin.com/in/ross-jonathan/

[12]https://www.fool.com/investing/2025/12/02/michael-burry-just-...

[13]https://groq.com/newsroom/groq-raises-750-million-as-inferenc...

[14]https://www.techradar.com/pro/security/could-the-ai-bubble-be...

本文经原作者授权,由Baihai IDP编译。如需转载译文,请联系获取授权。

原文链接:

https://philippeoger.com/pages/deep-dive-into-nvidias-virtuou...

导读

AI 编码工具正在从"智能补全"演进为能自主完成复杂任务的 Coding Agent。本文基于开源项目源码研究与实践经验,系统性地拆解 Coding Agent 的工作原理。旨在帮助开发者在了解Coding Agent后,与AI伙伴更好的协作配合,更高效的提问和拿到有效结果。

01 背景

AI 编码工具的发展速度快得有点"离谱"。从开始使用 GitHub Copilot 的代码补全,到使用Claude Code、Cursor、Comate IDE等完成复杂编程任务,AI 不再只是个「智能补全工具」,它能读懂你的代码库、执行终端命令、甚至帮你调试问题,成为你的“编码伙伴”。

我自己在团队里推 AI 编码工具的时候,发现一个很有意思的现象:大家都在用,但很少有人真正理解它是怎么工作的。有人觉得它"很神奇",有人吐槽它"经常乱来",还有人担心"会不会把代码搞乱"。这些困惑的背后,其实都指向同一个问题:我们对这个"伙伴"还不够了解。

就像你不会无脑信任一个新来的同事一样,要和 AI 编码伙伴配合好,你得知道它的工作方式、能力边界、以及怎么"沟通"才更有效。

在经过多次的实践尝试后,我尝试探索它的底层原理,并写下了这篇文章记录,主要围绕了这些内容展开:

  • Coding Agent 的核心工作机制,包括身份定义、工具调用、环境感知等基础组成。
  • 从零实现一个最小化 Coding Agent 的完整过程,以建立对 Agent 工作流程的直观理解。
  • 上下文管理、成本控制、冲突管控等生产环境中的关键技术问题及其解决方案。
  • Rule、MCP、Skill 等能力扩展机制的原理与应用场景。

在了解原理后,我和伙伴的协作更佳顺畅,让伙伴更清晰的了解我的意图,我拿到有效的回答。

02 概念

2.1 从Workflow到Agent

取一个实际的例子:休假申请。

如果我们的需求非常简单:

一键申请明天的休假。

在这里插入图片描述

这个需求可以被简化为一个固定的工作流

  1. 打开网页。
  2. 填写起始时间。
  3. 填写结束时间。
  4. 填写休假原因。
  5. 提交表单。

全过程没有任何模糊的输入,使用程序化即可完成,是最原始的工作流形态。

如果需求再模糊一些:

申请后天开始3天休假。

这个需求的特点是没有明确的起始和截止时间,需要从语义上分析出来

  1. 起始时间:后天。
  2. 休假时长:3天。
  3. 转换日期:10.14 - 10.16。
  4. 执行申请:提交表单。

这是一个工作流中使用大模型提取部分参数的典型案例,是模型与工作流的结合。

如果需求更加模糊:

国庆后休假连上下个周末。

这样的需求几乎没有任何直接确定日期的信息,同时由于年份、休假安排等动态因素,大模型不具备直接提取参数的能力。将它进一步分解,需要一个动态决策、逐步分析的过程:

  1. 知道当前年份。
  2. 知道对应年份的国庆休假和调休安排。
  3. 知道国庆后第一天是星期几。
  4. 国庆后第一天到下个周末设为休假日期。
  5. 额外补充调休的日期。
  6. 填写并提交表单。

可以看出来,其中1-5步都是用来最终确定休假日期的,且需要外部信息输入,单独的大模型无法直接完成工作。这是一个典型的Agent流程,通过大模型的智能工具访问外部信息结合实现用户需求。

2.2 什么是Agent

Agent是以大模型为核心,为满足用户的需求,使用一个或多个工具,自动进行多轮模型推理,最终得到结果的工作机制。

2.3 什么是Coding Agent

在Agent的基本定义的基础上,通过提示词、上下文、工具等元素强化“编码”这一目的,所制作的特化的Agent即为Coding Agent。

Coding Agent的最大特征是在工具的选取上,模拟工程师进行代码编写的环境,提供一套完整的编码能力,包括:

  • 阅读和查询代码:

    • 读取文件,对应 cat 命令。
    • 查看目录结构,对应 tree 命令。
    • 通配符查找,对应 ls命令(如 **/*.test.tssrc/components/**/use*.ts)。
    • 正则查找,对应grep 命令(如function print\(.+\) 可以找函数定义)。
    • LSP(Language Server Protocol),用于提供查找定义、查找引用、检查代码错误等能力。
  • 编写或修改代码:

    • 写入文件。
    • 局部编辑文件。
    • 删除文件。
  • 执行或交互命令:

    • 执行终端命令。
    • 查看终端命令stdout输出。
    • 向终端命令stdin 输入内容。

除此之外,通常Coding Agent还具备一些强化效果而设定的工具,通常表现为与Agent自身或外部环境进行交互,例如经常能见到的TODO、MCP、Subagent等等。

03 内部组成

3.1 上下文结构

3.2 身份定义

一个Agent首先会将模型定义成一个具体的身份(红色与橙色部分),例如在社区里常见的这样的说法:

You are a Senior Front-End Developer and an Expert in React, Nexts, JavaScript, TypeScript, HTML, CSS and modern UI/UX frameworks.

在身份的基础上,再附加工作的目标和步骤拆解,比如Cline有类似这样的内容:

https://github.com/cline/cline/blob/4b9dbf11a0816f792f0b3229a08bbb17667f4b73/src/core/prompts/system-prompt/components/objective.ts

  1. Analyze the user's task and set clear, achievable goals to accomplish it. Prioritize these goals in a logical order.
  2. Work through these goals sequentially, utilizing available tools one at a time as necessary. Each goal should correspond to a distinct step in your problem-solving process. You will be informed on the work completed and what's remaining as you go.
  3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis within <thinking></thinking> tags. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params). DO NOT ask for more information on optional parameters if it is not provided.
  4. Once you've completed the user's task, you must use the attempt_completion tool to present the result of the task to the user. You may also provide a CLI command to showcase the result of your task; this can be particularly useful for web development tasks, where you can run e.g. open index.html to show the website you've built.
  5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.

不用特别仔细地看每一句话,多数Coding Agent会提供一些详实的行动准则、目标要求,这部分称为“Guideline”。

有一些Coding Agent可以在多种模式(或者说智能体)之间进行切换,例如Cursor有Edit、Ask、Plan等,RooCode有Architect、Orchestrator等,有些产品还支持自定义模式。

Cursor

RooCode

选择不同的模式时,实际上会产生不同的目标要求、行为准则,即不同的Guideline环节。因此系统提示词中的身份部分,通常会分成不变的Base Prompt(红色)和可变的Agent Prompt(橙色)两个部分来管理,实际开始任务时再拼装起来。

3.3 工具调用

Agent的另一个最重要的组成部分是工具,没有工具就无法称之为一个Agent。让Agent能够使用工具,就必须要有2部分信息:

  1. 有哪些工具可以用,分别是什么作用。
  2. 如何指定使用一个工具。

对于第一点(哪些工具),在Agent开发过程中,一般视一个工具为一个函数,即由以下几部分组成一个工具的定义:

  1. 名称。
  2. 参数结构。
  3. 输出结构。

实际在调用模型时,“输了结构”往往是不需要提供给模型的,但在Agent的实现上,它依然会被预先定义好。而“名称”和“参数结构”会统一组合成一个结构化的定义,通常所有工具都只接收1个参数(对象类型),用JSON Schema表示参数结构。

一个典型的工具定义:

{
  "name": "read",
  "description": "Read the contents of a file. Optionally specify line range to read only a portion of the file.",
  "parameters": {
    "type": "object",
    "properties": {
      "path": {
        "type": "string",
        "description": "The file path to read from"
      },
      "lineStart": {
        "type": "integer",
        "description": "The starting line number (1-indexed). If not specified, reads from the beginning of the file."
      },
      "lineEnd": {
        "type": "integer",
        "description": "The ending line number (1-indexed). If not specified, reads to the end of the file."
      }
    },
    "required": ["path"]
  }
}

可以简单地把这个工具理解成对应的TypeScript代码:

interface ReadToolParameter {
        path: string;
        lineStart?: number;
        lineEnd?: number;
}

async function read(parameters: ReadToolParameter) {
        // 工具实现
}

对于第2点(指定使用工具),则是要让大模型知道工具调用的具体格式。这在业界通常有2种做法。

第1种以Claud Code、Codex等为典型,使用大模型提供的Function Calling格式调用,分为以下几步:

  1. 在调用大模型时,通过一个tools 字段传递所有的工具定义。
  2. 模型会返回一个消息中包含tool_calls 字段,里面每一个对象是一个工具的调用,使用id 作为唯一标识。
  3. 工具产生的结果,以一条role: 'tool' 的消息返回,其中tool_call_id 与调用的id对应,content 是工具的结果(这里各家模型厂商的实现略有不同,其中Anthropic要求role: user,但content字段中传递toolResult,其结构是[{type: 'tool_result',tool_use_id: toolBlock.id, content: toolResultContent}],tool_use_id与调用的id对应)。

第2种方式是以Cline、RooCode为典型,使用一种自定义的文本格式来表示工具调用,通常选择XML的结构,例如对于Cline,读取一个文件的结构如下:

<read_file>
<path>src/index.ts</path>
</read_file>

只要在模型返回的消息中出现这样的结构,就会被解析为一个工具调用,得到的结果以普通的role: 'user' 的消息返回,包括实际内容和一些提示相关的信息。

Content of src/index.ts:

Note:

- this file is truncated to line 1000, file has a total 2333 lines.
- use read_file with line_start and line_end parameters to read more content.
- use seach_in_files tool searching for specific patterns in this file.

...

3.4 环境感知

Coding Agent之所以可以在一个代码库上执行任务,除了通过工具来遍历、检索代码外,另一个因素是Agent实现会在调用模型时主动地提供一部分与项目有关的信息。

其中对Coding Agent工作最有用的信息之一是代码库的结构,即一个表达出目录、文件结构的树型区块。这部分信息通常会符合以下特征:

  1. 尽可能地保留目录的层级结构,使用换行、缩进的形式表达。
  2. 遵循 .gitignore 等项目配置,被忽略的文件不会表现在树结构中。
  3. 当内容过多时,有一定的裁剪的策略,但同时尽可能多地保留信息。

以Cursor为例,这部分的内容大致如下:

<project_layout>
Below is a snapshot of the current workspace's file structure at the start of the conversation. This snapshot will NOT update during the conversation. It skips over .gitignore patterns.

codex-cursor/
  - AGENTS.md
  - CHANGELOG.md
  - cliff.toml
  - codex-cli/
    - bin/
      - codex.js
      - rg
    - Dockerfile
    - package-lock.json
    - package.json
    - scripts/
      - build_container.sh
      - build_npm_package.py
      - init_firewall.sh
      - [+4 files (1 *.js, 1 *.md, 1 *.py, ...) & 0 dirs]
  - codex-rs/
    - ansi-escape/
      - Cargo.toml
      - README.md
      - src/
        - lib.rs
</project_layout>

当内容数量超过阈值时,会采用广度优先的保留策略(即尽可能地保留上层目录结构),同时对于被隐藏的文件或子目录,会形如 [+4 files (1 *.js, 1 *.md, 1 *.py, ...) & 0 dirs]这样保留一个不同文件后缀的数量信息。

除了目录结构外,还有一系列默认需要模型感知的信息,在一个Coding Agent的工作环境中,它通常分为2大类,各自又有一系列的细项:

  1. 系统信息:

    1. 操作系统(Windows、macOS、Linux,具体版本)。
    2. 命令行语言(Shell、Powershell、ZSH)。
    3. 常见的终端命令是否已经安装( python3nodejqawk等,包含具体版本)。
    4. 代码库目录全路径。
  2. 为Agent扩展能力的信息:

    1. Rule(自动激活的部分)。
    2. Skill(摘要描述部分)。
    3. MCP(需要的Server和Tool列表)。
    4. Memory(通常是全量)。

需要注意的是,环境信息这部分,一般不出现在系统提示词中,而是和用户提问的消息放置在一起。

3.5 简单实现

在身份定义、工具调用、环境感知这3部分最基础的Agent组成都达成后,简单地使用大模型的API,进行自动化的工具调用解析、执行、发送新一轮模型调用,可以非常简单地实现一个最小化的Coding Agent。

可以尝试用以下的提示词,使用任意现有的Coding Agent产品,为你编写一个实现,并自己调试一下,感受Coding Agent的最基础的逻辑:

我希望基于大模型实现一个Coding Agent,以下是我的具体要求:

1. 使用Claude作为模型服务商,使用环境变量管理我的API Key。
2. 默认使用Claude Sonnet 4.5模型。
3. 使用Anthropic's Client SDK调用模型。
4. 不需要支持流式输出。
5. 使用TypeScript编写。

以下是Agent提供的工具:

1. read({path: string}):读取一个文件的内容
2. list({directory: string}):列出一个目录下的一层内容,其中目录以`/`结尾
3. write({path: string, content: string}):向文件写入内容
4. edit({path: string, search: string, replace: string}):提供文件中的一块内容

以下是交互要求:

1. 通过NodeJS CLI调用,支持`query`和`model`两个参数,可以使用`yargs`解析参数。
2. 在System消息中,简短地说明Coding Agent的角色定义、目标和行为准则等。
3. 在第一条User消息中,向模型提供当前的操作系统、Shell语言、当前目录绝对路径信息,同时包含跟随`query`参数的内容,组织成一条模型易于理解的消息。
4. 对每一次模型的工具调用,在控制台打印工具名称和标识性参数,其中标识性参数为`path`或`directory`,根据工具不同来决定。
5. 如果模型未调用工具,则将文本打印到控制台。

请在当前目录下建立一个`package.json`,并开始实现全部的功能。

04 优质上下文工程

4.1 成本控制

大模型是一个非常昂贵的工具,以Claude为例,它的官方API价格如下:

我们可以观察到一些特征:

  1. 输出的价格是输入的5倍(但实际考虑到输出与输出的数量比例,输出的价格根本不值一提)。
  2. 缓存输入(Cache Writes)比正常输入(Base Input)更贵一些,约1.25倍。
  3. 缓存命中(Cache Hits)的价格比正常输入(Base Input)要便宜很多,为1/10的价格。

这就意味着,一个良好使用缓存的Agent实现,其成本会比不用缓存降低8-10倍。因此所有的Coding Agent一定会细致地梳理内容结构,最大化利用缓存

在大模型的API中,缓存通常以“块”为单位控制,例如:

  1. 系统提示词中不变的部分。
  2. 系统提示词中可变部分。
  3. 工具定义。
  4. 每一条消息,单条消息也可以拆成多个块。

继续观察Claude对于缓存控制的文档:

可以看到,在大模型API中各种参数一但有所变动,缓存都会大量失效(至少消息缓存全部失效,大概率系统缓成失效),这就会造成成本的极大提升。因此,在Coding Agent实现中,都会从一开始就确定所有参数,整个任务不做任何变更。一些很经典的实例:

  1. 一次任务不会一部分消息开思考模式,一部分不开,因为思考参数会让全部的消息缓存失效。
  2. 切换不同模式(如Edit、Ask、Plan)时,虽然能使用的工具不同,但只是在消息中增加说明,而不会真的将 tools 字段改变。

另外,Coding Agent会尽可能保持历史消息内容完全不变,以最大化地缓存消息。例如对于一个进行了10轮模型调用的任务,理论上第10次调用中,前9轮的消息内容都会命中缓存。但如果此时擅自去修改了第1轮的工具调用结果(例如试图删除读取的文件内容),看似可能消息的长度减少了,但实际因为缓存被破坏,造成的是10倍的成本提升。

总而言之,缓存是一个至关重要的因素,Coding Agent的策略优化通常以确保缓存有效为前提,仅在非常必要的情况下破坏缓存

4.2 空间管理

Coding Agent因为会自动地与大模型进行多轮的交互,随着不断地读入文件、终端命令输出等信息,上下文的长度会变得非常的大,而大模型通常只具备128K左右的总长度,因此如何将大量内容“适配”到有限的长度中,是一个巨大的挑战。

控制上下文长度的第一种方式是“裁剪”,即在整个上下文中,将没用的信息删除掉。试想如下的场景:

  1. 模型读取了一个文件的内容。
  2. 模型将文件中 foo 这一行改成了 bar
  3. 模型又将文件中 eat 这一行改成了 drink

假设我们对模型每一次修改文件,都返回最新的文件内容,如果这个文件有1000行,那么1次读取、2次修改,就会产生3000行的空间占用

一种优化方式就是,在这种连续的读-改的场景下,只保留最后一条消息中有全文内容,即上述3次模型调用后,出现在上下文中的内容实际是这样的:

<!-- Assistant -->
read(file)

<!-- User -->
[This file has been updated later, outdated contents are purged from here]

<!-- Assistant -->
edit(file, foo -> bar)

<!-- User -->
The edit has been applied successfully.

--- a/file
+++ b/file
@@ -23,1 +23,1 @@
-foo
+bar

[This file has been updated later, outdated contents are purged from here]

<!-- Assistant -->
edit(file, eat -> drink)

<!-- User -->
The edit has been applied successfully, the new file content is as below:

{content of file}

可以看到,通过将连续对同一文件的修改进行裁剪,可以只保留最新的内容,同时又使用unidiff 之类的形式保留中间编辑的差异信息,最大限度地降低空间占用,又能保留模型的推理逻辑。

但裁剪不能使用在非连续的消息中,随意地使用剪裁逻辑,很有可能破坏消息缓存结构,进而使模型调用的输入无法通过缓存处理,几倍地增加模型的调用成本。

即便裁剪有一定效果,但随着更多的内容进入到上下文中,始终会有将上下文占满的时候,此时模型将完全无法进行推理。为了避免这种情况出现,Coding Agent通常会使用“压缩”这一技术,即将前文通过模型摘要成少量的文字,同时又保留比较关键的推理链路。

通常,压缩在上下文即将用完的时候触发,如已经使用了90%的上下文则启动压缩,压缩的目标是将90%的内容变为10%的长度,即省出80%的空间供后续推理。

压缩本身是一个模型的任务,即将所有的上下文(可以选择性地保留最新的1-2对消息)交给模型,同时附带一个压缩的要求,让模型完成工作。这个压缩的要求的质量将决定压缩的最终结果,一个比较典型的实现是Claude Code的“八段式摘要”法:

const COMPRESSION_SECTIONS = [
  "1. Primary Request and Intent",    // 主要请求和意图
  "2. Key Technical Concepts",        // 关键技术概念
  "3. Files and Code Sections",       // 文件和代码段
  "4. Errors and fixes",              // 错误和修复
  "5. Problem Solving",               // 问题解决
  "6. All user messages",             // 所有用户消息
  "7. Pending Tasks",                 // 待处理任务
  "8. Current Work"                   // 当前工作
];

通过将信息压缩成8部分内容,能够最大限度地保留工作目标、进度、待办的内容。

4.3 独立上下文

在实际的应用中,其实大概率是不需要128K上下文用满的,但真实表现又往往是上下文不够用。这中间存在的差异,在于2类情况:

  1. 为了满足一个任务,需要收集大量的信息,但收集到正常信息的过程中,会引入无效的、错误的内容,占用上下文。
  2. 一个任务足够复杂,分解为多个小任务后各自占用部分上下文,但加起来以后会超出限制。

试想一下,对于一个这样的任务:

修改我的Webpack配置,调整文件拆分逻辑,让最终产出的各个JS文件大小尽可能平均。

但是很“不幸”地,这个项目中存在6个 webpack.config.ts文件,且最终splitChunks 配置在一个名为 optimization.ts 的文件中管理,那么对于Coding Agent来说,这个任务中就可能存在大量无意义的上下文占用:

  1. 读取了6个 webpack.config.ts ,一共2000行的配置内容,但没有任何splitChunks 的配置,包含了大量 import 其它模块。
  2. 又读取了10个被 import 的模块,最终找到了 optimization.ts 文件。
  3. 经过修改后,执行了一次 npm run build 来分析产出,发现JS的体积不够平均。
  4. 又修改 optimization.ts ,再次编译,再看产出。
  5. 循环往复了8次,终于在最后一次实现了合理的splitChunks 配置。

这里面的“6个 webpack.config.ts ”、“10个其它模块”、“8次优化和编译”都是对任务最终目标并不有效的内容,如果它们占用150K的上下文,这个任务就不得不在中途进行1-2次的压缩,才能够最终完成。

为了解决这个问题,当前多数的Coding Agent都会有一个称为“Subagent”的概念。就好比一个进程如果只能使用4GB的内存,而要做完一件事需要16GB,最好的办法就是开5个进程。Subagent是一种类似子进程的,在独立的上下文空间中运行,与主任务仅进行必要信息交换的工作机制

再回到上面的案例,在Subagent的加持下,我们可以将它变成以下的过程:

  1. 启动一个Subagent,给定目标“找到Webpack文件拆分的代码”。

    1. 读取6个 webpack.config.ts
    2. 读取10个被 import 的模块。
    3. 确定目标文件 optimization.ts
    4. 返回总结:在 optimization.ts 中有文件拆分的配置,当前配置为……。
  2. 启动一个Subagent,给定目标“修改 optimization.ts ,使产出的JS体积平均,执行 npm run build 并返回不平均的文件“。

    1. 修改 optimization.ts
    2. 执行 npm run build,得到命令输出。
    3. 分析输出,找到特别大的JS文件,返回总结:配置已经修改,当前 xxx.js 体积为平均值的3倍(723KB),其它文件体积正常。
  3. 启动一个Subagent,给宝目标“分析 dist/stats.json,检查 xxx.js 中的模块,修改 optimization.ts 使其分为3个250KB左右的文件,执行 npm run build并返回不平均的文件”。

    1. ……
    2. ……
  4. 继续启动6次Subagent,直到结果满意。

不难看出来,这种模式下主体的Coding Agent实际是在"指挥"Subagent做事,自身的上下文占用是非常有限的。而Subagent仅“专注”于一个小目标,也不需要太多的上下文,最终通过这类不断开辟新上下文空间的方式,将一个复杂的任务完成。

4.4 注意力优化

如果你经常使用Coding Agent,或在业界早期有过比较多的使用经验,你可能会发现这种情况:Coding Agent在完成一个任务到一半时,忘了自己要做什么,草草地结束了任务,或偏离了既定目标产生很多随机的行为。

会发生这样的情况,有一定可能是裁剪、压缩等策略使有效的上下文信息丢失了,但更多是因为简单的一个用户需求被大量的代码内容、命令输出等推理过程所掩盖,权重弱化到已经不被大模型“注意到”,因此最初的目标也就完全丢失了。

Coding Agent一个很重要的任务,就是在长时间运作的同时随时调整大模型的注意力,使其始终聚焦在最终目标、关注当前最需要做的工作,不要偏离预先设定的路线。为了实现这一效果,Coding Agent产品提出了2个常见的概念。

第一称为TODO,在很多的产品中,你会看到Agent先将任务分解成几个步骤,转为一个待办列表。这个列表在界面上始终处于固定的位置,随着任务的推进会逐步标记为完成。这个TODO实际上并不是给用户看的,而是给模型看的

在实际的实现中,每一次调用模型时,在最后一条消息(一般就是工具调用的结果)上,除了原始消息内容外,会增加一个称为“Reminder”的区域。这个区域因为始终出现在所有消息的最后,通常来说在模型的注意力中优先级更高,而且绝对不会受其它因素影响而消失

Reminder中可以放置任意内容,比较经典的有:

  1. TODO及进度。用于模型时刻理解目标、进展、待办。
<reminders>
- Planned todos:
  - [x] Explore for code related to "print" function
  - [x] Add "flush" parameter to function
  - [ ] Refactor all "print" function calls to relect the new parameter
</reminders>
  1. 工具子集。如前面《缓存》相关的描述,因为修改工具定义会使缓存失效,因此当切换模式使得可用的工具减少时,一般仅在Reminder中说明部分工具不可用,由模型来遵循这一约束,而不是直接删除部分工具。
<!-- 切换至Ask模式 -->
<reminders>
- You can ONLY use these tools from now on:
  - read
  - list
  - grep
  - bash
</reminders>
  1. 行为指示。例如当模型连续多次给出名称、参数都一模一样的工具调用时,说明模型处在一种不合理的行为表现上,此时在Reminder中增加提示,让模型感知到当前状态的错误,就有可能调整并脱离错误的路线。
<!-- Assistant -->
read(file)

<!-- User -->
The file content: ...

<!-- Assistant -->
read(file)

<!-- User -->
The file content: ...

<reminders>
- Your are using read tool the second time with exactly the same parameters, this usually means an unexpected situation, you should not use this tool again in your response.
</reminders>
  1. 状态提示。例如激活某一个Skill时,Reminder中可以提示“当前正在使用名为X的Skill“,这种提示可以让模型更加专注于完成一个局部的工作。
<reminders>
- You are currently working with the skill "ppt" active, be focused on this task until you quit with exit_skill tool.
</reminders>

需要额外注意的是,Reminder仅在最后一条消息中出现,当有新的消息时,旧消息上的Reminder会被移除。基于这一特征,我们知道Reminder是永远无法命中缓存的,因此Reminder部分的内容长度要有控制,避免造成过多的成本消耗。

4.5 冲突管控

随着Coding Agent能力的发展,当下执行的任务时间越来越长、编辑的文件越来越多,同时更多的用户也习惯于在Agent工作的同时自己也进行编码工作,甚至让多个Agent任务并发执行。这种“协同”形态下,不少用户曾经遇到过这样的问题:

自己将Agent生成的代码做了一些修正,但之后Agent又把代码改了回去。

这个现象的基本原因也很清楚,就是Agent并不知道你改动过代码。例如以下的过程使Agent读取并编辑了一个文件:

<!-- Assistant -->
read(file)

<!-- User -->
The file content:
...
console.log('hello');
...
<!-- Assistant -->
edit(file, hello -> Hello)

<!-- User -->
Edit has been applied successfully.

这个时候,在模型见到的上下文中,这个文件中的代码显然是console.log('Hello'); 。假设乃又将它改成了console.trace('Hello'); ,后面模型依然会基于.log 来修改代码,用户看起来就是代码“改了回去”。

解决这种共同编辑文件的冲突,实际上有多种方法:

  • 加锁法。当Agent读取、编辑一个文件时,更新模型认知的文件内容的快照。当这个Agent再一次编辑这个文件时,读取文件当前的实际内容,和快照做比对,如果内容不一样,拒绝这一次编辑,随后要求Agent重新读取文件(更新快照与实际内容一致)再进行编辑。这是一种主流的做法,不过Agent实现上的细节比较重
<!-- Assistant -->
edit(file, console.log...)

<!-- User -->
This edit is rejected, the file has been modified since your last read or edit, you should read this file again before executing any write or edit actions.

<!-- Assistant -->
read(file)

<!-- User -->
The file content: ...

<!-- Assistant -->
edit(file, console.trace...);
  • 推送法。监听所有模型读取、编辑过的文件的变更,当文件发生变更时,在下一次模型调用时,不断通过Reminder区域追加这些变更,让模型“实时”地知道文件有所变化,直到文件被下一次读取。这种方式能让模型更早地感知变化,但推送信息可能过多影响成本和推理速度。
<!-- Assistant -->
run_command(ls)

<!-- User -->
The command output: ...

<reminders>
- These files have been modified since your last read or edit, you should read before write or edit to them:
  - file
  - file
  - ...
</reminders>
  • 隔离法。使用Git Worktree方案,直接让不同的Agent任务在文件系统上隔离,在一个独立的Git分支上并行工作,相互不受干扰。在任务完成后,用户检查一个任务的全部变更,在采纳时再合并回实际的当前Git分支,有冲突的由用户解决冲突。这种方法让Agent根本不需要考虑冲突问题,但缺点是系统资源占用高,且有合并冲突风险

文件编辑冲突只是一个比较常见的现象,实际上用户和Agent、多个Agent并行工作,可能造成的冲突还有很多种,例如:

用户敲了半行命令 ls -,Agent直接在终端里敲新的命令 grep "print" -r src执行,导致最后的命令是 ls -grep "print" -r src ,是一个不合法的命令。

终端的抢占也是一种冲突,但相对更容易解决,只要让每一个Agent任务独占自己的终端,永远不与用户、其它Agent任务相交叉即可。

4.6 持久记忆

我们都知道,模型是没有状态的,所以每一次Agent执行任务,对整个项目、对用户的倾向,都是从零开始的过程。这相当于历史经验无法积累,很多曾经调整过的细节、优化过的方向都会被重置。虽然可以通过比如Rule这样的方式去持久化这些“经验”,但需要用户主动的介入,使用成本是相对比较高的。

因此当前很多Coding Agent产品都在探索“记忆”这一能力,争取让Agent变得用的越多越好用。记忆这个话题真正的难点在于:

  1. 如何触发记忆。
  2. 如何消费记忆。
  3. 什么东西算是记忆。

首先对于“如何触发”这一问题,常见于2种做法:

  1. 工具型。定义一个 update_memory 工具,将记忆作为一个字符串数组看待,工具能够对其进行增、删改,模型在任务过程中实时地决定调用。往往模型并不怎么喜欢使用这类工具,经常见于用户有强烈情感的描述时才出现,比如“记住这一点”、“不要再……”。
  2. 总结型。在每一次对话结束后,将对话全部内容发送给模型,并配上提示词进行记忆的提取,提取后的内容补充到原本记忆中。总结型的方案往往又会过度地提取记忆,将没必要的信息进行持久化,干扰未来的推理。
  3. 存储型。不进行任何的记忆整理和提取,而是将所有任务的原始过程当作记忆,只在后续“消费”的环节做精细的处理。

然后在“如何消费”的问题下,也常见有几种做法:

  1. 始终附带。记忆内容记录在文件中,Agent实现中将文件内容附带在每一次的模型请求中。即模型始终能看到所有的记忆,这无疑会加重模型的认知负担,也占用相当多的上下文空间,因为很多记忆可能是与当前任务无关的。
  2. 渐进检索。本身不带记忆内容到模型,但将记忆以文件系统的形式存放,Agent可以通过readlistgrep 等工具来检索记忆。配合“存储型”的触发方式,能让全量的历史任务都成为可被检索的记忆。但这种方式要求模型有比较强的对记忆的认知,在正确的时刻去找相关的记忆。但往往因为根本不知道记忆里有什么,进而无法知道什么时候应该检索,最终几乎不触发检索。

而最终的问题,“什么东西是记忆”,是当下Coding Agent最难以解决的问题之一。错误的、不必要的记忆甚至可能造成实际任务效果的下降,因此精确地定义记忆是Agent实现的首要任务。

通常来说,记忆会分为2种大的方向:

  1. 事实型。如“使用4个空格作为缩进”、“不要使用any 类型“,这些都是事实。事实是无关任何情感、不带主观情绪的。
  2. 画像型。如”用户更喜欢简短的任务总结“就是一种对用户的画像。画像是单个用户的特征,并不一定与项目、代码、架构相关。

在Coding Agent上,往往更倾向于对”事实型“的内容进行记忆,而不考虑用户画像型的记忆。

同时,从业界的发展,可以看到越来越多的模型厂商在从底层进行记忆能力的开发,如最近Google的Titan架构就是一种记忆相关的技术。可能未来某一天,Agent实现上已经不需要再关注记忆的逻辑与实现,模型自身将带有持久化的记忆能力。

05 能力扩展

在实际应用中,还需要一些机制来让Agent更好地适应特定的项目、团队和个人习惯。当前主流的Coding Agent产品都提供了Rule、MCP、Skill这三种扩展能力,它们各有侧重,共同构成了Agent的能力增强体系。

5.1 Rule

当面对业务的repo往往存在一些领域相关的知识而非模型的知识库中已有的内容,这些往往需要凭借老员工的经验或者读取大量代码库的信息进行总结后才能明白,这些内容便适合放到Rule中,作为静态的不会频繁改动的内容放入Environment Context中长期Cache。

好的Rule应当足够精简、可操作且范围明确,人看不懂的规则或者描述不清的规则模型是一定搞不定无法遵守的。

  • 将Rule控制在 500 行以内。
  • 将较大的规则拆分为多个可组合的规则,采取按需的方式,按照 文件路径/关键场景 激活Rule;对于特定场景激活的Rule,采取编写索引的方式创建Rule,让模型渐进式激活,比如项目针对网络请求和错误处理相关做了项目维度的封装处理,但这种情况并不是每个文件ts/tsx文件都会遇到的诉求,比如在项目的rules目录下创建index.mdr(curso是.mdc文件),编写下面的激活的条件:

    • 需要进行API调用获取数据
    • 处理异步操作的错误和加载状态

-   当编码涉及以下任一情况时,必须立刻阅读 \[08-api-error-handling.mdc\](mdr:.cursor/rules/08-api-error-handling.mdc)
    
  • 提供具体示例或参考文件,针对xx情况正确的方式是\`code\`。
  • 避免模糊的指导,比如交互式的东西模型交互不了,不需要写进去。
  • 为了模型能够积极验证每次改动是否符合预期,告知模型改动后可以执行的正确的构建命令,以及某些自定义命令(比如自动化测试)引导模型在后台启动命令,在xx秒后读取日志文件的内容进行结果的判断。

5.2 MCP

MCP(Model Context Protocol)是Anthropic提出的一种标准化的工具扩展协议,它允许开发者以统一的方式为Coding Agent添加新的能力。

与Rule的"声明式约束"不同,MCP是一种实时工具调用协议,即通过MCP server的方式进行连接,来扩展Agent可以做的事情。

一个典型的场景是集成外部服务。比如你的项目托管在GitHub上,可以让Agent直接访问GitHub实现创建Issue、查询PR状态、添加评论等功能:

{
    "mcpServers": {
        "github": {
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-github"],
            "env": {
                "GITHUB_PERSONAL_ACCESS_TOKEN": "<your-github-token>"
            }
        }
    }
}

配置好后,Agent就能在代码审查过程中自动创建Issue记录问题、查询相关PR的讨论、甚至根据代码变更自动生成commit message。

MCP的另一个优势是实现门槛低。一个MCP Server本质上就是一个标准输入输出的程序,它通过JSON-RPC协议与Agent通信,当模型需要外部能力的时候,调用MCP Server,而模型无需关心其内部代码实现,Agent只需要按照固定的协议去连接获取内容。

5.3 Skill

5.3.1 什么是Skill

随着模型能力的提升,使用Agent完成的任务复杂度逐渐增加,使用Coding Agent可以进行本地代码执行和文件系统完成跨领域的复杂任务。但随着这些Agent的功能越来越强大,我们需要更具可组合性、可扩展性和可移植性的方法,为它们配备特定领域的专业知识,因此Agent Skill作为一种为Agent扩展能力的标准诞生。Skill 将指令、脚本和资源的文件夹打包,形成专业领域的知识,Agent在初始化的时候会获取可用的Skills列表,并在需要的时候动态加载这些内容来执行特定任务。

随着 Skill 复杂性的增加,它们可能包含过多的上下文信息,无法放入单个配置文件中 SKILL.md,或者某些上下文信息仅在特定场景下才相关。在这种情况下,Skill可以在当前目录中bundle额外的文件,并通过文件名引用这些文件,这些额外的文件提供了更多详细信息,Coding Agent 可以根据需要选择浏览和查找这些信息。Skill 是渐进式触发的, 因此 SKILL.mdnamedescription很关键,这会始终存在于Agent的环境上下文中提供给模型,模型会根据这些描述信息来决定是否在当前任务中触发该Skill,当你明确希望使用某个Skill完成任务,可以在prompt中指定“使用xxxx Skill完成xx任务”。

5.3.2 Skill和代码执行

LLM在很多任务上表现出色,但许多操作需要使用编写代码 -> 代码执行的方式,带来更高效的操作、确定性的以及可靠性的结果。生成式的模型常常通过生成可执行代码的方式去验证/计算结果。

代码既可以作为可执行工具,也可以作为文档。Skill中应该明确让模型是应该直接运行脚本,还是应该将其作为参考信息读取到上下文中。

5.3.3 如何创建Skill

每个Skill由一个必需的 SKILL.md 文件和可选的bundle资源组成,Skill 应该只包含完成任务所需的信息。

skill-name/
├── SKILL.md (必需)
│   ├── YAML frontmatter 元数据 (必需)
│   │   ├── name: (必需)
│   │   ├── description: (必需,这是 skill 的主要触发机制,帮助模型理解何时使用该 skil)
│   │   └── compatibility: (可选)
│   └── Markdown 说明 (必需)
└── bundle的资源 (可选)
    ├── scripts/          - 可执行代码 (Python/Bash/等)
    ├── references/       - 需要时加载到上下文的文档
    └── assets/           - 用于输出的文件 (模板、图标、字体等)

举一个具体的例子,比如当我们需要进行批量项目的技术栈migrate,比如将less迁移postcss,中间涉及一系列的复杂步骤,比如:

  • 安装postcss以及postcss plugin的依赖
  • 配置postcss的config
  • 分析项目用到了哪些less varibale替换成css vars
  • 删除mixin并替换
  • 一系列的其他兼容less的语法转换...
  • 替换文件后缀

上面的工作可以通过清晰的流程描述,并配合脚本实现,因此可以作为一个Skill将经验变成可复制的,一个less-to-postcss的skill的结构:

5.3.4 Skill的使用

人人都可以创建Skill,也可以让Agent来编写Skill,这是Skill非常便捷的地方。Skill通过instructions和code赋予Coding Agent新的能力。虽然这使其功能强大并有很高的自由度,但也意味着恶意SKill可能会在其使用环境中引入漏洞,诱使模型窃取数据并执行非预期操作。仅从可信来源安装Skill,如果无法确信来源可信,在使用前请务必进行彻底审核。

Skill的出现并不是替代MCP的出现,而是相互配合,在合适的场景下选取Skill或是MCP。某些任务Skill和MCP Server均可完成,但Skill通过执行代码的方式可以一次性加载完整流程,但MCP Server要经历多次查询和多轮对话往返,这种情况下Skill更为合适,但这不意味着绝对的优势,比如标准化文档创建这个典型的场景,创建PPT/Word/Excel在本地使用Skill即可完成,但数据的提供则需要借助MCP Server进行查询。因此Skill擅长的是在本地通过执行 code的方式完成复杂任务,在用户私有数据、动态数据查询这些情况下Skill就无法搞定了,这和用户的数据库以及隐私强关联,需要让模型无法感知在执行过程中的隐私信息,Skill能够与MCP Server互补完成更为复杂的流程。

🧭 写在前面:为什么用“提及度”看 CRM 市场

这篇文章会以「在媒体与研究报告中被频繁提及」为线索,盘点几款在 2026 年依然保持高曝光的主流 CRM(注意:这不等同于任何官方榜单或权威排名)。

我会尽量引用权威研究机构、评测平台与行业媒体的公开信息,并从市场视角解释一件事:
为什么总是这些名字反复出现在关键报告、行业解读和选型清单里?

image.png

🔍 为什么“提及度”值得看?

在 CRM 领域,“提及度高”通常意味着三件事:

  • 市场覆盖面广:跨行业、跨规模都能看到它的身影。
  • 细分场景成熟:例如销售自动化(SFA)、服务管理、营销自动化等,都有清晰定位。
  • 生态与口碑数据充足:集成伙伴多、实施与咨询经验多、用户评价可参考。

因此,当研究机构、评测平台或行业媒体在做趋势分析、产品对比、象限/波浪图,或者用户评价榜单时,这些品牌就会变成天然的“参照系”和“样本库”,被反复提到。

从不同角色的视角来看,大致是这样运作的:

  • 研究机构视角
    以销售自动化(SFA)等子市场为评估单位,按功能完备度、愿景前瞻性、执行能力等维度进行对比(如 Gartner 对 SFA 市场的相关报告与解读文章)。
  • 评测平台视角
    依托大量“已验证用户评论”,以评分、使用体验、市场热度等维度做动态排序(例如 G2 的 CRM 分类页,会列出热门产品并支持对比)。
  • 行业媒体视角
    更关心“是否企业级就绪”“典型客户画像”“适配场景”,会在各种「最佳 CRM」「选型清单」文章中把这些产品列为常见备选。

🌐 2026 年提及度较高的几款主流 CRM

(按常见曝光顺序归纳,非严格排名

下面这几款,是在研究报告、评测平台、行业媒体中都比较常被提到的产品。我会重点放在:它们为什么总出现在视野里。


1)Zoho CRM:性价比 + 套件化 + 全球化带来的高讨论度

Zoho CRM 的高提及度,主要来自两个层面:

  • 研究机构 / 官方传播层面
    Zoho 官方公开资料中,会引用其在 Gartner SFA 相关评估中的定位(例如被归类为 “Visionary” 等,具体以官方引用和原始报告为准)。
    这一类传播,会把 Zoho 放进“有前瞻性的销售自动化供应商”语境下反复出现。
  • 评测平台 / 用户口碑层面
    在 G2 等评测平台的 CRM 分类中,Zoho CRM 通常是热门产品之一
    对潜在客户来说,它经常出现在“同类对比 + 用户评论”的选型路径里,尤其当用户搜索「性价比」「一体化套件」时,会很容易看到它。

综合来看,Zoho CRM 经常被提起,是因为它在预算敏感、但又想要完整业务套件的组织中,有稳定的心智位置。


2)Salesforce:企业级 CRM 的“默认参照系”

在很多讨论中,Salesforce 都被当作 CRM 的“基准线”来使用:

  • 对于大型企业复杂业务流程,Salesforce 一直保持强存在感;
  • 围绕销售自动化平台(SFA)的行业与研究机构讨论中,它几乎是必被提及的对照对象;
  • 生态与应用市场(AppExchange)、合作伙伴体系非常丰富,使它成为“生态型 CRM 平台”的典型样本。

因此,即使企业最后不选 Salesforce,也会拿它来做功能、价格与架构的对比参照


3)Microsoft Dynamics 365:深度融入 Microsoft 生态的常见选项

Dynamics 365 的提及度,很大程度源自企业对“Microsoft 体系一体化”的偏好:

  • 很多组织已经深度使用 Office 365、Teams、Azure、Power BI 等 Microsoft 产品,
    在此基础上选 CRM 时,Dynamics 365 的集成体验和统一账号/数据体系就变得很有吸引力。
  • 在公开信息和市场传播中,也能看到微软围绕 Gartner SFA 相关认可进行宣传,这进一步巩固了它在“企业级 CRM 候选清单”里的曝光。

简单理解:只要企业是重度 Microsoft 用户,Dynamics 365 几乎一定会被提上讨论桌。


4)HubSpot:增长团队与中小企业的“常见第一反应”

在大量「最佳 CRM」「产品对比指南」「营销工具推荐」等内容中,HubSpot 经常出现,关键标签是:

  • 上手快、体验好:对非 IT 背景的市场和销售团队很友好;
  • 营销 + 销售协同强:从获客、内容触达、线索到销售跟进,有比较连贯的一体化体验;
  • 定价与模块划分相对清晰,适合SMB 和增长团队从轻量开始逐步扩展。

因此,在“希望快速上线、重视获客转化闭环”的选型场景下,HubSpot 几乎是标配候选之一。


5)Oracle:大型企业与复杂业务版图中的常驻选手

在各类围绕 SFA/CRM 的机构解读与媒体综述里,Oracle 通常会与 Salesforce、Microsoft 一起被并列讨论,原因包括:

  • 大型企业和复杂行业场景(如金融、电信等),Oracle 仍然在应用版图中占据一席之地;
  • 对一些已在 Oracle 体系中投入较多的客户来说,选型时会优先考虑在现有技术与数据体系上扩展 CRM。

因此,它虽然在大众媒体的“话题热度”可能不如某些新锐工具,但在企业级对比表格中依然有稳定席位


6)SAP:从 ERP 体系延伸出来的 CRM 选择

在各种“企业级 CRM 供应商清单”里,SAP 也几乎是被固定写上的名字之一,典型场景是:

  • 企业本身 ERP / 供应链 / 财务等核心流程已经高度 SAP 化
  • 在 CRM 选型时,更倾向于保持统一架构、统一治理与端到端数据链路

因此,在谈到“是否适配集团级、制造业/复杂供应链企业”时,SAP CRM 通常会作为典型选项出现。


📊 一张表看懂:这些高提及度 CRM 各自擅长什么?

下面这张表,用更偏“选型语言”的方式,总结了这些产品在媒体/报告中的常见定位,以及更适配的典型场景。

CRM媒体 / 报告常见提法(概括)更常见的适配场景
Zoho CRM性价比、一体化套件、覆盖面广预算敏感,但希望用一套工具覆盖更多业务环节的团队
Microsoft Dynamics 365与 Microsoft 生态深度协同、企业落地成熟已深度使用 Microsoft 技术栈(Office、Teams、Azure 等)的组织
HubSpot易用、增长友好、营销销售一体SMB / 增长团队,强调快速上线和获客转化闭环
Salesforce生态强、扩展多、平台与套件化多事业部、复杂流程,强定制 + 强生态依赖的企业级客户
Oracle大型企业应用版图、复杂业务与数据整合行业复杂度高,对治理、合规和集成要求高的大型组织
SAP企业级、与核心业务系统深度协同已是 SAP 体系客户,强调端到端流程与统一管控的集团型企业

这些定位之所以会被反复提起,本质上是因为它们分别占据了不同的典型购买路径

  • 生态型:以应用生态、ISV、合作伙伴为核心(典型如 Salesforce)。
  • 平台型:强调与既有技术平台统一(如 Microsoft Dynamics 365)。
  • 套件型:用一套工具覆盖多条业务链(如 Zoho CRM)。
  • 增长型:优先服务营销 / 增长 / SMB 快速起盘(如 HubSpot)。
  • ERP 延伸型:从既有 ERP / 核心系统向前台业务延伸(如 Oracle、SAP)。

🧩 对市场人员 / 选型团队的落地建议:如何“用好提及度”

“提及度高”不是终点,而是一个筛选入口。更实用的做法,是把它变成一个结构化的选型步骤:

1. 先按业务复杂度分层

把自己大致放在以下哪一层:

  • 增长团队 / 早期阶段
    目标是快速获客、跑通基础销售流程,对流程严谨度要求没那么高,敏捷和易用更重要。
  • 多部门协同阶段
    市场、销售、客服等多个团队需要在同一套系统里协作,对流程配置、权限、报表有一定要求。
  • 集团化治理 / 企业级阶段
    强调跨事业部、跨地区的统一流程、统一数据与内控合规,CRM 需要和大量已有系统集成。

你会发现,媒体与评测文章里的高频候选,刚好覆盖这三层典型场景


2. 再看你更信哪种“证据类型”

可以有意识地分流信息来源,而不是把所有资料混在一起看:

  • 如果你更看重口碑与易用性

    • 重点看 G2、Capterra、TrustRadius 等评测平台的用户评论和对比页面;
    • 筛选和自己行业、团队规模相似的用户体验,参考他们的“踩坑点”。
  • 如果你更想站在研究机构的框架下决策

    • 关注细分市场(如 SFA、营销自动化、服务管理等)的象限 / 波浪图和公开解读;
    • 重点理解:他们在评估“执行能力”“产品愿景”“市场覆盖”时各自看重什么。

3. 最后靠 PoC 验证,而不是靠“提及度”下注

比较稳妥的路径是:

  1. 把“高提及度产品”当作候选池入口,而不是结论;
  2. 从中挑出 2–4 款,做一个 2–4 周的 PoC(概念验证)

    • 用你的真实数据,把关键流程跑一遍:
      线索 → 商机 → 报价 → 合同 / 回款
    • 同时验证:权限、报表、移动使用体验、对接现有系统等关键点;
  3. 把“提及度 + 证据类型 + PoC 结果”综合起来再决策,而不是只看某一个维度。

这样做的好处是:你既借用了市场的“集体经验”,又保留了适配自身业务的判断空间,在预算和时间上都是更划算的决策方式。

摘要
随着智能网联汽车渗透率持续提升,以及相关监管体系与行业标准的逐步完善,车云协同平台正从“增值能力”演进为支撑安全运行与规模化发展的关键基础设施。

一方面,围绕事故事件数据记录(EDR)及关键信息管理,监管与行业规范对数据的完整性、时效性与可追溯性提出了更高要求;另一方面,面向高阶辅助驾驶与自动驾驶的应用场景,车端、边缘与云端之间的实时协同决策、安全预警与状态同步,对系统的低延迟、高可靠与跨地域架构能力提出了更高挑战。

传统依赖多种中间件拼装而成的烟囱式架构,在面对海量并发接入、跨区域数据同步以及毫秒级响应需求时,逐渐暴露出复杂度高、时延不可控、运维成本陡增等问题。

以 Redis 企业版作为统一、高性能的实时数据层与协同中枢,构建新一代智能驾驶车云协同平台,既能够稳健支撑监管与行业规范下的数据管理要求,也为实时安全预警、远程诊断、数字孪生及未来智能交通协同应用提供可持续演进的技术基础。


一、核心挑战:从合规要求到业务高线
构建满足未来需求的车云协同平台,必须同时跨越三大挑战:

  • 挑战一:高可靠事故数据管理与上报能力
    在事故或异常事件发生后,关键数据需要被完整记录、可靠传输并可被及时调取或上报。任何数据丢失、延迟或一致性问题,都会对事故分析、责任认定及安全改进带来风险。这要求通信链路与数据平台具备电信级可靠性与端到端可追溯能力。
  • 挑战二:亿级并发的“双向实时风暴”
    平台需管理百万甚至千万级车辆的同时在线连接,处理车辆高频上传的状态信息(如每秒数次的位置、电池数据),并实时下发指令(如预警、升级)。这是一个典型的高吞吐、低延迟、双向通信场景。
  • 挑战三:“云-边-端”协同的“决策延迟”
    从边缘事件感知(如路侧单元 RSU 发现危险)到云端全局决策,再到车辆执行指令,整个闭环对时延极为敏感。例如,在协同安全预警场景中,过高的端到端延迟将显著降低风险规避效果。

二、Redis企业版:车云协同的实时数据基座
Redis企业版以其独特的技术特性,成为应对上述挑战的理想选择:

  • 高可靠、可扩展的通信总线:Redis Stream数据结构提供了基于消费者组的、持久化的消息队列,确保每一条事故上报消息的至少一次(或精确一次) 可靠投递。其性能远超传统消息队列(如RabbitMQ),且与发布/订阅(Pub/Sub) 模式结合,可灵活支撑指令的实时广播与点对点通信。
  • 全球多活与毫秒级数据同步:Active-Active Geo-Distribution 功能支持跨地域多个数据中心的无冲突双向同步。这意味着在上海和法兰克福的数据中心可以同时写入和读取同一车辆的状态,并保持强一致性。这不仅提供了跨大洲的灾难恢复能力,更能让全球车辆就近接入,获得低于50毫秒的本地读写延迟。
  • 多模型数据融合与实时查询:车辆数据多源异构。Redis企业版原生支持 JSON(存储复杂的车辆档案与状态)、时间序列(记录速度、电量等连续指标)、地理空间(实时追踪车辆位置)等多种数据结构。这使得一个平台即可替代传统的“消息队列+关系型数据库+缓存”组合,简化架构,并支持复杂的实时查询(如“找出某区域所有电量低于20%的物流车辆”)。
  • 边缘智能赋能:Redis on Flash 与轻量级部署能力,使得在车端网关或区域边缘节点运行Redis实例成为可能。结合 RedisAI,可在边缘侧直接运行轻量模型,实现本地数据的实时预处理与关键事件(如驾驶员状态异常)的即时判断,仅将结果或高价值数据上传云端,大幅节省带宽并降低响应延迟。

三、一体化车云协同架构设计
该架构以 Redis 企业版为核心,贯通车端、边缘与云端,统一承载合规数据上报与实时协同能力。
image.png
核心数据流与组件解析:

  1. 高可靠事故与事件数据上报流

    • 车辆发生事故 → 车载终端将EDR数据包写入本地缓冲区 → 通过安全链路写入最近区域的Redis节点(使用Stream数据结构)→ 区域中心的后台服务(消费者组)立即消费该消息 → 进行数据验证、脱敏、格式转换 → 通过标准化接口对接监管系统或企业内部平台。整个过程基于Stream的持久化与确认机制,确保数据零丢失。
  2. 车辆数字孪生实时镜像:

    • 每辆车的状态(如vehicle:VIN123:status)以一个JSON文档实时更新。其连续变化的位置(经纬度、海拔)同步存入一个时间序列,并通过 GEOADD 命令更新到地理空间索引集合中。
    • 应用查询时,可毫秒级获取单车全貌,或通过 GEORADIUS 命令查询某地点周围所有车辆。这构成了车队管理、智能调度、动态保险等业务的实时数据基础。
  3. 云边端协同安全预警流:

    • 边缘:路侧单元(RSU)通过本地RedisAI分析感知数据,发现异常(如路面遗撒物)。
    • 云端:RSU将事件发布至云端Redis的预警频道(Pub/Sub)。云端实时事件处理引擎(RedisGears)被触发,立即查询地理空间索引,找出正在驶向该风险区域的车辆列表。
    • 车端:预警指令通过 Pub/Sub 实时下发至相关车辆的通信频道。车辆终端订阅该频道,在百毫秒级内收到预警并提示驾驶员。

四、关键场景与业务价值
image.png

结语
面向智能驾驶与智能网联汽车的规模化发展,高可靠的数据管理能力是安全运行的基础,而“云-边-端”协同创新则是释放业务价值的关键。

2Redis 企业版凭借其极致性能、多活架构与多模型融合能力,为车云协同平台提供了一种同时兼顾监管适配性、实时性与系统演进能力的技术路径。选择 Redis 企业版,不仅是选择一个数据库,更是选择了一套能够伴随智能驾驶业务持续扩展与创新的实时数据基础设施。

摘要
随着自动驾驶技术从原型验证迈向规模化商用,研发范式正经历从“以算法为中心”向“以数据为中心”的根本性转变。海量、高维、多模态的道路采集数据,已不再只是测试过程中的副产物,而是驱动算法持续演进、提升系统安全冗余和泛化能力的核心生产资料。

然而,当前主流的数据处理模式仍以离线存储与批处理为主,数据在“采集—上传—存储—筛选—标注—训练—验证”之间流转缓慢,形成长周期、低反馈的闭环,逐渐成为制约自动驾驶技术迭代效率的重要瓶颈。

Redis 企业版作为一款面向实时与 AI 场景设计的数据平台,凭借其多模型数据结构、亚毫秒级访问延迟、内存计算能力以及 AI 原生扩展机制,为构建新一代“实时数据加速层”与“智能数据筛选平台”提供了坚实的技术基础。

本方案系统性阐述如何基于 Redis 企业版,完成从“数据存储与归档”向“数据理解与智能利用”的跃迁,构建一个能够加速算法创新、提升数据利用率、并在可控成本下实现规模扩展的自动驾驶数据闭环体系。


一、行业趋势与核心技术挑战
自动驾驶系统的成熟度,本质上取决于其数据闭环运行的效率与质量。当前行业普遍面临以下三类挑战:

1.数据规模爆炸与实时性不足
搭载多颗高分辨率摄像头、激光雷达、毫米波雷达与高精定位模块的测试车辆,在真实道路运行中每日可产生 TB 级甚至更高规模的原始数据。
在传统架构下,这些数据往往需要经过集中上传、对象存储落盘、离线处理后,才能被算法与标注团队使用,数据延迟以小时甚至天为单位,难以支撑高频、小步快跑式的算法迭代。

2.高价值“长尾场景”难以被及时发现
真正推动自动驾驶算法性能跃迁的,并非大量常规驾驶场景,而是占比极低却风险极高的长尾与极端场景(Corner Cases),例如:

  • 恶劣天气下的感知退化
  • 非标准交通参与者行为
  • 复杂施工、事故或临时交通组织变化
    在 PB 级数据湖中依赖人工回看或静态规则筛选这些场景,不仅效率低下,且高度依赖经验,成为研发效率的主要瓶颈之一。

3.多模态异构数据协同困难
自动驾驶数据闭环涉及多种数据形态:

  • 非结构化数据:视频、点云
  • 结构化数据:车辆 CAN / 传感器状态
  • 半结构化数据:标注信息、事件日志
  • 模型与版本元数据
    在传统“多系统拼装式”架构下,这些数据分散在对象存储、关系型数据库、搜索系统和消息队列中,跨模态联合查询与关联分析复杂且成本高昂,制约了数据价值的进一步释放。

二、Redis 企业版的核心价值定位
Redis 企业版并非仅用于缓存加速,而是一个面向实时数据与智能应用的统一数据平台(Real-Time Data Platform),在自动驾驶数据闭环中具备独特优势。

1.高吞吐、低延迟的数据流转能力
Redis 的内存计算架构可提供亚毫秒级读写延迟,适合承载高并发、高频率的数据流。

  • Redis Streams 提供持久化、有序的数据流模型与消费者组机制,可用于构建可靠的数据接入与分发管道
  • 在部分自动驾驶数据采集与处理场景中,Streams 可作为传统消息系统的轻量化替代或补充,显著降低端到端延迟与系统复杂度(具体取舍需结合吞吐规模与历史回溯需求评估)

2.多模型数据的统一承载能力
Redis 企业版原生支持多种数据模型:

  • JSON:车辆状态、标注与任务元数据
  • TimeSeries:高频传感器与车辆运行状态
  • Geospatial:轨迹、地图要素与空间查询
  • Vector:场景特征、感知结果向量化表达
  • Graph:数据、模型、标注、测试之间的关系建模
    这些能力使多模态数据得以在同一高性能平台内协同存储与联合查询,显著降低系统集成复杂度。

3.面向 AI 的原生计算与推理能力
通过 RedisAI 模块,可将训练完成的深度学习模型(支持 TensorFlow、PyTorch、ONNX 等主流格式)直接部署在 Redis 集群中,实现:

  • 数据就地推理(In-Data Inference)
  • 特征提取与初步场景理解的实时执行
  • 减少数据在系统间搬运与序列化开销
    这为实时智能筛选、在线预标注等能力提供了关键技术支撑。

4. 企业级可靠性与数据韧性
Redis 企业版提供完善的企业级能力,包括:

  • 持久化机制(RDB + AOF)
  • 跨可用区 / 跨地域的 Active-Active 架构
  • 自动故障转移与在线扩缩容
    确保关键路采数据与生产级服务具备高可用性与业务连续性。

三、总体技术架构:自动驾驶数据闭环的“智能中枢”
下图展示了以 Redis 企业版为核心的自动驾驶实时数据与智能筛选平台总体架构。
image.png
架构要点说明

  • 数据接入与预处理:通过 Redis Streams 接收车辆数据流,结合 RedisGears 在入库阶段完成轻量 ETL、数据校验与初步特征生成
  • 智能存储与索引:

    • 高频状态数据驻留内存
    • 特征向量支持相似度搜索
    • 多条件混合查询(时间、空间、语义、向量)
  • 自动分层存储:通过 Redis 企业版 Auto Tiering,将历史数据透明下沉至 SSD,在性能与成本之间取得平衡

四、典型应用场景与业务价值
场景一:实时长尾场景发现与预警
通过在数据流入口部署轻量化感知或场景识别模型,系统可在数据生成阶段实时识别潜在高风险或高价值场景,并自动标记、优先存储与推送。
价值体现:

  • 关键场景发现从“事后分析”变为“实时捕获”
  • 研发人员可更快聚焦真实风险点
    场景二:高效的训练数据供给与样本挖掘
    将清洗后、高价值的训练样本及其元数据作为热数据缓存于 Redis 中,为分布式训练集群提供低延迟数据访问,并支持向量化困难样本挖掘。
    价值体现:
  • 提升训练资源利用率
  • 缩短模型迭代周期
  • 改善模型在极端场景下的表现

场景三:全链路数据资产可追溯管理
利用 Redis Graph 构建数据、标注、模型与测试结果之间的关系网络,实现端到端的版本追溯与审计。
价值体现:

  • 提升研发过程透明度
  • 支撑 ASPICE、ISO 26262 等质量与安全合规要求

结语
在自动驾驶竞争进入深水区后,真正拉开差距的已不再只是单点算法能力,而是数据被理解、被利用、被反馈的效率与智能程度。
Redis 企业版通过将高速数据处理、多模型数据管理与 AI 原生计算能力融合于一体,为自动驾驶企业提供了一条清晰、可落地的路径,将海量数据从“负担”转化为可持续演进的“核心资产”,为迈向更高级别自动驾驶奠定坚实的数据基础设施。

在数据分析场景中,日期维度的聚合分析是高频需求——无论是按周统计销售数据、按月汇总项目进度,还是按自定义周期分析业务趋势,都需要对日期数据进行灵活分组。传统透视表的日期处理往往局限于固定的年、月、日层级,若要实现按周、15天等自定义周期分组,需手动预处理数据或编写复杂公式,不仅操作繁琐,还容易因数据同步不及时导致分析偏差。

为解决这一痛点,SpreadJS V19.0 重磅推出透视表日期分组(Date Group)功能,支持按自定义天数灵活分组,完美适配周报、自定义周期分析等场景,让时间维度的数据聚合更高效、更贴合业务需求。下面,我们将深入解析这一特性的核心价值与使用细节。

核心功能解析:灵活配置,精准聚合日期数据

SpreadJS V19.0 的透视表日期分组功能以“自定义性强、适配场景广”为核心设计理念,提供全方位的日期分组配置能力,满足不同业务场景的分析需求:
在这里插入图片描述

1. 自定义天数分组,适配多元业务需求

支持按任意天数设置分组间隔(groupInterval),彻底摆脱固定时间层级的限制:

  • 典型场景适配:设置“7天”为分组间隔,即可快速实现周报数据聚合,无需手动拆分日期区间;
  • 自定义周期支持:根据业务需求灵活设置分组天数,如15天(半月报)、30天(月度滚动分析)、90天(季度趋势分析)等,轻松应对多样化的时间维度统计需求;
  • 生效规则明确:groupInterval 仅在按“天”分组时生效,确保配置逻辑清晰,避免混淆。

2. 灵活控制起止时间,精准圈定分析范围

日期分组支持自定义起止时间(start/end),同时提供智能默认规则,兼顾灵活性与便捷性:

  • 智能默认逻辑:若未手动设置起止时间,系统自动读取原始日期字段的最小值和最大值作为分组范围,无需额外配置;
  • 自定义范围支持:可根据分析需求手动设定 start 和 end 时间,例如仅分析“2024年Q2”(4月1日-6月30日)的数据,精准圈定目标区间;
  • 边界校验机制:系统强制要求 end 时间晚于 start 时间,避免无效配置;若起止时间间隔小于设置的 groupInterval,则直接按实际间隔分组,确保分组逻辑合理。

3. 分组项显示精细化控制,兼顾完整性与可读性

针对分组结果的显示,提供多重配置选项,平衡数据完整性与视觉可读性:

  • 无数据分组项控制:分组后可能出现无数据的区间(如某周无销售记录),可通过设置“show items with no data”显示这些空值分组项,确保时间维度的完整性;默认不显示空值分组项,避免报表冗余;
  • 超出范围数据处理:超出起止时间范围的日期数据,会被自动分配到特殊分组,以“< start时间”或“> end时间”标识,清晰区分有效分析区间与异常数据,便于后续数据校验。

4. 标准化时间单位,确保分组准确性

日期分组的最小单位为“天”,无论原始日期数据是否包含时分秒信息,系统都会自动将其转换为当天的00:00:00进行分组计算:

  • 避免时间精度干扰:例如原始数据中“2024-05-10 14:30:00”和“2024-05-10 23:59:00”会被归为同一组,确保日期分组的准确性;
  • 简化数据处理逻辑:无需手动统一日期格式,系统自动标准化处理,降低操作门槛。

典型应用场景:让时间维度分析更贴合业务

这一特性的推出,让透视表的日期分析能力全面升级,在多个核心业务场景中发挥关键价值:

1. 周报/半月报快速生成

市场、销售等部门需要按周或半月汇总数据时,无需手动拆分日期区间:只需将日期字段拖入透视表行/列区域,设置分组天数为7天或15天,系统自动聚合对应区间的数据,快速生成周报、半月报,效率提升80%以上。

2. 自定义周期业务分析

针对特殊业务周期(如电商大促活动14天周期、项目迭代21天周期),可灵活设置分组天数,实时分析活动期间的业务数据趋势,无需修改数据源或编写复杂计算逻辑。

3. 跨时间段对比分析

需要对比不同年份同一周期的数据时(如2023年Q3第1周 vs 2024年Q3第1周),可通过自定义起止时间锁定对应区间,结合透视表的筛选功能,快速实现跨年度、跨周期的对比分析,助力业务趋势判断。

4. 数据合规与追溯

在金融、医疗等需要精准时间追溯的行业,可通过固定起止时间和分组间隔,标准化日期数据的聚合方式,确保分析结果的一致性和可追溯性,符合行业合规要求。

操作指南:3步实现日期分组,上手即会

SpreadJS V19.0 的日期分组功能操作简洁,无需复杂配置,3步即可完成:

  1. 插入透视表并添加日期字段:在SpreadJS设计器中插入透视表,将需要分组的日期字段拖入“行标签”或“列标签”区域;
  2. 打开日期分组设置:右键点击日期字段,选择“分组”选项,弹出分组配置对话框;
  3. 配置分组参数并应用:

    1. 选择分组单位为“天”;
    2. 设置分组天数(groupInterval),如7天(周报);
    3. 按需自定义起止时间(start/end),默认可不填;
    4. 勾选“show items with no data”(可选,需显示空值分组项时启用);
    5. 点击“确定”,系统自动完成日期分组,透视表实时更新聚合结果。

注意事项:这些细节让分组更精准

为确保日期分组功能的使用效果,以下关键细节需留意:

  1. groupInterval 生效条件:仅当分组单位选择“天”时,自定义天数(groupInterval)才会生效;若选择年、月、日等固定层级,该参数不生效;
  2. 起止时间格式:自定义 start/end 时,需遵循标准日期格式(如“2024-01-01”),系统会自动识别并转换;
  3. 空值分组项默认行为:默认不显示无数据的分组项,若需完整展示时间区间,需手动启用“show items with no data”;
  4. 时间精度处理:原始日期数据的时分秒信息会被忽略,统一按“天”为单位进行分组,若需保留时分秒级别的分析,需提前对数据进行预处理。

总结与展望:让数据分析更贴合业务节奏

SpreadJS V19.0 推出的透视表日期分组功能,以“灵活配置、精准聚合、操作便捷”为核心优势,彻底解决了传统透视表日期分析的局限性,让时间维度的数据聚合更贴合业务需求,大幅降低数据分析门槛,提升工作效率。

作为一款面向企业级应用的纯前端表格控件,SpreadJS 始终聚焦开发者与终端用户的实际需求,持续优化透视表等核心功能——除了日期分组,V19.0 还为透视表带来了拖动自定义排序、受保护工作表中启用透视表等多项增强能力,全方位提升数据处理与分析体验。

如需了解更多功能细节,可访问 SpreadJS 官网 查看产品文档,或通过 在线 Demo 直接体验新特性。SpreadJS V19.0 即将正式发布,敬请期待这款更强大、更灵活的前端表格控件,为你的业务系统注入新的活力!

摘要
软件定义汽车(SDV)的时代,空中升级(OTA)能力已从“功能”演进为汽车的“生命线”。它承载着功能迭代、安全修复与用户体验提升的核心使命。然而,面对千万级的庞大车队、GB级的升级包体、跨洲际的网络环境以及绝对零容忍的升级安全要求,传统OTA架构在效率、可靠性与智能化方面面临严峻考验。本方案提出,以Redis企业版为核心实时数据引擎,构建新一代智能OTA平台。该平台不仅能够实现升级包的全球分钟级同步与智能边缘分发,更能支撑全链路可观测的灰度发布与秒级触达的安全回滚,将OTA从一项高风险运维活动,转变为稳定、高效、可运营的数字化服务。

一、OTA演进下的核心挑战
现代智能汽车OTA已超越简单的“推包安装”,成为一个复杂的分布式系统工程:

  • 挑战一:分发规模与成本的指数级增长:单一车型的软件版本可能超过100GB,而一次全量升级活动需覆盖百万辆汽车。采用中心化分发将产生天量的跨境带宽成本与漫长的下载时间,用户体验难以保障。
  • 挑战二:灰度发布与流量调控的精细化管理:为控制风险,升级必须遵循从1%到100%的精细化灰度节奏。平台需要实时、动态地管理海量车辆的分组、策略与状态,并能根据故障指标(如安装失败率、系统崩溃率)自动决策暂停或回滚,这对状态管理和决策实时性要求极高。
  • 挑战三:升级安全的“零信任”与“可追溯”:升级过程必须保证数据的完整性(包体未被篡改)、原子性(要么完全成功,要么完全回退)和可审计性(每一步操作皆有记录)。任何环节的纰漏都可能导致车辆“变砖”,引发大规模安全事故。

二、Redis企业版:OTA系统的智能数据中枢
Redis企业版凭借其独特的技术组合,成为化解OTA复杂性的战略性组件:

  • 全球智能分发网络基石:Active-Active地理分布式部署支持升级包元数据与任务指令在全球多个数据中心间实时同步,为构建私有化、低延迟的内容分发网络提供了数据层基础。结合自动分层(Auto Tiering) ,可将高频访问的最新升级包置于内存,将历史版本透明下沉至SSD,实现性能与成本的最佳平衡(存储成本降低约70%)。
  • 高性能、高可靠的任务编排引擎:Redis Stream 与 Sorted Set 数据结构是构建复杂任务队列的理想选择。它们能够以毫秒级延迟管理数百万车辆的升级状态流转(待推送、下载中、安装中、成功/失败),并支持基于优先级、区域、车型等多维度的灵活调度。
  • 全链路可观测性与自动化触发器:Redis TimeSeries 模块可高效存储和聚合全量升级过程的性能指标与日志。RedisGears 的函数功能允许在数据库内部设置复杂触发器,例如,当“安装失败率”在5分钟内超过0.1%时,自动暂停当前批次任务并告警,实现从“监控”到“动作”的闭环自动化。
  • 坚如磐石的数据持久化与高可用:通过同步持久化(AOF with fsync always) 与跨区域复制,确保每一次任务分配、每一条车辆状态更新都不会丢失。其99.999%的高可用性保障了OTA管理控制面自身7x24小时不间断服务。

架构方案:云边协同的智能OTA平台
以下架构描绘了以Redis企业版为“智能中枢”的下一代OTA平台,如何协同云端与边缘,完成从包管理到安全回滚的全流程。

核心工作流解析:

  1. 升级包全球同步与边缘预热:

    • 新的升级包在“包工厂”生成并完成签名后,其元数据(版本号、车型、依赖、哈希值)通过 Active-Active 同步至全球所有区域的Redis集群。
    • 智能调度器根据各区域车辆分布,将包体文件提前推送至各边缘节点Redis集群的SSD层。当车辆发起下载请求时,边缘节点可快速从本地SSD或内存提供服务,下载速度提升300% 以上。
  2. 精细化灰度发布与实时调控:

    • 运维人员在控制台创建升级任务,定义灰度批次(如:内部员工1% -> 先锋用户5% -> 全面推送)。该任务被转化为一个主任务Stream和多个批次Sorted Set(按车辆VIN分片)。
    • 智能调度器作为消费者,从Stream中读取任务,并根据规则从相应批次的Sorted Set中获取车辆列表,通过Pub/Sub或指令通道向车辆下发升级通知。
    • 车辆端上报的每一个状态(下载进度、安装结果)都实时更新到该车辆对应的状态Hash中。RedisGears 脚本持续监控聚合指标,一旦触发预设规则(如失败率超标),则自动修改任务状态或触发回滚流程。
  3. 安全回滚与全链路追溯:

    • 回滚被设计为一个标准的“升级任务”,其回滚包已在边缘节点就绪。当自动或手动触发回滚时,调度器会优先为受影响车辆创建高优先级的回滚任务。
    • 整个升级生命周期的所有事件(任务创建、指令下发、状态变更、异常告警)均作为时间序列数据存入 Redis TimeSeries,并与具体的车辆VIN、任务ID关联,提供毫秒级精度的全链路追溯能力,满足最高级别的审计要求。

    关键场景与价值量化
    image.png

结语
在软件定义汽车的竞赛中,OTA的效能直接决定了车企数字化运营的高度与速度。Redis企业版通过将实时数据同步、智能任务编排、多模型存储与边缘计算能力深度融合,为车企提供了一个不仅强大而且“聪慧”的OTA数据基座。这不仅仅是技术的升级,更是运营理念的革新——从被动的、高风险的手动操作,迈向主动的、数据驱动的、全球一体化的软件服务交付。选择Redis企业版,即是选择为未来十年海量车队的软件生命周期管理,构建一个可靠、高效且充满智能的“指挥中心”。

浏览网页经常遇到 Imgur 图片加载不出来的情况?
即使挂了梯子,也可能因为节点屏蔽等原因导致裂图,体验极差。

最近更新了 Links Helper (链接助手),重点增强了 图片代理功能,无需复杂配置即可完美解决这个问题。

🛠️ Links Helper:图片代理与浏览辅助

核心功能:图片代理

  • 无需梯子:通过公共代理服务中转,直连加载 Imgur 等被墙图床的图片。
  • 智能检测:自动检测当前网站 CSP (内容安全策略) 限制。例如在 GitHub 上会自动禁用代理以避免报错,并还原原始图片。
  • 自动修复:自动检测并替换裂图链接,无感体验。
  • 可定制性:支持自定义代理域名列表;支持按站点单独开关代理功能。
  • 流量节省:开启 转换为 WebP 格式 后,可显著减少流量消耗并加快加载速度。

使用方法

  1. 安装脚本/扩展


  2. 开启功能


    • 安装后打开脚本/扩展的 设置 菜单。
    • 勾选 启用将图片链接转为代理链接 (默认关闭)。
    • 推荐勾选 转换为 WebP 格式
  3. 配置规则 (重要):


    • 代理域名列表 (Proxy Domains) 中,默认包含 i.imgur.com
    • 你可以根据需要添加其他域名,例如 2libra.com

screenshot-2026-01-21-09-44-52

其他实用功能

  • 新标签页打开:智能控制链接打开方式,站外链接自动新标签页打开。
  • 文本转链接:自动识别并转换帖子中的纯文本 URL 为可点击链接。
  • 图片链接转图片:自动将原本只是 URL 的图片链接转换为直接显示的图片。

🔗 项目地址

欢迎大家试用反馈,有问题可以在 GitHub 提 Issue 或直接留言。

数字公告板提供商 Pinterest 发布了一篇文章,解释了其新平台Moka在大规模数据处理方面的未来蓝图。该公司正在将核心工作负载从老化的 Hadoop 基础设施迁移到基于 Kubernetes 的系统上,该系统运行在亚马逊 EKS 上,以 Apache Spark 作为主要引擎,并即将支持其他框架。

 

在一个包含两篇文章的博客系列中,Soam Acharya、Rainie Li、William Tom 和 Ang Zhang 描述了 Pinterest 大数据平台团队如何考虑下一代大规模数据处理平台的替代方案,因为现有的基于 Hadoop 的系统(内部称为 Monarch)的局限性变得越来越明显。他们将 Moka 作为搜索的结果,以及基于 EKS 的云原生数据处理平台,该平台现在运行的生产负载达到了 Pinterest 的规模。该系列的第一部分关注整体设计和应用层。相比之下,第二部分转向作者所说的“Moka 的基础设施重点方面,包括经验和未来方向”。

 

文章从实际角度描述了向 Kubernetes 的转变。它展示了一个全行业的转变,即大型技术公司现在将 Kubernetes 视为数据的控制平面,而不仅仅是无状态的服务平台。在大数据社区日益增长的受欢迎程度和越来越多的采用的鼓励下,团队探索了基于 Kubernetes 的系统,作为 Hadoop 2.x 最有可能的替代品。任何候选平台都必须满足可扩展性、安全性、成本以及托管多个处理引擎的精确标准。Moka 是如何在不放弃现有 Spark 投资的情况下现代化 Hadoop 时代的数据平台的一个例子。

 

第二篇文章的核心主题是如何在 Kubernetes 上以非常大的规模运行 Spark。作者解释了他们如何围绕 Moka 添加日志、指标和作业历史服务,以便工程师可以在不了解底层集群拓扑的情况下调试和调整作业。他们使用 Fluent Bit 对日志集合进行标准化,并使用 OpenTelemetry 和 Prometheus 兼容的端点发布统一指标。这为基础设施和应用程序团队提供了系统健康的一致视图。

 

Pinterest 还投资于通过基础设施即代码的方式使平台可重复使用。在文章中,团队概述了他们如何使用 Terraform 和 Helm 创建 EKS 集群、配置网络和安全以及部署支持组件,如 Spark 历史服务器。

 

Pinterest 的工程师还讨论了处理不同的硬件架构。他们描述了他们如何构建多架构镜像,以便他们的数据工作负载在 Intel 和基于 ARM 的实例上运行良好,包括 AWS Graviton,并将此与集群规模的成本和效率目标联系起来。InfoQ 编辑 Eran Stiller 在 LinkedIn上对该项目中的总结指出,Moka“提供了容器级别的隔离、ARM 支持、YuniKorn 调度,并通过整合工作负载和跨实例类型的自动扩展实现了显著的成本节省”。这些细节将工作置于云用户寻求在不牺牲性能的情况下削减基础设施成本的更大趋势之中。

 

关于处理引擎的更广泛的行业对话为 Pinterest 的故事增添了细微差别。在另一篇LinkedIn帖子中,Acharya 写道:“虽然 Spark 是我们的主要主力,但 Moka 的成功意味着 Pinterest 的其他用例也在效仿:Flink Batch 已经投入生产,Apache Ray 紧随其后,Flink Streaming 也将在今年晚些时候推出”。通过对 Spark 和 Flink 技术的深入探讨,我们可以了解到这一点的重要性。强调 Spark 仍然非常适合大型批处理和交互式分析工作负载,而 Flink 是“为实时、有状态的流处理而构建的”,具有严格的逐事件处理。团队将 Moka 呈现为一个灵活的基础,可以根据特定工作负载的需求添加不同的引擎,而不是一个只支持 spark 的平台。

 

外部观察者从 Pinterest 案例中吸取了教训。ML工程师通讯将 Moka 文章描述为“在 Kubernetes 上部署 EKS 集群、Fluent Bit 日志、OTEL 指标管道、镜像管理和 Spark 的自定义 Moka UI”的例子,将其与其他现代数据基础设施案例研究并列。这些反应表明,Moka 被视为一类云原生数据系统的参考架构。

 

然而,团队确实将他们的迁移工作呈现为一个正在进行的旅程,而不是一个已经完成的项目。在博客和进一步的LinkedIn帖子中,Pinterest 作者讨论了“经验和未来的方向”,并描述了早期概念验证如何导致随着对新堆栈的信心增长而逐步远离 Hadoop 的迁移。Acharya 指出,“最好的问题出现在规模上”,构建平台涉及“解决难题”,因为团队转移了实际工作负载。对于其他组织来说,这种经验可能是最重要的教训。复制围绕 Kubernetes、EKS 和 Spark 的技术选择相对简单,但从遗留系统中解耦并投资于可观测性、自动化和多引擎支持的过程可能是未来真正的工作。

 

原文链接:

https://www.infoq.com/news/2026/01/pinterest-kubernetes-bigdata/

导言

在复杂信息爆炸与高强度研发协作中,知识的垂直解构与深度对齐是保持组织竞争力的关键。缺乏有效的堆栈式归纳机制,团队往往会面临逻辑断层、执行偏差、深度知识难以回溯等挑战。通过使用堆栈式知识归纳软件,团队可以将信息按层级嵌套、堆栈对齐的方式进行归纳,确保每一条知识都能向上溯源目标,向下穿透细节,从而显著提升团队的深度思考能力与知识流转效率。

摘要

本文介绍了堆栈式知识归纳软件在处理复杂逻辑中的重要性,并精选推荐了5款适用于不同层级归纳场景的工具。通过分析这些软件的垂直架构与嵌套特点,帮助团队选择最适合的工具来构建深度知识栈。此外,文中还提供了堆栈化归纳的设计逻辑与实施策略,助力团队建立纵向对齐的知识管理体系。

一、 为什么需要堆栈式知识归纳软件?

在处理高复杂度项目或深度研发时,知识往往需要按照堆栈层级进行纵向归集与对齐。没有合理的堆栈式归纳工具,团队将面临以下几大困境:

  • 逻辑断层:底层执行动作与高层战略目标脱节,无法闭环回溯。
  • 进度模糊:缺乏穿透视图,无法从宏观层面一眼洞察微观节点的真实状态。
  • 认知过载:平铺的信息无法体现逻辑的主次,导致关键路径被噪音湮没。
  • 协作脱节:团队成员因缺乏统一的层级视角,在多级拆解中产生理解偏差。

引入一款支持堆栈式嵌套归纳的软件,能够帮助团队通过垂直化的架构管理,提升信息的逻辑密度与检索精度。此类软件能将知识按父子关系层层堆叠,确保每一个细节节点都具备完整的上下文语境,减少重复沟通与认知成本。

二、 堆栈式知识归纳软件的作用

堆栈式知识归纳软件是指那些支持将信息按无限嵌套、垂直对齐单元进行层级归纳,并提供深度下钻视图的工具。这类工具的核心作用是帮助团队将碎片化的执行记录转化为结构化的逻辑栈,确保每个层级的产出都能得到精准的归因与追踪。其关键特点在于具备强大的纵向架构能力,能够在保持信息深度的同时,通过折叠与穿透机制维持视图的简洁高效,让团队在宏观与微观之间自由切换。

三、 堆栈式归纳的典型应用场景

堆栈式知识归纳软件适用于需要处理严密逻辑、深度架构或多层级任务的场景。以下是此类工具的典型应用:

  1. 复杂研发架构管理:在软件或硬件研发中,将顶层架构逐层分解为模块、组件及原子代码,实现全链路逻辑归纳;
  2. 深度项目WBS分解:利用堆栈结构对大型工程进行工作分解(WBS),确保每一个子任务都能垂直映射到里程碑节点;
  3. 多级需求溯源体系:从市场需求到产品功能,再到开发任务,构建完整的垂直对齐堆栈,防止需求流失;
  4. 标准化作业流程(SOP)嵌套:将复杂的作业规范拆解为多层级操作说明,提升新成员对深度业务的学习效率;
  5. 战略目标层级对齐:通过堆栈式结构将OKR或KPI从组织层层透传至个人,实现上下同欲的逻辑闭环。

四、 5款值得一试的堆栈式知识归纳软件(精选推荐)

1. 板栗看板

专注于无限层级嵌套与垂直对齐的堆栈式管理工具

  • 核心特性:支持卡片无限嵌套,提供独特的“树状+看板”双重维度,实现任务层级的深度解构;
  • 适配场景:研发团队、复杂项目管理、多层级SOP归纳;
  • 优势亮点:通过直观的层级下钻功能,板栗看板能完美解决普通工具“扁平化”的痛点,让再复杂的项目也能通过堆栈结构一览无余。
    在这里插入图片描述

2. Workflowy

极致简约的无限层级大纲式归纳软件

  • 核心特性:基于单一列表的无限节点嵌套,支持极致的缩放(Zoom-in/out)与归纳;
  • 适配场景:个人深度思考、项目逻辑建模、碎片信息层级化;
  • 优势亮点:专注“点、线、面”的纵向堆叠,适合快速捕捉灵感并将其无缝嵌入现有的逻辑堆栈中。
    在这里插入图片描述

3. Heptabase

结合视觉白板与原子化堆栈的知识建模工具

  • 核心特性:支持将笔记块放入多层级卡片盒,通过视觉化的方式呈现知识的堆栈关系;
  • 适配场景:学术研究、复杂业务分析、学习体系构建;
  • 优势亮点:它不仅能进行堆栈归纳,还能通过白板连线展示跨堆栈的横向逻辑,兼顾了深度与广度。
    在这里插入图片描述

4. Airtable

基于多表关联与分级视图的结构化堆栈平台

  • 核心特性:通过强关联关系实现不同表单间的层级跳转,支持按属性进行多级分组归纳;
  • 适配场景:资产管理、中后台流程监控、标准化数据归档;
  • 优势亮点:Airtable 的数据库逻辑允许用户自定义复杂的垂直对应关系,适合对大量标准化堆栈进行参数化管理。
    在这里插入图片描述

5. ClickUp

多层级任务架构与高度自定义的团队协作软件

  • 核心特性:提供“空间-目录-列表-任务-子任务”的五级固定堆栈架构,支持精细化的属性继承;
  • 适配场景:大中型团队协同、全流程项目管控、多维度任务分发;
  • 优势亮点:其严格的层级逻辑确保了大规模协作时的信息有序,是典型的工程级堆栈管理工具。
    在这里插入图片描述

五、 各软件的选型建议

选择堆栈式知识归纳软件时,应根据逻辑的深度、协作的复杂度以及对“可视化下钻”的需求来决定:

1. 追求极简与逻辑深度

对于侧重个人思考或纯逻辑建模的用户,Workflowy 的极简大纲能提供无干扰的堆栈归纳体验。

2. 复杂研发与可视化穿透

若团队需要在执行中实时穿透进度,板栗看板 凭借其直观的嵌套卡片视图,是中小型研发团队实现垂直对齐的最优解。

3. 数据驱动与标准化堆栈

如果归纳内容需要高度结构化并支持大量筛选、自动化操作,Airtable 能够提供最稳健的数据库式堆栈支撑。

4. 大型组织的全方位管控

针对需要多部门协作、分权管理的场景,ClickUp 的五层固定架构能确保知识在复杂体系中不失序。

六、 Q\&A:关于堆栈式知识归纳你可能遇到的问题

Q1:堆栈层级分得太深,找东西像“套娃”一样麻烦怎么办? A:建议配合全局搜索与快速导航功能,并利用“路径面包屑”定位。同时,在顶层建立索引页或仪表盘,确保核心堆栈节点触手可及。

Q2:如何平衡堆栈的深度与执行的灵活性? A:遵循“逻辑深拆、执行轻快”的原则。建议将深度逻辑留在归纳层,而在最底层的原子任务层保持简洁,避免因层级过多导致操作繁琐。

Q3:如何防止堆栈式归纳沦为行政负担? A:采用“边做边归纳”的模式,将归纳动作嵌入任务生命周期中,利用工具提供的模板化功能降低重复搭建堆栈的成本。

七、 结语

堆栈式知识归纳软件是攻克复杂管理难题的利器。通过科学的层级设计与垂直归档,团队能够将凌乱的信息转化为逻辑严密的资产栈,实现从“碎片化堆砌”到“系统化对齐”的质变。借助 板栗看板WorkflowyClickUp 等工具,知识管理将不再是沉重的负担,而是驱动组织持续深耕与极速进化的逻辑引擎。

深度决定高度,堆栈式知识归纳软件让每一份思考都拥有厚实的基石。

TabTab 更新:Zen 模式、一键分享导入、标签组支持

作为一个常年开着 50+ 标签页的重度用户,我一直在寻找一个好用的标签管理工具。市面上的方案要么太重,要么体验不好,所以我自己做了 TabTab。最近上线了几个新功能,来和大家分享一下。


🧘 Zen 模式 - 专注浏览,远离干扰

这是这次更新我最喜欢的功能。

日常使用标签管理器时,左边空间列表、右边实时标签栏,功能是全的,但有时候就是想安静地浏览收藏的链接,不想被各种操作按钮干扰。

Zen 模式就是为此而生:

  • 极简界面:只保留当前空间的内容,没有侧边栏,没有编辑按钮
  • 多种主题:默认简约风、吉卜力风格(手绘感)、毛玻璃效果,总有一款适合你
  • 沉浸体验:点击即打开,鼠标移到右上角才显示退出按钮
  • 记住偏好:下次打开自动进入上次的模式

在搜索框旁边点击 Zen 按钮即可进入。强烈推荐用吉卜力主题配合暗色模式,非常治愈。


🔗 一键分享 & 一键导入

之前有用户反馈想把自己整理的资源合集分享给朋友,或者在不同设备间快速同步。

现在支持了:

分享

  • 点击空间标题旁的「分享」按钮
  • 生成一个短链接( 30 天有效)
  • 对方打开链接,点击「导入到 TabTab 」,完成

大概这个样子👉 https://s.tabtab.xyz/link/6b969f3dbf4e6201feeb3f3c5354516a

导入

  • 支持从分享链接一键导入
  • 支持从浏览器书签快速导入
  • 支持从 Toby 迁移过来

管理分享链接

登录后,在侧边栏「 My Links 」页面可以:

  • 查看所有分享过的链接
  • 一键复制、删除不需要的链接
  • 链接到期自动清理

分享内容存储在服务端,敏感链接请谨慎分享。


📁 标签组支持

Chrome 的标签组功能很好用,但之前 TabTab 的实时标签栏没有识别它们。

现在优化了:

  • 右侧实时标签栏会按标签组聚合显示
  • 支持一键保存整个标签组到合集
  • 支持修改标签组颜色
  • 支持一键关闭整个标签组

这样从浏览器标签组到 TabTab 的工作流就顺畅多了。


其他优化

  • 卡片样式支持「普通」和「紧凑」两种
  • 合集支持移动到其他空间
  • 支持置顶合集
  • 修复了一些同步和 UI 问题


下载地址

导语

随着 DevSecOps 的不断推进,应用安全已被广泛纳入SDLC的各个阶段。然而,在代码扫描、依赖分析、漏洞检测等能力逐步成熟的同时,一个长期存在却难以解决的问题始终横亘在安全工程实践中:安全工具“能发现问题”,却难以判断问题是否真实、是否可利用、是否值得优先处理。大量规则驱动的扫描结果不仅带来了高误报率,也持续消耗着研发与安全团队的精力。

近年来随着大语言模型(LLM)的快速发展,为这一困境提供了新的可能。不同于传统规则或静态特征匹配,LLM 在语义理解、上下文推理和条件组合分析方面展现出独特优势,使其具备参与安全“判断层”的潜力。将 LLM 引入 SDLC,不再只是生成代码或辅助文档,而是尝试参与到安全结果的理解、验证与决策之中。

本文结合实际应用安全建设经验,围绕 LLM 在 SDLC 中的落地实践展开,重点探讨其在硬编码、SCA、漏洞挖掘等场景中的应用方式与工程化思路。

SDLC 应用安全流程

image-20251217111844592

SDLC名词解释

  • SAST(静态应用安全测试)通过对源代码或编译产物进行静态分析,在不运行系统的情况下发现潜在的安全缺陷,如 SQL 注入、XSS、不安全函数调用和硬编码敏感信息等,适合在开发阶段提前发现问题。
  • SCA(软件成分分析)聚焦于项目中使用的第三方开源组件,识别依赖库及其传递依赖中已知的安全漏洞、风险版本和许可证问题,帮助团队降低因外部组件引入的安全风险。
  • DAST(动态应用安全测试)在系统运行状态下,从攻击者视角对应用进行测试,通过捕获流量包修改参数重放,模拟真实攻击行为验证系统是否存在可被实际利用的漏洞,如注入攻击、未授权访问等
  • 硬编码(Hard Coding),是指在程序中直接把固定的值写死在源码里,而不是通过配置文件、环境变量等方式获取,比如下面这些情况,都属于硬编码:用户名、密码、token或加密密钥等

为什么我们需要SDLC?

产品一句话需求 → 开发自己理解 → 按照个人习惯去开发 → 功能上线后出现大量漏洞 → 被外部利用造成损失

而SDLC要做的就是把漏洞扼杀于摇篮之中,而不是靠后期凭经验渗透测试发现。

但目前传统的SDLC存在大量告警/误报,推送大量工单给研发会导致业务间摩擦度增加,因此理想情况是把真正需要修复的工单交给研发处理

硬编码规则下引入AI判断,减少误报

问题背景:目前硬编码扫描是根据规则的正则匹配,存在一定的局限性和误报

image-20251217171931177

整体流程

结合硬编码规则 + AI 判断保留高召回,同时降低误报率

image-20251230150538531

硬编码规则先行

使用固定规则(正则、逻辑判断)先筛掉明显非风险项,让 AI 只处理模糊/不确定案例

AI 判断做辅助

只对硬编码规则未覆盖、可疑的候选项输出风险判断,输出结果可附置信度或分类标签

置信度 + 白名单控制

AI 输出带置信度,低于阈值直接忽略,对常见合法值、默认值设置白名单

提示词 promot

通过定位文件的位置,结合上下文判断实际风险等级,把AI分析结果输出

你是一个资深应用安全专家,精通代码安全、凭证泄露、真实攻击利用分析。

现在给你一个【疑似硬编码凭证】的扫描结果,请你进行【可利用性研判】。

输入信息如下(JSON):
%s

请严格按以下维度进行分析:
1. 该硬编码是否为真实敏感凭证
2. 是否存在被外部攻击者利用的可能
3. 是否依赖运行环境
4. 泄露后的安全影响
5. 修复建议

请以 JSON 格式输出分析结果

模型输入字段释义

字段 释义
match 匹配到的硬编码内容(部分脱敏显示)
rule key类型
path 硬编码所在的完整文件路径
branch 分支
code 上下5行代码

增加输出长度,避免截断

"extra_body": map[string]interface{}{
            "think_mode":        true,
            "max_output_tokens": 1024, 

实现效果

image-20251217195004928

如果是走正常的流程,secret_value会被 generic-api-key规则名字标记严重程度为medium

image-20251218143042933

开启AI分析选项后,通过定位文件的位置,结合上下文交给ai分析,AI判断实际危害程度为低

在代码中发现硬编码的敏感信息'DEMO_SECRET',其值为'secret_value'。根据规则'generic-api-key',这可能是一个API密钥或其他类型的敏感凭证。该变量位于'E:\SDLC平台\backend\uploads\demo.py_scan\demo.txt'文件中,并且注释表明它看起来像一个Key,但无实际用途。由于这是一个测试环境中的示例代码,风险相对较低。

image-20251218142746712

掩码输出硬编码片段

image.png

代码中存在:

const apiKey = "sk_live_9f83a0b7..."

AI分析后会直接原样输出,给出完整的佐证片段,这样是不符合数据安全合规要求的,就会产生 二次扩散风险

正确掩码后的做法,AI 只需知道这是一个硬编码密钥

const apiKey = "*MASKED_SECRET*"

实现效果

通过 AI 研判对硬编码、潜在风险及非生产路径问题进行自动识别与筛选,各产品待修复量平均下降约52.8%

image.png

价值体现:在保证安全覆盖率的前提下,AI 自动化研判显著提升效率,降低人工排查压力,推动安全研判进入智能化阶段

  • AI 判断为 False:AI 判定为误报,可直接关闭
  • AI 判断为 True 但 NonLive:问题真实但不在生产路径,可降低风险等级处理
  • AI 研判后待修复:确认真实且影响生产,需进入修复流程

SCA可利用性与真实风险判断

从官方文档 https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components 描述可看到,涉及版本都需要更新到对应补丁

image-20251223145325095

但从甲方安全运营的角度会存在以下这些问题:

1.大版本的更新会存在项目兼容性问题,不好推进

2.涉及仓库数量较多,如果全部同时进行整改将会是极大的工作量

如果我们深入分析后会发现,并不是在版本范围内就存在漏洞,还需要额外的条件满足才能利用

客户端请求 Server Action
  ↓
执行 Server Action (接收用户输入)
  ↓
react-server-dom-webpack 序列化响应
  ↓
【漏洞点】反序列化时未正确验证输入
  ↓
恶意 payload 被执行 → RCE

必要利用条件

条件 是否必须 说明
App Router ✅ 必须 提供 RSC / Flight 机制
Server Actions ✅ 必须 提供反序列化入口
用户可控输入 ✅ 必须 构造恶意 payload

整体流程

  • 核心思路:证明SCA漏洞代码是否被业务代码真实调用,如果不可达那么这个SCA漏洞在该仓库就不可利用
  • 调用链路:业务代码中是否存在外部可控输入→ 漏洞组件危险函数的真实可达路径

image-20251221173914270

HTTP 请求 (scaHandler) -- 输入CVE编号
    ↓
CVE 分析 (runSCACVEAnalysis)
    ├─ 步骤 2.1: Google 搜索受影响版本
    ├─ 步骤 2.2: Qwen 识别依赖组件搜索官网信息
    ├─ 步骤 2.3: 搜索引擎寻找对应PoC
    ├─ 步骤 2.4: Qwen 提取结构化信息
    └─ 步骤 2.5: Claude 最终安全分析
    ↓
仓库分析(可选)
    ├─ 方法一: 依赖分析 (analyzeRepositoryVulnerability)
    └─ 方法二: 锚点分析 (analyzeRepositoryWithAnchor)

google搜索引擎调用

调用google进行联网搜索,局限性 key限制每天100个

https://console.cloud.google.com/apis/credentials

凭证-创建凭证

image-20251217162129486

启用custom search api

image-20251217161840875

https://programmablesearchengine.google.com/controlpanel/create

在这个地方可以定义调用的搜索引擎

image-20251217162013410

优化阶段1:多个源进行信息整合导致出错

初步阶段测试发现,Qwen去重整理逻辑导致结果出现缺失

image-20251223150956095

因此后续直接选用官方源,保证结果数据的准确性

官方情报来源

序号 来源机构 描述 链接
1 美国国家漏洞数据库 (NVD) 官方 CVE 条目,包含漏洞详情、受影响版本、CWE、CVSS 等信息 https://nvd.nist.gov
2 CVE 官方记录 (CVE.org) 官方 CVE ID 登记与记录 https://www.cve.org
3 React 官方安全公告 (React Team / Meta) 官方漏洞公告及修复版本说明 https://react.dev
4 加拿大网络安全中心 (Cyber Centre) 官方安全公告、漏洞说明 https://www.cyber.gc.ca
5 Google Cloud 官方博客 官方补丁指引及响应措施 https://cloud.google.com

优化阶段2:未关联间接受影响组件导致结果不准

在漏洞受影响的范围很多都只提及了react组件,但是有其他间接依赖组件如next也会受到影响,因此在爬取网站内容需要把这部分信息也整理进来

虽然应用使用了受影响的 React 版本(19.0.0)并启用了 React Server Components 功能,但 React Server Components 的漏洞版本范围是 19.0.0-19.2.0,而当前仓库使用的是 react-server-dom-webpack 19.0.0。关键问题是该仓库使用的是 Next.js 16.0.6,而 CVE-2025-55182 主要影响独立的 React Server Components 实现,Next.js 有自己的 Server Components 实现机制,不直接受此 CVE 影响。条件1不满足,因此漏洞不可利用

image-20251223151142011

优化阶段3:规范性提示词输入

这里有三个关键点:

将「CVE 知识」作为输入,而不是让 LLM 自行理解

  • 不依赖模型对 CVE 的主观理解或记忆
  • 由安全侧明确提供:漏洞成因和可利用条件链(Exploit Preconditions)
  • 避免模型自由发挥导致的误报或信息污染

在目标代码仓库中,验证漏洞可利用条件是否成立

  • 不做漏洞解读
  • 不做风险定级臆断
  • 不基于版本号直接下结论

将每个 CVE 拆解为一组必须同时满足的利用条件

  • 逐条在仓库中进行验证:任一关键条件不满足 → 漏洞不可达,不构成真实风险
  • 代码结构、依赖使用情况及配置与对外暴露面

最终提示词

你是一名资深应用安全分析师。请基于我提供的 SCA 扫描结果,对发现的第三方组件漏洞进行【汇总型安全分析输出】,输出需包含以下部分(使用简体中文):

1. 漏洞基本信息
   - 受影响组件 / 编程语言 / 版本
   - CVE 编号
   - 漏洞类型

2. 漏洞原理说明
   - 从安全分析视角解释漏洞成因
   - 重点描述漏洞触发机制(如反序列化、解析、路由处理等)
   - 对未公开的内部实现需明确说明"细节未披露",避免推测

3. 影响评估
   - 可造成的安全影响(如拒绝服务、信息泄露等)
   - 对业务连续性、系统稳定性和可用性的潜在影响

4. 攻击前置条件
   - 环境条件(框架、运行模式、功能开启情况等)
   - 依赖条件(受影响的第三方组件)
   - 攻击者权限要求(是否需要认证、是否可远程触发)

5. 涉及模块或组件范围
   - 受影响的框架模块或依赖包名称
   - 若具体函数或代码位置未公开,需明确说明
   - **必须列出所有依赖关系**:如果漏洞影响底层组件,必须说明哪些上层框架/库可能间接受影响,包括具体的组件名称和受影响版本范围

6. 可利用性与 EXP 情况说明
   - 是否存在已公开的 PoC / EXP
   - EXP 的公开来源类型(如 GitHub、安全研究博客等)
   - 利用复杂度与稳定性评估(概念验证 / 可重复利用 / 条件受限)
   - 输出poc/exp

7. 修复与缓解建议
   - 官方推荐的修复方式(安全版本升级 / 官方补丁)
   - 可选的临时缓解措施(如限制接口访问、WAF、防护策略等)

8. 验证与复现说明(高层级)
   - 给出验证思路而非攻击步骤
   - 描述在存在漏洞情况下的典型现象(如服务挂起、资源异常)

9. 信息来源说明
   - 明确标注信息来源类型(NVD、官方博客、安全公告、PoC 仓库等)
   - 不编造或推测来源
   - **重要**:references 字段必须包含完整的 URL(以 http:// 或 https:// 开头),例如:
     - 正确:https://github.com/msanft/CVE-2025-55182
     - 错误:github: msanft/CVE-2025-55182 或 github.com/msanft/CVE-2025-55182
     - 如果搜索结果中有链接,必须提取完整的 URL 格式

输出风格要求:
- 安全评估报告风格
- 用词克制、客观、中立
- 不渲染攻击效果,不放大风险,不自主推测
- 优先使用官方来源信息,避免"未确认"或"可疑"的评估

漏洞分析示例1:CVE-2025-55182

受影响的系统情况

app-router-vulnerable/app/api/action/route.ts

'use server'

export async function testAction(formData: FormData) {
  const data = Object.fromEntries(formData)
  return {
    message: 'Server action executed',
    data: data
  }
}

使用 App Router 并启用了 Server Actions 的应用系统,受CVE-2025-55182影响

  • ✅ 使用 app/ 目录(App Router 结构)
  • ✅ 接收 FormData 作为参数
  • ✅ 使用 react-server-dom-webpack 进行序列化/反序列化

image-20251223144630570

不受影响的系统情况

  • ❌使用 pages/ 目录(Pages Router 结构)
  • ❌不使用 Server Actions
  • ❌不依赖 React Flight Protocol 序列化

使用 Pages Router 的 Next.js 应用,即使引入同样处于受影响范围内的版本,也不受 CVE-2025-55184 漏洞影响,ai分析结果符合预期

image-20251222110315768

漏洞分析示例2:CVE-2021-44228

受影响的系统情况

环境情况:

  • Log4j 版本:2.14.1 (漏洞影响范围内)⚠️
  • JNDI:✅ 允许
  • 网络:✅ 可访问 LDAP / RMI

综上所述,满足漏洞触发条件,因此AI研判该仓库受影响

image-20251222145542970

不受影响的系统情况

环境情况:

  • Log4j 版本:2.14.1(漏洞影响范围内)⚠️
  • JNDI:❌ 被禁用
  • 网络:❌ 无法访问外部 LDAP

虽然在漏洞版本内,但是-Dcom.sun.jndi.ldap.object.trustURLCodebase=false ,因为${jndi:...} 被禁用不会被解析

image-20251222151248340

AI 分析判断仓库不受影响,符合预期

image-20251222115823103

模型费用对比及选择

根据官方获取定价数据:

https://platform.claude.com/docs/en/about-claude/pricing?utm_source=chatgpt.com

在选择前首先我们要定义模型好坏的标准,从数据表现出发而不是个人主观经验判断

如果追求 准确率 可以选择claude-sonnet-4@20250514,追求 性价比 但又有不错的准确率可以选择gemini-2.5-flash

项目 3 5 6 7 8
使用模型 gemini-2.5-pro claude-sonnet-4@20250514 claude-haiku-4-5 Qwen2.5-Coder-14B gemini-2.5-flash
准确率 95% 100% 87.50% 75% 87.50%
单个 CVE 分析平均费用(USD) 0.33 0.69 0.19 -- 0.08

白盒代码审计

存在的难点

  • 代码文件很长
  • 需要多文件上下文结合分析
  • 需要精确定位行号、变量流、调用链

上述这些问题都会导致大量的token消耗,其他chat型大多数每一轮 = 重新塞一堆代码进 prompt

模型选择

Cursor 最大优势:通过索引 + 增量上下文,节约 token 消耗,适合多轮、持续审计

最关键的一点是,他是按照提问次数来计费的,它把一次提问变成了一次完整的白盒审计任务执行

维度 / AI ChatGPT (Web/API) Claude Gemini GitHub Copilot Cursor
上下文获取方式 手动粘贴文件 手动粘贴 / 长上下文 手动粘贴 IDE 补全 自动索引 + AST
重复 token 消耗 极低
多轮审计成本 指数级上升 平稳 / 增量消耗
跨文件调用分析 手动复制 手动复制 自动关联
白盒审计推荐度 ⭐⭐⭐ ⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐

提示词promot

明确任务定位

让 LLM 清楚自己在做什么,而不是默认行为(如写总结、生成报告),避免 LLM 自动归纳/总结导致丢失重要信息差异。

你不是在写安全报告,而是在做“证据整理(evidence collection)”。
你的目标是保留信息差异,而不是消除差异。

使用“反总结”指令

通常 LLM 会倾向总结、归纳,在 prompt 中明确要求保留原始信息、对比差异、不要归类

请逐条保留所有原始输入中的差异信息,不要合并或总结条目。
每条信息保持独立输出。

明确输出结构

指定输出格式,避免每次输出不统一,便于后续自动化分析/汇总

请按照以下 JSON 格式输出:
[
  {"source": "文件A", "line": 23, "content": "..."},
  {"source": "文件B", "line": 45, "content": "..."}
]

强化“证据导向”

提示 LLM 输出时只保留事实,不做主观判断

只提取事实性内容,不要加入主观评论或判断。
标明来源和行号。

分步任务处理

对于复杂信息,分步任务处理比一次性要求总结更稳妥,避免分析中断停止,输出更结构化、更精确

第一步:提取每个输入文件的独立事件。
第二步:标记事件的时间戳和来源。
第三步:保留所有差异,不进行合并。

根据框架语言输入前置知识

不同的语言审计的方法和思路不一样,在让AI分析代码时候需要提供一些前置知识,这能让 AI 更精确地聚焦在“可能的风险点”,而不是泛泛地猜测

像SQL注入,不同语言的sink点也不完全相同

语言 方法 / 函数 示例代码 / SQL
Golang (*gorm.io/gorm).Where db.Where(StringData).First(&data)
Golang (*github.com/jmoiron/sqlx.DB).Queryx db.Queryx(query, params)
Java mybatis like select * from users where username like '%${name}%'
Java mybatis order by select * from users order by ${orderby}
Java mybatis-plus apply wrapper.eq("id", id).apply("username=" + name);
Python pymysql execute sql = "select * from users where username = '%s'" % (name)
Node.js mysql query sql = "select * from users where username = ${name}"

在Shiro和Spring Security中,可以配置哪些API不需要进行权限校验

  • 在Shiro中,可以使用Shiro的过滤器链(Filter Chain)来配置不需要进行权限校验的API
  • 在Spring Security中,可通过继承WebSecurityConfigurerAdapter类并重写其中的configure()方法,配置不需要进行权限校验的API

image-20251230153740679

像上述的内容可作为前置知识给AI输入,增加其分析的准确性

1. web.xml / Spring 配置分析
找出其中配置的可直接前台访问的 .jsp、.do、.action、.html、.json、.servlet 等接口路径。
指明配置项与访问路径的对应关系:
web.xml → <servlet-mapping>、<url-pattern>
@Controller、@RestController、@RequestMapping 等注解标注的接口
检查是否存在匿名访问的接口(无登录/权限验证拦截)。
检查 Filter、Interceptor、SecurityConfig、WebSecurityConfigurerAdapter 等中是否存在鉴权绕过配置。

2. classes / lib / jar 源码分析
对比 WEB-INF/classes 下的 .class 文件与反编译后的 .java 文件。
对 lib 下的 .jar 文件进行反编译,检查是否包含业务逻辑代码。
逐一分析对应的 Controller、Service、DAO、Repository 层实现:
对应的请求路径(前台/后台)
涉及的外部依赖或第三方库(如 HttpClient、JdbcTemplate、Hibernate 等)
标注潜在的高危点:未校验的用户输入、外部命令调用、文件上传写入、动态 SQL 拼接等。

3. 识别调用链路
标识所有暴露给前端或外部调用者的接口(如 REST API、RPC Endpoint、Controller 方法、Servlet)。
确定入口函数是否为用户完全可控(如 request.getParameter()、@RequestParam、@RequestBody)。
检查系统是否已接入统一认证(如 Spring Security / JWT / OAuth2 / Session)。
深入分析完整调用链:
Controller → Service → Repository → 外部系统
判断入口是否存在强约束:
用户归属验证
签名、时间戳、防重放机制
输出是否可以绕过认证或越权。

4. 重点模块审计(前台与后台分开)
重点排查以下常见的漏洞类型:
漏洞类型    漏洞Sink点(常见函数 / 类)   审计描述
SQL 注入  Statement.executeQuery(), Statement.executeUpdate(), JdbcTemplate.queryForList(), createNativeQuery(), EntityManager.createQuery()  检查点:SQL 是否通过字符串拼接、+、String.format、concat 等方式插入用户输入(如 Request 参数)。优先关注 MyBatis 自定义 SQL 与原生 JDBC 使用场景。
命令执行(RCE)   Runtime.getRuntime().exec(), ProcessBuilder.start(), ShellUtils.exec()  检查点:是否拼接用户输入到命令中,或允许上传执行脚本。
文件上传 / 任意文件写入   MultipartFile.transferTo(), FileOutputStream.write(), Files.write(), FileUtils.copyInputStreamToFile()  检查点:是否校验扩展名、MIME、目录路径;是否防止 .jsp、.jspx、.java 等脚本文件上传。
反序列化    ObjectInputStream.readObject(), JSON.parseObject(), Yaml.load(), XStream.fromXML()  检查点:是否对外部输入执行反序列化;是否使用存在漏洞的库(如 fastjson < 1.2.83, Jackson 未加白名单)。
任意文件读取  Files.readAllBytes(), FileInputStream, IOUtils.toString(), response.getOutputStream().write()   检查点:是否直接读取用户指定路径;是否存在目录遍历绕过。
路径遍历    new File(), Paths.get(), ServletContext.getRealPath(), File.delete()    检查点:是否存在 ../ 等拼接导致目录逃逸。
XXE(XML 外部实体)   DocumentBuilderFactory.newInstance(), SAXParserFactory.newInstance(), XmlMapper.readValue() 检查点:是否关闭外部实体解析;是否解析来自不可信来源的 XML。
SSRF    HttpURLConnection, HttpClient.get(), RestTemplate.getForObject(), URL.openConnection()  检查点:是否允许用户指定 URL 并由服务器发请求;是否存在内网访问风险。
XSS response.getWriter().write(), 模板引擎输出 (<%= ... %>, Thymeleaf, Freemarker)    检查点:是否未进行 HTML/JS 输出转义。
认证绕过 / 越权   缺少 @PreAuthorize、@Secured、Session 检查或过滤器逻辑错误    检查点:检查接口访问控制逻辑,是否能直接调用他人资源。

5. 输出结构(每个发现需包含以下部分)
每个发现必须包含以下字段:
风险点名称
漏洞类型 + 影响接口 + 文件路径
漏洞成因
简述代码逻辑错误或输入未过滤的原因。

在net系统中,首先对dll进行反编译,然后让AI去关联路由和实现方法

image-20251230154740599

### 审计和输出要求:

1. **web.config 分析**  
   - 找出其中配置的可直接前台访问的 `.ashx``.aspx` asmx ascx 文件。  
   - 指明配置项与访问路径的对应关系。  

2. **bin 目录源码分析**  
   - 逐一对应 `bin` 下的 `.dll` 与其反编译出来的 `.cs` 文件。  
   - 分析对应的 `.ashx` 或 `.aspx` 、ascx  asmx方法实现。  
   - 如果代码中存在潜在的高危点,需要重点标注   

3. 识别调用链路 
* (本文件内的路由/XXX 根据情况调整) 函数是暴露给前端或外部调用者的接口(如 API/RPC/Controller),其 request 对象是完全用户可控的
* 当前系统默认已接入统一认证中间件(如 JWT / Session / OAuth2),调用该函数的用户通常已登录
* 需要分析完整的调用链路,包括所有被调用的 Service 层、Repository 层和外部依赖
* 需要判断入口处有强约束(如强校验 user 归属/租户隔离/签名+时效+重放防护)
分析接口是通过什么鉴权的,尝试进行绕过,深入分析所有前台可访问的文件并挖掘漏洞
在项目中搜索所有 ASMX 接口,重点关注是否可匿名调用的未授权端点,并给出利用的wsdl方式和数据包

4.漏洞Sink点 
| 漏洞类型                       | 漏洞Sink点                                                   | 审计描述                                                     |
| ------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| **SQL 注入**                   | `ExecuteNonQuery()`, `ExecuteReader()`, `ExecuteScalar()`, `SqlDataAdapter.Fill()`, `ExecuteSqlCommand()`, `ExecuteSqlRaw()`, `CreateSQLQuery()`, `connection.Query()` | **检查点**:查找 SQL 语句是否通过字符串拼接或格式化(`+`, `String.Format`, `$""`)将 `Request/Query/Form/Cookie` 等直接插入。 |
| **命令执行(RCE)**            | `Process.Start()`, `ProcessStartInfo.FileName`, `ProcessStartInfo.Arguments` | **检查点**:是否把用户输入拼接到命令或传给 shell/PowerShell, `FileName` 与 `Arguments` 是否来自外部 |
| **文件上传 / 任意文件写入**    | `SaveAs()`, `WriteAllBytes()`, `WriteAllText()`, `FileStream.Write()` | **检查点**:是否校验扩展名、MIME、内容类型、文件名(路径分隔符)、以及保存目录权限;是否防止覆盖已有文件,上传可执行脚本(`.aspx`/`.ashx`)getshell |
| **反序列化**                   | `BinaryFormatter.Deserialize()`, `SoapFormatter.Deserialize()`, `JsonConvert.DeserializeObject()`, `LosFormatter.Deserialize()` | **检查点**:反序列化是否对不可信输入(Request、Cookie、ViewState、文件等)执行;是否使用不安全的序列化库(BinaryFormatter、SoapFormatter) |
| **任意文件读取**               | `File.ReadAllBytes()`, `File.ReadAllText()`, `Response.WriteFile()`, `Response.TransmitFile()`, `File()` | **检查点**:是否将用户参数直接作为文件路径输出或读取;是否存在未做路径合法化的文件下载接口。 |
| **路径遍历**                   | `Server.MapPath()`, `Path.Combine()`, `File.Delete()`, `Directory.GetFiles()` | **检查点**:路径拼接是否包含未过滤的用户输入;`Path.Combine` 后是否做规范化校验。 |
| **XXE(XML External Entity)** | `XmlDocument.LoadXml()`, `XmlDocument.Load()`, `XmlReader.Create()`, `DataSet.ReadXml()` | **检查点**:XML 解析是否启用了外部实体解析(DTD);是否解析来自不受信任来源的 XML。 |
| **SSRF**/远程文件下载          | `WebClient.DownloadString()`, `HttpClient.GetAsync()`, `WebRequest.Create()`, `HttpClient.PostAsync()` WebClient.DownloadFile()、HttpClient.GetStreamAsync()、HttpClient.GetByteArrayAsync()、WebRequest.GetResponseStream() | **检查点**:是否允许用户指定 URL 并由服务器发起请求;是否对目标地址做白名单或内部地址检测。 |   

5. **输出结构**(每个发现都要包含以下部分)  
   - 风险点名称  
   - 漏洞成因(为什么可能触发)  
   - 攻击面分析(攻击者可能会怎么尝试)  
   - 关键代码片段(只展示相关函数或方法)  

黑盒漏洞挖掘

个人观点

目前市面上大量工具打着AI 自动化漏洞挖掘智能分析攻击链路的旗号,看似很酷炫但本质上是在用通用 Agent 架构包装传统扫描器。大多数通过 MCP 将模型与各类工具(发包、爬虫、指纹识别等)连接起来,试图让 AI 自主探索、组合工具、推导攻击路径,看起来“智能”“自动化”,但在真实黑盒安全场景中,这条路线存在根本性的工程与成本问题。MCP会不断尝试调用工具,然后根据结果修正答案,这样的操作会导致token消耗爆炸产生高额的费用,整体ROI其实为负

因此我认为,AI 在黑盒场景下的正确打开方式,不是无限制 Agent + MCP 调工具而是针对场景去挖掘漏洞

目前对于SSRF、SQL注入这些探测已经很成熟了,因此我觉得未来方向应该着重于逻辑漏洞挖掘


1.黑盒安全不是“探索型问题”,而是“验证型问题”

黑盒漏洞挖掘的核心并不在于“能不能想到攻击手法”,而在于:

  • 请求是否真实命中业务路径
  • 返回数据是否具备越权或敏感属性
  • 漏洞是否可稳定复现、可被证明成立

2.MCP 在黑盒场景下看起来智能,后期成本指数级失控,最终只能靠人工兜底

很多黑盒 MCP 服务在 Demo 中看起来效果不错,但问题往往出现在规模化运行之后:

  1. 请求数不可预测,模型为了提高“理解度”,会自然倾向于多次发包、多角度验证,但每一次都是真实成本。
  2. 工具调用链不可收敛, MCP 允许模型自由组合工具,但攻击链并不等于漏洞成立,复杂路径只会带来更多误报。
  3. 误报无法自动止损, AI 很容易给出“疑似漏洞”的判断,而这些“疑似”最终都需要人工复现,成本极高。

3.黑盒 AI 必须是“场景化裁判”,而不是“自由探索者”

真正可落地的黑盒 AI,不是让模型“自己决定下一步做什么”,而是先由人或规则系统把问题压缩成一个最小可验证场景

也就是说:

  • 场景先被定义(如 IDOR、越权、未授权访问、信息泄露)
  • 输入、对照条件、请求模板全部固定
  • 模型只负责判断结果是否成立

IDOR越权

流程设计

目前对于IDOR越权需要对多个参数进行构造和分析,会耗费大量的时间精力,因此我觉得AI赋能这个场景具有比较大的可塑性

image-20251226185049132

实现效果

1.处理成标准的输入格式,burp导出数据包,右键选择save items

image-20251225184509517

自动解析处理成规范输入格式,在demo目录生成随机文件夹用于后续分析

image-20251226155418681

2.根据数据包中参数让ai判断是否存在可遍历性,可遍历性参数生成测试用例

【AI分析判定规则】
✔ 认为“可遍历”的参数:
- 纯数字:1、12、12345
- 明显自增 ID:orderId、userId、uid、id、page
- 数字 + 简单前缀后缀(如:10001、20002)

✘ 认为“不可遍历”的参数:
- 高随机字符串
- 明显 UUID / hash / token
- 大小写字母 + 数字混合、长度较长的字符串
  例如:hjk2bvadn、A9xPqL0Zk

仅对“可遍历参数”继续后续步骤。

image-20251226155757838

3.调用net/http库进行发包

image-20251226162713151

根据PII、参数分析等规则划分为高中低风险

image-20251229154323772

4.结束在前端展示,输出消耗token费用和耗时

image-20251226162525983

输出风险参数及测试用例数据包

image-20251226162630427

promot提示词
你是一名专业的 Web 安全测试与越权漏洞挖掘专家,请严格按照以下步骤对给定的数据包列表进行越权分析,不要跳步,不要假设结果。

【输入】
我将提供一批 HTTP 数据包(GET / POST 请求),每个数据包包含:
- 请求方法
- URL
- 请求参数(GET 参数或 POST body)
- 原始响应状态码
- 原始响应内容长度

【分析目标】
判断接口是否可能存在 越权漏洞(IDOR / BOLA / 水平越权 / 垂直越权)。

--------------------------------------------------
【分析步骤】

第一步:参数提取
1. 如果是 GET 请求:
   - 提取 URL 中的所有参数,例如:
     /api/xxx?aaa=1&bbb=abc
2. 如果是 POST 请求:
   - 提取 body 中的参数,例如:
     ccc=1&ddd=3
   - JSON、form、x-www-form-urlencoded 均需解析

--------------------------------------------------
第二步:参数可遍历性判断
对每一个参数的值进行可遍历性分析:

【判定规则】
✔ 认为“可遍历”的参数:
- 纯数字:1、12、12345
- 明显自增 ID:orderId、userId、uid、id、page
- 数字 + 简单前缀后缀(如:10001、20002)

✘ 认为“不可遍历”的参数:
- 高随机字符串
- 明显 UUID / hash / token
- 大小写字母 + 数字混合、长度较长的字符串
  例如:hjk2bvadn、A9xPqL0Zk

仅对“可遍历参数”继续后续步骤。

--------------------------------------------------
第三步:控制变量法修改参数
对每一个可遍历参数,单独进行修改,其他参数保持完全不变。
修改每一个参数生成一个测试用例,与原数据包进行对比

【修改规则】
- 数字参数:+1 或 -1
  例如:
  12345 → 12346
- 每次只修改一个参数
- 不同时修改多个参数

--------------------------------------------------
第四步:响应对比分析
对比【原始请求】与【修改参数后的请求】的响应:

重点关注:
1. HTTP 状态码
2. 响应内容长度
3. 响应语义是否发生变化

--------------------------------------------------
第五步:越权判定逻辑(核心)

【疑似存在越权漏洞】
满足以下所有条件:
- 修改参数后返回 HTTP 状态码为 200
- 响应内容长度发生明显变化
- 未命中任何权限拒绝关键字
→ 判定为:⚠️ 疑似存在越权漏洞(需要人工进一步确认)

【判定为不存在越权漏洞】
满足任意一个条件:
- 返回 HTTP 状态码为 403
- 或响应内容命中以下任一权限拒绝关键字(大小写不敏感):

(?i)permission\s*denied
(?i)access\s*denied
(?i)\bforbidden\b
(?i)unauthorized
(?i)not\s*authorized
(?i)not\s*allowed
(?i)no\s*permission
(?i)permission\s*required
(?i)insufficient\s*permission
(?i)insufficient\s*permissions
(?i)insufficient\s*privilege
(?i)insufficient\s*privileges
(?i)authentication\s*failed
(?i)authentication\s*required
(?i)login\s*required
(?i)not\s*logged\s*in
(?i)session\s*expired
(?i)invalid\s*session
(?i)invalid\s*token
(?i)token\s*expired
(?i)token\s*invalid
(?i)missing\s*token
(?i)jwt\s*expired
(?i)jwt\s*invalid
(?i)role\s*not\s*allowed
(?i)role\s*denied
(?i)authorization\s*failed
(?i)permission\s*check\s*failed
(?i)access\s*control\s*deny
(?i)rbac\s*deny
(?i)policy\s*denied
(?i)policy\s*reject
(?i)resource\s*access\s*denied
(?i)resource\s*not\s*owned
(?i)not\s*your\s*resource
(?i)resource\s*not\s*(found|exist)
(?i)record\s*not\s*(found|exist)
(?i)request\s*blocked
(?i)request\s*denied
(?i)security\s*policy\s*violation
(?i)access\s*blocked
(?i)\b403\b

→ 判定为:✅ 当前参数未发现越权漏洞

--------------------------------------------------
第六步:结果输出格式(必须遵守)

对每一个接口输出以下内容:

- 接口路径
- 请求方法
- 可遍历参数列表
- 被修改的参数及修改方式
- 原始响应状态码 / 长度
- 修改后响应状态码 / 长度
- 判定结论:
  - 「疑似越权漏洞」
  - 或「未发现越权」

如无法判断,明确说明原因,不要猜测。
模型费用对比及选择

通过多轮测试,在生成测试样例和判断PII数据准确率方面,各模型性能差异性不大,因此优先选择价格更便宜的模型model

测试下来,gpt-4.1-nano兼顾速度和费用优先选择小任务可以选择Qwen

模型 描述 单接口消耗(USD) 单接口消耗(RMB) 推荐指数
gpt-5-nano 付费最便宜,主要是慢,一个请求需要等待3-5秒,不建议 $0.82 美分 $0.057 人民币 ⭐⭐
gpt-4.1-nano 成本略高于 5-nano,但判断更稳,速度快,推荐 $0.91 美分 0.064元人民币 ⭐⭐⭐⭐⭐
Qwen 免费,速度快,但是限频1分钟60次,容易429超时,数量少可选择 ⭐⭐⭐

浏览插件自动化点击触发API

现在基于API测试越权已经实现了,要想实现全自动化挖洞还需要尽可能全的数据包,在甲方场景我们可以通过捕获流量重放去实现

在渗透攻防的场景下,如果需要人工一个个点击显得有点呆了,因此决定开发一个浏览器插件自动化触发button事件点击和提交表单

https://github.com/Pizz33/Xiadian_browser

image-20251230112252903

智能元素识别

通过 isElementVisible() 函数进行识别button等点击元素

function findClickableElements() {
  const selectors = [
    'button:not([disabled])',
    'a[href]:not([href="#"]):not([href="javascript:void(0)"])',
    'input[type="submit"]:not([disabled])',
    'input[type="button"]:not([disabled])',
    '[role="button"]:not([disabled])',
    '[onclick]',
    '.btn:not([disabled])',
    '.button:not([disabled])',
    '[class*="button"]:not([disabled])',
    '[class*="btn"]:not([disabled])'
  ]
动态内容监听
const observer = new MutationObserver(() => {
  if (isRunning) {
  }
})

observer.observe(document.body, {
  childList: true,  // 监听子节点变化
  subtree: true     
})
脚本注入与消息传递
  • 延迟等待机制,确保脚本完全加载后再发送消息
  • 通过 chrome.tabs.sendMessage 实现跨模块通信
startBtn.addEventListener('click', async () => {
  const value = parseInt(inputValue.value) || 1
  console.log('[Popup] 开始按钮被点击,输入值:', value)

  // 重置统计
  updateStats(0, 0)

  // 保存状态
  if (chrome.storage && chrome.storage.local) {
    chrome.storage.local.set({
      isRunning: true,
      inputValue: value
    })
  }
主处理流程
  • 定时执行机制:使用 setInterval 每 2 秒执行一次,控制操作频率
  • 去重处理:使用 Set 数据结构记录已处理元素,避免重复操作
  • 逐个处理按钮:每次只处理一个可点击元素,避免操作过快导致页面异常
function processPage() {
  if (!isRunning) {
    console.log('[自动点击助手] 未运行,跳过处理')
    return
  }

  // 1. 查找所有可点击的元素
  const clickableElements = findClickableElements()

  // 2. 查找所有输入框
  const inputElements = findInputElements()
  console.log('[自动点击助手] 找到输入框:', inputElements.length, '个')

  // 3. 处理输入框(遍历所有未处理的)
  inputElements.forEach((input, index) => {
    if (!processedElements.has(input)) {
      console.log(`[自动点击助手] 处理输入框 ${index + 1}:`, input)
      fillInput(input)
      processedElements.add(input)
      filledCount++
      updateStats()
    }
  })

  // 4. 处理可点击元素(每次只点击一个,避免过快)
  if (clickableElements.length > 0) {
    const unprocessedElements = clickableElements.filter(el => !processedElements.has(el))
    if (unprocessedElements.length > 0) {
      const element = unprocessedElements[0]
      console.log('[自动点击助手] 准备点击元素:', element)
      clickElement(element)
      processedElements.add(element)
      clickedCount++
      updateStats()
    }
  }
}

流程设计优化

在满足我们的需求后,我们还可以对流程进行调整节省消耗

  • 每个文件夹独立调用AI分析 ---> 统一收集所有参数,一次性AI分析
  • AI调用次数 = API文件夹数量 ---> AI调用次数 = 1(参数分析)+ N(PII命中时的响应分析)
  • 测试用例生成 ---> AI测试用例直接生成(+1/-1),不调用AI
  • 处理顺序:串行处理每个文件夹 ---> 处理顺序:并行处理多个文件夹

image.png

详细对比

阶段 旧流程耗时 新流程耗时 优化比例
参数收集 10秒 8秒 20%↓
AI参数分析 100秒(100次调用) 3秒(1次调用) 97%↓
测试用例生成 50秒(AI生成) 1秒(直接生成) 98%↓
测试用例验证 120秒 100秒 17%↓
AI响应分析 20秒(50次调用) 8秒(20次调用) 60%↓
总计 300秒 120秒 60%↓

Token消耗对比

类型 旧流程 新流程 节省
参数分析Token 150K 2K 98.7%↓
响应分析Token 50K 20K 60%↓
总计 200K 22K 89%↓

题目描述

地上有⼀个 m ⾏和 n 列的⽅格。⼀个机器⼈从坐标(0,0) 的格⼦开始移动,每⼀次只能向左,右,上,下四个⽅向移动⼀格,但是不能进⼊⾏坐标和列坐标的数位之和⼤于 k 的格⼦。 例如,当k 为 18 时,机器⼈能够进⼊⽅格(35,37) ,因为 3+5+3+7 = 18 。但是,它不能进⼊⽅格(35,38) ,因为 3+5+3+8 = 19 。请问该机器⼈能够达到多少个格⼦?

示例1

输⼊:5,10,10
返回值:21

示例2

输⼊:10,1,100
返回值:29

说明:[0,0],[0,1],[0,2],[0,3],[0,4],[0,5],[0,6],[0,7],[0,8],[0,9],[0,10],[0,11],[0,12],[0,13],[0,14],[0,15],[0,16],[0,17],[0,18],[0,19],[0,20],[0,21],[0,22],[0,23],[0,24],[0,25],[0,26],[0,27],[0,28] 这29种,后⾯的[0,29] , [0,30] 以及[0,31] 等等是⽆法到达的。

思路及解答

DFS(深度优先搜索)

深度优先搜索算法,也就是 DFS ,⾸先需要初始化数组,注意是 boolean 类型的⼆元数组。边初始化
边计算位数的和,判断如果⼤于等于阈值的话,就直接置为 true ,也就是已经被访问到(但是这⼀部分计⼊结果)。

然后遍历每⼀个元素,只要 i , j 不在合法的索引范围或者是已经被访问过,都会直接返回
false 。

否则的话,可访问的数量 +1 ,并且递归遍历上下左右四个元素,返回最终的可访问的个数。

DFS 会优先同⼀个⽅向,⼀直⾛下去,不撞南墙不回头,直到条件不满⾜的时候,才会回头。回头之后,每次只会回头⼀步,往另外⼀个⽅向去,同样是⼀头扎进去。

假设有⼀个 4 x 4 的⽅格,从第⼀个开始遍历,假设遍历顺序是上,右,下,左,那么遍历的顺序如下:

public class Solution {
    public int movingCount(int threshold, int rows, int cols) {
        if (rows > 0 && cols > 0) {
            boolean[][] visited = new boolean[rows][cols];
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    // 如果⼤于阈值,设置已被访问过
                    visited[i][j] = ((getSum(i) + getSum(j)) > threshold);
                }
            }
            return getNum(visited, 0, 0, 0);
        }
        return 0;
    }
    
   // 获取可以被访问的个数
   private int getNum(boolean[][] visited, int i, int j, int count) {
        if (i < 0 || j < 0 || i >= visited.length || j >= visited[0].length ||
            visited[i][j]) {
            return count;
        }
        count++;
        visited[i][j] = true;
        count = getNum(visited, i, j + 1, count);
        count = getNum(visited, i, j - 1, count);
        count = getNum(visited, i + 1, j, count);
        count = getNum(visited, i - 1, j, count);
        return count;
   }
   
    // 计算位数之和
   private int getSum(int num) {
        int result = 0;
        while (num > 0) {
            result = result + num % 10;
            num = num / 10;
        }
        return result;
    }
}
  • 时间复杂度:最坏的情况是将所有的格⼦都遍历⼀遍, O(m*n) 。
  • 空间复杂度:借助了额外的空间保存是否被访问过,同样为O(m*n) 。

BFS(⼴度优先搜索)

⼴度优先搜索,也就是没进⾏⼀步,优先搜索当前点的各个⽅向上的点,不急着往下搜索,等搜索完当前点的各个⽅向的点,再依次把之前搜索的点,取出来,同样先搜索周边的点...

这样直到所有都被搜索完成。

同样有⼀个 4 x 4 的⽅格,从第⼀个开始遍历,假设遍历顺序是上,右,下,左,那么遍历的顺序如下:

在上⾯的过程图示中,我们可以发现,访问是有顺序的,每遍历⼀个新的⽅块,都会标⼀个顺序,然后按照顺序遍历其四个⽅向。

这也就是⼴度优先搜索的本质,我们需要⼀个队列,来保存遍历的顺序,每次都从队列⾥⾯取出⼀个位置,遍历其四周的⽅块,每次遍历到的点,都会放到队列⾥⾯,这样直到队列为空的时候,也就是全部遍历完成。

import java.util.LinkedList;
import java.util.Queue;

public class Solution13 {
    public int movingCount(int threshold, int rows, int cols) {
        boolean[][] visited = new boolean[rows][cols];
        int count = 0;
        
        Queue<int[]> queue = new LinkedList<>();
        // 把第⼀个点加到队列⾥⾯
        queue.add(new int[]{0, 0});
        
        while (queue.size() > 0) {
            // ⼀直取数据,直到队列为空
            int[] x = queue.poll();
            // 取出来的数据,包含x,y坐标
            int i = x[0], j = x[1];
            // 如果访问过或者不符合,直接下⼀个
            if (i >= rows || j >= cols || threshold < getSum(i) + getSum(j) || visited[i][j]) continue;
            
            // 置为访问过
            visited[i][j] = true;
            // 数量增加
            count++;
            // 右
            queue.add(new int[]{i + 1, j});
            // 下
            queue.add(new int[]{i, j + 1});
       }
       return count;
   }
   
    // 计算位数之和
   private int getSum(int num) {
        int result = 0;
        while (num > 0) {
            result = result + num % 10;
            num = num / 10;
        }
        return result;
    }
}
  • 时间复杂度:最坏的情况是将所有的格⼦都遍历⼀遍, O(m*n) 。
  • 空间复杂度:借助了额外的空间保存是否被访问过,同样为O(m*n) 。

动态规划(最优解)

利用递推关系式,避免重复计算。

  • 格子(i,j)可达 ⇔ 数位和满足条件 ∧ (左边格子可达 ∨ 上边格子可达)
  • dpi表示(i,j)是否可达,基于左边和上边格子的状态:dp[i][j] = (digitSum(i) + digitSum(j) ≤ k) && (dp[i-1][j] || dp[i][j-1])
public class Solution {
    public int movingCount(int m, int n, int k) {
        if (k == 0) return 1;
        
        // dp[i][j]表示格子(i,j)是否可达
        boolean[][] dp = new boolean[m][n];
        dp[0][0] = true;  // 起点可达
        int count = 1;     // 起点已计入
        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 跳过起点和数位和超限的情况
                if ((i == 0 && j == 0) || digitSum(i) + digitSum(j) > k) {
                    continue;
                }
                
                // 检查是否可以从左边或上边到达当前格子
                if (i - 1 >= 0) {
                    dp[i][j] |= dp[i - 1][j];  // 从上边来
                }
                if (j - 1 >= 0) {
                    dp[i][j] |= dp[i][j - 1];  // 从左边来
                }
                
                // 如果当前格子可达,计数加1
                count += dp[i][j] ? 1 : 0;
            }
        }
        
        return count;
    }
    
    private int digitSum(int num) {
        int sum = 0;
        while (num > 0) {
            sum += num % 10;
            num /= 10;
        }
        return sum;
    }
}
  • 时间复杂度:O(mn),双重循环遍历所有格子
  • 空间复杂度:O(mn),dp数组的空间

欢迎试玩 👏 https://aeriszhu.com/interlocking-puzzle/ 可以说一下游戏反馈怎么样,下面是一些截图和 gif 。









备注:仓库在 https://github.com/anig1scur/interlocking-puzzle

备注:所有 Puzzle 数据来自于 https://github.com/Linsanity81/High-LevelPuzzle/tree/main/puzzle 感谢


补充一个操作手册:

拖拽:旋转视角
双击:选中 piece
拖拽高亮的 piece / 方向键 + WS 键:拖拽 piece
空格:进入透视模式
R:重置