包含关键字 typecho 的文章

重塑传统自动化漏洞挖掘的Multi-Agent框架攻防一体化实践

前段时间在某大厂做安全研究时,针对SDLC的重复性审计工作结合大模型Agent思索了一些可行的思路,便在不断摸索中构建了一个Multi-Agent的协同漏洞挖掘框架系统,目前个人使用来看对于开源的web应用的实战效果相比传统的SAST、DAST以及纯LLM的漏洞挖掘工具来说还是很不错的,便记录此篇框架实现过程和当今Agent赋能漏挖的可行性与优势供师傅们交流指点....

0x00 传统漏洞挖掘的困局

当前针对Web应用后端的自动化漏洞挖掘技术主要受困于“覆盖率”与“准确性”难以两全的矛盾:

  • 传统的静态分析技术虽能提供全量的代码覆盖,但由于缺乏对程序运行时状态和复杂业务逻辑的语义理解,往往导致海量的误报噪声,极大地增加了安全工程师的审计成本
  • 而动态应用程序安全测试虽能在黑盒方面挖掘漏洞更具真实性,却受限于黑盒视角的路径探索能力,难以触及深层业务逻辑,会存在很多漏报
  • 目前大语言模型的出现为代码语义分析带来了新的契机,但受限于Context Window 的约束以及生成式模型固有的幻觉问题,直接依赖原生LLM进行大规模代码审计往往导致分析结果碎片化且缺乏可信度,并且直接将代码喂给大模型容易受与漏洞无关代码的影响

0x01 探索漏洞挖掘框架的新出路?

在探索新的框架实现时,我们可以思考是否能将黑白盒的现有技术互补结合来引导漏洞挖掘?以及我们可以看到几年LLM与Agent相关技术如MCP、RAG的工程化落地,能否用LLM赋于框架更好的语义理解和丰富的上下文能力,再通过Agent做一套自动化流程?

为突破上述技术瓶颈,我在探索新的漏洞挖掘框架时也看了一些目前学术界的相关LLM赋能的研究与github开源的技术实现,总体的探索方法还是在论文与现实实践中思考各个方面的优势与缺陷,最终确定做一个基于Muti-Agent协同的智能化漏洞挖掘框架:构建一个从静态分析到动态验证的闭环生态。技术上引入MCP 来作为连接LLM推理能力与静态分析工具的桥梁,利用RAG 技术通过构建高质量漏洞专家知识库来校准模型判定,深度缓解LLM的“幻觉”与知识盲区;同时,结合运行时自动化的流量Fuzz模糊测试技术,将白盒的逻辑推演与黑盒的攻击验证深度融合,减少漏洞的误报和漏报。

这里放一个当时挖到的有CNVD证书的水洞,通过项目上传与聊天,自动化分析审计出多处SQL注入漏洞,并且能够给出攻击POC,以及后续完整的修复方案

image.png

0x02 框架核心:打破黑白盒壁垒

该框架核心架构旨在重构传统安全检测的边界,提出了一种 “白盒语义指引黑盒,黑盒动态验证白盒”的深度融合范式。框架并非单一工具的线性叠加,而是一个基于Multi-Agent编排(Agent Orchestration)的异构系统。

  • 白盒分析维度:框架引入了MCP作为智能体的执行接口,驱动底层的静态分析工具与正则匹配引擎,对代码AST进行初步扫描,快速锚定潜在的危险函数调用Sink。为解决静态分析中常见的上下文缺失问题,进一步融合了RAG 技术:通过引入高质量的博客记录的高精度漏洞知识库,系统能够为大语言模型提供特定漏洞类型的完备的Context上下文与判定依据,从而在保持高代码覆盖率的同时,抑制传统模式匹配带来的误报,实现了从“语法”到“语义”的代码的全面理解提升。
  • 黑盒验证维度:框架构建了运行时的自动化Fuzz模糊测试。该模块独立承担着对Web通用漏洞(如XSS、SQL注入)及敏感信息泄露的覆盖任务。当白盒Agent发现疑似逻辑漏洞时,通过黑盒上的Fuzz可在流量侧生成针对性的变异Payload进行动态优化,通过分析HTTP响应状态来实证漏洞的可利用性。

我认为将静态视角的逻辑推演与动态视角的攻击验证相结合的机制,能极大地提升了漏洞检测的置信度,实现了真正意义上的全链路攻防评估,刚开始时候画的大致架构草图,仅贴示了主要功能,一些细节实现并未展示:

image.png

0x03 智能化Agent设计细节

1. Static Orchestration Agent:基于MCP协议的异构工具编排

在传统的LLM应用中,模型往往被禁锢在文本交互的孤岛中,难以触及本地庞大的代码仓库,且面临着Context Window对海量代码理解的限制。本框架设计的漏洞定位Agent,本质上是一个 静态分析增强型智能体(Static Orchestration Agen) ,通过引入MCP与构建Prompt定义角色任务将LLM从被动的文本生成者转变为主动的工具使用者,通过静态分析获取代码结构中的丰富语义上下文

MCP驱动的“深层感知”

不同于简单的API调用,MCP协议使得Agent能够理解工具的输入输出Schema,实现复杂的推理链条:

  • 工具与模型的语义对齐:通过定义标准化的MCP接口,将底层的静态代码分析工具封装为LLM可调用的能力。
  • 意图驱动的执行:构造合适的CoT思维链Prompt让Agent根据当前的分析任务代码(例如“寻找未授权访问漏洞”),自主决策调用何种工具、传入何种参数。这可以让Agent模拟安全专家的思维过程,主动去探测代码中的漏洞点。

SINK点定位与攻击面收敛

针对LLM处理大规模代码时的“大海捞针”难题,高效定位漏洞利用链

  • SINK点精准锚定:Agent并不直接阅读全量代码,而是利用MCP驱动底层扫描器,基于AST解析和高精度的正则模式,快速提取代码中的SINK点(需要根据不同语言类型的不同漏洞进行扩充分类)

image.png

  • 代码切片与上下文聚焦:一旦定位到SINK点,系统会通过静态分析工具获取sink点污染的上下文Code Slice,并且做到变量语句级,将无关语句统统移除(这里详细的实现师傅们可以去阅读Joern等工具的源码和他的论文,主要在于CPG代码属性图的构建和后向切片等算法技术)。极大地收敛了分析范围,过滤大量无关业务代码,确保输送给LLM进行深度研判的每一行代码都具有潜在的安全价值(无论是控制流还是数据依赖流都对漏洞的存在有潜在的约束和影响)。这不仅大幅降低了Token消耗,更显著提升了后续漏洞验证的准确性。

2. Contextual Reasoning Agent:基于RAG的领域知识增强与检索优化

作为本框架保障检测精度的核心组件,校验 Contextual Reasoning Agent承担着“校验”的角色。针对通用大语言模型在特定安全领域存在的专业知识匮乏逻辑幻觉 问题,本模块引入RAG 技术,人为构建了一个可随时扩展的领域专家知识文档库,通过实时注入精确的先验知识来约束和校准模型的推理过程。

RAG知识库的结构化重构与向量化

为了让非结构化的安全知识能够被机器高效理解,摒弃粗暴的文本截断,采用基于Markdown语法树的结构化清洗策略。系统依据标题层级对海量的漏洞PoC、修复方案及原理分析文档进行逻辑切分,确保每个Chunk都包含完整的语义单元

例如一个简易的MARKDOWN文档:

image.png

动态滑窗与重叠分块策略

在知识切片过程中,为了规避硬切分导致的语义断层,切片策略采用基于重叠策略(Overlapping Strategy)的动态滑窗机制

  • 语义连贯性保障:设定固定的Token阈值作为基础窗口大小,同时引入预设比例的重叠缓冲区。每一分块的末尾段落会被完整保留并作为下一分块的起始上下文。
  • 边界信息无损传输:这种机制确保了跨越分块边界的逻辑描述(如一段跨越多行的代码逻辑或长难句的漏洞解释)不会被割裂,保证了向量检索时上下文信息的完整性与连贯性。

image.png

向量检索与推理运行

采用all-MiniLM-L6-v2模型作为Embedding引擎。该模型在保持低延迟推理的同时,在多语言的语义相似度任务上有更好的泛化能力;数据库采用集成Qdrant向量数据库,支撑大规模向量的高并发检索

  • 上下文感知的推理校准:当定位Agent上报疑似SINK点时,校验Agent会提取当前代码特征,在向量库中实时检索最相似的Top-K个历史漏洞模式和修复示例。这些检索结果被作为增强上下文 注入到LLM的Prompt中,迫使模型基于检索到的“事实依据”而非单纯的概率预测进行最终判定,减少了误报的产生

0x04 动态流量FUZZ

我从以往的安全研究触发,针对通用型漏洞的工具做了大量的调研,并基于BurpSuite原生API开发了自动化Fuzz工具如:反射性和存储型XSS、SSRF、CORS、敏感信息泄露等(同时也是在锻炼开发能力,也让日常重复性漏洞渗透工作能够做的更高效),再结合MCP集成给Agent。该模块并非简单的随机测试,而是作为一个流式检测组件,实时拦截、解析并重放业务流量,对潜在漏洞动态扫描。而对于敏感信息泄露则是比较容易 ,针对Spring Boot Actuator、Swagger UI、Druid Monitor等常见中间件的指纹来做识别。同时,结合模式匹配,对响应包中的JWT Token、阿里云AK/SK、AWS凭证等高熵字符串进行实时监测,有效发现硬编码或调试信息泄露。

下面挑了几个通用型漏洞的Fuzz来做简单做下原理解释

1. 通用XSS漏洞的自动化Fuzz

比如针对XSS反射型和存储型漏洞,开发时采用了全量参数解析+动态污点标记的检测策略,确保对异构http包结构中参数的全面覆盖。

  • 深度参数提取与结构化解析
    不仅仅局限于URL Query参数,还有针对JSON、XML、Multipart-form等多种数据格式的解析器。能够递归遍历HTTP Request Body中的每一层嵌套结构,提取所有用户可控的叶子节点作为Fuzz入口。
  • 唯一性污点标记
    为了解决并发扫描时的结果混淆问题,引擎摒弃了静态Payload,转而采用动态生成的唯一性测试标记


    • Payload构造:Timestamp + RandomStr + Vector(例如:CurrentTime等高熵字符串)
    • 状态映射表:内存中维护一张高并发的HashMap,记录RequestID <-> ParameterName <-> UniquePayload的映射关系。
    • 响应回显与验证
      发送测试请求后,引擎自动捕获HTTP Response,通过高效的字符串匹配算法检索之前的唯一标记。一旦检测到标记回显且上下文未经过滤(如HTML实体编码缺失),即判定存在可疑XSS漏洞,并自动关联原始请求数据生成漏洞条目。

(当时研究设计思路时绘制的草图)

image.png

2. 访问控制与配置缺陷的CORS漏洞检测

自动化Fuzz HTTP请求头中的Origin字段,构造包括恶意第三方域名、特殊字符(如null)及子域名在内的多种变异Payload

  • 高危利用判定:当响应头Access-Control-Allow-Origin和攻击者Payload一样或为小写null,且同时存在Access-Control-Allow-Credentials: true时,将其标记为高危漏洞。此类配置允许攻击者绕过同源策略(SOP)窃取用户敏感数据
  • 严格语法校验:针对协议规范的边缘场景进行校验,例如检测到Access-Control-Allow-Origin: Null(大写)时,引擎会自动识别其为无效配置(浏览器不识别大写Null),从而将其作为无效处理
    以及服务端错误配置导致Access-Control-Allow-Origin始终和Origin一样,这里放一张示例图便于理解:

image.png

0x05 构建认知型安全智能体的未来图景

在对Multi-Agent探索自动化漏洞挖掘实践的探索过程中,其实我们一直在试图回答一个核心问题:如何在安全攻防领域,构建一个具备“感知-推理-决策-行动”完整闭环的智能系统。目前的Agent主要还停留在“检测与验证”阶段,之后更完备的阶段是自动化环境的感知探索与白盒源码的结合,以及能够基于当前的Shell环境或数据库权限,自主规划后续的横向移动与权限提升路径。另一个重要的方面是自适应Payload生成:比如利用强化学习反馈机制,让Agent在面对WAF拦截时,能够动态调整Payload的混淆策略,实现智能化的WAF绕过

希望本文的实践能为各位师傅提供一种新的视角供师傅们交流指点~

struct类型的定义以关键字struct开头,后跟struct的名字,接着是定义在一对花括号中的struct定义体。struct定义体中可以定义一系列的成员变量、成员属性、静态初始化器、构造函数和成员函数。

定义struct类型

以下是定义struct类型的一个示例:

struct Rectangle {
    let width: Int64
    let height: Int64

    public init(width: Int64, height: Int64) {
        this.width = width
        this.height = height
    }

    public func area() {
        width * height
    }
}

上例中定义了名为Rectangle的struct类型,它有两个Int64类型的成员变量width和height,一个有两个Int64类型参数的构造函数init,以及一个成员函数area,用于返回width和height的乘积。

1. struct成员变量

struct成员变量分为实例成员变量和静态成员变量(使用static修饰符修饰,且必须有初值),二者访问上的区别在于实例成员变量只能通过struct实例访问,静态成员变量只能通过struct类型名访问。

实例成员变量定义时可以不设置初值(但必须标注类型),如上例中的width和height。也可以设置初值,例如:

struct Rectangle {
    let width = 10
    let height = 20
}

2. struct静态初始化器

struct支持定义静态初始化器,并在静态初始化器中通过赋值表达式来对静态成员变量进行初始化。

静态初始化器以关键字组合static init开头,后跟无参参数列表和函数体,且不能被访问修饰符修饰。函数体中必须完成对所有未初始化的静态成员变量的初始化,否则编译报错。

struct Rectangle {
    static let degree: Int64
    static init() {
        degree = 180
    }
}

一个struct中最多允许定义一个静态初始化器,否则报重定义错误。

struct Rectangle {
    static let degree: Int64
    static init() {
        degree = 180
    }
    static init() { // 错误!用前面的静态init函数重新定义
        degree = 180
    }
}

3. struct构造函数

struct支持两类构造函数:普通构造函数和主构造函数。

普通构造函数以关键字init开头,后跟参数列表和函数体,函数体中必须完成对所有未初始化的实例成员变量的初始化,否则编译报错。

struct Rectangle {
    let width: Int64
    let height: Int64

    public init(width: Int64, height: Int64) { // 错误! 'height'未在构造函数中初始化
        this.width = width
    }
}

一个struct中可以定义多个普通构造函数,但它们必须构成重载,否则报重定义错误。

struct Rectangle {
    let width: Int64
    let height: Int64

    public init(width: Int64) {
        this.width = width
        this.height = width
    }

    public init(width: Int64, height: Int64) { // 正确!用第一个init函数重载
        this.width = width
        this.height = height
    }

    public init(height: Int64) { // 错误!使用第一个init函数重新定义
        this.width = height
        this.height = height
    }
}

