2026年2月

春节快到了,想生成一些个性化的异形红包和动态红包,于是用 AI 开发了一款微信红包封面生成工具。

研究微信红包开发规范

微信红包封面设计规范

使用 AI 来开发的大致思路

开发流程

简单总结就是:用 AI 生成完整图,然后通过代码切割、抠图、自动化压缩,然后导出微信要求的素材,上传到微信红包后台通过审核即可。

部分产品截图

产品截图 1

产品截图 2

产品截图 3

产品截图 4

产品截图 5

成果

有了工具做微信红包封面就快了,最终用这个工具制作了 60 多款微信红包封面,包括 42 款普通红包封面、13 款异形红包封面、5 款动态红包封面,全部收录到我的「来财」小程序里。有需要的欢迎自取,签到或分享后使用金币可以免费兑换。

普通红包效果

普通红包 1

普通红包 2

普通红包 3

普通红包 4

普通红包 5

异形红包效果

异形红包 1

异形红包 2

异形红包 3

异形红包 4

动态红包效果

因为 V2EX 不支持显示视频,请移步小程序看效果。

领取方式

来财小程序二维码

提醒:小程序第 2 个菜单里签到或分享后可获得金币,使用金币兑换即可,动态红包效果很好,推荐使用。

如题,之前是有两个美区的 ID ,一个是 Gmail 邮箱,一个是 QQ 邮箱

原本两个都运行正常,大陆地区使用,平时 wifi ,有时用梯子,日常购买美区 AI 产品也都通过 Gift Card ,
Gift Card 也都一直找同家比较熟悉的代购。

上上周,忽然 Gmail 邮箱的 ID 没有任何理由被封,并且客服告知是永久封 😒

有在想是不是平时不经常使用,只有更新 App 时候切换 Appstore 账号引起的? 所以想,如何能让另外一个( QQ 邮箱的)美区 ID 更保持官方眼中的正常?

专门腾出一台设备,长期保持 iCloud+Appstore 在线?只用梯子,不用 wifi ?

请赐教

阿里云函数计算 AgentRun 正式推出全新 知识库功能,为智能体(Agent)注入更强的语义理解与上下文感知能力。通过深度集成 百炼知识库RAGFlow 知识库,AgentRun 让开发者能够轻松构建具备“知识”的智能应用,真正实现“更懂用户、更贴场景、更高效响应”。

为什么需要知识库?

在传统智能体开发中,模型往往依赖通用训练数据,缺乏对特定业务、私有文档或实时信息的理解能力。这导致其在面对专业领域问题、企业内部知识或个性化需求时表现受限。

AgentRun 的知识库功能正是为解决这一痛点而生——它将外部知识源无缝接入智能体运行流程,通过 检索增强生成(RAG) 技术,让智能体在回答问题、执行任务时,能动态调用相关知识,大幅提升准确性、专业性与可信度。

双引擎支持:百炼 + RAGFlow,覆盖多元知识形态

百炼知识库绑定

函数计算AgentRun可以绑定您账号下已经创建好的阿里云百炼知识库

进入到创建页面,输入知识库名称、描述,选择知识库类型为“百炼”,可以多选绑定您账号下已经在阿里云百炼控制台创建好的多个知识库。填写检索配置后,点击创建知识库,即可将您的阿里云百炼知识库绑定至AgentRun平台。

RAGFlow知识库绑定

函数计算AgentRun可以绑定您账号下已经创建好的RAGFlow知识库。如果您没有RAGFlow知识库,可以点击此链接,一键在SAE上创建RAGFlow。

进入到创建页面,输入知识库名称、描述,选择知识库类型为“RAGFlow”,填写您已部署的RAGFlow的BaseURL、Dataset IDs和API-KEY(将其保存在凭证中)。填写检索配置后,点击创建知识库,即可将您自建的RAGFlow知识库绑定至AgentRun平台。

RAGFlow知识库详细配置获取方式,可参考此文档

三大集成方式,灵活适配各类开发场景

函数计算AgentRun 知识库功能支持快速创建集成、代码集成和MCP集成三种方式,满足不同技术栈和开发习惯。

快速创建Agent集成知识库功能

对于希望快速验证想法或加速产品迭代的团队,AgentRun 提供了低代码、可视化的知识库绑定能力。开发者只需登录 AgentRun 控制台,选择已创建的百炼或 RAGFlow 知识库,将其关联到目标智能体,并配置简单的检索参数(如返回结果数量、相似度阈值等),即可完成集成——全程无需编写一行代码。

这一模式极大降低了技术门槛,让产品经理、运营人员甚至非技术背景的创新者也能参与智能体的构建与优化。无论是搭建内部知识问答机器人、客户自助服务助手,还是快速验证某个垂直领域的 AI 应用场景,都能在几分钟内完成部署并上线试用

代码集成知识库查询能力对于追求极致灵活性与控制力的开发者,AgentRun 提供了原生代码级知识库接入能力。您可以在代码逻辑中,调用AgentRun SDK的知识库检索接口,根据业务上下文动态发起检索请求,精准筛选并注入最相关的信息片段到智能体的推理流程中。您可以使用AgentRun SDK,调用以下封装的接口,进行单知识库查询或多知识库查询。

fromagentrun.knowledgebaseimportKnowledgeBase
## 获取单知识库,进行查询
knowledgebase=KnowledgeBase.get_by_name("ragflow-test")
single_kb_retrieve_result=knowledgebase.retrieve("<your-query>")
print(single_kb_retrieve_result)
## 获取多知识库,进行查询,支持跨供应商知识库类型检索
multi_kb_retrieve_result=KnowledgeBase.multi_retrieve(
    query="<your-query>",
    knowledge_base_names=["ragflow-test","<your-knowledge-base-name-2>"],
)
print(multi_kb_retrieve_result)

同样,您可以集成LangChain框架,将知识库的查询能力集成在工具或上下文中。

"""AgentRun 知识库智能体集成代码示例

使用前,请参考https://docs.agent.run/docs/tutorial/quick-start 配置好相应认证信息和环境变量

curl http://127.0.0.1:9000/openai/v1/chat/completions -X POST \
    -H "Content-Type: application/json" \
    -d '{"messages": [{"role": "user", "content": "什么是Serverless?"}], "stream": true}'
"""

import json
import os
from typing import Any

from langchain.agents import create_agent
import pydash

from agentrun import Config
from agentrun.integration.langchain import model
from agentrun.integration.langchain import knowledgebase_toolset
from agentrun.integration.langgraph.agent_converter import AgentRunConverter
from agentrun.knowledgebase import KnowledgeBase
from agentrun.server import AgentRequest, AgentRunServer
from agentrun.server.model import ServerConfig
from agentrun.utils.log import logger

# 请替换为您已经创建的 模型 名称
AGENTRUN_MODEL_SERVICE = os.getenv("AGENTRUN_MODEL_SERVICE", "<your-model-service>")
AGENTRUN_MODEL_NAME = os.getenv("AGENTRUN_MODEL_NAME", "<your-model-name>")
KNOWLEDGE_BASES = os.getenv("AGENTRUN_KNOWLEDGE_BASES", "ragflow-test").split(",")

if AGENTRUN_MODEL_NAME.startswith("<") or not AGENTRUN_MODEL_NAME:
    raise ValueError("请将 MODEL_NAME 替换为您已经创建的模型名称")

## 加载知识库工具,知识库可以以工具的方式供Agent进行调用
knowledgebase_tools = []
if KNOWLEDGE_BASES and not KNOWLEDGE_BASES[0].startswith("<"):
    knowledgebase_tools = knowledgebase_toolset(
        knowledge_base_names=KNOWLEDGE_BASES,
    )
else:
    logger.warning("KNOWLEDGE_BASES 未设置或未替换,跳过加载知识库工具。")

agent = create_agent(
    model=model(AGENTRUN_MODEL_SERVICE, model=AGENTRUN_MODEL_NAME, config=Config(timeout=180)),
    tools=[
        *knowledgebase_tools,   ## 通过工具集成知识库查询能力
    ],
    system_prompt="你是一个 AgentRun 的 AI 专家,可以通过查询知识库文档来回答用户的问题。",
)


async def invoke_agent(request: AgentRequest):
    messages = [
        {"role": msg.role, "content": msg.content}
        for msg in request.messages
    ]

    # 如果配置了知识库,查询知识库并将结果添加到上下文
    if KNOWLEDGE_BASES and not KNOWLEDGE_BASES[0].startswith("<"):
        # 获取用户最新的消息内容作为查询
        user_query = None
        for msg in reversed(request.messages):
            if msg.role == "user":
                user_query = msg.content
                break

        if user_query:
            try:
                retrieve_result = await KnowledgeBase.multi_retrieve_async(
                    query=user_query,
                    knowledge_base_names=KNOWLEDGE_BASES,
                )
                # 直接将检索结果添加到上下文
                if retrieve_result:
                    messages.append({
                        "role": "assistant",
                        "content": json.dumps(retrieve_result, ensure_ascii=False),
                    })
            except Exception as e:
                logger.warning(f"知识库检索失败: {e}")

    input: Any = {"messages": messages}

    converter = AgentRunConverter()
    if request.stream:

        async def async_generator():
            async for event in agent.astream(input, stream_mode="updates"):
                for item in converter.convert(event):
                    yield item

        return async_generator()
    else:
        result = await agent.ainvoke(input)
        return pydash.get(result, "messages[-1].content", "")


AgentRunServer(
    invoke_agent=invoke_agent,
    config=ServerConfig(
        cors_origins=[
            "*"
        ]
    ),
).start()
注意⚠️:如果您选择了RAGFlow的知识库,需要确保您的Agent运行环境和RAGFlow的BaseURL的地址处于同一网络环境下,否则AgentRun SDK将无法调用RAGFlow的API实现查询能力。

通过代码集成,AgentRun 赋予开发者“全栈可控”的能力——既享受函数计算的弹性与免运维优势,又保留对智能体认知过程的深度掌控,真正实现“知识为我所用,逻辑由我定义”。

MCP集成:将知识库检索作为Agent的工具调用

AgentRun知识库率先实现“Agentic RAG”(智能体RAG)模式——将传统静态检索升级为动态、可编程的智能体工具调用。具体而言,用户可一键将知识库发布为MCP,使其成为大语言模型(LLM)可主动调用的工具之一。在此模式下,LLM不再被动接收上下文,而是具备“工具使用能力”,在推理过程中自主判断何时调用RAG、数据库查询、库存检查等工具,并基于返回结果进行多步推理与任务分解。这种机制使RAG从单一检索功能转变为智能体工具箱中的灵活组件,与其他工具并列协作,显著提升复杂任务的处理能力。其工作方式更贴近人类“思考—行动—反思”的认知流程:模型先分析问题,制定计划,再按需调用多个工具获取信息,最终整合结果生成答案。

进入其他 >> 工具管理 >> 工具市场,可以搜索到“AgentRun知识库MCP” 工具模板,点击安装后,填写知识库名称和类型,即可将知识库的查询能力一件发布成MCP工具给大模型进行调用。

创建完毕后,点击工具详情,即可看到集成调用的工具地址:

基于MCP工具标准协议,AgentRun 支持以标准化方式对接知识库服务,实现跨平台、跨模型的上下文注入能力,保障架构的开放性与可扩展性。

结语:从“能回答”到“真理解”,智能体正在拥有“知识之眼”

AgentRun 知识库功能的上线,不仅是一次技术能力的升级,更标志着智能体发展迈入新阶段——从依赖通用语料的“泛化应答”,转向基于专属知识的“情境理解”。当智能体能够随时调用企业文档、行业规范、用户历史甚至实时数据,它便不再只是一个语言模型的接口,而成为一个具备领域认知、上下文记忆与决策依据的数字协作者

未来,随着知识库的持续进化——支持多模态内容、动态更新、跨源推理——AgentRun 将进一步降低构建“有知识、有逻辑、有温度”智能体的门槛。

我们相信,真正的智能,不在于模型有多大,而在于是否“懂你所需、知你所问、信你所依”。