除了可以定义若干普通的以init为名字的构造函数外,struct内还可以定义(最多)一个主构造函数。主构造函数的名字和struct类型名相同,它的参数列表中可以有两种形式的形参:普通形参和成员变量形参(需要在参数名前加上let或var),成员变量形参同时扮演定义成员变量和构造函数参数的功能。

使用主构造函数通常可以简化struct的定义,例如,上述包含一个init构造函数的Rectangle可以简化为如下定义:

struct Rectangle {
    public Rectangle(let width: Int64, let height: Int64) {}
}

主构造函数的参数列表中也可以定义普通形参,例如:

struct Rectangle {
    public Rectangle(name: String, let width: Int64, let height: Int64) {}
}

如果struct定义中不存在自定义构造函数(包括主构造函数),并且所有实例成员变量都有初始值,则会自动为其生成一个无参构造函数(调用此无参构造函数会创建一个所有实例成员变量的值均等于其初值的对象);否则,不会自动生成此无参构造函数。例如,对于如下struct定义,注释中给出了自动生成的无参构造函数:

struct Rectangle {
    let width: Int64 = 10
    let height: Int64 = 10
    /* Auto-generated memberwise constructor:
    public init() {
    }
    */
}

4. struct成员函数

struct成员函数分为实例成员函数和静态成员函数(使用static修饰符修饰),二者的区别在于:实例成员函数只能通过struct实例访问,静态成员函数只能通过struct类型名访问;静态成员函数中不能访问实例成员变量,也不能调用实例成员函数,但在实例成员函数中可以访问静态成员变量以及静态成员函数。

下例中,area是实例成员函数,typeName是静态成员函数。

struct Rectangle {
    let width: Int64 = 10
    let height: Int64 = 20

    public func area() {
        this.width * this.height
    }

    public static func typeName(): String {
        "Rectangle"
    }
}

实例成员函数中可以通过this访问实例成员变量,例如:

struct Rectangle {
    let width: Int64 = 1
    let height: Int64 = 1

    public func area() {
        this.width * this.height
    }
}

5. struct成员的访问修饰符

struct的成员,包括成员变量、成员属性、构造函数、成员函数、操作符函数,可以用4种访问修饰符修饰:private、internal、protected和public,缺省的修饰符是internal。

  • private表示在struct定义内可见。
  • internal表示仅当前包及子包内可见。
  • protected表示当前模块可见。
  • public表示模块内外均可见。

下面的例子中,width是public修饰的成员,在类外可以访问,height是缺省访问修饰符的成员,仅在当前包及子包可见,其他包无法访问。

package a
publicstructRectangle {
    public var width: Int64
    var height: Int64
    private var area: Int64
    ...
}

func samePkgFunc() {
    var r = Rectangle(10, 20)
    r.width = 8               // Ok: public 'width' can be accessed here
    r.height = 24             // Ok: 'height' has no modifier and can be accessed here
    r.area = 30               // 错误!, private 'area' can't be accessed here
}
package b
import a.*
main() {
    var r = Rectangle(10, 20)
    r.width = 8               // Ok: public 'width' can be accessed here
    r.height = 24             // 错误!, no modifier 'height' can't be accessed here
    r.area = 30               // 错误!, private 'area' can't be accessed here
}

6. 禁止递归struct

递归和互递归定义的struct均是非法的。例如:

struct R1 { // 错误!'R1' 递归引用自身
    let other: R1
}
struct R2 { // 错误!'R2' 和 'R3' 递归引用自身
    let other: R3
}
struct R3 { // 错误!'R2' 和 'R3' 递归引用自身
    let other: R2
}

创建struct实例

定义了struct类型后,即可通过调用struct的构造函数来创建struct实例。在struct定义之外,通过struct类型名调用构造函数。例如,下例中定义了一个Rectangle类型的变量r。

let r = Rectangle(10, 20)

创建了struct实例之后,可以通过实例访问它的(public修饰的)实例成员变量和实例成员函数。例如,下例中通过r.width和r.height可分别访问r中width和height的值,通过r.area()可以调用r的成员函数area。

let r = Rectangle(10, 20)
let width = r.width   // width = 10
let height = r.height // height = 20
let a = r.area()      // a = 200

如果希望通过struct实例去修改成员变量的值,需要将struct类型的变量定义为可变变量,并且被修改的成员变量也必须是可变成员变量(使用var定义)。举例如下:

struct Rectangle {
    public var width: Int64
    public var height: Int64

    public init(width: Int64, height: Int64) {
        this.width = width
        this.height = height
    }

    public func area() {
        width * height
    }
}

main() {
    var r = Rectangle(10, 20) // r.width = 10, r.height = 20
    r.width = 8               // r.width = 8
    r.height = 24             // r.height = 24
    let a = r.area()          // a = 192
}

在赋值或传参时,会对struct实例进行复制,生成新的实例,对其中一个实例的修改并不会影响另外一个实例。以赋值为例,下面的例子中,将r1赋值给r2之后,修改r1的width和height的值,并不会影响r2的width和height值。

struct Rectangle {
    public var width: Int64
    public var height: Int64

    public init(width: Int64, height: Int64) {
        this.width = width
        this.height = height
    }

    public func area() {
        width * height
    }
}

main() {
    var r1 = Rectangle(10, 20) // r1.width = 10, r1.height = 20
    var r2 = r1                // r2.width = 10, r2.height = 20
    r1.width = 8               // r1.width = 8
    r1.height = 24             // r1.height = 24
    let a1 = r1.area()         // a1 = 192
    let a2 = r2.area()         // a2 = 200
}

mut函数

struct类型是值类型,其实例成员函数无法修改实例本身。例如,下例中,成员函数g中不能修改成员变量i的值。

struct Foo {
    var i = 0

    public func g() {
        i += 1  // 错误!无法在实例成员函数中修改实例成员变量的值
    }
}

mut函数是一种可以修改struct实例本身的特殊的实例成员函数。在mut函数内部,this的语义是特殊的,这种this拥有原地修改字段的能力。

:只允许在interface、struct和struct的扩展内定义mut函数,禁止在class中定义mut函数。

mut函数与普通的实例成员函数相比,多一个mut关键字来修饰。

例如,下例中在函数g之前增加mut修饰符之后,即可在函数体内修改成员变量i的值。

struct Foo {
    var i = 0

    public mut func g() {
        i += 1  // 正确
    }
}

参考引用

背景

在开发“智能带办”应用时涉及到用户体系,开发阶段使用固定验证码形式跑通,在上线前准备接入短信服务时却遇到了难题,短信服务目前只对企业开发者开放了,个人开发者没办法再使用短信服务。为了顺利上架,退后求其次,改为了使用邮箱验证码等了。

邮箱验证码登录有两个弊端,一是不方便,很多用户进来发现是邮箱验证码登录不方便直接就退出应用了;二是合规风险,在申请安全评估报告时如果涉及到用户体系要求实名,邮箱没办法保证实名,还得再加入额外的实名体系,不仅麻烦而且很多都限制个人开发者没法使用。

其实最开始也考虑过要接入华为登录,看了一键登录文档发现也是只针对企业开发者,以为也是只有企业开发者可以使用,后面看了“华为账号登录”后发现个人开发者也可以使用,只是取不到手机号,正好不使用手机号可以规避合规方面的风险。
image.png

华为登录能力介绍

华为账号服务简介

Account Kit(华为账号服务)提供简单、快速、安全的登录功能,让用户快捷地使用华为账号登录应用。用户授权后,Account Kit可提供头像、昵称、手机号码等信息,帮助应用更了解用户。华为账号服务提供了登录、获取华为账号用户信息、未成年模式等。在开发过程中涉及下面几个概念:

  • OpenID:应用维度用户标识符,是华为账号用户在应用/元服务的唯一标识。不同应用/元服务(不管是否在同一个开发者账号下)获取到用户的OpenID不同。
  • UnionID:开发者维度用户标识符,华为账号用户同一开发者账号下的唯一标识。开发者有多个应用/元服务时,同一个开发者账号下的应用/元服务获取到用户的UnionID相同。
  • GroupUnionID:关联主体账号组维度用户标识符,是华为账号用户在关联主体账号组内的唯一标识。不同开发者账号加入同一关联主体账号组后,其组内所有开发者的应用/元服务获取到用户的GroupUnionID相同。
  • permission:数据或接口权限,通过该权限判断应用是否能获取对应数据或调用对应接口。
  • scopes:scope列表,用于获取用户数据。开发者向华为账号服务申请不同类型用户数据的标识。比如头像昵称(profile)、匿名手机号(quickLoginAnonymousPhone)等。
  • Authorization Code:授权码,用户使用华为账号登录成功之后,可通过返回的凭据解析出授权码,通过授权码可获取Access Token、Refresh Token、ID Token等。
  • Access Token:访问凭证,是访问被权限管控资源的应用级凭证。可使用Access Token调用获取用户信息接口获取用户信息。
  • ID Token:用户身份凭证,是OIDC (OpenID Connect) 协议相对于OAuth 2.0 协议扩展的一个用户身份凭证,包含用户信息。用户使用华为账号登录成功之后,可通过返回的凭据解析出Authorization Code、ID Token等数据。

在我们接口华为用户服务后,可以使用OpenId和UnionID绑定我们自己的账号体系。

华为账号服务交互流程

由于个人开发者无法使用“一键登录”,本文主要介绍 “华为账号登录”按钮登录。使用按钮登录我们可以使用Account Kit提供的华为账号登录按钮及服务端交互获取华为账号用户身份标识UnionID、OpenID,通过UnionID、OpenID完成用户登录;或者与应用账号完成绑定,绑定后用于登录或者验证。

华为账号登录按钮包含文本、标志和文本、标志三种样式,以满足应用对界面风格一致性和灵活性的要求。
image.png

账号服务开发者与华为能力交互流程如下图所示:
image.png

交互流程说明如下:
流程说明:

  1. 调用登录按钮展示登录页阶段(序号1-3):

    1. 用户打开应用进行登录,应用设置LoginType类型为LoginType.ID后拉起应用自己的登录页并展示“华为账号登录”按钮,用户点击按钮,请求华为账号授权信息。
  2. 用户点击登录阶段(序号4-6):

    1. 如华为账号未登录,将拉起华为账号登录页,用户登录后,将返回Authorization Code等数据给应用。
    2. 如华为账号已登录,将直接返回Authorization Code等数据给应用。
  3. 用户关联应用账号阶段(序号7-16):

    1. 应用服务端通过Authorization Code获取到Access Token,再使用Access Token调用解析凭证接口获取用户相关信息。通过Authorization Code凭证获取用户信息可以有效避免黑客通过数据遍历、身份伪造、重放攻击等手段导致的安全风险。
    2. 应用服务端将业务登录凭证SessionId、UnionID/OpenID传给应用,应用获取到UnionID/OpenID可用于判断华为账号是否登录等功能。
    3. 应用对用户身份标识UnionID/OpenID、业务登录凭证SessionId信息进行认证后,通过UnionID/OpenID判断用户是否已关联应用系统数据库,如已关联,则完成用户登录;如未关联,则创建新用户,绑定UnionID/OpenID。

华为账号服务提供了LoginWithHuaweiIDButton组件,构造中需要传入LoginWithHuaweiIDButtonParams类型和 LoginWithHuaweiIDButtonController类型的参数,LoginWithHuaweiIDButtonParams属性如下:

名称类型只读可选说明
styleStyleLoginWithHuaweiIDButton组件的样式。支持样式包括:BUTTON_RED、BUTTON_WHITE、BUTTON_WHITE_OUTLINE、BUTTON_BLACK、ICON_RED、ICON_WHITE、ICON_WHITE_OUTLINE、ICON_BLACK、ICON_GRAY、BUTTON_GRAY、BUTTON_CUSTOM。
borderRadiusnumber按钮边框圆角半径。取值范围:[0,+∞),值小于0时,按0处理。默认值:height属性取值的一半。单位:vp。
iconRadiusnumberIcon类型按钮的半径。取值范围:[0,+∞),值小于0时,按0处理。默认值:24。单位:vp。
supportDarkModeboolean表示按钮的样式是否随系统深浅色模式变化。true:按钮的样式会随着系统深浅色模式变化。false:按钮的样式不会随着系统深浅色模式变化。默认值:true。
loginTypeLoginType华为账号登录类型。默认值:LoginType.ID。一键登录请使用LoginType.QUICK_LOGIN。
textAndIconStyleboolean是否展示图文混合样式的华为账号登录按钮。true:按钮支持Icon和文字混合样式。false:按钮仅支持文本样式。默认值:false。当loginType不等于LoginType.QUICK_LOGIN且style等于BUTTON_RED、BUTTON_WHITE、BUTTON_WHITE_OUTLINE、BUTTON_BLACK、BUTTON_GRAY时该参数生效。起始版本:5.0.0(12)
customButtonParamsCustomButtonParamsBUTTON_CUSTOM按钮样式参数。起始版本:5.0.0(12)
verifyPhoneNumberboolean华为账号用户在过去90天内未进行短信验证,是否拉起Account Kit提供的短信验证码页面。true:拉起Account Kit提供的短信验证码页面。false:不拉起Account Kit提供的短信验证码页面。需要应用验证手机号时效性。默认值:true。起始版本:5.0.0(12)
extraStyleExtraStyle如果应用想使用华为账号提供的固定样式之外的效果,可使用此接口自定义按钮样式。起始版本:5.0.0(12)
loginButtonTextTypeLoginButtonTextType当loginType为LoginType.QUICK_LOGIN时,可传入此参数,控制按钮文本内容显示。默认值:LoginButtonTextType.QUICK_LOGIN。当该参数为LoginButtonTextType.QUICK_LOGIN时,按钮文本内容显示“华为账号一键登录”。当该参数为LoginButtonTextType.QUICK_REGISTRATION时,按钮文本内容显示“华为账号一键注册”。起始版本:5.0.0(12)
riskLevelboolean是否需要获取华为账号用户风险等级。仅登录类型为LoginType.QUICK_LOGIN时需要设置该参数。true:需要获取用户风险等级。false:不获取用户风险等级。默认值:false。起始版本:5.1.0(18)
securityVerificationboolean用户开启华为账号一键登录增强身份验证后,应用会在登录过程中通过华为账号使用生物识别或短信进行身份验证。如果需要获取用户一键登录增强身份验证的开关状态,需设置该字段为false。仅登录类型为LoginType.QUICK_LOGIN时需要设置该参数。true:响应结果HuaweiIDCredential将不会返回 enableSecurityVerification。false:响应结果HuaweiIDCredential将返回 enableSecurityVerification。默认值:true。起始版本:6.0.0(20)

智能带办接入过程

目前应用只支持华为登录,页面UI如下:
image.png

在页面中配置红色的LoginWithHuaweiIDButton:

LoginWithHuaweiIDButton({  
    params: {  
      // LoginWithHuaweiIDButton支持的样式  
      style: loginComponentManager.Style.BUTTON_RED,  
      // 账号登录按钮在登录过程中展示加载态  
      extraStyle: {  
        buttonStyle: new loginComponentManager.ButtonStyle().loadingStyle({  
          show: true  
        })  
      },  
      // LoginWithHuaweiIDButton的边框圆角半径  
      borderRadius: 24,  
      // LoginWithHuaweiIDButton支持的登录类型  
      loginType: loginComponentManager.LoginType.ID,  
      // LoginWithHuaweiIDButton支持按钮的样式跟随系统深浅色模式切换  
      supportDarkMode: true  
    },  
    controller: this.controller  
  })  
}  
.height(40)  
.width('100%')  
.margin({top:50})  
.padding({left:25, right:25})