AgentRun,正让每一个智能体,学会思考,更学会理解。

上周末闲着没事,写了个控制米家(小米)设备的小插件( Skill )。

给 OpenClaw 装上后,怎么形容那种感觉呢?——你家的 AI 突然“活”了,它和物理世界打通了!

第一次测试时,我试探性地发了一句:“帮我关闭小米台灯”。

仅仅几秒钟,指尖还在键盘上,旁边的台灯“啪”地一声熄灭了。

哪怕我是开发者,那一刻还是忍不住喊了一句:“卧槽,太神奇了!”哈哈!


当然,可能会有朋友说:这不就是个遥控器吗?

No No No ,思路一定要打开!

普通的智能家居是你发出指令,它执行动作。

但 OpenClaw 是有“脑子”的。

比如,我刚才试了一下这个指令:

“控制小米台灯,帮我编排一场 60 秒的‘灯光秀’。”

结果……我旁边的台灯开始了疯狂整活:一会开一会关,忽明忽暗,颜色从明亮白光到温馨暖黄来回切换,炸裂感十足!

不要忘了它能控制的可不仅是台灯。

小爱音箱、吸顶灯、夜灯、扫地机器人、电动窗帘…… 所有接入米家的设备,现在都归 OpenClaw 统一指挥。

比如我让 OpenClaw 每天早上 8 点主动问一下小米音响今天的天气、路况。 早上一起床,小米音响冷不丁的冒出来一句:“北京,今天天气 xx 度,路况良好”。

脑洞再大一些,想一下还能怎么玩?例如:

“在看恐怖电影的时候,让 openclaw 来编排一下“恐怖氛围感”,突然灯光自动调节为暗红色,窗帘自动拉上,空调吹出冷风,客厅台灯不断开关(闪烁),小爱同学音响冷不丁的播放《小白船》,扫地机器人满屋跑~”

如果你不知道怎么玩,我准备了一个提示词( Prompt ),你发给 AI 试试:

如果 OpenClaw 可以控制家里的小米设备,请问会发生什么好玩的事情?发挥你的想象力

如果你有更好的想法,可以在评论区提出,我回家后试试看!哈哈

项目地址放在这里了,欢迎 Star
https://github.com/dean2021/mijia-device-manager

我对 1 有点奇怪的感觉,但是不知道哪里怪,这个人说的一些正是我觉得奇怪的,海草歌、红白机还有刘启狠他爸(有些美式电影的感觉)。有些我不认同,有些我是认同的,不过 2 我没什么意见,就是内容量太多,不看网上的一些解说,自己看要多看看才明白。

我对 2(或者 1 和 2 吧)的周边圈钱不是很认可,周边又垃圾又贵,一本书卖一百多(我准备买,又没买,看了 PDF 感觉还行,但是价格我还是不能接受),其他周边跟电影价值观也是格格不入,嘴上人类,然后猛卖垃圾周边。(以前不敢说,毕竟粉丝群体多,怕被喷)



浏览网页经常遇到 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 或直接留言。

大家好,前微软工程师一枚,最近自己在做独立开发。
痛点很简单:经常在 ChatGPT 聊半天,它突然开始胡说八道。这时候想换 Claude 试试,但复制粘贴之前的对话太痛苦了,而且上下文太长还会超 Token 。

所以撸了个 Chrome 插件:

  1. 一键总结:自动把当前的对话历史总结成一个 Prompt,丝滑极简。
  2. 无缝跳转:直接跳转到 Claude/Grok/Gemini/Perplexity,并自动填入总结好的 Prompt 。
  3. 隐私安全:你的 API Key 和聊天记录只存在本地 LocalStorage,我不存任何数据。
  4. 成本:插件完全免费,消耗的是你自己的 OpenRouter Key (也支持选免费模型白嫖)。

目前还在内测(还没上架商店),感兴趣的兄弟私信我发你安装包体验一下?求拍砖。
Demo

一、ArkUI框架概述

ArkUI是OpenHarmony生态中核心的UI渲染框架,采用声明式开发范式,支持多设备(手机、平板、PC等)多端统一开发。开发者通过ArkTS语言描述界面,框架负责组件树构建、布局测量、渲染绘制及事件处理。底层由方舟运行时引擎驱动,协同无障碍、国际化等系统能力,保障高性能与良好用户体验。

二、开发范式与执行机制

  1. 开发范式
    当前ArkUI中主流的开发范式采用ArkTS声明式范式,支持多端统一UI描述。在一些需要更高性能的场景下,可以采用Native API进行开发。
  2. 代码执行流程
    ETS源码经IDE编译生成ABC中间指令文件,打包成HAP安装包。应用启动时,原能力子系统启动对应应用进程,ArkUI子系统负责组件创建与渲染,最终由图形侧执行渲染指令,完成界面展示。
    image.png

    三、渲染核心流程与状态管理

  3. 组件树构建
    框架在运行时维护组件树的压栈与出栈,动态构建UI组件树结构。
    image.png
  4. 布局测量与渲染绘制
    父节点传递约束条件,子节点自底向上计算尺寸和位置,完成布局测量。随后根据信息发送渲染指令,执行绘制操作,生成最终界面。
    image.png
  5. 差异更新机制
    通过装饰器(如@State、@Provider)实现状态观察,观察过程识别“脏”组件,即需要更新的组件。
    ArkUI区分两类“脏”状态:
  6. 布局脏:影响尺寸和位置,需重新测量布局,以及判定影响范围。
  7. 绘制脏:仅影响样式,重绘但不重新布局。
    image.png
    状态变更触发依赖收集,精准标记相关组件为脏,在布局过程只更新需要刷新的组件,避免造成组件树的重建。

    四、ArkUI应用开发性能优化方案

    1、创建过程优化
    方案1:使用组件懒加载机制,减少创建数量,提升响应速度。在滚动过程中进行数据读取和加载,使用LazyForEach仅渲染可视区域项,避免一次性数据加载过多,解决页面加载耗时长问题,关于长列表优化可以参考长列表加载丢帧优化。
    image.png
    方案2:高负载场景分帧渲染,将本来一帧内加载的数据分成多帧加载,但是分帧渲染需要开发者计算每帧中加载多少数据,操作复杂,因此在必要的情况下才推荐使用。详请可点击查看
    image.png

2、布局过程优化
方案1:精简组件数量,使用扁平化布局组件(如RelativeContainer、Grid)替代多层Column/Row嵌套,减少中间节点数量。
image.png
方案2:利用布局边界减少布局计算
①对固定尺寸组件设置具体宽高,限制布局影响范围。
②优先使用无状态组件@Builder替代@Component,减少状态依赖。
image.png

3、更新过程优化
复用替代重建, 利用组件复用机制,减少滑动过程中组件创建、布局开销,提升帧率。
image.png

4、状态管理优化
可以采用状态管理V2进行开发,状态管理V2相对于状态管理V1优化了更新方式,由V1的对象级观察,优化为属性级观察,可以降低状态更新时带来的开销。详细内容参考:状态管理V2。

五、工具链支持与性能分析

推荐使用DevEco Studio内置工具:

  • AppAnalyzer:实现“体检-报告-修复”一体化流程,快速定位布局耗时及性能瓶颈。
  • 通过工具量化指标,结合业务场景,精准实施优化策略。
    image.png
  • ArkUI Inspector:用于可视化的展示UI组件树,分析UI的布局层次和参数。使用方法可以参考ArkUI Inspector使用说明
  • CPU Profiler:Profiler:用于在运行过程中抓取trace和调用栈对耗时点进行分析,使用方法可以参考CPU Profiler的使用指导分析的思路可以参考常用Trace的含义。

六、性能标准与实践建议

  • 帧率要求:120fps设备单帧耗时≤8ms,90fps设备单帧耗时≤12ms。
  • 响应速度:页面跳转及交互反馈延迟需低于用户感知阈值,保证流畅体验。
    实践中应结合懒加载、分帧渲染、组件复用、扁平化布局及状态管理优化等多种手段,综合提升应用性能和用户体验。

七、总结

ArkUI框架通过声明式开发范式和高效的状态管理机制,实现了灵活且高性能的UI渲染。性能优化需基于框架运行机制,结合具体业务场景,重点控制组件数量、优化更新粒度、合理利用复用与懒加载策略。借助DevEco Studio提供的丰富工具链,开发者可快速定位性能瓶颈,持续提升鸿蒙应用的流畅度和响应速度。

八、更多参考

1、界面渲染性能优化
2、AppAnalyzer

所有人【华为专家面对面01期】ArkUI框架运行原理与常见性能优化方案 

了解ArkUI渲染的基本流程,探索通过节点优化、懒加载、预加载、组件复用等技术手段,提升列表场景下应用的流畅度,打造极致流畅的界面体验。
➡️ 详情点击

背景: 部门新招了一个新同事.由领导招进来的.领导不懂技术.
经过半个月的观察,可以确认这个同事完全不懂技术.纯当场搜豆包 AI 来应对各种问题.
(因为领导让我给他对接个接口,我给他发了服务器 IP 协议 路由 请求头 请求体 返回值 和完整示例.一个 http 请求他三天都没调通.他看示例上的 url 是本地 IP,都不知道替换成服务器的 IP,一直说报错报错.)
但是这同事又喜欢主动参与项目,主动去做技术难点攻坚.我本着不管闲事的原则.尽量不说什么了.
但是他这种行为极可能会闯祸.导致项目出线上事故,我怕被牵连进去...
该怎么应对这种情况?

不会吧,你不会现在写代码还是靠拼时长吧,不会吧?

我曾经也认为,优秀的后端工程师就是写得快、写得多。直到我发现,我把大量时间浪费在了配置环境、用Print查Bug、以及手动排查内存泄漏上。

真正的效率提升,不仅仅是手速变快了,今天分享8个彻底改变我开发逻辑的工具,它们把我的焦虑变成了生产力。

ServBay:我再也不想在本地环境上浪费一秒钟

image.png

说实话,每次接手新项目或者维护老项目,最让我头疼的不是代码逻辑,而是 Go 环境配置

以前为了跑一个老项目,我得去改 .bash_profile,去整 GOPATH,甚至因为版本冲突把本地环境搞得一团糟。如果是混合技术栈,比如还得跑个Java服务,那简直是灾难。

而 ServBay,它就是拯救我于水深火热中的。它能够了一键安装,多版本共存。我可以同时保留 Go 1.11 和最新的 Go 1.24。

现在,环境配置对我来说就是点一下鼠标的事。这种隔离且并存的能力,让我工作效率蹭蹭的。

Delve:求求大家,别再用Print调试了

曾几何时,我也是Print党。遇到Bug,就在代码里疯狂塞 fmt.Println("111")fmt.Println("here")

但在Go的高并发场景下,这种做法是一时爽,事后火葬场。Goroutine一多,控制台输出乱成一锅粥,根本看不出谁先谁后。

Delve 能够仔细剖析正在运行的程序。

不需要修改代码,直接启动调试:

dlv debug main.go

遇到死锁或者逻辑诡异的地方,打个断点,直接看内存里的变量状态。它能清晰地展示出每一个Goroutine停在哪里。自从用了Delve,我解决并发Bug的时间从半天缩短到了几分钟。

Cobra:写出让人想用的CLI工具

image.png

以前写内部脚本,我总是偷懒直接解析 os.Args。结果就是,过了一个月,连我自己都忘了参数顺序是怎样的,同事用起来更是怨声载道。

后来我强制自己用 Cobra, 它是Kubernetes都在用的库。用它写出来的工具,天生就带有规范的帮助文档(--help)和子命令结构。

看看这个架子,写出来就显得很专业:

package main

import (
        "fmt"
        "github.com/spf13/cobra"
)

func main() {
        var rootCmd = &cobra.Command{
                Use:   "deploy",
                Short: "一键部署工具",
                Run: func(cmd *cobra.Command, args []string) {
                        fmt.Println("正在执行部署逻辑...")
                },
        }
        // 哪怕只是个内部工具,也要像模像样
        rootCmd.Execute()
}