控制器controller定义如下:

controller: loginComponentManager.LoginWithHuaweiIDButtonController =  
  new loginComponentManager.LoginWithHuaweiIDButtonController()  
    .setAgreementStatus(loginComponentManager.AgreementStatus.NOT_ACCEPTED)  
    .onClickLoginWithHuaweiIDButton((error: BusinessError, response: loginComponentManager.HuaweiIDCredential) => {  
      if (error) {  
        this.dealAllError(error);  
        return;  
      }  
  
      if (response) {  
        Logger.i(TAG, 'Succeeded in getting response.');  
        const authCode = response.authorizationCode;  
        // 开发者处理authCode  
        this.getUserInfoPermission(authCode)  
      }  
    });

在controller中获取回调,如果登录成功则通过authorizationCode继续申请用户华为头像和昵称授权:

getUserInfoPermission(authCode:string){  
  // 创建授权请求,并设置参数  
  const authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();  
  // 获取头像昵称需要传如下scope  
  authRequest.scopes = ['profile'];  
  // 若开发者需要进行服务端开发以获取头像昵称,则需传如下permission获取authorizationCode  
  authRequest.permissions = ['serviceauthcode'];  
  // 用户是否需要登录授权,该值为true且用户未登录或未授权时,会拉起用户登录或授权页面  
  authRequest.forceAuthorization = true;  
  // 用于防跨站点请求伪造  
  authRequest.state = util.generateRandomUUID();  
  // 执行授权请求  
  try {  
    const controller = new authentication.AuthenticationController(this.getUIContext().getHostContext());  
    controller.executeRequest(authRequest).then((data) => {  
      const authorizationWithHuaweiIDResponse = data as authentication.AuthorizationWithHuaweiIDResponse;  
      const state = authorizationWithHuaweiIDResponse.state;  
      if (state && authRequest.state !== state) {  
        Logger.i(TAG, `Failed to authorize. The state is different, response state: ${state}`);  
        return;  
      }  
      Logger.i(TAG,'Succeeded in authentication.');  
      const authorizationWithHuaweiIDCredential = authorizationWithHuaweiIDResponse?.data;  
      const avatarUri = authorizationWithHuaweiIDCredential?.avatarUri;  
      const nickName = authorizationWithHuaweiIDCredential?.nickName;  
      // 开发者处理avatarUri, nickName  
      const authorizationCode = authorizationWithHuaweiIDCredential?.authorizationCode;  
      Logger.i(TAG, 'getUserInfoPermission:' + JsonUtils.toJSONString(authorizationWithHuaweiIDCredential))  
      this.sendLoginRequest(authorizationCode??authCode)  
      // 涉及服务端开发以获取头像昵称场景,开发者处理authorizationCode  
    }).catch((err: BusinessError) => {  
      this.dealAllError(err);  
    });  
  } catch (error) {  
    this.dealAllError(error);  
  }  
}

用户授权成功后请求服务端接口,服务端通过authorizationCode调用华为服务获取accessToken,接着获取用户信息,绑定自己的账号体系返回自己账号体系的token即可。通过下面接口获取用户级凭证:

POST /oauth2/v3/token HTTP/1.1
Host: oauth-login.cloud.huawei.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=<code>&client_id=<client_id>&client_secret=<client_secret>

接着通过下面示例获取用户昵称和头像:

POST /rest.php?nsp_svc=GOpen.User.getInfo HTTP/1.1
Host: account.cloud.huawei.com
Content-Type: application/x-www-form-urlencoded

access_token=<Access Token>

必须在手机上调起授权获取用户授权后这里才可以请求到用户头像和昵称。

总结

本次“智能带办”应用的登录体系接入实践,源于上线前短信服务仅对企业开发者开放的限制,迫使我们从固定验证码、邮箱验证码转向华为账号登录方案。初期因误判“一键登录”仅限企业开发者而忽略“华为账号登录”,后发现个人开发者虽无法获取手机号,但恰好规避了邮箱登录的用户体验差(用户因不便退出)与实名合规风险(需额外实名体系),成为关键破局点。

华为账号服务(Account Kit)通过OpenID(应用唯一标识)、UnionID(开发者唯一标识)等核心概念,为个人开发者提供了安全高效的登录能力:既支持自定义样式的登录按钮(如本文配置的红色BUTTON_RED按钮),又通过Authorization CodeAccess Token→用户信息的流程保障安全,避免身份伪造等风险。接入过程中,我们通过LoginWithHuaweiIDButton组件实现前端交互,结合服务端解析凭证绑定自有账号体系,最终完成用户登录闭环。

此次实践的核心启示在于:面对企业级服务限制时,需深度挖掘平台对个人开发者的差异化能力——华为账号登录虽不提供手机号,却以“去实名化”特性解决了合规痛点,同时依托成熟的OAuth 2.0/OIDC协议与丰富组件(如支持深色模式、自定义圆角的按钮),兼顾了开发效率与用户体验。未来,可进一步探索UnionID在多应用间的用户打通能力,或结合GroupUnionID拓展关联主体场景,持续完善登录体系的灵活性与扩展性。

本系列介绍增强现代智能体系统可靠性的设计模式,以直观方式逐一介绍每个概念,拆解其目的,然后实现简单可行的版本,演示其如何融入现实世界的智能体系统。本系列一共 14 篇文章,这是第 14 篇。原文:Building the 14 Key Pillars of Agentic AI

优化智能体解决方案需要软件工程确保组件协调、并行运行并与系统高效交互。例如预测执行,会尝试处理可预测查询以降低时延,或者进行冗余执行,即对同一智能体重复执行多次以防单点故障。其他增强现代智能体系统可靠性的模式包括:

  • 并行工具:智能体同时执行独立 API 调用以隐藏 I/O 时延。
  • 层级智能体:管理者将任务拆分为由执行智能体处理的小步骤。
  • 竞争性智能体组合:多个智能体提出答案,系统选出最佳。
  • 冗余执行:即两个或多个智能体解决同一任务以检测错误并提高可靠性。
  • 并行检索和混合检索:多种检索策略协同运行以提升上下文质量。
  • 多跳检索:智能体通过迭代检索步骤收集更深入、更相关的信息。

还有很多其他模式。

本系列将实现最常用智能体模式背后的基础概念,以直观方式逐一介绍每个概念,拆解其目的,然后实现简单可行的版本,演示其如何融入现实世界的智能体系统。

所有理论和代码都在 GitHub 仓库里:🤖 Agentic Parallelism: A Practical Guide 🚀

代码库组织如下:

agentic-parallelism/
    ├── 01_parallel_tool_use.ipynb
    ├── 02_parallel_hypothesis.ipynb
    ...
    ├── 06_competitive_agent_ensembles.ipynb
    ├── 07_agent_assembly_line.ipynb
    ├── 08_decentralized_blackboard.ipynb
    ...
    ├── 13_parallel_context_preprocessing.ipynb
    └── 14_parallel_multi_hop_retrieval.ipynb

深度推理的多跳检索

许多复杂的用户查询并非单一问题,而是比较性的、多步骤的调研任务,需要从多个不同来源的文档中综合信息。

并行多跳

解决方案是 并行多跳检索(Parallel Multi-Hop Retrieval) 架构,这种模式将 RAG 系统提升为真正的调研代理,工作流模拟人类研究员如何处理复杂问题的过程:

  1. 分解(Decompose):高级元代理首先分析复杂的用户查询,将其分解为几个更简单、独立的子问题。
  2. 分散(并行检索):每个子问题都被派发给各自的专用检索代理。这些代理并行运行,每个代理执行标准 RAG 流程,为特定子问题寻找答案。
  3. 收集与综合:元代理收集所有子问题的答案,进行最终推理步骤,将它们综合为对原始复杂查询的单一、全面的答案。

我们将以一个无法通过单一检索回答的比较性问题为例,构建并比较简单 RAG 系统与多跳 RAG 系统,证明只有多跳系统才能成功收集必要的证据,以提供准确且富有洞察力的最终答案。

首先为初始分解步骤定义 Pydantic 模型,从而结构化元代理规划阶段输出的内容。

from langchain_core.pydantic_v1 import BaseModel, Field
from typing import List

class SubQuestions(BaseModel):
    """分解代理输出的Pydantic模型,包含一组独立的子问题"""
    questions: List[str] = Field(description="A list of 2-3 simple, self-contained questions that, when answered together, will fully address the original complex query.")

这个 SubQuestions 模型是元代理首次行动的合约,迫使 LLM 将复杂查询分解为一系列简单、可回答的问题,是并行"分而治之"策略的基础步骤。

然后构建高级多跳系统作为 LangGraph 图。第一个节点将是"分解器",即元代理的规划角色。

from typing import TypedDict, List, Dict, Annotated
import operator

class MultiHopRAGState(TypedDict):
    original_question: str
    sub_questions: List[str]
    # 字典以问题作为键,存储每个子问题的答案
    sub_question_answers: Annotated[Dict[str, str], operator.update]
    final_answer: str

# 节点 1:分解器(元代理的第一步)
decomposer_prompt = ChatPromptTemplate.from_template(
    "You are a query decomposition expert. Your job is to break down a complex question into simple, independent sub-questions that can be answered by a retrieval system. "
    "Do not try to answer the questions yourself.\n\n"
    "Question: {question}"
)

decomposer_chain = decomposer_prompt | llm.with_structured_output(SubQuestions)

def decomposer_node(state: MultiHopRAGState):
    """获取原始复杂问题并将其分解为子问题列表"""
    print("--- [Meta-Agent] Decomposing complex question... ---")
    result = decomposer_chain.invoke({"question": state['original_question']})
    print(f"--- [Meta-Agent] Generated {len(result.questions)} sub-questions. ---")
    return {"sub_questions": result.questions}

decomposer_node 是研究代理的战略大脑,它不会尝试回答查询,其唯一且关键的任务是分析用户意图并将其分解为一组独立、可并行化的研究任务。

下一个节点将并行为每个子问题协调执行标准的 RAG 流程。

from concurrent.futures import ThreadPoolExecutor, as_completed

# 标准、自包含的RAG链,是并行检索代理的“引擎”
sub_question_rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | generator_prompt
    | llm
    | StrOutputParser()
)

def retrieval_agent_node(state: MultiHopRAGState):
    """节点 2:为每个子问题并行运行完整 RAG 进程"""
    print(f"--- [Retrieval Agents] Answering {len(state['sub_questions'])} sub-questions in parallel... ---")
    
    answers = {}
    # 用 ThreadPoolExecutor 对每个子问题并发运行‘sub_question_rag_chain’
    with ThreadPoolExecutor(max_workers=len(state['sub_questions'])) as executor:
        # 为每个待回答子问题构建一个 future
        future_to_question = {executor.submit(sub_question_rag_chain.invoke, q): q for q in state['sub_questions']}
        for future in as_completed(future_to_question):
            question = future_to_question[future]
            try:
                answer = future.result()
                answers[question] = answer
                print(f"  - Answer found for sub-question: '{question}'")
            except Exception as e:
                answers[question] = f"Error answering question: {e}"
    # 将结果收集到“sub_question_answers”字典中
    return {"sub_question_answers": answers}

retrieval_agent_node 是系统中的分散-聚合核心,接收 sub_questions 列表,并用 ThreadPoolExecutor 将每个条目分配到各自独立的 RAG 链。这是一种强大的并行形式,同时运行多个完整 RAG 流程。在所有并行代理找到答案后,该节点将所有发现汇总到 sub_question_answers 字典中。

最后,“合成器”节点作为元代理的最终步骤,将并行发现整合为一个连贯的答案。

# 节点 3:合成器(元代理的最后一步)
synthesizer_prompt = ChatPromptTemplate.from_template(
    "You are a synthesis expert. Your job is to combine the answers to several sub-questions into a single, cohesive, and comprehensive answer to the user's original complex question.\n\n"
    "Original Question: {original_question}\n\n"
    "Sub-Question Answers:\n{sub_question_answers}"
)

synthesizer_chain = synthesizer_prompt | llm | StrOutputParser()

def synthesizer_node(state: MultiHopRAGState):
    """获取子问题的答案,并合成最终的全面答案"""
    print("--- [Meta-Agent] Synthesizing final answer... ---")
    
    # 将收集的子问题答案格式化为最终提示
    sub_answers_str = "\n".join([f"- Q: {q}\n- A: {a}" for q, a in state['sub_question_answers'].items()])
    
    final_answer = synthesizer_chain.invoke({
        "original_question": state['original_question'],
        "sub_question_answers": sub_answers_str
    })
    return {"final_answer": final_answer}

synthesizer_node 是至关重要的最终推理步骤,它本身不执行任何检索,任务是接收 sub_question_answers 中的预处理事实,并将其构造为能直接回应用户原始复杂查询的连贯叙述。

最后按线性顺序组装图:分解 -> 并行检索 -> 综合。

from langgraph.graph import StateGraph, END

workflow = StateGraph(MultiHopRAGState)
workflow.add_node("decompose", decomposer_node)
workflow.add_node("retrieve_in_parallel", retrieval_agent_node)
workflow.add_node("synthesize", synthesizer_node)

workflow.set_entry_point("decompose")

workflow.add_edge("decompose", "retrieve_in_parallel")
workflow.add_edge("retrieve_in_parallel", "synthesize")
workflow.add_edge("synthesize", END)
multi_hop_rag_app = workflow.compile()

并行多跳检索

给两个系统一个复杂且需要比较的问题,这个问题无法通过单次检索调用正确回答,从而对比分析两种查询方式。

# 查询需要比较两个产品,信息在独立、不重叠的文档中
user_query = "Compare the QLeap-V4 and the Eco-AI-M2, focusing on their target use case and power consumption."

# --- 执行简单 RAG ---
print("="*60)
print("                  SIMPLE RAG SYSTEM OUTPUT")
print("="*60 + "\n")
print(f"Final Answer:\n{simple_answer}")

# --- 执行多跳 RAG ---
print("\n" + "="*60)
print("                 MULTI-HOP RAG SYSTEM OUTPUT")
print("="*60 + "\n")
print("--- Sub-Question Answers ---")
for i, (q, a) in enumerate(multi_hop_result['sub_question_answers'].items()):
    print(f"{i+1}. Q: {q}\n   A: {a}")
print("\n--- Final Synthesized Answer ---")
print(multi_hop_result['final_answer'])