把烂脚本变成正规军,Cobra是门槛最低的选择。

GoVet:编译通过不代表逻辑正确

编译器只能告诉我们语法没错,但它不管逻辑是不是弱智。

我有一次在 if 条件里把 == 写成了 =(虽然Go通常会报错,但在某些特定构造下容易混淆),或者在循环里错误地使用了闭包变量,导致线上数据全错。

GoVet 就是为了拦截这种低级但致命的错误存在的。

go vet ./...

它专门扫描那些“看起来对,但执行起来会炸”的代码。比如 Printf 的参数类型不对,或者不可达的代码块。现在我把它做进了提交前的钩子(Pre-commit hook),不通过Vet检查的代码,根本不允许提交。

Golangci-lint:我的全自动代码洁癖管家

image.png

团队协作最怕什么?怕每个人的代码都有自己的想法。

与其在Code Review时因为“花括号换不换行”或者“变量名太短”吵架,不如直接上 Golangci-lint

它不是一个工具,它是一个聚合器,并行跑了50多个检查器。

golangci-lint run

配置好 .golangci.yml 后,它就是就是一个无情的检查机器。未使用的变量、过高的圈复杂度、拼写错误,它全能抓出来。它让Code Review回归到了关注业务逻辑本身,而不是纠结语法细节。

Pprof:甚至能看清内存的毛细血管

服务上线后CPU突然飙高,或者内存缓慢泄漏,这时候看日志是没用的。以前我只能靠猜,现在我靠 Pprof

只需要在代码里加一行副作用引入:

import _ "net/http/pprof"

然后启动个HTTP服务,就能通过浏览器看到程序的X光片。

我也经常用命令行来生成火焰图:

go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile

哪一行代码占用了最多的CPU,哪个对象在这个瞬间分配了最多的内存,一目了然。不夸张地说,Pprof 给了我一种“上帝视角”。

Godotenv:别把秘密写在代码里

有些初级事故是因为把数据库密码或者AWS Key直接硬编码在代码里,然后推到了Git仓库。

Godotenv 是我所有项目的标配。

开发时,我只需要在本地建一个 .env 文件:

DB_SECRET=123456
DEBUG_MODE=true

代码里直接读:

import "github.com/joho/godotenv"

func init() {
    // 自动加载,从此告别硬编码
    _ = godotenv.Load() 
}

这样既方便本地调试,又彻底杜绝了泄密风险。

Gosec:上线前的最后一道防线

image.png

即便有了前面的工具,安全漏洞依然防不胜防。比如随机数生成器用得不安全,或者TLS配置太弱。

人工审查很难发现这些隐患,但 Gosec 可以。

它会扫描代码的抽象语法树(AST),专门寻找安全漏洞。

gosec ./...

它会直接甩一份报告给我,告诉我哪一行代码可能导致SQL注入,哪里的文件权限设置太宽泛。对于金融类或者对安全性要求高的项目,这是必须要跑的流程。


低效是一种选择,而你本可以拒绝

开发者的黄金时间极其有限。是用这仅有的精力去和环境配置搏斗、去肉眼查错,还是把它们交给工具,自己专注于构建复杂的系统逻辑?

这不只是工具的差异,这是职业生涯的加速度差异。

从今天开始,选两个装上,别让重复劳动毁了你的创造力。

一、背景介绍

AviatorScript 是一门寄生在 JVM(Hosted on the JVM)上的表达式语言,常用于规则计算、条件判断等场景。由于其执行环境与 Java 运行时深度绑定,一旦表达式内容可被外部控制,就可能引入严重的安全风险。

在 Jeecg-Boot 等项目中,历史上曾出现过 AviatorScript 相关漏洞。围绕这些漏洞,社区中也公开了多种利用 PoC。本文将首先分析这些历史 PoC 的利用方式及其局限性,随后引出一种仅依赖 JDK 的新利用思路


二、历史 PoC 利用方式分析

2.1 基于 Spring 生态的利用方式

目前网络上公开的 AviatorScript PoC,大多依赖 Spring 相关组件,其典型特征包括:

  • 使用 org.springframework.util.ClassUtils 获取默认 ClassLoader
  • 使用 org.springframework.util.Base64Utils 解码字节码
  • 使用 org.springframework.cglib.core.ReflectUtils.defineClass 动态定义类

这类 PoC 的核心思路是:

在 AviatorScript 表达式中动态注入并加载恶意字节码,从而触发类初始化逻辑。


2.2 BCEL 相关利用方式

另一类历史 PoC 依赖 JVM 内部的 BCEL 机制,例如:

  • com.sun.org.apache.bcel.internal.util.ClassLoader

通过将字节码编码进类名,实现“类名即字节码”的效果。

这种方式在早期 JDK 中较为流行,但存在以下问题:

  • 对 JDK 版本要求苛刻
  • 在高版本 JDK 中逐渐失效


2.3 历史 PoC 的局限性总结

综合来看,历史 PoC 普遍存在以下局限:

  1. 强依赖 Spring 或特定框架
  2. 对组件版本和 JDK 版本敏感


三、AviatorScript 的表达式调用能力

根据官方文档说明,AviatorScript 的表达式调用规则主要包括:

  1. 使用 new 关键字实例化 Java 对象
  2. 自 5.2.1 版本起,支持使用 Class.Method(args) 语法直接调用 Java 的 public static 方法

文档位置: https://www.yuque.com/boyan-avfmj/aviatorscript/

这些特性决定了 AviatorScript 天然具备与 JVM 深度交互的能力。


四、新的利用思路:仅依赖 JDK 的攻击面

AviatorScript 可以直接调用 Java 的 public static 方法。在这一特性基础上,如果能够找到 JDK 中合适的工具类,就可以进一步扩展表达式语言本身的能力边界。

通过在 JDK 中检索相关类,可以发现 sun.reflect.misc.MethodUtil 这一工具类。该类中提供了大量与反射相关的 public static 方法,其中 invoke 方法尤为关键。

image.png


MethodUtil.invoke 方法概述

MethodUtil.invoke 的核心作用是对反射调用进行封装,其逻辑上需要三个参数:

  1. Method —— 目标方法的反射对象
  2. Object —— 目标方法所属的实例对象
  3. Object[] —— 方法调用时使用的参数数组

只要能够在 AviatorScript 表达式中成功构造这三个参数,就可以通过 MethodUtil.invoke 间接调用任意实例方法。


Method 参数的构造方式

sun.reflect.misc.MethodUtil 中,除了 invoke 方法外,还提供了用于获取 Method 对象的辅助方法,例如 getMethod

image-1.png

getMethod 的作用是:

  • 指定目标类(Class 对象)
  • 指定方法名称
  • 指定方法参数类型列表

即可返回对应的 Method 实例。

由于 AviatorScript 本身支持:

  • Class.forName 获取类对象
  • 基本类型与数组的构造
  • public static 方法的直接调用

因此,在表达式执行阶段,Method 对象本身是可以被完整构造出来的。


目标对象(Object 参数)的来源

invoke 的第二个参数用于指定反射调用的目标对象实例。

在 Java 中,只要能够获取到目标类的实例,即可调用其对应的实例方法。

在 AviatorScript 环境下,实例对象的来源通常包括:

  • 已存在的单例对象
  • 通过静态工厂方法获取的实例
  • 通过 new 关键字创建的对象

这使得 MethodUtil.invoke 在表达式语言中具备了较高的灵活性。


参数数组(Object[])的构造

第三个参数为方法调用时使用的参数数组。

AviatorScript 提供了 tuple / array 等基础构造能力,可以将单个或多个参数封装为对象数组,从而满足反射调用对参数形式的要求。

image-2.png

image-3.png

至此,MethodUtil.invoke 所需的三个参数:

  1. Method
  2. 目标对象
  3. 参数数组

均可以在表达式层面完成构造。


利用思路的关键点总结

从整体利用思路来看,其核心不在于某一个具体 API,而在于以下几点:

  • AviatorScript 允许调用 JDK 的 public static 方法
  • MethodUtil 对反射调用进行了统一封装
  • 反射调用本身可以突破“只能调用静态方法”的限制
  • 整个过程不依赖任何第三方框架

这意味着,即使在不存在 Spring、BCEL 等组件的环境中,只要表达式执行上下文未对 JDK 内部工具类进行限制,仍然可能存在可被利用的攻击面。


基于以上分析我们可以构造出以下POC

use sun.reflect.misc.MethodUtil;

MethodUtil.invoke(MethodUtil.getMethod(Class.forName("java.lang.Runtime"),"exec",seq.array(java.lang.Class,Class.forName("java.lang.String"))),Runtime.getRuntime(),tuple('open /System/Applications/Calculator.app'))

image-6.png

小结

通过 sun.reflect.misc.MethodUtil,可以将 AviatorScript 的能力从:

仅能调用 Java 的 public static 方法

进一步扩展为:

在表达式执行阶段,间接调用任意实例方法,例如直接通过调用ScriptEngineManager执行JS代码,达到注入内存马等操作。

use javax.script.ScriptEngineManager;
use sun.reflect.misc.MethodUtil;
MethodUtil.invoke(MethodUtil.getMethod(Class.forName("javax.script.ScriptEngine"),"eval",seq.array(java.lang.Class,Class.forName("java.lang.String"))),MethodUtil.invoke(MethodUtil.getMethod(Class.forName("javax.script.ScriptEngineManager"),"getEngineByExtension",seq.array(java.lang.Class,Class.forName("java.lang.String"))),new ScriptEngineManager(),tuple("js")),tuple("java.lang.Runtime.getRuntime().exec(\\"open /System/Applications/Calculator.app\\");"));

image-5.png

五、实战

在本地搭建积木报表环境进行测试和验证,具体步骤如下:

生成 JS 类加载器

image-9.png

编码与拼接

  • 将生成的 JS 代码进行 Base64 编码。
  • 按照 POC(概念验证)示例,将编码后的内容拼接成完整 payload。
    image-8.png

发送 payload 与连接内存马

  • 通过 HTTP 请求将拼接好的 payload 发往测试环境。
  • 如果 payload 执行成功,可以在内存中看到远程加载的类,进一步连接内存马进行控制或交互。

image-10.png

六、意外收获

在翻阅AviatorScript文档时,发现AviatorScript提供了一个简单的文件 IO 模块实现,可以直接 require('io') 进来使用。从来达到不引入java层面类进行写入文件操作。使用这种方式写入文件在一定情况下可以绕过AviatorScript的一些安全配置。

image-4.png

let io = require('io');

let file = io.file("/tmp/aviator\_test.jsp");

io.spit(file, "Hello world\\r\\nAviator is great!");

image-7.png

一、Oracle数据库故障描述
一个Oracle数据库故障表现为ASM磁盘组掉线,ASM实例无法挂载(mount)。数据库管理员自行进行简单修复,未能成功,随后联系北亚数据恢复中心恢复数据。

二、Oracle数据库故障分析方法
北亚企安数据恢复工程师先对底层磁盘展开分析,从组成ASM磁盘组的磁盘中提取ASM元数据作进一步研究。经分析发现,ASM存储元数据已损坏,这就是diskgroup无法挂载的原因。接着,北亚企安数据恢复工程师重组ASM存储空间,导出其中的数据库文件,再对导出的文件进行检测与恢复。若检测显示数据文件完整,后续可直接用其启动数据库;若文件也损坏,则需对底层文件进行解析和恢复。

三、Oracle数据库数据恢复过程
1、按上述方法分析和提取底层数据,得到ASM元数据,借助其重组出ASM存储空间。
2、得到ASM存储空间后,使用北亚自主开发的ASM解析工具(也可用其他常见工具或自编脚本)解析ASM结构,目的是获取ASM中的数据文件。
北亚企安数据恢复—oracle数据恢复
3、对提取的Oracle数据库文件进行检测。
检测结果:
北亚企安数据恢复—oracle数据恢复
4、利用北亚自主开发的oracle数据库解析工具,解析所有数据文件中的数据记录,然后按用户需求导入到新数据库中。
北亚企安数据恢复—oracle数据恢复