# --- 最终分析 ---
print("\n" + "="*60)
print("                     ACCURACY & QUALITY ANALYSIS")
print("="*60 + "\n")
print("**Simple RAG Performance:**")
print("- Result: COMPLETE FAILURE.")
print("- Reason: The user's query contained terms for both products. Vector search found the documents that were, on average, most semantically similar to the entire query, retrieving only documents about the Eco-AI-M2. It completely failed to retrieve any information about the QLeap-V4. Without the necessary context for both products, a comparison was impossible.\n")
print("**Multi-Hop RAG Performance:**")
print("- Result: COMPLETE SUCCESS.")
print("- Reason: The system's intelligence was in the initial decomposition step. The Meta-Agent broke the complex comparative query into two simple, focused sub-questions: 1. Get info on Product A. and 2. Get info on Product B. The parallel Retrieval Agents had no trouble answering these simple questions, each retrieving the correct, focused context. The final Synthesizer agent then received a perfect, complete set of facts about both products, making the final comparison trivial.")

输出为……

#### 输出 ####
============================================================
                  SIMPLE RAG SYSTEM OUTPUT
============================================================

Final Answer:
Based on the provided context, the Eco-AI-M2 chip is designed for edge computing and mobile devices, with a primary feature of low power consumption at only 15W under full load. The context does not contain information about the QLeap-V4, so I cannot provide a comparison.

============================================================
                 MULTI-HOP RAG SYSTEM OUTPUT
============================================================
--- Sub-Question Answers ---
1. Q: What is the target use case and power consumption of the QLeap-V4?
   A: The QLeap-V4 processor is designed for maximum performance in data centers, with a primary use case of large-scale AI model training. It consumes 1200W of power under full load.
2. Q: What is the target use case and power consumption of the Eco-AI-M2?
   A: The Eco-AI-M2 chip is designed for edge computing and mobile devices like drones and smart cameras. Its key feature is low power consumption, drawing only 15W under full load.
--- Final Synthesized Answer ---
The QLeap-V4 and the Eco-AI-M2 are designed for very different purposes, primarily distinguished by their target use case and power consumption.
-   **QLeap-V4**: This is a high-performance processor intended for data centers. Its main use case is large-scale AI model training, and it has a high power consumption of 1200W.
-   **Eco-AI-M2**: This is a low-power chip designed for edge computing and mobile devices. Its focus is on energy efficiency, consuming only 15W, making it suitable for applications like drones and smart cameras.

最终分析得出明确结论,性能差异并非渐进式,而是一次能力上的飞跃。

  • 单次检索步骤无法解决比较查询歧义,仅检索了两个产品中的一个上下文,从根本上无法收集必要的证据。
  • 多跳系统之所以成功,是因为没有试图一次性回答复杂问题,而是识别了查询的比较性质,并将问题分解。
  • 通过并行、专注的 RAG 代理来解决每个简单的子问题,确保收集了所有必要证据,最后的综合步骤只是简单的将预先处理的事实结合起来。

Hi,我是俞凡,一名兼具技术深度与管理视野的技术管理者。曾就职于 Motorola,现任职于 Mavenir,多年带领技术团队,聚焦后端架构与云原生,持续关注 AI 等前沿方向,也关注人的成长,笃信持续学习的力量。在这里,我会分享技术实践与思考。欢迎关注公众号「DeepNoMind」,星标不迷路。也欢迎访问独立站 www.DeepNoMind.com,一起交流成长。

本文由mdnice多平台发布

最近看到一个职场社区帖子,吐槽了一个关于面试和 offer 的相关话题,参与讨论的同学非常多。

问题描述差不多是这样:

“我发现凡是给 offer 的公司,面试时基本不问技术细节,那些问得又多又细的公司,后面基本就没下文了……”

那关于这个问题,不知道大家有没有类似的体验或者经历?

你信心满满地去一家公司,面试官是个看起来技术大拿模样的人,一上来就给你整了个高并发场景下的分布式锁实现,问你 JVM 调优的十八般武艺,甚至还要跟你探讨一下 Linux 内核的源码细节。

你虽然答得满头大汗,但自我感觉还不错,仿佛自己把毕生所学都展示出来了。

但是最后结果呢?客气地送你一句等通知,然后便石沉大海。或者回去等了个三五天、一个星期,最后等来的是一句冰冷的不合适。

而反观另外一些面试经历,你可能就是抱着去溜达一圈的心态去转转的,面试让你感觉像在聊天,聊聊项目,聊聊过往经历,聊聊技术。

你心里还在犯嘀咕,没了?就这?

结果第二天,HR 就打电话过来找你谈薪,然后询问入职时间,速度快得让你怀疑人生。

看到这里,你是不是也挺疑惑,这到底是为什么?

难道某些公司就爱玩反向筛选?还是说问技术细节本身就是一种送客的委婉方式?这背后到底有没有什么可以遵循的逻辑原理可以分析分析。

所以今天咱们也用一篇文章的篇幅来聊一聊这个话题,也欢迎大家分享交流自己的观点和看法。

对于那些问得又细又深,最后却没给 offer 的,往往有这么几种情况。

第一种,也是最现实、最常见的大环境筛选

什么意思呢?

现在的求职大环境大家也知道,岗位有限,候选人太多。HR 和面试官手里攥着一堆 985、211 甚至大厂背景的简历。

简单点说,他们不缺候选人,所以他们有资格挑。

对于中间段位的候选人,也就是我们大多数普通人,他们不需要看你有多优秀,只需要找出你简历里的一个瑕疵,一个技术细节没答上来,或许就有可能会把你刷掉。毕竟对于他们来说,能选择的太多。

其次,还有一个比较现实的问题是,对于那些问得细的公司,不代表真的招人

当一个团队实际并不缺人,或者只是抱着宁缺毋滥的心态在招人时,他们就有资本去挑刺。

这时候面试官常常带着一种找漏洞的心态。他们的问题像一张细密的筛网,目的似乎不是看你有多合适,而是为了证明你哪里不合适。

说实话,这种还是挺恶心的。

第三种,也是最最扎心的一种情况:你只是他们的「免费咨询顾问」

更直白一点说就是在套方案。

现在的行情下,很多公司业务停滞,不怎么招人,但又面临一些棘手的技术难题。

他们打着招聘的旗号,实际上是把市场上优秀的工程师请过来,所谓的面试其实也就是一场免费的头脑风暴。他们会故意引导你去讲你上一家公司的架构设计、服务拆分方案、甚至是具体的排错思路。

整个面试过程你自认为胸有成竹,方案和思路也讲得滔滔不绝,殊不知,人家还另有企图呢。

有一说一,这种是最最恶心的一种情况。

而对于那些问得不多、但 offer 倒是给的挺痛快的公司,通常又是怎么回事呢?

首先,这往往意味着这个公司是「真·缺人」呐。

这种公司通常处于一种“生死存亡”或者业务极速扩张的阶段。老板或者团队负责人可能已经被缺人折磨得寝食难安了。

他们的核心诉求非常明确:找个能立刻干活、能立刻上手的人。

这时候,他们不会跟你去扯什么虚头巴脑的设计模式,更不会去考你那些冷门的技术知识。

他们关心的是:你能不能明天就来上班,你能不能把这个烂摊子代码接过去维护,你能不能抗住连续一个月的强度。

在这种极度的需求面前,所谓的技术细节反倒成了次要的。

但是说实话,这种 offer 虽然来得容易,但兄弟记住,这往往也是把双刃剑

因为“真·缺人”的背后,往往意味着技术债巨多、管理混乱,或者是一个谁都不愿意接的坑。

拿到这种 offer,你既可能是一飞冲天的救世主,也有可能是一头扎进泥潭的接盘侠。

当然,还有一种情况,虽然不那么好听,但也必须提一嘴。

那就是,有些公司其实是在广撒网。他们可能并没有确切的 HC,或者他们需要的只是一个廉价的劳动力。

对于这种公司,问太多技术细节反而会吓跑你,他们更希望用更轻松的面试体验和更高薪的承诺来把你招进去,至于技术匹配度嘛,额……那是入职以后的事情了。

文章的最后我想说的是,面试是一个双向选择的过程,也是一个互相试探的过程。

当你遇到那个问得特别细的面试官时,别急着心里骂娘,也别急着觉得自己没戏了。你可以试着把这场技术拷问变成一场技术交流。

如果对方是在套方案,你可以适当保留,点到为止;如果对方是真的在考察技术深度,那正好展示你的技术功底。

而当你遇到那个聊两句就给 offer 的公司时,也别急着狂喜。

可以多问问团队现状,问问业务体量,问问技术栈,这时候,一定要记住,你该反问的要反问,该考察的要考察

因为虽说大环境寒冷,但是我觉得找到一个不坑的公司有时候比拿到一个所谓的 offer 更加重要,大家觉得呢?

好了,今天就先聊这么多吧,希望能对大家有所启发,我们下篇见。

注:本文在GitHub开源仓库「编程之路」 https://github.com/rd2coding/Road2Coding 中已经收录,里面有我整理的6大编程方向(岗位)的自学路线+知识点大梳理、面试考点、我的简历、几本硬核pdf笔记,以及程序员生活和感悟,欢迎star。

大家好,我是R哥。

话说我昨天不是发了《IDEA 出现重大 Bug!不要升级!不要升级!》这篇文章吗?

今天上午就收到了某同学的反馈:

今天确实也收到 IDEA 2025.3.1.1 版本的更新了:

难道 IntelliJ IDEA 连夜就修复了我这个 BUG??

这也太巧了吧?!

抱着预期的心情更新了 2025.3.1.1,结局让我有点失望,还是那样。。

删除各种缓存,试了各种方法都没有用,就差重装了(估计也没用),社区一堆的 BUG 贴都还是 OPEN 状态呢。

于是我去查了 2025.3.1.1 的更新说明:

https://youtrack.jetbrains.com/articles/IDEA-A-2100662602/Int...

确实修复了几个大 BUG,包括 IDEA 2025.3.1 打开大 Maven 项目时会卡死的问题也修复了,但弹窗空白这个 BUG 并没有涵盖其中。。

似乎官方是解决不了这个 BUG?

这个问题在 24.2.5 版本后就开始出现了,一直都没有解决,一个这么重大的 BUG 拖了这么久不修复,着实难以理解!

先勉强用着吧,后面如果官方修了,或者有绕过方案,我也会第一时间再跟大家同步。

好了,今天的分享就到这里了,后面我也会分享更多好玩的 Java 技术和最新的技术资讯,关注Java技术栈第一时间推送。

版权声明: 本文系公众号 "Java技术栈" 原创,转载、引用本文内容请注明出处,抄袭、洗稿一律投诉侵权,后果自负,并保留追究其法律责任的权利。

在企业级表格应用场景中,排版规整度直接影响文档的专业质感与可读性——无论是财务报表、项目方案还是正式汇报材料,文本在单元格内的分布均匀性往往成为细节加分项。此前,面对“文本两端对齐”这一高频排版需求,开发者常需通过复杂自定义实现,且难以保证与Excel的兼容性。

SpreadJS V19.0 正式推出单元格两端对齐(Justify Alignment) 功能,完美复刻Excel排版逻辑,兼顾美学呈现与实用体验,为纯前端表格应用带来排版升级,让专业文档制作更高效、更精准。

在这里插入图片描述

一、核心功能:双向对齐,文本分布更均匀

两端对齐功能提供水平与垂直两个维度的精准排版能力,适配不同文本展示需求,实现“边界对齐、内部均匀”的视觉效果:

1. 水平两端对齐(Horizontal Justify)

  • 核心逻辑:每行文本的首字符紧贴单元格左边界,末字符对齐右边界,仅最后一行保持左对齐
  • 实现原理:通过智能调整字间距与行间距,让文本在水平方向均匀分布,避免单侧留白过多的问题
  • 适用场景:长文本段落展示(如项目说明、备注信息)、多列数据标签对齐

2. 垂直两端对齐(Vertical Justify)

  • 核心逻辑:文本首行紧贴单元格上边界,末行对齐下边界;若仅含一行文本,则保持顶部对齐
  • 实现原理:通过调整行间距优化垂直方向分布,解决多行文本垂直居中时上下留白不均的痛点
  • 适用场景:高单元格内多行文本书写(如产品描述、规格说明)、复杂表格布局中的文本适配

3. 组合对齐:水平+垂直双向优化

支持同时启用水平与垂直两端对齐,让文本在单元格内实现“上下左右全边界对齐、内部均匀分布”,适用于对排版精度要求极高的正式文档(如财务报表附注、合同条款)。

二、特性亮点:适配多元场景,兼顾兼容性与灵活性

1. 自动换行强制启用,无需手动配置

启用两端对齐时,系统将自动开启“自动换行”功能,文本将根据单元格宽度智能拆分换行,避免因手动设置遗漏导致的排版错乱,降低操作门槛。

2. 无缝适配合并单元格

针对合并后的大尺寸单元格,两端对齐功能可根据合并后的实际宽高自适应调整文本分布,无需额外设置适配规则,完美支持复杂表格布局(如报表标题、分类汇总区域)。

3. 普通文本与富文本全面支持

无论是基础纯文本,还是包含字体样式、颜色、链接的富文本,均可正常使用两端对齐功能。仅需注意:富文本在旋转文本场景下需遵循特殊适配逻辑,确保排版一致性。

4. 智能分词规则,适配多语言场景

针对不同语言文本的排版特性,两端对齐功能内置智能分词策略:

  • 普通文本:按空格分词,多个连续空格仅第一个用于分词,其余保留为文本一部分(例:"This a word" 分词为 ["This", " a", " word"])
  • CJK(中日韩)文本:整体视为一个“词”,但内部空格可作为分割依据(例:"这是Example サンプル예시" 分词为 ["这是", "Example", "サンプル", "예시"])
  • 支持自定义分词逻辑:通过 CultureManager 配置分词规则,满足特殊业务场景需求

三、使用场景:覆盖企业级文档核心需求

  1. 财务报表制作:会计科目说明、报表附注等长文本区域,通过水平两端对齐实现多列文本整齐排列,提升报表专业度
  2. 正式文档导出:需导出为PDF的合同、方案文档,通过双向两端对齐保证与Excel源文件排版一致,避免导出后格式错乱
  3. 复杂表格布局:合并单元格较多的仪表盘、数据看板,通过垂直两端对齐优化文本垂直分布,让界面更规整
  4. 多语言文档处理:支持中英文、中日韩等多语言文本的均匀排版,适配国际化业务场景

在这里插入图片描述

四、注意事项:这些细节让排版更精准

  1. 自动换行强制生效:启用两端对齐后,将忽略手动关闭的“自动换行”设置,优先保证排版效果
  2. 部分功能兼容限制:

    1. 缩小字体填充(shrink to fit):多行文本场景下不生效,两端对齐逻辑优先
    2. 显示省略号(ellipsis):两端对齐功能优先生效,省略号设置将被忽略
    3. 缩进(indent):水平两端对齐时,缩进设置无效,文本将紧贴左右边界
  3. 富文本特殊适配:旋转状态下的富文本需注意排版预览,建议结合实际效果调整单元格尺寸

五、总结:排版升级,效率与专业度双提升

SpreadJS V19.0 两端对齐功能的推出,不仅填补了纯前端表格在专业排版领域的空白,更通过“Excel兼容、智能适配、低操作门槛”的设计,让开发者无需编写复杂自定义代码,即可快速实现高质量排版效果。