四、Oracle数据库数据恢复成功
通过重组ASM存储空间、对ASM磁盘底层解析,导出恢复后的数据库文件,并进一步对这些文件进行底层解析,再按用户要求将数据导入新数据库。北亚企安数据恢复工程师抽查数据表验证恢复数据,未发现异常,随后通知用户方进行全面数据验证,结果显示数据恢复完整,本次Oracle数据库数据恢复成功。
北亚企安数据恢复—oracle数据恢复

如果你也在折腾“让大模型在本地改代码”的 Agent,八成遇到过这种崩溃瞬间:模型说要执行命令、工具跑完回传一大坨、上下文越滚越长、性能忽高忽低、最后还把权限问题搞成“你敢让我删库我就敢执行”……😅

OpenAI 的 Codex CLI(本地跨平台软件代理)把这套流程摊开讲得很直白:核心就是 agent loop(代理循环)——一个负责“用户 ↔ 模型 ↔ 工具”编排的 harness。把 loop 设计对了,Agent 才不会像一只喝了三杯咖啡的无头苍蝇。

下面就用更接地气(但依然严谨)的方式,把 Codex 的 agent loop 彻底“拆开看看”,顺便给做 Java/Python 工程化的同学一些能直接落地的套路。

image

1)所谓 Agent Loop:其实就是“反复问、反复干、反复追加”的流水线

Codex 这套 loop 的节奏非常规律:

  1. 收用户输入:把用户的话塞进要发给模型的 prompt(注意:真实 prompt 不是一段字符串,而是“多条消息/多种 item 的列表”)。
  2. 模型推理(inference):把 prompt 送到模型,让模型输出。
  3. 分支:模型输出要么是

    • 最终回复(assistant message):这回合结束;
    • 工具调用(tool call):比如让 agent 执行 ls、读文件、跑测试等。
  4. 执行工具 + 追加结果:agent 执行工具,把工具输出追加回 prompt,再请求模型下一轮推理。
  5. 循环直到停止调用工具:最后必须以 assistant message 收尾(哪怕主要产出是“本地代码改动”)。

一回合(turn)里可能有很多次“推理↔工具”迭代;多回合(multi-turn)则会把历史对话都带上,prompt 越滚越长:

image

这也解释了为什么 Agent 工程化最容易踩的坑,永远是这两座大山:

  • 性能(请求体越来越大,推理越来越贵,缓存还经常失效)
  • 上下文窗口(context window)不够用(尤其单回合工具调用特别多的时候)

2)Codex 如何“组装 prompt”:不是你以为的一段文本,而是一串分角色的 item

Codex CLI 用的是 Responses API,而不是让用户直接手搓 prompt。用户提交的 JSON 里最关键的三块是:

  • instructions:系统/开发者指令(Codex 既支持用户配置,也有模型内置 base instructions)
  • tools:可调用的工具定义列表(Codex 内置 shell、plan 等,也可接 MCP 工具,甚至用 web_search)
  • input:多条 item 的数组(消息、文件、图片、推理结果、工具调用/输出等都在这里)

Codex 会先往 input 里插入一堆“铺垫项”,再追加用户的真实提问。典型的插入顺序包含:

  • developer 消息:权限/沙箱说明(只约束 Codex 自带的 shell 工具)
  • developer 消息:用户自定义 developer_instructions(可选)
  • user 消息:用户指令聚合(可选)(例如 AGENTS.md/AGENTS.override.md、skills 等)
  • user 消息:环境上下文(cwd、shell 等)

示例(权限/沙箱说明):

<permissions instructions>
  - description of the sandbox explaining file permissions and network access
  - instructions for when to ask the user for permissions to run a shell command
  - list of folders writable by Codex, if any
</permissions instructions>

示例(环境上下文):

<environment_context>
  <cwd>/Users/mbolin/code/codex5</cwd>
  <shell>zsh</shell>
</environment_context>

而真正发到 Responses API 的 input item,是这种结构:

{
  "type": "message",
  "role": "user",
  "content": [
    {
      "type": "input_text",
      "text": "Add an architecture diagram to the README.md"
    }
  ]
}

另外,Codex 还会把工具定义塞到 tools 里,长得大概这样(里面同时包含 shell、plan、web_search、以及 MCP 工具):

[
  // Codex's default shell tool for spawning new processes locally.
  {
    "type": "function",
    "name": "shell",
    "description": "Runs a shell command and returns its output...",
    "strict": false,
    "parameters": {
      "type": "object",
      "properties": {
        "command": {"type": "array", "description": "The command to execute"},
        "workdir": {"description": "The working directory..."},
        "timeout_ms": {"description": "The timeout for the command..."}
      },
      "required": ["command"]
    }
  },

  // Codex's built-in plan tool.
  {
    "type": "function",
    "name": "update_plan",
    "description": "Updates the task plan...",
    "strict": false,
    "parameters": {
      "type": "object",
      "properties": {"plan": "...", "explanation": "..."},
      "required": ["plan"]
    }
  },

  // Web search tool provided by the Responses API.
  { "type": "web_search", "external_web_access": false },

  // MCP server tool (example).
  {
    "type": "function",
    "name": "mcp__weather__get-forecast",
    "description": "Get weather alerts for a US state",
    "strict": false,
    "parameters": {
      "type": "object",
      "properties": {"latitude": {}, "longitude": {}},
      "required": ["latitude", "longitude"]
    }
  }
]

Codex 文章里还有几张“prompt 快照”的图,直观看出服务器会把 system、tools、instructions、input 组装成最终 prompt:

image

3)流式推理 + 工具回填:SSE 事件才是“真实对话记录”

Codex 发起一次推理,Responses API 会用 SSE(Server-Sent Events)流式返回事件;这些事件不仅用来 UI 实时输出,还会被 Codex 转成内部对象,并追加回 input,供下一轮推理继续使用。

示例(SSE 事件流片段):