无论是企业级报表制作、正式文档导出,还是复杂表格布局设计,这一功能都能有效提升文档质感与可读性,同时降低开发与维护成本。SpreadJS 始终以“复刻Excel体验、赋能前端开发”为核心,持续优化细节功能,让纯前端表格应用更贴合企业实际业务需求。

SpreadJS V19.0 即将正式发布,更多实用特性等待解锁,敬请期待!如需提前体验两端对齐功能,可访问 SpreadJS 官方Demo 或联系技术支持获取试用版本。

事件背景

2026 年 1 月 16 日,OpenAI 通过官方 X(原 Twitter)账号正式宣布,将在未来数周内开始在 ChatGPT 的免费版和新推出的 ChatGPT Go($8/月)中测试广告投放。与此同时,Plus($20/月)、Pro($200/月)及企业版将继续保持无广告体验。这一决策迅速引发了科技圈的广泛关注和激烈讨论。

OpenAI官方公告推文

OpenAI 官方推文宣布广告计划,并发布广告原则说明 | 来源:X @OpenAI

值得注意的是,OpenAI 同时发布了一份详尽的"广告原则"(Our Ad Principles),试图向用户保证广告不会影响 ChatGPT 的回答质量和隐私保护。然而,这份承诺并未能平息用户的担忧——社交媒体上的反应呈现出高度两极分化的态势。

OpenAI 的广告原则解读

OpenAI广告原则详图

OpenAI 发布的广告原则框架:强调使命对齐、答案独立、对话隐私、用户控制与长期价值

📋 OpenAI 官方承诺清单

答案独立性:ChatGPT 的回答始终基于客观有用性,广告不会影响答案内容

对话隐私:不向广告商出售用户数据,对话内容保持私密

用户控制:用户可随时关闭个性化广告,清除广告相关数据

付费保护:Plus、Pro、Business、Enterprise 等高价层级永不显示广告

未成年保护:18 岁以下用户不会看到广告

敏感话题禁区:政治、健康、心理健康等敏感话题禁止广告投放

商业化背后的财务压力

从华尔街日报、彭博社等主流财经媒体的报道来看,OpenAI 此举并非心血来潮,而是面对真实财务压力的"不得已之举"。据公开数据显示:

在如此悬殊的付费转化率面前,广告变现被多家媒体评价为"不可避免"的选择。富国银行预测,ChatGPT 在搜索市场的占比将从 2025 年底的 17%增长到 2030 年的三分之一,这为广告业务提供了巨大的潜在市场空间。

社交媒体的激烈反应

公告发布后,X 平台上的评论区迅速沦陷。从截图可见,用户反应从嘲讽、愤怒到直接引用 Sam Altman 此前的反广告言论,形成了鲜明的对比和讽刺效果。

用户评论截图

X 平台用户对 OpenAI 广告公告的部分反应 | Grok 引用了 Altman 2024 年称广告是"反乌托邦"的言论

Sam Altman 在 2024 年曾称将广告嵌入 ChatGPT 回复是一种"反乌托邦"的想法:"很容易想象那种未来的反乌托邦场景——你问 ChatGPT 一个问题,它回答说'你应该考虑买这个产品'或者'你应该去这里度假'之类的。"

—— 来源:Grok @grok 引用 Altman 2024 年采访

这种前后矛盾的表态成为用户攻击的焦点。有用户直言:"直接说你们需要更多钱不就得了"(Just say you guys need more money),简洁而犀利地戳破了官方话术的包装。

四大核心担忧

1. 答案中立性与商业影响的矛盾

用户普遍担忧:一旦 AI 提供的建议与商业利益相关联,就很难保证答案仍然是纯粹基于"客观有用性"的判断。有用户形象地比喻:"感觉就像在心理咨询师办公室里竖起了广告牌。"

2. 数据隐私与"监听"恐惧

尽管 OpenAI 承诺"不会出售用户数据给广告商",但用户对此类承诺持谨慎态度。有 Reddit 用户反映,在 ChatGPT 中讨论特定话题后,很快在其他平台看到相关广告,这加深了数据被滥用的担忧。

3. 前科问题:App Recommendations 事件

2025 年 12 月,ChatGPT Plus 付费用户在对话中看到来自 Target、Peloton 等品牌的"推荐"。OpenAI 最初辩称这不是广告,只是应用发现功能,但最终在用户强烈反对下关闭了该功能。首席研究官 Mark Chen 道歉承认公司"做得不够好"。这一事件严重损害了用户对 OpenAI 承诺的信任。

4. Instagram 模式类比的逻辑悖论

CEO Sam Altman 提到欣赏 Instagram 的广告模式,但用户尖锐地指出:Instagram 之所以成功,正是因为 Meta 大规模收集和出售了用户的个人数据——这与 OpenAI 声称的"隐私优先"立场本质矛盾,形成了无法调和的逻辑悖论。

广告形态预览

根据 OpenAI 展示的概念图,广告将以"Sponsored"标签的形式出现在 ChatGPT 回复的底部,与回答内容明确分离。在下图的示例中,当用户询问墨西哥晚宴菜谱时,系统在给出食谱建议后,底部会显示相关食材的赞助商购买链接。

ChatGPT广告界面示例

ChatGPT 广告投放概念设计:广告以"Sponsored"标签形式出现在回复底部,与答案内容分离

这种设计理论上可以降低用户对答案被"污染"的担忧,但批评者指出,长期来看广告逻辑一旦被引入系统,算法污染可能是微妙且难以察觉的——即使不是故意为之。

前科回顾:信任的裂痕

  • 2024 年 Altman 公开反对广告

Sam Altman 在采访中称将广告嵌入 ChatGPT 回复是"反乌托邦"的想法,表示更倾向于订阅模式以避免用户成为产品。

  • 2025 年 12 月 App Recommendations 争议

ChatGPT Plus 付费用户发现对话中出现 Target、Peloton 等品牌推荐。OpenAI 先是否认为广告,后在舆论压力下关闭该功能并道歉。

  • 2026 年 1 月 16 日正式宣布广告测试

OpenAI 官宣在免费版和 Go 版本中测试广告,同时发布"广告原则"框架,承诺付费用户永远不会看到广告。

这一系列事件的累积效应是:用户现在不再轻易相信 OpenAI 关于"高价订阅永远不会有广告"的承诺。Reddit 社区中大量评论指出,这正是流媒体巨头采用过的老套路——"先在免费端试水,再慢慢侵入付费端"。

有条件的宽容声音

💡部分理性用户的接受条件

并非所有反应都是负面的。部分用户认为,如果 OpenAI 能够做到以下几点,免费用户看广告是一种合理的交换:

1. 透明性:广告必须明确标注为"Sponsored",不能伪装成自然回答

2. 相关性:广告应与当前对话相关,而非完全无关的干扰

3. 可控性:用户可以关闭个性化广告设置,或清除用于投放广告的对话记录

4. 底线:高价订阅(Plus/Pro)必须永远保持无广告体验

这类"有条件宽容"的声音提醒我们,用户并非完全不能接受商业化,关键在于执行的边界和信任的维护。

行业视角:竞争压力与战略转向

从更宏观的行业视角来看,OpenAI 的这一决策也反映了 AI 领域日益激烈的商业化竞争。谷歌的 Gemini 和 Meta 的 AI 产品已经内置广告机制,OpenAI 不想在市场份额争夺中落于下风。

Marketing AI Institute 的分析尤其指出,OpenAI 内部正面临巨大的商业化压力。公司聘请前 Facebook 和 Instacart 高管 Fidji Simo 担任应用业务 CEO,这一人事任命本身就暗示了公司的战略方向——从技术研究机构向消费级商业平台的全面转型。

OpenAI 的创新尝试在于"对话语境驱动的广告"(contextual ads triggered by current conversation),理论上这种做法可以降低隐私风险。但实践中,用户很难确信系统不会进行隐蔽的数据关联。

结论:信任与商业化的钢丝行走

社交媒体反应以担忧和怀疑为主,核心议题围绕信任、隐私和"前科"问题。用户普遍采取了"show me"的态度——可以测试,但任何迹象表明承诺被破坏就会转向竞品。

主流媒体的评价则务实与批判并存:认可这一决策的商业必然性,但广泛质疑其能否在不伤害信任的前提下成功。最尖锐的评论来自社区用户的讽刺——AGI 实际上是"Ads Generating Income"(广告创造收入)。这反映了一个更深层的焦虑:开放人工智能的使命(AGI 造福全人类)与商业化压力之间可能存在根本性冲突。

OpenAI 正在走钢丝——既要维持无广告体验的付费用户的付费意愿,又要通过免费/低价层的广告收入覆盖高昂的运营成本。这个平衡能维持多久,将决定 ChatGPT 是否会重蹈社交媒体平台从纯净到被商业完全入侵的老路。

如果你跟我一样,同时在用 Claude Code, Cursor, Windsurf, Copilot 等好几个工具,肯定被这件事折磨过:

每改一次 Custom Instruction 或 Rule ,都要去四五个地方手动同步一遍。 只要漏掉一个,AI 的表现就打折扣。

我撸了个小工具 AI Global ,核心逻辑只有一句话:一个中心化配置,全平台指令同步。

🚀 核心功能:

一键分发:支持自动识别并同步 30+ 种 AI 工具(几乎涵盖目前市面所有主流工具)。

共享大脑:所有工具的指令自动合并到 global.md ,改一处,处处生效。

模块化资源:你可以沉淀一套自己的 .md 技能库、规则库,所有 AI 助手瞬间共享你的“武器库”。

极简且美观:全 256 色系着色的 CLI 输出,状态一目了然。

安全第一:自带备份逻辑,支持一键 unlink 无痕还原。

🛠️ 安装使用:

npm install -g ai-global

然后只需运行

ai-global

它会自动帮你把家里的活儿全干了。

GitHub:

https://github.com/nanxiaobei/ai-global

欢迎大家体验,有喜欢的工具没适配的,欢迎提 Issue 或 PR !

GistLedger

GistLedger 是一个基于 GitHub Gist 的极简个人记账应用。它利用 GitHub Gist 作为免费、私有的云端数据库,实现数据的安全存储与多端同步。

体验地址: https://gist-ledger.knowsky404.com

🌐 核心理念: Own your data (数据隐私) | Serverless (无后端) | Lightweight (轻量化)

Deploy with Vercel

📸 项目预览

Transaction Form
History View
Statistics View

✨ 功能特性

1. 📝 极简记账 (Journal)

  • 快速录入: 支持收入/支出切换,金额、分类、日期、备注一键录入。
  • 最近记录: 首页实时展示最近 5 笔交易,方便快速核对。
  • 完全私有: 数据仅存储在你的 GitHub Gist 中,无第三方服务器读取。

2. 📊 统计报表 (Statistics)

  • 双重视图:
    • 月度视图: 聚焦本月收支,展示当年 12 个月的收支变化趋势,辅助判断本月消费水位。
    • 年度视图: 聚焦全年收支,展示近 5 年的长期收支变化趋势,掌握宏观财务健康状况。
  • 多维筛选: 支持按分类(可多选)筛选统计数据,例如查看“餐饮”+“交通”的年度支出趋势。
  • 动态图表: 交互式图表实时响应筛选和日期切换。

3. 🔍 查询管理 (Query)

  • 多维筛选: 支持按类型(收入/支出)、日期范围、关键词(分类/备注)进行组合查询。
  • 数据管理: 支持对历史记录进行修改删除
  • 客户端分页: 即使数据量大也能流畅分页浏览。

🛠 技术栈

🚀 快速开始

前置准备

  1. 拥有一个 GitHub 账号。
  2. 生成一个 GitHub Personal Access Token (Classic)
    • Scope 权限: 必须勾选 gist 权限。

本地运行

# 1. 克隆项目
git clone https://github.com/KnowSky404/gist-ledger.git
cd gist-ledger

# 2. 安装依赖 (推荐使用 pnpm)
pnpm install

# 3. 启动开发服务器
pnpm dev

使用说明

  1. 打开应用后,在登录页输入你的 GitHub Personal Access Token
  2. 点击 **"连接数据库"**。
    • 如果是首次使用,应用会自动在你的 Gist 中创建一个名为 GistLedger-Data 的私有 Gist 和 ledger_data.json 文件。
    • 如果已有数据,会自动同步拉取。
  3. 开始记账!你的 Token 和 Gist ID 会保存在本地浏览器缓存中,下次访问无需重复输入(除非清除缓存或点击退出)。

🔒 数据安全

  • 应用不会将你的 Token 发送给除 GitHub API 以外的任何服务器。
  • 数据存储在你的私有 Gist 中,只有拥有该 Token 的人才能访问。
  • 建议定期备份 Gist 数据或使用 GitHub 的版本历史功能回滚误操作。

📄 License

GNU General Public License v3.0 (GPL-3.0)

在 ArkUI 里,做主题和平时做样式是两件事:

  • 样式:某个组件单独改 fontColorbackgroundColor
  • 主题:一整块区域里的组件,整体按一套规则变色

API Version 12 开始,ArkUI 提供了一个专门做「局部主题」的组件:WithTheme
它不负责画 UI,只负责一件事:给作用域里的组件套一层主题/深浅色规则

这篇文章就是一份可以直接上手的 WithTheme 自学指南,适合发社区、做笔记或带项目里落地。


一、WithTheme 是什么?

官方定义很简单:

  • WithTheme 是一个主题作用域容器
  • 只接受一个子组件(可以是 Column / Row / 自定义组件);
  • 只负责两件事:

    • 配置这一块区域用哪套 自定义主题颜色theme);
    • 控制这一块区域的 深色 / 浅色模式colorMode)。

基础信息:

  • 支持版本:从 API Version 12 开始;
  • 系统能力SystemCapability.ArkUI.ArkUI.Full
  • 元服务:从 API 12 开始支持元服务 API;
  • 不支持通用属性、不支持通用事件(它只是“包裹容器”,样式写在子组件上)。

二、WithTheme 能影响哪些组件?

不是所有组件都会响应 WithTheme,这点很关键。当前支持的系统组件包括:

  • 输入类:TextInputSearch
  • 按钮 & 徽标:ButtonBadgeCounter
  • 轮播 & 选择类:SwiperSelectMenu
  • 文本类:Text
  • 选择器类:

    • TimePickerDatePickerTextPicker
    • CheckboxCheckboxGroupRadio
    • Slider
  • 状态展示类:

    • ProgressTogglePatternLockQRCode
  • 分隔类:Divider
简单记:表单控件 + 按钮 + 文本 + 分隔线,大部分能跟着 WithTheme 一起变。

三、核心接口与配置项

3.1 WithTheme 基本接口

WithTheme(options: WithThemeOptions) {
  // 只能有一个子组件
  // 这个子组件里面可以再写 Column/Row/自定义组件
}
注意:WithTheme 不支持通用属性和通用事件,需要把布局、点击等逻辑写在内部组件上。

3.2 WithThemeOptions 结构

interface WithThemeOptions {
  theme?: CustomTheme        // 自定义主题配色
  colorMode?: ThemeColorMode // 深浅色模式
}
  • theme?: CustomTheme

    • 用于指定 WithTheme 作用域内组件的缺省配色
    • 默认:undefined,表示跟随系统 token 默认样式。
  • colorMode?: ThemeColorMode

    • 控制作用域内组件的深色/浅色模式
    • 默认:ThemeColorMode.SYSTEM(跟随系统)。

3.3 CustomTheme 类型

type CustomTheme = CustomTheme
  • CustomTheme 实际上是一个接口;
  • 搭配 CustomColors 一起使用,用来描述一整套颜色体系(比如一套绿色主题、一套红色主题)。

四、局部深浅色:colorMode 实战

很多页面希望做到:

  • 整体跟随系统;
  • 但某一块区域 强制深色(比如顶部 Banner)或 强制浅色(比如活动卡片)。

这时可以用 WithTheme 搭配 colorMode

4.1 深浅色资源准备:dark.json

image.png

要让深浅色生效,先准备深色资源文件 dark.json,例如:

{
  "color": [
    {
      "name": "start_window_background",
      "value": "#000000"
    }
  ]
}

4.2 示例:同一页面展示默认、Dark、Light 三种区域

image.png

@Entry
@Component
struct Index {
  build() {
    Column() {
      // ① 系统默认区域
      Column() {
        Text('无WithTheme')
          .fontSize(40)
          .fontWeight(FontWeight.Bold)
      }
      .justifyContent(FlexAlign.Center)
      .width('100%')
      .height('33%')
      .backgroundColor($r('app.color.start_window_background'))

      // ② 局部强制深色模式
      WithTheme({ colorMode: ThemeColorMode.DARK }) {
        Column() {
          Text('WithTheme')
            .fontSize(40)
            .fontWeight(FontWeight.Bold)
          Text('DARK')
            .fontSize(40)
            .fontWeight(FontWeight.Bold)
        }
        .justifyContent(FlexAlign.Center)
        .width('100%')
        .height('33%')
        .backgroundColor($r('sys.color.background_primary'))
      }

      // ③ 局部强制浅色模式
      WithTheme({ colorMode: ThemeColorMode.LIGHT }) {
        Column() {
          Text('WithTheme')
            .fontSize(40)
            .fontWeight(FontWeight.Bold)
          Text('LIGHT')
            .fontSize(40)
            .fontWeight(FontWeight.Bold)
        }
        .justifyContent(FlexAlign.Center)
        .width('100%')
        .height('33%')
        .backgroundColor($r('sys.color.background_primary'))
      }
    }
    .height('100%')
    .expandSafeArea(
      [SafeAreaType.SYSTEM],
      [SafeAreaEdge.TOP, SafeAreaEdge.END, SafeAreaEdge.BOTTOM, SafeAreaEdge.START]
    )
  }
}

使用建议:

  • 想让某个模块始终深色:WithTheme({ colorMode: ThemeColorMode.DARK })
  • 想让底部工具条固定浅色:ThemeColorMode.LIGHT
  • 根节点跟系统,局部区域用 WithTheme 做反色/特殊效果,是比较推荐的实践。

五、自定义主题:CustomTheme + CustomColors 实战

除了深浅色,有时我们希望整块区域用一套品牌色,比如「绿色主题卡片」vs「红色活动卡片」。

这时用 CustomTheme 来定义一套颜色,然后交给 WithTheme

5.1 定义颜色集合 CustomColors

import { CustomTheme, CustomColors } from '@kit.ArkUI';

class GreenColors implements CustomColors {
  fontPrimary = '#ff049404';
  fontEmphasize = '#FF00541F';
  fontOnPrimary = '#FFFFFFFF';
  compBackgroundTertiary = '#1111FF11';
  backgroundEmphasize = '#FF00541F';
  compEmphasizeSecondary = '#3322FF22';
}

class RedColors implements CustomColors {
  fontPrimary = '#fff32b3c';
  fontEmphasize = '#FFD53032';
  fontOnPrimary = '#FFFFFFFF';
  compBackgroundTertiary = '#44FF2222';
  backgroundEmphasize = '#FFD00000';
  compEmphasizeSecondary = '#33FF1111';
}
实际项目里可以按照设计给的 token 表来映射,保持命名和 UI 视觉规范一致。

5.2 封装成 CustomTheme

class PageCustomTheme implements CustomTheme {
  colors?: CustomColors

  constructor(colors: CustomColors) {
    this.colors = colors
  }
}

5.3 使用 WithTheme 控制局部主题

下面这个例子展示了一个典型的用法:
上半部分使用系统默认按钮配色
下半部分被 WithTheme 包裹,使用可切换的自定义主题

@Entry
@Component
struct IndexPage {
  static readonly themeCount = 3;

  themeNames: string[] = ['System', 'Custom (green)', 'Custom (red)'];

  themeArray: (CustomTheme | undefined)[] = [
    undefined,                              // 系统默认主题
    new PageCustomTheme(new GreenColors()), // 绿色主题
    new PageCustomTheme(new RedColors())    // 红色主题
  ]

  @State themeIndex: number = 0;

  build() {
    Column() {
      // 区域一:未使用 WithTheme,系统默认配色
      Column({ space: '8vp' }) {
        Text('未使用WithTheme')

        // 点击切换下方 WithTheme 的配色
        Button(`切换theme配色:${this.themeNames[this.themeIndex]}`)
          .onClick(() => {
            this.themeIndex = (this.themeIndex + 1) % IndexPage.themeCount;
          })

        // 系统默认按钮配色
        Button('Button.style(NORMAL) with System Theme')
          .buttonStyle(ButtonStyleMode.NORMAL)
        Button('Button.style(EMP..ED) with System Theme')
          .buttonStyle(ButtonStyleMode.EMPHASIZED)
        Button('Button.style(TEXTUAL) with System Theme')
          .buttonStyle(ButtonStyleMode.TEXTUAL)
      }
      .margin({ top: '50vp' })

      // 区域二:使用 WithTheme,局部换肤
      WithTheme({ theme: this.themeArray[this.themeIndex] }) {
        Column({ space: '8vp' }) {
          Text('使用WithTheme')
          Button('Button.style(NORMAL) with Custom Theme')
            .buttonStyle(ButtonStyleMode.NORMAL)
          Button('Button.style(EMP..ED) with Custom Theme')
            .buttonStyle(ButtonStyleMode.EMPHASIZED)
          Button('Button.style(TEXTUAL) with Custom Theme')
            .buttonStyle(ButtonStyleMode.TEXTUAL)
        }
        .width('100%')
      }
    }
  }
}

效果:

  • 上半部分:始终采用系统默认主题;
  • 下半部分:随着按钮点击,在 System / Green / Red 三种主题间切换;
  • 完全局部生效,不影响其他页面和组件。

六、常见使用场景

结合上面的能力,WithTheme 很适合这些场景:

  1. 局部夜间模式

    • 例如:播放器底部控制条、评论区、侧边栏等;
    • 根页面跟系统,某个区域用深色:
    WithTheme({ colorMode: ThemeColorMode.DARK }) {
      // 播放控制区 / 评论列表
    }
  2. 卡片级换肤 / 品牌卡片

    • 营销活动卡片、会员卡片、小程序入口等:
    WithTheme({ theme: new PageCustomTheme(new GreenColors()) }) {
      // 活动卡片 / 会员卡片布局
    }
  3. 表单区域统一风格

    • 一个复杂表单里用到 Button / TextInput / Checkbox / Slider 等:
    • 全部丢在 WithTheme 里,做一套专门的表单主题。
  4. 多主题 Demo / 设置页

    • 设置页里提供「主题预览」;
    • 上方一个切换按钮,下面用了多个 WithTheme 区块分别展示效果。

七、容易踩的点 & 调试建议

  1. 子组件只能一个

    • WithTheme 的子节点只能是一个组件;
    • 如果有多个,请用 Column/Row/自定义组件包一层。
  2. 不是所有组件都响应主题

    • 自绘组件(Canvas、Shape 等)不会自动跟主题;
    • 自定义组件如果内部没用系统控件,也看不到效果。
  3. 内部写死颜色会覆盖部分主题

    • 比如你在 Button 上手动设置了 backgroundColor('#FF0000')
    • 这可能会盖住主题里本来给它配置的一些颜色表现;
    • 建议:尽量用 buttonStylefontColor + 主题,让主题主导,而不是全部手写 Hex。
  4. 深浅色看起来没变化?

    • 检查是否已经配置 dark.json 等资源;
    • 检查是不是本身背景就接近黑/白,导致肉眼不明显;
    • 可以临时多放一些 Text / Button 观察效果。

八、总结

WithTheme 的定位可以一句话概括:

内外解耦:全局主题搞整体,WithTheme 专门做“局部换肤 + 局部深浅色”。

掌握它之后,你可以在 ArkUI 里轻松实现:

  • 某一块区域固定深色 / 浅色;
  • 某类卡片、一段区域统一走品牌主题色;
  • 在一个页面里同时展示多套主题效果,而不影响全局。

周五了,利用摸鱼时间为 2Libra 打造了新的油猴脚本 - 2Libra Plus。

✨ 主要功能

1. 通知中心增强

  • 未读消息高亮:自动检测通知列表中的未读条目,并在左侧添加醒目的橙色竖线标记,帮助你快速定位未处理的通知。
  • 自动已读(可选):支持进入通知页后自动将当前页消息标记为已读,减少重复点击操作(默认开启,可在设置中关闭)。

screencapture

2. 主题列表增强

  • 回复时间颜色渐变:根据你上次在首页查看时间,将最新回复显示得更醒目,较久之前的回复颜色更浅,帮助你一眼区分「最近更新」和「很久没动」的帖子。为了避免频繁刷新带来的视觉抖动,「上次查看时间」在 5 分钟内不会更新;最新回复会使用 --color-primary 颜色展示,更加醒目。

screencapture

3. 个性化设置

  • 提供可视化的设置面板,可随时开启或关闭特定功能,按需定制你的使用体验。

!. 还会有更多功能...

⬇️ 安装方法

相关链接

建议反馈

大家如有需要的功能,尽管提,觉得有用我会考虑加进去。

流量统计功能文档


仓库地址:https://gitee.com/teanary/teanary_service

目录

功能概述

流量统计功能用于统计网站前台的访问数据,包括真人访问和爬虫访问。系统会自动区分访问者类型,并记录详细的访问信息,帮助管理员了解网站的访问情况。

主要功能

  • ✅ 自动统计前台访问流量
  • ✅ 区分真人访问和爬虫访问
  • ✅ 识别爬虫来源(Google、Bing、Baidu等)
  • ✅ 缓存数据,批量写入数据库(每5分钟)
  • ✅ 自动清理过期数据(默认保留90天)
  • ✅ 提供统计看板和详细列表页面

功能特性

1. 智能过滤