data: {"type":"response.reasoning_summary_text.delta","delta":"ah ", ...}
data: {"type":"response.reasoning_summary_text.delta","delta":"ha!", ...}
data: {"type":"response.reasoning_summary_text.done", "item_id":...}
data: {"type":"response.output_item.added", "item":{...}}
data: {"type":"response.output_text.delta", "delta":"forty-", ...}
data: {"type":"response.output_text.delta", "delta":"two!", ...}
data: {"type":"response.completed","response":{...}}

如果模型输出了 function_call,Codex 执行工具后会把 推理摘要、函数调用、函数输出 一股脑追加回下一次请求的 input,示例:

[
  /* ... original items ... */
  {
    "type": "reasoning",
    "summary": [
      {"type": "summary_text", "text": "**Adding an architecture diagram for README.md**\n\nI need to..."}
    ],
    "encrypted_content": "gAAAAABpaDWNMxMeLw..."
  },
  {
    "type": "function_call",
    "name": "shell",
    "arguments": "{\"command\":\"cat README.md\",\"workdir\":\"/Users/mbolin/code/codex5\"}",
    "call_id": "call_8675309..."
  },
  {
    "type": "function_call_output",
    "call_id": "call_8675309...",
    "output": "<p align=\"center\"><code>npm i -g @openai/codex</code>..."
  }
]

对应的第二张快照图:

image

当模型终于输出 assistant message(不再请求工具)时,这个 turn 才算结束:

data: {"type":"response.output_text.done","text":"I added a diagram to explain...", ...}
data: {"type":"response.completed","response":{...}}

用户再发一句话,就进入下一 turn:需要把上一次 assistant message 和本次 user message 一起追加进去:

[
  /* ... all items from the last request ... */
  {
    "type": "message",
    "role": "assistant",
    "content": [{ "type": "output_text", "text": "I added a diagram to explain the client/server architecture." }]
  },
  {
    "type": "message",
    "role": "user",
    "content": [{ "type": "input_text", "text": "That's not bad, but the diagram is missing the bike shed." }]
  }
]

第三张快照图更能说明:这玩意儿会一直长一直长……

image

4)性能:请求体“看似二次方”,但缓存命中能救命

很多同学看到“每次把所有历史 input 都带上”第一反应:这不就是网络传输二次方增长吗? 没错。

Responses API 支持 previous_response_id 来减少重复传输但Codex 当前选择不用它,主要为了两点:

  • 保持请求无状态:对 API 提供方更友好。
  • 支持 ZDR(Zero Data Retention):不在服务端存历史数据,避免与“零数据保留”冲突;同时 reasoning 的 encrypted_content 允许服务端解密以保留“模型理解”,但不持久化用户数据本身。

真正的性能关键在于:prompt caching。缓存命中要求非常苛刻:必须是精确的前缀匹配。因此缓存命中要求非常苛刻:必须是精确的前缀匹配。因此) Codex 特别强调“旧 prompt 是新 prompt 的 exact prefix”,这不是强迫症,是省钱省到骨子里了😂。

哪些变化会导致 cache miss?

  • 中途改 tools(甚至工具顺序不稳定都会炸)
  • model(模型内置指令变了)
  • 改沙箱配置、审批策略、cwd 等

Codex 的一个经典教训:早期接入 MCP 工具时,因为 工具枚举顺序不一致 导致缓存频繁失效(工具数量一多,直接“性能心电图”📉)。

为了尽量保住前缀,Codex 对“中途配置变更”的处理是:追加新消息,而不是修改旧消息:

  • 沙箱配置/审批模式变化:追加新的 developer <permissions instructions>
  • cwd 变化:追加新的 user <environment_context>

这招非常值得抄作业:你改的是“环境状态”,但你不能动 prompt 的“历史前缀”。

5)上下文窗口:不够用怎么办?压缩(compaction)才是正解

Agent 聊着聊着就爆 context window,这事不是“会不会发生”,而是“什么时候发生”。Codex 的策略是超过阈值就 compact:把冗长的 input 替换成一个更短、但能代表历史的 item 列表,让后续推理还能“记得发生过啥”。

早期需要手动 /compact;后来 Responses API 提供了专门的 compaction endpoint:
https://platform.openai.com/docs/guides/conversation-state#co...

它会返回一组可直接替代原 input 的 items,其中包含 type=compaction 以及不透明的 encrypted_content,用于保留模型的“潜在理解”。Codex 现在会在超过 auto_compact_limit 后自动触发这件事。


6)给做 Java/Python 工程化 Agent 的“抄作业清单”✅

把 Codex 的设计拆完,可以总结出几条非常硬核、非常工程的结论:

  1. 把 prompt 当“不可变日志”来设计
    追加新事件,不修改旧事件。你改旧的,缓存就没了;你改多了,定位就疯了。
  2. 工具列表要稳定排序 + 版本可控
    工具顺序不稳定 ≈ 主动放弃缓存。生产环境别玩“每次启动动态扫描全部工具再随机排列”的刺激游戏。
  3. 权限/沙箱必须写进 prompt
    这不是文档,这是模型行为约束的一部分。尤其是 shell 这类高危工具,不写清楚“哪些目录可写、何时需要用户确认”,迟早出事故。
  4. 流式事件要落地成结构化记录
    SSE 不只是展示给用户看的“打字效果”,而是下一轮推理的输入材料。要能回放、能审计、能复现。
  5. 上下文管理要有自动化机制
    compaction 不是锦上添花,是续命针。阈值触发、摘要策略、加密理解保留,都是工程必须项。

当 Agent loop 真正做对了,你会发现“让模型写代码”不再是玄学,更像一套可控的流水线:可追溯、可审计、可缓存、可压缩、可扩展

而 Codex 把这套“写代码的 AI 代理”最核心的一环——loop——摊开讲清楚了:所谓智能体,很多时候并不是模型多聪明,而是 harness 多靠谱。

(Codex 开源仓库: https://github.com/openai/codex


喜欢就奖励一个“👍”和“在看”呗~

image

发了 500 块的卡,没有年夜饭,一切都是老规矩。
年终奖最后一天晚上到账。

明天打扫卫生,因为有同事 14 号不来公司,就提前一天打扫。

这个时候是最开心的时候,期待着放假、期待着年终奖。

你们都发了什么啊?