系统会自动排除以下请求:

  • ❌ 管理后台(/manager/*
  • ❌ 个人中心(/user/*
  • ❌ API 路由(/api/*
  • ❌ 静态资源(.css, .js, .jpg, .png 等)
  • ❌ 非 GET 请求

2. 爬虫识别

系统能够识别以下类型的爬虫:

搜索引擎爬虫:

  • Google (Googlebot)
  • Bing (Bingbot)
  • Baidu (Baiduspider)
  • Yandex (Yandexbot)
  • Yahoo (Slurp)
  • DuckDuckGo (Duckduckbot)
  • Sogou (Sogou)

社交媒体爬虫:

  • Facebook (Facebookexternalhit)
  • Twitter (Twitterbot)
  • LinkedIn (Linkedinbot)
  • Pinterest (Pinterestbot)

其他爬虫:

  • Semrush (Semrushbot)
  • Ahrefs (Ahrefsbot)
  • Majestic (Mj12bot)
  • Dotbot
  • 以及其他通用爬虫(bot、crawler、spider等)

3. 数据记录

每条流量记录包含以下信息:

  • 路径 (path): 访问的页面路径
  • 方法 (method): HTTP 方法(通常为 GET)
  • IP 地址 (ip): 访问者的 IP 地址
  • 用户代理 (user_agent): 浏览器或爬虫的用户代理字符串
  • 来源页面 (referer): 来源页面的 URL
  • 语言 (locale): 访问时使用的语言代码
  • 是否爬虫 (is_bot): 是否为爬虫访问
  • 爬虫来源 (spider_source): 爬虫的具体来源(如 google、bing 等)
  • 访问次数 (count): 同一分钟内相同路径的访问次数
  • 统计时间 (stat_date): 统计日期(精确到分钟)

技术架构

数据流程

用户访问 → TrackTraffic 中间件 → 缓存数据 → 批量写入队列 → 数据库

核心组件

  1. 中间件 (TrackTraffic)

    • 位置:app/Http/Middleware/TrackTraffic.php
    • 功能:拦截请求,记录流量数据到缓存
  2. 批量写入任务 (BatchWriteTrafficStatsJob)

    • 位置:app/Jobs/BatchWriteTrafficStatsJob.php
    • 功能:每5分钟批量将缓存数据写入数据库
  3. 数据清理命令 (CleanOldTrafficStats)

    • 位置:app/Console/Commands/CleanOldTrafficStats.php
    • 功能:清理超过指定天数的历史数据
  4. 数据模型 (TrafficStatistic)

    • 位置:app/Models/TrafficStatistic.php
    • 功能:定义数据结构和查询方法
  5. 管理界面

    • 统计看板:app/Filament/Manager/Pages/TrafficStatistics.php
    • 详细列表:app/Filament/Manager/Resources/TrafficStatisticResource.php

缓存机制

  • 使用 Laravel Cache 存储临时流量数据
  • 缓存键格式:traffic:queue:Y-m-d-H-i
  • 缓存过期时间:1小时
  • 每5分钟批量写入一次数据库

配置说明

1. 中间件注册

中间件已在 routes/web.php 中注册:

Route::prefix('{locale}')->middleware([
    SetLocaleAndCurrency::class, 
    \App\Http\Middleware\TrackTraffic::class
])->group(function () {
    // 前台路由
});

2. 定时任务配置

routes/console.php 中已配置:

// 流量统计批量写入任务(每5分钟执行一次)
Schedule::command('app:batch-write-traffic-stats --queue')
    ->everyFiveMinutes()
    ->withoutOverlapping()
    ->runInBackground();

// 流量统计数据清理任务(每天凌晨2点执行,清理90天前的数据)
Schedule::command('app:clean-old-traffic-stats')
    ->dailyAt('02:00')
    ->withoutOverlapping();

3. 数据库表结构

表名:traffic_statistics

主要字段:

  • id: 主键(雪花ID)
  • path: 访问路径(索引)
  • method: HTTP 方法(索引)
  • ip: IP 地址(索引)
  • user_agent: 用户代理
  • referer: 来源页面
  • locale: 语言代码(索引)
  • is_bot: 是否为爬虫(索引)
  • spider_source: 爬虫来源(索引)
  • count: 访问次数
  • stat_date: 统计时间(索引,精确到分钟)

使用方法

1. 查看统计看板

  1. 登录管理后台
  2. 导航到 统计流量统计看板
  3. 可以查看:

    • 总访问量、页面浏览量、独立IP、独立页面
    • 真人访问和爬虫访问的对比
    • 热门页面 Top 20
  4. 支持筛选:

    • 日期范围:今天、昨天、最近7天、最近30天、最近90天
    • 访问者类型:全部、真人访问、爬虫访问

2. 查看详细列表

  1. 登录管理后台
  2. 导航到 统计流量明细
  3. 可以查看每条访问记录的详细信息
  4. 支持筛选:

    • 访问类型(真人/爬虫)
    • 爬虫来源
    • 日期范围

3. 手动触发批量写入

如果需要立即将缓存数据写入数据库,可以执行:

php artisan app:batch-write-traffic-stats

4. 手动清理数据

清理超过指定天数的数据:

# 清理90天前的数据(默认)
php artisan app:clean-old-traffic-stats

# 清理30天前的数据
php artisan app:clean-old-traffic-stats --days=30

# 清理180天前的数据
php artisan app:clean-old-traffic-stats --days=180

数据管理

数据保留策略

  • 默认保留时间:90天
  • 清理时间:每天凌晨2点自动执行
  • 清理方式:分批删除,每批1000条记录

数据统计方法

获取指定时间范围内的统计数据
use App\Models\TrafficStatistic;
use Illuminate\Support\Carbon;

// 获取最近7天的所有数据
$startDate = Carbon::today()->subDays(6);
$endDate = Carbon::today()->endOfDay();
$stats = TrafficStatistic::getStatsByDateRange($startDate, $endDate);

// 只获取真人访问数据
$humanStats = TrafficStatistic::getStatsByDateRange($startDate, $endDate, false);

// 只获取爬虫访问数据
$botStats = TrafficStatistic::getStatsByDateRange($startDate, $endDate, true);
获取热门页面
// 获取最近7天的热门页面 Top 10
$topPages = TrafficStatistic::getTopPages($startDate, $endDate, 10);

// 只获取真人访问的热门页面
$topHumanPages = TrafficStatistic::getTopPages($startDate, $endDate, 10, false);

// 只获取爬虫访问的热门页面
$topBotPages = TrafficStatistic::getTopPages($startDate, $endDate, 10, true);

常见问题

Q1: 为什么有些访问没有被统计?

A: 系统会自动排除以下请求:

  • 管理后台和个人中心的访问
  • API 路由
  • 静态资源文件
  • 非 GET 请求

如果您的访问路径符合以上条件,将不会被统计。

Q2: 数据多久写入一次数据库?

A: 系统每5分钟自动批量写入一次。如果需要立即写入,可以手动执行 php artisan app:batch-write-traffic-stats 命令。

Q3: 如何修改数据保留时间?

A: 有两种方式:

  1. 修改定时任务:编辑 routes/console.php,修改 --days 参数
  2. 手动执行:执行 php artisan app:clean-old-traffic-stats --days=天数

Q4: 爬虫识别不准确怎么办?

A: 可以修改 app/Http/Middleware/TrackTraffic.php 中的 isBot()getSpiderSource() 方法,添加或修改爬虫识别规则。

Q5: 如何查看缓存中的数据?

A: 可以使用 Laravel Tinker:

php artisan tinker

然后执行:

// 查看某个时间点的队列
Cache::get('traffic:queue:2026-01-17-14-30');

// 查看所有流量相关的缓存键(需要 Redis)
Redis::keys('traffic:*');

Q6: 数据量很大,会影响性能吗?

A: 系统采用了以下优化措施:

  • 使用缓存暂存数据,减少数据库写入频率
  • 批量写入,每5分钟写入一次
  • 使用索引优化查询性能
  • 自动清理过期数据,控制数据量

如果数据量仍然很大,可以考虑:

  • 缩短数据保留时间
  • 增加批量写入频率
  • 优化数据库索引

Q7: 如何禁用流量统计?

A:routes/web.php 中移除 TrackTraffic::class 中间件即可。

Q8: 可以统计其他路径吗?

A: 可以修改 app/Http/Middleware/TrackTraffic.php 中的 shouldTrack() 方法,调整过滤规则。

相关文件

  • 中间件:app/Http/Middleware/TrackTraffic.php
  • 批量写入任务:app/Jobs/BatchWriteTrafficStatsJob.php
  • 清理命令:app/Console/Commands/CleanOldTrafficStats.php
  • 数据模型:app/Models/TrafficStatistic.php
  • 统计看板:app/Filament/Manager/Pages/TrafficStatistics.php
  • 详细列表:app/Filament/Manager/Resources/TrafficStatisticResource.php
  • 数据库迁移:database/migrations/2026_01_17_204550_create_traffic_statistics_table.php

更新日志

2026-01-17

  • ✅ 初始版本发布
  • ✅ 支持真人/爬虫区分
  • ✅ 支持爬虫来源识别
  • ✅ 自动批量写入和清理

文档版本:1.0
最后更新:2026-01-17

Burp Suite Professional 2026.1 发布,新增功能简介

Burp Suite Professional 2026.1 (macOS, Linux, Windows) - Web 应用安全、测试和扫描

Burp Suite Professional, Test, find, and exploit vulnerabilities.

请访问原文链接:https://sysin.org/blog/burp-suite-pro/ 查看最新版。原创作品,转载请保留出处。

作者主页:sysin.org


Burp Suite Professional,更快、更可靠的安全测试,领先的 Web 安全测试工具包。

roadmap

Burp Suite Pro 简介

Burp Suite Professional 是一套用于测试 web 安全性的高级工具集 —- 所有这些都在一个产品中。从一个基本的拦截代理到尖端的 Burp 扫描器,使用 Burp Suite Pro,正确的工具只需点击一下就可以了。

强大的自动化让您有更多的机会做您最擅长的 (sysin),而 Burp Suite 处理容易实现的目标。先进的手动工具将帮助你识别目标更微妙的盲点。

Burp Suite Pro 是由一个研究团队开发的。这意味着在发布之前,发现成果已经包含在最新更新中。 pentesting 工具将使您的工作更快,同时让您了解最新的攻击向量。

Burp Suite 专业版

新增功能

Professional / Community 2026.1

2026 年 1 月 16 日

本次版本引入了 Discover 选项卡、通过命令面板实现的更快表格导航、更智能的 SQL 注入检测、对 NTLM 的 SPNEGO 支持,以及其他改进内容,同时还包含一次 Java 更新和浏览器升级。

使用全新的 Discover 选项卡探索 Burp

已将原有的 Learn 选项卡替换为 Discover,这是一个经过精心策划的起点,旨在帮助你探索 Burp Suite 的全部潜力。Discover 会根据你所使用的版本重点展示关键功能、工作流程和学习资源 (sysin),帮助你最大化利用当前可用的工具。

无论你是刚开始使用 Burp、在打磨成熟的工作流程,还是借助 Burp AI 提升技能,在 Burp 中始终都有新的内容值得探索。

通过命令面板实现更快的表格导航

现在,你可以使用命令面板在 Burp Suite 中的大多数表格里快速跳转到指定位置。新增了三个命令:

  • Go to top: 跳转到所选表格的第一行
  • Go to bottom: 跳转到所选表格的最后一行
  • Go to entry: 根据条目 ID 跳转到指定行

这些功能让你在不滚动、不丢失当前位置、也无需反复调整过滤条件的情况下 (sysin),更快速、更轻松地浏览大型表格。

更智能的基于时间的 SQL 注入检测

Burp Scanner 现在会过滤由 Web 应用防火墙(WAF)对可疑载荷进行延迟处理而导致的误报。这在此类场景下提升了对真实基于时间的 SQL 注入漏洞的检测准确性。

通过 SPNEGO 支持 NTLM 身份验证

Burp 现在可以配置为使用 SPNEGO 编码来处理 NTLM 令牌。

Java 更新

已将 Burp 使用的 Java 版本更新至 Java 25.0.1

浏览器升级

已将 Burp 内置浏览器升级至 Chromium 143.0.7499.193(Windows 与 Mac),以及 143.0.7499.192(Linux)。

下载地址

Burp Suite Professional 2026.1, 16 January 2026

Architectures/DescriptionFile name (Professional)
Apple Intel x64 Installerburpsuite_pro_macos_x64_v2026_1.dmg
Apple ARM64/M Chips Installerburpsuite_pro_macos_arm64_v2026_1.dmg
Linux x64 Installerburpsuite_pro_linux_v2026_1.tgz
Linux ARM64 Installerburpsuite_pro_linux_arm64_v2026_1.tgz
Windows x64 Installerburpsuite_pro_windows-x64_v2026_1.exe
Windows ARM64 Installerburpsuite_pro_windows-arm64_v2026_1.exe

for macOSBurp Suite Professional 2026.1 for macOS x64 & ARM64 - 领先的 Web 渗透测试软件

for WindowsBurp Suite Professional 2026.1 for Windows x64 - 领先的 Web 渗透测试软件

更多:HTTP 协议与安全

本文的目标是解释为什么现代LLM架构在前馈部分使用

SwiGLU

作为激活函数并且已经放弃了

ReLU

神经网络本质上是一系列矩阵乘法,如果我们堆叠线性层而不使用任何激活函数:

无论你堆叠多少层,它仍然只是一个线性变换,网络只能学习线性关系。

激活函数引入了非线性,使网络能够逼近复杂的非线性函数,这是深度学习表达能力的基础。

ReLU有什么问题?

ReLU

确实彻底改变了深度学习:

它简单、快速,并且解决了

sigmoid

tanh

等函数存在的梯度消失等问题。

虽然人们通常会列出使用

ReLU

时可能遇到的问题,比如神经元死亡等等,但这些问题要么是理论上的,要么在大多数情况下可以通过现代神经网络技术(批量归一化、自适应学习权重等)很好的避免。

不过在进入SwiGLU之前,我们先来看一个激活函数 Swish,它是 SwiGLU 的组成部分。

Swish是一个"自门控"激活函数:输入 (x) 乘以其自身的sigmoid σ(x),它充当一个,控制有多少输入能够通过。

看看门的行为:

当x非常负时:σ(x) ≈ 0,所以门是关闭的(抑制输出)

当x非常正时:σ(x) ≈ 1,所以门是完全打开的(几乎原样通过输入)

尽管公式稍微复杂一些,

Swish

的行为与

ReLU

非常相似。

Swish比ReLU更好吗?

Swish

被发现比

ReLU

效果更好,但就像深度学习中的许多事情一样我们并不确切知道为什么

Swish

效果更好,不过倒是可以总结出以下的区别:

没有硬梯度截断

看上面的图,主要区别就是它们如何处理负输入:

ReLU:在零处硬截断

当x<0时:输出 = 0 且 梯度 = 0。这就是神经元死亡问题(尽管如前所述,通常可以通过BatchNorm等现代技术来避免)

Swish:平滑、渐进地趋近于零

对于负x:梯度渐近趋近于零,但对于有限值永远不会精确等于零/所以理论上神经元总是可以接收更新(尽管对于非常负的输入,更新可能可以忽略不计)

平滑性

ReLU

在x=0处有不连续性(导数从0跳到1)。

Swish

在任何地方都是无限可微的,这意味着梯度景观是平滑的。这种平滑性是否有助于

Swish

的性能还不是100%清楚但它可能有助于优化

什么是门控线性单元(GLU)?

下面就是

SwiGLU

的另外一个组件。让我们来谈谈 GLU

其中:

x是输入

W 和 V 是权重矩阵

b和c是偏置向量

是逐元素乘法

σ 是sigmoid函数

GLU

使用门控机制在这方面与

Swish

有些相似。而它们区别在于GLU不是对所有特征应用相同的变换(恒等变换)然后用固定函数(sigmoid)进行门控,而是使用两个独立的线性投影:

xW+ b 这只是取输入并对其进行变换。它通常被称为 内容路径

σ(xV + c):这第二部分说明每个特征的内容应该让多少通过,因此它被称为 门路径

所以GLU

实际上可以被认为是

Swish` 的泛化

逐元素乘法 允许选择内容的哪些元素可以通过。当 σ(xV + c) 接近0时,门可以完全抑制某些特征,而当 σ(xV + c) 接近1时则完全让其他特征通过。

门控的具体示例

假设我们有一个4维向量 x = [1.0, -0.5, 2.0, 0.3]

GLU对同一个输入应用2个变换:

  1. 通过内容路径对内容进行变换:xW + b。假设它产生 [2.0, -1.5, 3.0, 0.5]1. 第2个变换应该扮演门的角色: σ(xV + c)。假设它产生 [0.9, 0.1, 0.95, 0.05]

GLU输出是它们的逐元素乘积:

GLU output = [2.0 × 0.9, -1.5 × 0.1, 3.0 × 0.95, 0.5 × 0.05] = [1.8, -0.15, 2.85, 0.025]

得到的结果如下:

特征1:内容为正(2.0),门值高(0.9)→ 强烈通过(1.8)

特征2:内容为负(-1.5),门值低(0.1)→ 被阻挡(-0.15)

特征3:内容为正(3.0),门值非常高(0.95)→ 完全通过(2.85)

特征4:内容较小(0.5),门值非常低(0.05)→ 被抑制(0.025)

这样网络学习了复杂的决策规则:"对于像x这样的输入,放大特征1和3,但抑制特征2和4。"

那么SwiGLU是什么?

现在我们有了所有的组成部分,

SwiGLU

(Swish门控线性单元)简单地结合了Swish和GLU:

它不是像GLU那样使用sigmoid作为门,而是使用Swish。这就是为什么它被称为 Swish + GLU

那么公式的每个部分做什么呢?这与GLU的逻辑完全相同,改变的只是门控函数。

  • Swish(xW):门——决定每个特征有多少可以通过
  • xV:内容——正在传输的实际信息
  • :逐元素乘法——将门应用于内容

为什么SwiGLU效果这么好?

从经验上看,SwiGLU在LLM中优于其他激活函数(尽管目前还不确定VLM的情况)。但为什么呢?

乘法交互创建特征组合

考虑每种架构计算的内容:

标准FFN(ReLU/GELU):

output = activation(xW₁) @ W₂

每个输出维度是激活特征的加权和,激活是逐元素应用的——特征在激活内部不会相互交互。

SwiGLU FFN

output = (Swish(xW) ⊙ xV) @ W₂

逐元素乘法 在两条路径之间创建乘积。如果我们用 g = Swish(xW)c = xV 表示,那么在最终投影之前的输出维度 igᵢ × cᵢ

这就是为什么这很重要:gᵢcᵢ 都是输入特征的线性组合(在Swish之前)。它们的乘积包含像 xⱼ × xₖ 这样的交叉项。网络可以学习 WV,使得某些输入特征组合被放大或抑制。

这类似于为什么注意力机制很强大,注意力计算 softmax(QKᵀ)V,其中 QKᵀ 乘积捕获查询和键特征之间的交互。SwiGLU为FFN带来了类似的乘法表达能力。

为什么不在门中使用sigmoid而是使用Swish?

GLU使用sigmoid:σ(xW) ⊙ xV。sigmoid的问题在于它会饱和。对于大的正或负输入,σ(x) ≈ 1σ(x) ≈ 0,且梯度 ∂σ/∂x ≈ 0,门就会被“冻结”了。

Swish对于正输入不会饱和,它近似线性增长(就像

ReLU

)。这意味着:- 梯度通过门路径流动得更好 - 门可以调节而不仅仅是开/关切换

平滑性

另外就是SwiGLU是无限可微的,这种平滑性可能有助于优化稳定性。

总结

SwiGLU的强大来自于其门控机制和乘法交互。通过将输入分成两条路径并将它们相乘,网络可以学习哪些特征组合是重要的——类似于注意力机制如何通过 QKᵀ捕获交互。

结合Swish的非饱和梯度,这使得SwiGLU对于大型模型特别有效。

https://avoid.overfit.cn/post/3fa28c75fb0b4874aa297defa145ec4a

作者:Safouane Chergui

兄弟们,撸了一个小工具,Netstat Cat 是一款 Windows 桌面应用程序,我说白了,就是 netstat 的 GUI 版本网络活动监控应用程序端口查询变得更加简单。

当然,目前是完全开源且免费使用, 有不能满足的需求或者发现 bug ,欢迎提 issue ,🐶

先放 README 链接:Netstat Cat README @Github

再上图:

亮色主题
暗色主题

支持语义化的结构化查询,比如:


Release 下载地址: https://github.com/XueshiQiao/netstat-cat/releases/


Have fun!

最近总感觉电脑不对劲,卡卡的,今天开机打开任务管理器就发现吃了 22G,我的内存 32G,网上搜了一下,有说超级预读取导致的,有说自动分配虚拟内存导致的,我还没试,只试了一个mdsched.exe(太慢了,没跑完我就关了,实际还是有点用,再开机只有 19%-22%,之前是 72%左右)

image
之前(打开 Chrome,3 个选项卡,部分小软件)

image
运行重启后(打开 Chrome,5 个选项卡,登录 QQNT,部分小软件)

后面再出现问题我再看看,目前先这样吧,内存检测太慢了。

第一次发帖,有点小紧张 x

以前使用 Shoka 主题,但无奈性能感觉跟不上了,再加上自己又是前端开发者,于是写了一个博客主题,感觉功能差不多了打算发一下,鞭策自己更新。

一个萌系 / 二次元 / 粉蓝配色的博客主题,适合 ACG 、前端、手账向个人站,性能优异。

命名灵感来源于 “小春日和”(こはるびより)指的是晚秋到初冬这段时期,持续的一段似春天般温暖的晴天。也就是中文中的 "小阳春"。

博客整体设计灵感来自 Hexo 的 Shoka 主题,用更现代的技术栈打造属于你的个人博客。

  • 基于 Astro,静态输出,加载轻快
  • 萌系 / 二次元 / 粉蓝配色,适合 ACG 、前端、手账向个人站
  • 支持多分类、多标签,但不会强迫你用复杂信息架构
  • 尽可能的减少性能开销
  • 使用 pagefind 实现无后端的全站搜索
  • LQIP (低质量图片占位符),图片加载前显示渐变色占位
  • 评论组件可选 Waline / Giscus / Remark42

已经在 astro 主题商店上架啦~

主题: Koharu | Astro

我的博客: https://blog.cosine.ren

开源在 GitHub - cosZone/astro-koharu: astro-koharu 是一个萌系 / 二次元 / 粉蓝配色的 astro 主题博客,灵感来自 Hexo 的 Shoka 主题,加了很多自己的小巧思,性能优越。

是用爱发电的个人项目,喜欢的话欢迎 star 或者 fork 出去改~

持续迭代中,还有挺多 bug 在改,技术栈也挺激进的,不过我个人用起来很喜欢,毕竟就是照着自己喜欢的风格写的,还有很多小巧思不知道有没有人会探索到~

几篇关于实现过程中的技术解析文章:

功能特性

  • 基于 Astro 5.x,静态站点生成,性能优异
  • 优雅的深色 / 浅色主题切换
  • 基于 Pagefind 的无后端全站搜索
  • 可更换评论系统:支持 Waline(推荐)、Giscus、Remark42 三种评论组件,配置文件一键切换,主题自动跟随
  • 完整的 Markdown 增强功能(GFM、代码高亮、自动目录、Mermaid 图表、Infographic 信息图)
  • 灵活的多级分类与标签系统
  • [可开关] 多系列文章支持(周刊、书摘等自定义系列,支持自定义 URL slug)
  • 响应式设计
  • 草稿与置顶功能
  • 阅读进度条与阅读时间估算
  • 智能目录导航,支持 CSS 计数器自动编号(可按文章关闭)
  • 移动端文章阅读头部(显示当前章节标题、圆形阅读进度、可展开目录)
  • 友链系统与归档页面
  • RSS 订阅支持
  • 支持 LQIP:图片加载前显示渐变色占位,提升视觉体验
  • [可开关] 基于语义相似度的智能文章推荐系统,使用 transformers.js 在本地生成文章嵌入向量,计算文章间的语义相似度
  • [可开关] AI 自动摘要生成,自动生成摘要。
  • [可开关] 圣诞特辑:包含雪花飘落、圣诞配色、圣诞帽装饰、灯串装饰等节日氛围效果
  • 无后端站点公告系统:可通过配置文件管理公告,支持时间控制、多条公告堆叠、自定义颜色、hover 已读
  • 有样式的 RSS 订阅源链接
  • Koharu CLI:交互式命令行工具,支持备份 / 还原、内容生成、备份管理

Koharu CLI

博客自带交互式 CLI 工具,方便管理博客内容:

pnpm koharu              # 交互式主菜单
pnpm koharu backup       # 备份博客内容和配置
pnpm koharu restore      # 从备份恢复
pnpm koharu update       # 更新主题
pnpm koharu generate     # 生成内容资产 (LQIP, 相似度, AI 摘要)
pnpm koharu clean        # 清理旧备份
pnpm koharu list         # 查看所有备份 

备份与还原

更新主题

使用 CLI 自动更新主题(会自动备份 → 拉取 → 合并 → 安装依赖):

# 完整更新流程(默认会先备份)
pnpm koharu update

# 仅检查更新
pnpm koharu update --check

# 跳过备份直接更新
pnpm koharu update --skip-backup

配置说明

博客配置统一使用 config/site.yaml 文件管理,包括:

  • 站点基本信息(标题、副标题、作者等)
  • 社交媒体链接
  • 导航菜单
  • 特色分类和周刊配置
  • 分类映射(中文分类名 → URL slug)
  • 友链列表
  • 公告系统
  • 评论系统(Waline / Giscus / Remark42,推荐使用 Waline)
  • 数据统计(Umami)
  • 圣诞特辑开关

详细配置说明请参考文档。

评论系统切换

config/site.yaml 中通过 comment.provider 字段一键切换评论系统:

comment: provider: waline # 'waline' | 'giscus' | 'remark42' | 'none' waline: serverURL: https://your-waline-server.vercel.app # ... 其他配置 

推荐使用 Waline:自部署简单、功能丰富(Markdown、表情、邮件通知)、带访问量统计。详细配置请参考完整使用指南

文档

演示图 1

性能优异:目标是 PC 的全绿,但是随着功能迭代不可避免的需要反复检查!


📌 转载信息
原作者:
cosine_x
转载时间:
2026/1/18 19:23:27

问题:
如何为连接器的参考点附加质量和惯性矩,这些参考点目前只有坐标,没有与任意实体耦合,仅用来设置参考点,模拟运动,我想的是添加一个比较小的质量和惯性,这个质量和惯性,不影响给定的运动和力。应该多大的质量和惯性矩比较好?
做的动力学显式仿真,移动副的推力大概是 2000N,转动副转矩为 159644N.mm,我在仿真中忽略运动副的影响,只考虑弯曲模与管材接触时的弯曲力与反作用力

回答:
Gemini;


deepseek:


GPT:

那我问你,优先不设置啥意思?不设置要是能仿真,我会问你吗?
奇葩 GPT,很常识性的问题都不知道。我怀疑 GPT 没怎么训练过 ABAQUS 的知识。不止 abaqus 仿真,我感觉工程问题,问 GPT 都不怎么样。
ABAQUS 仿真,gemini=deepseek>gpt2


📌 转载信息
原作者:
walixia
转载时间:
2026/1/18 19:17:07

背景

由于鼠鼠我在服务器上没有 sudo 权限,没法使用 github 上提供的方法,而仅仅通过 SSH 反向隧道经常出现连接不稳定、端口假死或断连的情况。经过一番折腾,我找到了基于 Cloudflare Tunnel 的终极解决方案。本文将分享两种配置方法:一种适合临时测试(无域名),另一种适合长期稳定运行(有域名)。

准备工作:本地服务配置


方案一:我没有域名

如果你只是想临时测试一下,或者不想购买域名,可以使用 Cloudflare 提供的免费临时隧道。

步骤:

  1. 下载 Cloudflared 工具。
  2. 在下载目录打开 PowerShell,运行以下命令:
cloudflared.exe tunnel --url http://localhost:8045
  1. 终端会输出一个临时的公网地址,格式如:
    https://random-name.trycloudflare.com
  2. 复制这个地址,去服务器配置即可使用。


方案二:我有自己的域名

如果你有域名(托管在 Cloudflare),我们可以利用 Cloudflare Zero Trust 面板,将本地电脑变成一台 “服务器”,实现开机自启固定域名无需保持黑窗口运行

特点:稳定、专业、后台静默运行。

1. 创建隧道与安装服务

  1. 进入 Cloudflare Zero Trust 面板NetworksManage Tunnels

  2. 创建 Tunnel

  3. 在安装界面选择你 Antigravity Tools 所在的操作系统,复制那一长串安装命令进行安装。

  4. 成功提示Cloudflared agent installed successfully。此时服务已在后台安装,电脑重启也会自动连上。

2. 绑定域名

在你的 tunnel 中

点击 这个 routes 进入配置页面:

  • Subdomain: 填写前缀(如 api)。
  • Domain: 选择你的域名(如 example.xyz)。
  • Service:
  • Type: HTTP
  • URL: 127.0.0.1:8045 (或局域网 IP,或者是你的计算机名,我这里使用的是计算机名,因为我的电脑会经常到处跑)。

保存后,你的 API 地址就是固定的 https://api.example.xyz 了。


避坑指南:解决 524 Timeout 与断连

在使用过程中,我遇到了几个关键问题,这里给出解决方案:

1. 致命的 HTTP 524 Timeout 错误

现象:域名能 ping 通,但请求 API 时卡顿很久,最后报错 524: A timeout occurred
原因:Windows 默认将 localhost 解析为 IPv6 (::1),而某些服务只监听 IPv4。Cloudflare 转发请求时 “迷路” 了。
解决
在 Cloudflare 后台配置 Service URL 时,** 千万不要填 localhost:8045**

  • 稳妥写法:填 127.0.0.1:8045
  • 进阶写法:填局域网 IP,如 192.168.1.5:8045

2. 局域网 IP 变动怎么办?

如果你使用局域网 IP 配置,重启路由器后 IP 可能会变,这也是我目前使用的方法。
解决

在 Cloudflare Service URL 里填写计算机名,前提是你的计算机名是全网独一无二的?(如 http://My-Desktop:8045),让它通过主机名解析。

服务器端配置示例

最后,在远程 Linux 服务器上,只需要修改 JSON 配置文件中的 Base URL 即可:

{ "env": { "ANTHROPIC_AUTH_TOKEN": "sk-your-key-here", "ANTHROPIC_BASE_URL": "https://api.example.xyz", "ANTHROPIC_MODEL": "claude-sonnet-4-5-thinking" } } 

希望能帮助到佬友,有更好的方法大家也可以在下面提出来呀。


📌 转载信息
转载时间:
2026/1/18 19:16:42

各位佬友好!

最近在用 Boss 直聘招人,每天要刷几百个推荐简历,眼睛都看花了。

想筛选特定技能或经历的候选人,官方的筛选功能又不太够用,

找了一大圈全网的脚本基本都是求职者的。

于是自己撸了一个油猴脚本,分享给有同样需求的佬友们。

主要功能

  • 关键词高亮:设置关键词后,简历中出现的关键词会被黄色高亮标记出来

  • 筛选模式:开启后只显示匹配的简历,不匹配的直接隐藏掉

  • OR/AND 匹配:支持 "任意匹配" 和 "全部匹配" 两种模式

  • 多岗位配置:招多个岗位的佬友有福了,可以保存多套配置,一键切换

  • 面板可拖拽:控制面板支持拖拽移动,还能吸附到屏幕边缘自动收起

使用场景举例

比如招运营,想找有小红书经验的,设置关键词小红书,开启筛选模式,瞬间过滤掉不相关的简历,效率翻倍。

再比如招全栈,同时要求 ReactNode.js,用 AND 模式设置这两个关键词,只显示两个都有的候选人。

安装方法

  1. 先装个油猴插件 (Tampermonkey)
    https://www.tampermonkey.net/
  2. 新建脚本,把代码粘贴进去保存

CTRL+S 可以快速保存

  1. 打开 Boss 直聘推荐人才页面,右上角会出现控制面板
  2. 记得启用

一些说明

  • 脚本只在推荐人才页面 (https://www.zhipin.com/web/chat/recommend) 生效,不影响其他页面
  • 配置保存在浏览器本地存储,不会上传任何数据
  • 代码开源,佬友们可以自行审阅、魔改

后续计划

  • 这个版本我已经用了一段时间了,目前没发现什么新需求了,有需求可以提,我可以顺手帮忙优化

免责声明

Boss 对自动化、插件是禁止使用的,理论上本脚本可以做到 VIP 的筛选效果,但因为非 VIP 每日开聊次数很少,所以我仍然充值了 VIP

因此我会配合 VIP 的筛选 + 本脚本更精准的找到合适的候选人

代码

不想分享到 github 或者 greasyfork 怕有别的问题,所以用附件方式分享

v5.3.0
recommend_filter_v530.7z


欢迎佬友们试用和反馈,有问题直接帖子下面留言就行。

如果觉得有用,点个赞让更多 HR 佬友看到~



📌 转载信息
原作者:
91kevinshi
转载时间:
2026/1/18 19:11:52

今天使用 cc 的时候,我让它帮我总结一下一个 github 上的项目内容,它显示无法访问


问它怎么解决,它也没提出什么好方案,最后还是 gemini 给我解决的
然后是

解决方案


1. 代理开 tun 模式,最简单无脑的方法
2. 这种问题大概率是你的终端没走梯子,需要在你的终端上配置本地代理(只有当前终端窗口有效)
只要在 PowerShell 中输入以下命令(假设你的代理端口是 7890,请根据

实际情况修改

):

$Env:http_proxy="http://127.0.0.1:7890" $Env:https_proxy="http://127.0.0.1:7890" 

设置好后,再启动 Claude Code,这样它调用 curl 时就能连上 GitHub 了
最后,在分享一个我遇到的另一个问题
如果你使用 Shift+Tab 键,无法切换 plan 模式的话
大概率是 node 版本不支持,改用 Alt+M 就好了
如果佬友们,还有其他问题的话,可以看下这位佬的帖子


📌 转载信息
原作者:
furinayyds1
转载时间:
2026/1/18 19:11:42