包含关键字 typecho 的文章

这篇只讲这个工具的功能 JS。整体基于 Vue 组合式 API,核心流程是:

参数输入 -> 参数校验 -> 生成随机数 -> 排序与拼接 -> 统计/复制/下载

在线工具网址:https://see-tool.com/random-number-generator
工具截图:

1)状态建模:先把可变参数拆清楚

随机数工具看起来简单,但可变项不少:数字类型、是否去重、小数位、排序、分隔符、数量、范围、结果文本、结果数组。实现里把这些都放在响应式状态里,保证按钮点击、统计卡片、结果区同步更新。

const numberType = ref("integer");
const uniqueNumbers = ref("false");
const decimalPlaces = ref(2);
const sortOrder = ref("none");
const separatorSelect = ref("\\n");
const customSeparator = ref("");
const count = ref(10);
const minValue = ref(0);
const maxValue = ref(100);
const result = ref("");
const generatedNumbers = ref([]);

2)参数校验:先拦截,再计算

生成动作入口先做三层校验:

  1. min < max,否则区间无效。
  2. count 限制在 1 ~ 10000
  3. 整数去重模式下,生成数量不能超过区间容量。

第三条是关键,区间容量通过 Math.floor(max) - Math.ceil(min) 计算,避免出现“要求不重复但可选值不够”的情况。

if (minValue.value >= maxValue.value) return;
if (count.value <= 0 || count.value > 10000) return;

if (isUnique && numberType.value === "integer") {
  const range = Math.floor(maxValue.value) - Math.ceil(minValue.value);
  if (count.value > range) return;
}

3)随机生成核心:整数与小数两套逻辑

整数模式采用左闭右开区间 [min, max)

randomNum =
  Math.floor(Math.random() * (maxValue.value - minValue.value)) +
  minValue.value;

小数模式先算随机值,再按用户设定小数位做舍入:

randomNum = Math.random() * (maxValue.value - minValue.value) + minValue.value;
randomNum = parseFloat(randomNum.toFixed(decimalPlaces.value));

这样一来,小数展示和复制结果保持一致,不会出现界面显示 2 位、复制出去是长小数的问题。

4)去重实现:Set + 重试上限

去重模式使用 Set 记录已出现数字。每次生成如果命中重复就继续抽,直到拿到新值或达到重试上限。

const usedNumbers = new Set();
let attempts = 0;
const maxAttempts = 10000;

do {
  // 生成 randomNum
  attempts++;
} while (isUnique && usedNumbers.has(randomNum) && attempts < maxAttempts);

这个上限是防护措施,避免极端参数下死循环,保证函数一定能结束。

5)结果整理:排序、分隔符、文本输出一次完成

生成完数组后按选项排序,再用分隔符拼接成文本:

if (sortOrder.value === "asc") numbers.sort((a, b) => a - b);
else if (sortOrder.value === "desc") numbers.sort((a, b) => b - a);

generatedNumbers.value = numbers;
result.value = numbers.join(separator);

分隔符支持预设和自定义,并把 \n\t 转为真实换行和制表符,方便直接导出到文档或表格。

6)统计计算:基于结果数组实时派生

统计信息不单独存储,而是由 generatedNumbers 计算得到,包含数量、最小值、最大值、平均值。平均值通过 reduce 计算,展示前统一走格式化函数,避免浮点尾数影响阅读。

const avg = numbers.reduce((sum, num) => sum + num, 0) / numbers.length;

const formatNumber = (num) => {
  if (Number.isInteger(num)) return num.toString();
  return parseFloat(num.toFixed(6)).toString();
};

7)交互闭环:复制、下载、清空

这部分是工具可用性的最后一环:

  • 复制:调用 navigator.clipboard.writeText(result)
  • 下载:把结果写入 Blob,生成临时链接触发下载。
  • 清空:重置结果文本和结果数组。
const blob = new Blob([result.value], { type: "text/plain;charset=utf-8" });
const url = URL.createObjectURL(blob);

到这里,这个随机数工具的核心 JS 就完整了:先校验参数,再稳定生成,最后把结果以“可读、可复制、可下载”的形式交付给用户。

距离苹果2026年全球开发者大会(WWDC)仅剩三个月,关于iOS 27的爆料已然漫天飞舞。海外科技媒体Macrumors的最新汇总显示,这款新系统将承担两大历史级重任:为苹果首款折叠屏“iPhone Fold”铺路,以及完成iOS 26未能实现的AI(Apple Intelligence)深度重构。

【笔者观点】
库克的“挤牙膏”神话,正在被AI时代的狂飙突进无情打破。由于全新版Siri的难产,iOS 27与其说是按部就班的迭代,不如说是苹果为了掩盖iOS 26在AI布局上“全面脱节”而不得不打出的一张底牌。在微软和谷歌步步紧逼的当下,iOS 27已经没有任何试错的空间,这是一场不折不扣的生态保卫战。

一、 迟到的折叠屏,与苹果精准的“生态刀法”

苹果终于要在今年9月推出首款可折叠设备“iPhone Fold”了。据悉,该设备折叠状态下为5.5英寸,展开后则为7.8英寸,屏幕比例接近4:3。为了适配这一形态,iOS 27将引入分屏多任务处理,原生应用也会增设侧边栏。

令人瞩目的是,iPhone Fold尽管展开后尺寸逼近iPad Mini,但它运行的依然是iOS,且完全不支持iPad应用

【笔者观点】
折叠屏在安卓阵营早已杀成红海,苹果迟到至今才交卷,本质上是对现有硬件利润盘的极度保守。但最反常识的设定在于:一台展开近8英寸的设备,苹果居然死死掐住不让它兼容iPadOS。
这绝非技术瓶颈,而是苹果深思熟虑的商业精算——既要收割折叠屏带来的超高硬件溢价,又绝不允许这款设备蚕食掉iPad的任何一点生存空间。这种为了保护多条产品线销量而人为制造的“软件割裂”,或许会让iPhone Fold的用户体验变得极度别扭。

二、 Siri的“基因突变”与大模型主权让渡

Apple Intelligence版Siri曾是画下的大饼,如今要在iOS 27上强制兑现。新版Siri不仅具备个人情境感知、屏幕内容识别能力,更可怕的是实现了“跨应用操作”(例如:让Siri把刚才修好的照片直接通过邮件发给某人)。

同时,苹果正与谷歌紧密合作,计划在iOS 27中引入类似ChatGPT的聊天机器人版Siri,底层甚至动用了Google Gemini团队联合开发的定制AI模型。

【笔者观点】
请认清一个残酷的现实:苹果选择与谷歌合作接入Gemini模型,等同于在底层大模型能力上承认了阶段性败北。把Siri硬改成Chatbot不是自然进化,而是迫于无奈的“基因突变”。
但千万不要因此看轻苹果。苹果真正的恐怖护城河不在于AI模型本身有多聪明,而在于“系统最高控制权”。当Siri能够直接读取屏幕信息,并越过App围墙进行跨应用操作时,第三方App的入口价值将被瞬间抽干。这意味着,微信、支付宝等超级App在iOS生态内可能被降维打击,沦为苹果AI底层的“工具人组件”。

三、 Core AI与“雪豹式”重构:底层的彻底洗牌

除了前台功能的炫技,iOS 27的底层变动同样剧烈。苹果将推出全新的Core AI框架,全面取代现有的Core ML。

此外,彭博社马克·古尔曼将iOS 27描述为一次“雪豹式”(Snow Leopard)更新,意味着苹果正投入巨大精力清理iOS的陈年冗余代码,重写老旧功能,以换取极端的性能提升和流畅度。

【笔者观点】
所谓“雪豹式”更新,说穿了就是一块遮羞布,掩盖了iOS十几年祖传代码在AI时代已经臃肿不堪、运行效率低下的事实。不把这栋危楼的承重墙打掉重塑,根本跑不动未来的端侧大模型。
而强制用Core AI替换Core ML,则是苹果向全球千万开发者下达的最后通牒:在苹果的生态里玩AI,就必须臣服于苹果的新规矩。通过这种底层框架的裹挟,苹果正在不动声色地夺回AI应用生态的绝对定义权。

四、 卫星连接与被砍掉的Health+:生态扩张的暗战

在硬件协同上,iOS 27还准备解锁更高级的卫星通信能力,包括卫星版苹果地图、发照片甚至5G网络卫星连接。而在健康领域,原本完全由AI驱动的Health+订阅服务虽然规模缩水,但仍有部分功能植入系统。

【笔者观点】
别把卫星通信只看作“荒野求生”的噱头。让iPhone无需对准天空就能直连卫星网络,这背后潜藏着苹果试图逐渐摆脱对传统电信运营商依赖,构建属于自己“空天地一体”私域网络的巨大野心。
至于Health+订阅服务的缩水,反而证明了当前的AI能力在严肃医疗健康领域的无力感。在没有达到绝对精准之前,强行推出AI医疗诊断就是给苹果的法务部找麻烦。

总结:
iOS 27、macOS及其他新系统将在今年6月正式预览。这不再是一次简单的UI换肤或小修小补,而是苹果在AI落后焦虑与硬件创新瓶颈的双重重压下,进行的一次“刮骨疗毒”。至于市场买不买单,9月的折叠屏销量将给出最残酷的答案。


👇 欢迎关注我的公众号

在 AI 爆发的深水区,我们一起探索真正能穿越周期的技术价值。
微信搜索 【睿见新世界】 或扫描下方二维码,获取每周硬核技术推文:

微信图片_20260301232734_225_35.jpg

欢迎关注【睿见新世界】

一、 不是图像生成,而是“按需定制的交互代码白板”

据Anthropic公司周四(2026年3月13日)最新宣布,Claude正式推出了一项颠覆性的新功能:直接在聊天中生成交互式图表、图解和可视化内容。

值得注意的是,这绝不是Midjourney或DALL-E那种传统的AI图像生成器。当系统判断视觉呈现比长篇大论更有效,或者在用户的明确要求下,Claude会直接从零开始编写并即时渲染交互式的HTML和SVG文件。Anthropic官方将这一功能精准地比作:给Claude配备了一块“按需唤醒的智能白板”。

【笔者观点】
当所有大模型都在死磕“谁生成的像素画更逼真”时,Claude却悄悄偷了塔。 这是一个极其反常识的战略选择:像素级图像解决的是“娱乐与审美”问题,而基于HTML/SVG的交互式图表解决的是“认知与生产力”问题。Claude不仅是在提供一个新功能,它是在重新定义AI的输出媒介——从单向的“文本输出器”,进化成了能即时生成微型应用程序的“超级编译器”。如果你还在用大模型写干瘪的文章,那你已经被这个时代抛弃了一半。

二、 从“换轮胎”到“建筑解析”:AI终于学会了“用工具沟通”

在实际测试中,这项功能展现出了惊人的实用性。当测试者要求Claude展示“如何换轮胎”时,它没有像传统AI那样吐出上千字的说明书,而是在几秒钟内生成了一个包含7个步骤、附带所需工具和关键步骤说明的交互式流程图

更硬核的应用场景在于复杂信息的拆解。测试者要求Claude为一本小说中描述的房屋构建视觉呈现,Claude不仅准确还原了房屋结构,还将其做成了“全图可点击”的交互界面——点击窗户或门,就会弹出相应的隐藏信息。此外,Anthropic官方还展示了“可点击的交互式元素周期表”以及“纸飞机折叠动态教程”等案例。生成的图解甚至可以下载,或保存为工件(Artifacts)以便日后复用。

【笔者观点】
大段文字正在成为过去式,我们正在见证“静态长文”的消亡。 以前我们认为AI的终局是“极其聪明的对答机器”,但Claude这一手证明了:最高效的信息传递,永远是交互式可视化。这给所有内容创作者、教育工作者甚至UI设计师敲响了警钟——当一个免费的大模型能在3秒内生成一套可点击的交互式信息图表时,那些依靠制作静态PPT、繁琐说明书和基础前端展示吃饭的岗位,生存倒计时已经开始。

三、 跨越付费墙:无差别的降维打击

与很多AI厂商将核心能力封装在“Pro版”中不同,Anthropic宣布该功能将默认开启,且向所有计划类型(包括免费用户)全面开放。用户无需在设置中手动切换,系统会自动判断对话上下文,在最合适的时机直接抛出可视化内容。目前该功能已在网页和桌面版本上线,移动版也已在规划之中。

Anthropic的野心昭然若揭:他们要让Claude成为一个“能够自主选择最佳传递媒介的动态AI工具”。

【笔者观点】
免费且默认开启,这是Anthropic最犀利的一招阳谋。 为什么不收费?因为底层逻辑是“用户习惯的绑架”。一旦用户习惯了在提问后得到一个可以直接点击、拖拽、查看详情的交互式UI界面,他们就再也无法忍受其他竞品那种“干巴巴吐字”的便秘感了。ChatGPT等竞品如果不能迅速在“多模态交互渲染”上跟进,其在职场生产力领域的护城河将被彻底撕裂。对于普通用户而言,紧迫感同样存在:不再是“你会不会用AI提问”,而是“你会不会用AI的交互白板来重塑你的工作流”。

👇 欢迎关注我的公众号

在 AI 爆发的深水区,我们一起探索真正能穿越周期的技术价值。
微信搜索 【睿见新世界】 或扫描下方二维码,获取每周硬核技术推文:

微信图片_20260301232734_225_35.jpg

欢迎关注【睿见新世界】

微信公众号介绍

1.公众号的分类

我们平常在微信应用上会看到有很多的公众号,但是各自并不一样,公众号也分很多种类型,不过最常见的就是服务号和订阅号了。下面我们来看一下他们的区别:

  • 订阅号:为媒体和个人提供一种信息传播方式,主要偏于为用户传达资讯(类似报纸杂志),主要的定位是阅读,每天可以群发1条消息
  • 服务号:为企业,政府或组织提供对用户进行服务,主要偏于服务交互(类似银行提供服务查询),每个月只可群发4条消息
  • 企业微信:为企业,政府,事业单位,实现生产管理和协作运营的移动化,主要用于公司内部通讯使用,旨在为用户提供移动办公,需要先有成员的通讯信息验证才可以关注成功企业微信

订阅号和服务号有一个比较明显的区别就是:订阅号都是存放在一个名叫订阅号的文件夹中,点开才能看到所有关注过的订阅号,但是服务号却和好友一样直接就显示在聊天列表中。这个大家打开微信客户端便能看到

2.公众号注册流程

以注册订阅号为例,访问注册地址:https://mp.weixin.qq.com/cgi-bin/registermidpage?action=index...\_CN&token=

填写基本信息,验证邮箱

选择账号类型

账号主体类型选择个人,填写信息

填写公众号信息

创建成功后,可以进入公众号的管理界面

3.公众号的管理模式

3.1 编辑模式

主要针对非编程人员及信息发布类公众帐号 使用 。开启该模式后,可以方便地通过界面配置“自定义菜单”和“自动回复的消息”。好处是可视化界面配置,操作简单,快捷,但是功能有限

我们可以给自己的公众号设置关键词回复,收到已关注用户发送的消息,匹配到你好时回复一个你好~~

编辑模式只能预先自定义一些固定的规则和数据,这些数据会保存在微信服务器,只能完成一些简单的功能。如果要完成更复杂的功能,比如根据用户输入信息动态获取数据返回,则需要使用开发模式

3.2 开发模式

主要针对具备开发能力的人使用。开启该模式后,能够使用微信公众平台开放的接口,但是编辑模式的设置会失效,比如“自定义菜单”和“自动回复的消息”功能。通过编程方式可以实现更多复杂的功能,提供个性化服务。

总的来说,编辑模式就是为所有人提供的,如果你的需求仅仅只是最常见的菜单,自动回复等,使用编辑模式已经满足,但是如果你需求的功能比较复杂,比如需要从自己的业务系统数据库中查询数据返回给微信用户,就需要使用到开发模式。

接下来我们介绍开发模式的各种功能使用步骤。出于安全考虑,普通的订阅号权限非常有限,可以调用的接口非常有限,不便于测试

所以,为了帮助开发者快速了解和上手微信公众号开发,熟悉各个接口的调用,微信推出了公众帐号测试号,无需公众帐号、快速申请接口测试号,通过手机微信扫描 二维码 即可获得,利用测试号我们可以体验和测试更多高级功能。测试号申请地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

但测试号也不是万能的,部分高级功能,如微信支付,卡券功能等也是不开放的。如果要实现支付功能还是得去注册个正式的公众号。本文后续所有测试都基于测试公众号

接入开发模式

我们首先了解下微信与我们的服务器交互的过程:

当我们在微信app上,给公众号发送一条内容的时候,实际会发送到微信的服务器上,此时微信的服务器就会对内容进行封装成某种格式的数据比如xml格式,再转发到我们配置好的某个URL上,所以该URL实际就是我们处理数据的一个请求路径。所以该URL必须是能暴露给外界访问的一个公网地址,不能使用内网地址,生产环境可以申请腾讯云,阿里云服务器等,但是在开发环境中可以暂时利用一些软件来完成内网穿透

在进行和微信公众号服务器交互之前,需要进行接入验证。接入验证涉及的2个关键参数如下:

  • URL:就是指我们自己的服务器地址。该URL是开发者用来接收和响应微信消息和事件的接口URL(必须以http://或https://开头,分别支持80端口和443端口)
  • Token:可任意填写,用作生成签名(必须为英文或数字,长度为3-32字符)

接入验证的大致流程如下:

  1. 开发者提交URL和token信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
参数描述
signature微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp时间戳
nonce随机数
echostr随机字符串
  1. 开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:

    1. 将token、timestamp、nonce三个参数进行字典序排序
    2. 将三个参数字符串拼接成一个字符串进行sha1加密
    3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
    注意到微信发送的GET请求中并没有携带我们填写的token,而签名signature的生成过程中token是一个入参,所以我们填写的url处理程序中,校验signature时也需要token,这就要求我们在url对应的后端程序中定义token,要和通过微信页面填写的token一致。这样做是为了安全性,只有知道token才可以接入成功,避免了他人盗用公众号做操作

根据上述验证流程,我们创建一个Springboot应用,编写验证接口

@RestController
@RequestMapping("wechat/publicAccount")
public class WechatPublicAccountController {
    // 微信页面填写的token,必须保密
    private static final String TOKEN = "";

    @GetMapping("validate")
    public String validate(String signature,String timestamp,String nonce,String echostr){
        // 1. 将token、timestamp、nonce三个参数进行字典序排序
        String[] arr = {timestamp, nonce, TOKEN};
        Arrays.sort(arr);
        // 2. 将三个参数字符串拼接成一个字符串进行sha1加密
        StringBuilder sb = new StringBuilder();
        for (String temp : arr) {
            sb.append(temp);
        }
        // 这里利用了hutool的加密工具类
        String sha1 = SecureUtil.sha1(sb.toString());
        // 3. 加密后的字符串与signature对比,如果相同则该请求来源于微信,原样返回echostr
        if (sha1.equals(signature)){
            return echostr;
        }
        // 接入失败
        return null;
    }
}

利用内网穿透工具,将我们的本地应用地址映射到公网域名

然后在测试号页面填写回调url和正确的token,即可接入成功。如果token填错,将接入失败

特别注意:

  • 在接入成功后,后续所有微信发送过来的消息都会携带signaturetimestampnonce这3个参数,我们每次接收微信消息时都要跟初始接入一样去校验signature,以确保接收到的消息是微信发过来的,而不是其他人发过来的
  • 仅接入消息会携带echostr,后续所有微信发过来的消息不会携带echostr,所以可以根据请求是否携带了echostr来判断是否是接入消息。接入消息除了校验signature外要原样返回echostr,其他后续消息只需校验signature即可

消息接收与响应

1.接收消息

接入成功以后,我们就可以利用微信提供的接口实现各种功能。首先来看一下基本的消息接收和回复,官方文档位置如下

当关注了公众号的用户向公众号发送消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。所以我们要在Controller中新建一个处理方法

微信会将用户发送的消息信息封装到请求体的xml中,根据消息类型的不同,xml的格式也有所不同:

  • 文本消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[this is a test]]></Content>
  <MsgId>1234567890123456</MsgId>
</xml>
参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType消息类型,文本为text
Content文本消息内容
MsgId消息id,64位整型
  • 图片消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[image]]></MsgType>
  <PicUrl><![CDATA[this is a url]]></PicUrl>
  <MediaId><![CDATA[media_id]]></MediaId>
  <MsgId>1234567890123456</MsgId>
</xml>
参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType消息类型,图片为image
PicUrl图片链接(由系统生成)
MediaId图片消息媒体id,可以调用获取临时素材接口拉取数据。
MsgId消息id,64位整型
  • 语音消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1357290913</CreateTime>
  <MsgType><![CDATA[voice]]></MsgType>
  <MediaId><![CDATA[media_id]]></MediaId>
  <Format><![CDATA[Format]]></Format>
  <MsgId>1234567890123456</MsgId>
</xml>
参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType语音为voice
MediaId语音消息媒体id,可以调用获取临时素材接口拉取数据。
Format语音格式,如amr,speex等
MsgId消息id,64位整型
  • 视频消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1357290913</CreateTime>
  <MsgType><![CDATA[video]]></MsgType>
  <MediaId><![CDATA[media_id]]></MediaId>
  <ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>
  <MsgId>1234567890123456</MsgId>
</xml>
参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType视频为video
MediaId视频消息媒体id,可以调用获取临时素材接口拉取数据。
ThumbMediaId视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。
MsgId消息id,64位整型
  • 小视频消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1357290913</CreateTime>
  <MsgType><![CDATA[shortvideo]]></MsgType>
  <MediaId><![CDATA[media_id]]></MediaId>
  <ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>
  <MsgId>1234567890123456</MsgId>
</xml>
参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType小视频为shortvideo
MediaId视频消息媒体id,可以调用获取临时素材接口拉取数据。
ThumbMediaId视频消息缩略图的媒体id,可以调用获取临时素材接口拉取数据。
MsgId消息id,64位整型
  • 地理位置消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1351776360</CreateTime>
  <MsgType><![CDATA[location]]></MsgType>
  <Location_X>23.134521</Location_X>
  <Location_Y>113.358803</Location_Y>
  <Scale>20</Scale>
  <Label><![CDATA[位置信息]]></Label>
  <MsgId>1234567890123456</MsgId>
</xml>
参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType消息类型,地理位置为location
Location\_X地理位置纬度
Location\_Y地理位置经度
Scale地图缩放大小
Label地理位置信息
MsgId消息id,64位整型
  • 链接消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1351776360</CreateTime>
  <MsgType><![CDATA[link]]></MsgType>
  <Title><![CDATA[公众平台官网链接]]></Title>
  <Description><![CDATA[公众平台官网链接]]></Description>
  <Url><![CDATA[url]]></Url>
  <MsgId>1234567890123456</MsgId>
</xml>
参数描述
ToUserName接收方微信号
FromUserName发送方微信号,若为普通用户,则是一个OpenID
CreateTime消息创建时间
MsgType消息类型,链接为link
Title消息标题
Description消息描述
Url消息链接
MsgId消息id,64位整型

我们对比一下不同类型的xml数据包中的参数,ToUserNameFromUserNameCreateTimeMsgTypeMsgId这五个是公共的,所有类型都会带上这些参数。接下来,我们需要来了解这5个参数的具体意义:

  • ToUserName:文档上描述的是开发者微信号,实际上,直接把它当做你的公众号的微信号即可,表示的是发到那个公众号的意思。
  • FromUserName:与ToUserName相反,这是代表是由哪个用户发过来的,同一个用户发多条信息过来,FromUserName都是不变的。但这并不是用户的微信号,而是一个OpenID。那什么是OpenID呢:当用户和公众号发生了交互,微信服务器会为每个用户针对每个公众号产生一个OpenID(也就是指该OpenID是利用两个因素:用户和公众号来产生的,也就意味着如果该用户跟另外一个公众号交互,产生的OpenID也是不同的,这样安全性会比较高),如果一个公司有多个公众号,并且需要在多公众号、移动应用之间做用户共通,则需要使用UnionID,前往微信开放平台,将这些公众号和应用绑定到一个开放平台账号下,绑定后,一个用户虽然对多个公众号和应用有多个不同的OpenID,但他对所有这些同一开放平台账号下的公众号和应用,只有一个UnionID,可以在用户管理-获取用户基本信息(UnionID机制)文档了解详情。
  • CreateTime:消息创建时间
  • MsgType:用户发送的消息的类型,如text代表文本消息,image代表图片消息等。
  • MsgId:用户发送的每个消息都有自己的id,可以用于消息排重,比如微信服务器把xml消息包发送到URL了,但是五秒内微信服务器没有收到我们的响应,则会重新发起请求,总共重试三次。如果不做消息排重,那么用户可能就收到多条相同的响应消息了。
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试

接下来,我们可以创建一个封装消息的 实体类 ,把所有可接收到的参数都放进入,其他类型的暂时不演示,所以只在最后加入了文本和图片的参数。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class InMessage {
    // 开发者微信号
    protected String FromUserName;
    // 发送方帐号(一个OpenID)
    protected String ToUserName;
    // 消息创建时间
    protected Long CreateTime;
    /**
     * 消息类型
     * text 文本消息
     * image 图片消息
     * voice 语音消息
     * video 视频消息
     * music 音乐消息
     */
    protected String MsgType;
    // 消息id
    protected Long MsgId;
    // 文本内容
    private String Content;
    // 图片链接(由系统生成)
    private String PicUrl;
    // 图片消息媒体id,可以调用多媒体文件下载接口拉取数据
    private String MediaId;
}

这时候大家可能会有个疑问,为什么字段名称都是大写开头呢?因为微信服务器传过来的xml数据包中的xml元素都是大写开头的,如下所示:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[this is a test]]></Content>
  <MsgId>1234567890123456</MsgId>
</xml>

因为xml解析是大小写敏感的,所以为了方便封装,我直接把字段名设置为大写开头。当然,如果还是想要小写开头的字段,也是可以的,我们待会再说处理方式。

实体已经建好之后,我们就可以开始接收微信传过来的xml数据了。

第1步:在handleMessage方法的形参上添加消息实体类型的参数:

第2步:需要配合JAXB的注解来解析xml。在消息实体类上添加以下两个注解:

这2个注解的含义如下:

  • @XmlRootElement:是一个类级别注解,主要属性为name,意为指定根节点的名字。往上面看前面举了个微信传过来的xml数据的例子里,里面的根节点就是"xml",所以这里就直接设置name=“xml”
  • @XmlAccessorType:用于定义这个类中的何种类型需要映射到XML中

    • XmlAccessType.PROPERTY:代表映射这个类中的属性(get/set方法)到XML
    • XmlAccessType.FIELD:代表映射这个类中的所有字段到XML

之前我们为了方便解析,将消息实体类的属性命名成和xml中的节点一样都是大写开头,现在我们优化一下,按照java规范命名属性小写开头,然后利用@XmlElement给每个属性标注对应的xml节点名称

@Data
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class InMessage {
    /**
     * 开发者微信号
     */
    @XmlElement(name="FromUserName")
    protected String fromUserName;

    /**
     * 发送方帐号(一个OpenID)
     */
    @XmlElement(name="ToUserName")
    protected String toUserName;

    /**
     * 消息创建时间
     */
    @XmlElement(name="CreateTime")
    protected Long createTime;
    
    /**
     * 消息类型
     * text 文本消息
     * image 图片消息
     * voice 语音消息
     * video 视频消息
     * music 音乐消息
     */
    @XmlElement(name="MsgType")
    protected String msgType;

    /**
     * 消息id
     */
    @XmlElement(name="MsgId")
    protected Long msgId;

    /**
     * 文本内容
     */
    @XmlElement(name="Content")
    private String content;
    
    /**
     * 图片链接(由系统生成)
     */
    @XmlElement(name="PicUrl")
    private String picUrl;

    /**
     * 图片消息媒体id,可以调用多媒体文件下载接口拉取数据
     */
    @XmlElement(name="MediaId")
    private String mediaId;
}

然后我们在Controller接收消息的方法中设置断点,关注测试公众号,给它发送一个文本消息"你好",就可以接收到了

2.响应消息

官方文档:https://developers.weixin.qq.com/ doc /offiaccount/Message\_Management/Passive\_user\_reply\_message.html

当用户发送消息给公众号时(或某些特定的用户操作引发的 事件 推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复

微信服务器在将用户的消息发给公众号的开发者服务器地址(开发者中心处配置)后,微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次,如果在调试中,发现用户无法收到响应的消息,可以检查是否消息处理超时。关于重试的消息排重,有msgid的消息推荐使用msgid排重。事件类型消息推荐使用FromUserName + CreateTime 排重。

如果开发者希望增强安全性,可以在开发者中心处开启消息加密,这样,用户发给公众号的消息以及公众号被动回复用户消息都会继续加密。

假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示:

  1. 直接回复success(推荐方式)
  2. 直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)

一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:

  1. 开发者在5秒内未回复任何内容
  2. 开发者回复了异常数据,比如JSON数据等

另外,请注意,回复图片(不支持gif动图)等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。

各种类型的响应消息格式如下:

  • 回复文本消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[你好]]></Content>
</xml>
参数是否必须描述
ToUserName接收方帐号(收到的OpenID)
FromUserName开发者微信号
CreateTime消息创建时间 (整型)
MsgType消息类型,文本为text
Content回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示)
  • 回复图片消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[image]]></MsgType>
  <Image>
    <MediaId><![CDATA[media_id]]></MediaId>
  </Image>
</xml>
参数是否必须说明
ToUserName接收方帐号(收到的OpenID)
FromUserName开发者微信号
CreateTime消息创建时间 (整型)
MsgType消息类型,图片为image
MediaId通过素材管理中的接口上传多媒体文件,得到的id。
  • 回复语音消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[voice]]></MsgType>
  <Voice>
    <MediaId><![CDATA[media_id]]></MediaId>
  </Voice>
</xml>
参数是否必须说明
ToUserName接收方帐号(收到的OpenID)
FromUserName开发者微信号
CreateTime消息创建时间戳 (整型)
MsgType消息类型,语音为voice
MediaId通过素材管理中的接口上传多媒体文件,得到的id
  • 回复视频消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[video]]></MsgType>
  <Video>
    <MediaId><![CDATA[media_id]]></MediaId>
    <Title><![CDATA[title]]></Title>
    <Description><![CDATA[description]]></Description>
  </Video>
</xml>
参数是否必须说明
ToUserName接收方帐号(收到的OpenID)
FromUserName开发者微信号
CreateTime消息创建时间 (整型)
MsgType消息类型,视频为video
MediaId通过素材管理中的接口上传多媒体文件,得到的id
Title视频消息的标题
Description视频消息的描述
  • 回复音乐消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[music]]></MsgType>
  <Music>
    <Title><![CDATA[TITLE]]></Title>
    <Description><![CDATA[DESCRIPTION]]></Description>
    <MusicUrl><![CDATA[MUSIC_Url]]></MusicUrl>
    <HQMusicUrl><![CDATA[HQ_MUSIC_Url]]></HQMusicUrl>
    <ThumbMediaId><![CDATA[media_id]]></ThumbMediaId>
  </Music>
</xml>
参数是否必须说明
ToUserName接收方帐号(收到的OpenID)
FromUserName开发者微信号
CreateTime消息创建时间 (整型)
MsgType消息类型,音乐为music
Title音乐标题
Description音乐描述
MusicURL音乐链接
HQMusicUrl高质量音乐链接,WIFI环境优先使用该链接播放音乐
ThumbMediaId缩略图的媒体id,通过素材管理中的接口上传多媒体文件,得到的id
  • 回复图文消息
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[news]]></MsgType>
  <ArticleCount>1</ArticleCount>
  <Articles>
    <item>
      <Title><![CDATA[title1]]></Title>
      <Description><![CDATA[description1]]></Description>
      <PicUrl><![CDATA[picurl]]></PicUrl>
      <Url><![CDATA[url]]></Url>
    </item>
  </Articles>
</xml>
参数是否必须说明
ToUserName接收方帐号(收到的OpenID)
FromUserName开发者微信号
CreateTime消息创建时间 (整型)
MsgType消息类型,图文为news
ArticleCount图文消息个数;当用户发送文本、图片、语音、视频、图文、地理位置这六种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息
Articles图文消息信息,注意,如果图文数超过限制,则将只发限制内的条数
Title图文消息标题
Description图文消息描述
PicUrl图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200
Url点击图文消息跳转链接

我们可以看到回复的格式中除了都没有MsgId,只有普通文本和接收的格式基本是一样的,图片消息或其他类型的消息与接收到的格式相比,多了xml元素的嵌套,所以原先定义的接收消息实体类无法复用,我们再封装一个响应消息的实体类,这里暂时只考虑文本和图片类型

@Data
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class OutMessage {
    /**
     * 开发者微信号
     */
    @XmlElement(name="FromUserName")
    protected String fromUserName;

    /**
     * 发送方帐号(一个OpenID)
     */
    @XmlElement(name="ToUserName")
    protected String toUserName;

    /**
     * 消息创建时间
     */
    @XmlElement(name="CreateTime")
    protected Long createTime;

    /**
     * 消息类型
     * text 文本消息
     * image 图片消息
     * voice 语音消息
     * video 视频消息
     * music 音乐消息
     */
    @XmlElement(name="MsgType")
    protected String msgType;
    
    /**
     * 文本内容
     */
    @XmlElement(name="Content")
    private String content;

    /**
     * 图片的媒体id列表
     */
    @XmlElementWrapper(name="Image") // 表示MediaId属性内嵌于<Image>标签
    @XmlElement(name = "MediaId")
    private String[] mediaId;
}

@XmlElementWrapper注解可以在原xml结点上再包装一层xml,但仅允许出现在数组或集合属性上

现在我们来实现一下简单的回复消息:用户给我们发什么,我们就回复什么,只需要把接收到InMessage的内容设置到OutMessage上,并且把ToUserNameFromUserName的值设置为相反即可

@PostMapping(value = "validate", produces = "application/xml;charset=UTF-8")
public Object handleMessage(@RequestBody InMessage inMessage){
    // 创建响应消息实体对象
    OutMessage outMessage = new OutMessage();
    // 把原来的接收方设置为发送方
    outMessage.setFromUserName(inMessage.getToUserName());
    // 把原来的发送方设置为接收方
    outMessage.setToUserName(inMessage.getFromUserName());
    // 设置消息类型
    outMessage.setMsgType(inMessage.getMsgType());
    // 设置消息时间
    outMessage.setCreateTime(System.currentTimeMillis());
    // 根据接收到消息类型,响应对应的消息内容
    if ("text".equals(inMessage.getMsgType())){
        // 文本
        outMessage.setContent(inMessage.getContent());
    }else if ("image".equals(inMessage.getMsgType())){
        // 图片
        outMessage.setMediaId(new String[]{inMessage.getMediaId()});
    }
    return outMessage;
}
注意:这里要在接口的@PostMapping中指定produces = "application/xml;charset=UTF-8",表示返回的数据格式是xml,否则将默认以json格式返回

测试效果如下

关键字回复

在开发模式下要实现关键字回复非常简单,只要判断发送过来的文本消息中是否包含关键字,然后给予相应的回复即可

@PostMapping(value = "validate", produces = "application/xml;charset=UTF-8")
public Object handleMessage(@RequestBody InMessage inMessage){
    // 创建响应消息实体对象
    OutMessage outMessage = new OutMessage();
    // 把原来的接收方设置为发送方
    outMessage.setFromUserName(inMessage.getToUserName());
    // 把原来的发送方设置为接收方
    outMessage.setToUserName(inMessage.getFromUserName());
    // 设置消息类型
    outMessage.setMsgType(inMessage.getMsgType());
    // 设置消息时间
    outMessage.setCreateTime(System.currentTimeMillis() / 1000);
    // 根据接收到消息类型,响应对应的消息内容
    if ("text".equals(inMessage.getMsgType())){
        // 根据不同的关键字回复消息
        String inContent = inMessage.getContent();
        if (inContent.contains("游戏")){
            outMessage.setContent("仙剑");
        }else if (inContent.contains("动漫")){
            outMessage.setContent("进击的巨人");
        }else {
            outMessage.setContent(inContent);
        }
    }else if ("image".equals(inMessage.getMsgType())){
        // 图片
        outMessage.setMediaId(new String[]{inMessage.getMediaId()});
    }
    return outMessage;
}

事件推送

官方文档:https://developers.weixin.qq.com/doc/offiaccount/Message\_Management/Receiving\_event\_pushes.html

我们之前的案例都是用户发送信息过来,我们才回复的。但是,如果是关注的时候需要马上回复,就要使用到事件消息,实际上,微信已经提供给我们很多的事件

在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息。其中,某些事件推送在发生后,是允许开发者回复用户的,某些则不允许

1.关注/取消关注事件

用户在关注与取消关注公众号时,微信会把这个事件推送到开发者填写的URL。方便开发者给用户下发欢迎消息或者做帐号的解绑。为保护用户数据隐私,开发者收到用户取消关注事件时需要删除该用户的所有信息。

微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。

关于重试的消息排重,推荐使用FromUserName + CreateTime 排重。

假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。

推送XML数据包示例:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[subscribe]]></Event>
</xml>
参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType消息类型,event
Event事件类型,subscribe(订阅)、unsubscribe(取消订阅)

2.扫描带参数二维码事件

用户扫描带场景值二维码时,可能推送以下两种事件:

  1. 如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[subscribe]]></Event>
  <EventKey><![CDATA[qrscene_123123]]></EventKey>
  <Ticket><![CDATA[TICKET]]></Ticket>
</xml>
参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType消息类型,event
Event事件类型,subscribe
EventKey事件KEY值,qrscene\_为前缀,后面为二维码的参数值
Ticket二维码的ticket,可用来换取二维码图片
  1. 如果用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者。
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[SCAN]]></Event>
  <EventKey><![CDATA[SCENE_VALUE]]></EventKey>
  <Ticket><![CDATA[TICKET]]></Ticket>
</xml> 
参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType消息类型,event
Event事件类型,SCAN
EventKey事件KEY值,是一个32位无符号整数,即创建二维码时的二维码scene\_id
Ticket二维码的ticket,可用来换取二维码图片

3.上报地理位置事件

这个事件仅用于服务号,订阅号不行。用户同意上报地理位置后,每次进入公众号会话时,都会在进入时上报地理位置,或在进入会话后每5秒上报一次地理位置,公众号可以在公众平台网站中修改以上设置。上报地理位置时,微信会将上报地理位置事件推送到开发者填写的URL。

推送XML数据包示例:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[LOCATION]]></Event>
  <Latitude>23.137466</Latitude>
  <Longitude>113.352425</Longitude>
  <Precision>119.385040</Precision>
</xml>
参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType消息类型,event
Event事件类型,LOCATION
Latitude地理位置纬度
Longitude地理位置经度
Precision地理位置精度

4.自定义菜单事件

用户点击自定义菜单后,微信会把点击事件推送给开发者,请注意,点击菜单弹出子菜单,不会产生上报

点击菜单拉取消息时的事件推送

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[CLICK]]></Event>
  <EventKey><![CDATA[EVENTKEY]]></EventKey>
</xml>
参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType消息类型,event
Event事件类型,CLICK
EventKey事件KEY值,与自定义菜单接口中KEY值对应

点击菜单跳转链接时的事件推送

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[VIEW]]></Event>
  <EventKey><![CDATA[www.qq.com]]></EventKey>
</xml>
参数描述
ToUserName开发者微信号
FromUserName发送方帐号(一个OpenID)
CreateTime消息创建时间 (整型)
MsgType消息类型,event
Event事件类型,VIEW
EventKey事件KEY值,设置的跳转URL

事件推送案例演示

我们来实现一下用户关注公众号时接收推送消息并自动回复的功能

事件和消息都是推送到我们的URL上,怎么区分他们也很简单,通过MsgType这个属性,那么进一步再区分是关注还是取消关注,根据Event属性即可。所以,我们在原来的InMessage类,再添加一个Event属性

然后在Controller处理方法中添加对应逻辑

然后取消关注后再关注,即可展现效果

自定义菜单

1.自定义菜单简介

官方文档:https://developers.weixin.qq.com/doc/offiaccount/Custom\_Menus/Creating\_Custom-Defined\_Menu.html

自定义菜单能够帮助公众号丰富界面,让用户更好更快地理解公众号的功能。开启自定义菜单后,公众号界面如图所示:

请注意:

  • 自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
  • 一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“…”代替。
  • 创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。

自定义菜单接口可实现多种类型按钮,如下:

  1. click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
  2. view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。
  3. scancode\_push:扫码推事件用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。
  4. scancode\_waitmsg:扫码推事件且弹出“消息接收中”提示框用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。
  5. pic\_sysphoto:弹出系统拍照发图用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。
  6. pic\_photo\_or\_album:弹出拍照或者相册发图用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。
  7. pic\_weixin:弹出微信相册发图器用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。
  8. location\_select:弹出地理位置选择器用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。
  9. media\_id:下发消息(除文本消息)用户点击media\_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
  10. view\_limited:跳转图文消息URL用户点击view\_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
请注意,3到8的所有事件,仅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。9和10,是专门给第三方平台旗下未微信认证(具体而言,是资质认证未通过)的订阅号准备的事件类型,它们是没有事件推送的,能力相对受限,其他类型的公众号不必使用。

如果我们要给公众号创建自定义菜单,需要发送下列请求

http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access\_token=ACCESS\_TOKEN

请求体示例如下:

{
     "button":[
     {    
          "type":"click",
          "name":"今日歌曲",
          "key":"V1001_TODAY_MUSIC"
      },
      {
           "name":"菜单",
           "sub_button":[
           {    
               "type":"view",
               "name":"搜索",
               "url":"http://www.soso.com/"
            },
            {
                 "type":"miniprogram",
                 "name":"wxa",
                 "url":"http://mp.weixin.qq.com",
                 "appid":"wx286b93c14bbf93aa",
                 "pagepath":"pages/lunar/index"
             },
            {
               "type":"click",
               "name":"赞一下我们",
               "key":"V1001_GOOD"
            }]
       }]
 }
参数是否必须说明
button一级菜单数组,个数应为1~3个
sub\_button二级菜单数组,个数应为1~5个
type菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型
name菜单标题,不超过16个字节,子菜单不超过60个字节
keyclick等点击类型必须菜单KEY值,用于消息接口推送,不超过128字节
urlview、miniprogram类型必须网页 链接,用户点击菜单可打开链接,不超过1024字节。 type为miniprogram时,不支持小程序的老版本客户端将打开本url。
media\_idmedia\_id类型和view\_limited类型必须调用新增永久素材接口返回的合法media\_id
appidminiprogram类型必须小程序的appid(仅认证公众号可配置)
pagepathminiprogram类型必须小程序的页面路径

正确时的返回 JSON 数据包如下:

{"errcode":0,"errmsg":"ok"}

错误时的返回JSON数据包如下(示例为无效菜单名长度):

{"errcode":40018,"errmsg":"invalid button name size"}

其他类型的菜单示例如下

{
    "button": [
        {
            "name": "扫码", 
            "sub_button": [
                {
                    "type": "scancode_waitmsg", 
                    "name": "扫码带提示", 
                    "key": "rselfmenu_0_0", 
                    "sub_button": [ ]
                }, 
                {
                    "type": "scancode_push", 
                    "name": "扫码推事件", 
                    "key": "rselfmenu_0_1", 
                    "sub_button": [ ]
                }
            ]
        }, 
        {
            "name": "发图", 
            "sub_button": [
                {
                    "type": "pic_sysphoto", 
                    "name": "系统拍照发图", 
                    "key": "rselfmenu_1_0", 
                   "sub_button": [ ]
                 }, 
                {
                    "type": "pic_photo_or_album", 
                    "name": "拍照或者相册发图", 
                    "key": "rselfmenu_1_1", 
                    "sub_button": [ ]
                }, 
                {
                    "type": "pic_weixin", 
                    "name": "微信相册发图", 
                    "key": "rselfmenu_1_2", 
                    "sub_button": [ ]
                }
            ]
        }, 
        {
            "name": "发送位置", 
            "type": "location_select", 
            "key": "rselfmenu_2_0"
        },
        {
           "type": "media_id", 
           "name": "图片", 
           "media_id": "MEDIA_ID1"
        }, 
        {
           "type": "view_limited", 
           "name": "图文消息", 
           "media_id": "MEDIA_ID2"
        }
    ]
}

2.获取access\_token

在我们调用创建菜单接口之前,必须先获取调用微信接口的access_tokenaccess_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access\_token的存储至少要保留512个字符空间。access\_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access\_token失效

公众平台的API调用所需的access\_token的使用及生成方式说明:

  • 建议公众号开发者使用中控服务器统一获取和刷新access\_token,其他业务逻辑服务器所使用的access\_token均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致access\_token覆盖而影响业务;
  • 目前access\_token的有效期通过返回的expire\_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access\_token。在刷新过程中,中控服务器可对外继续输出的老access\_token,此时公众平台后台会保证在5分钟内,新老access\_token都可用,这保证了第三方业务的平滑过渡;
  • access\_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新access\_token的接口,这样便于业务服务器在API调用获知access\_token已超时的情况下,可以触发access\_token的刷新流程。
  • 对于可能存在风险的调用,在开发者进行获取 access\_token调用时进入风险调用确认流程,需要用户管理员确认后才可以成功获取。具体流程为:开发者通过某IP发起调用->平台返回错误码[89503]并同时下发模板消息给公众号管理员->公众号管理员确认该IP可以调用->开发者使用该IP再次发起调用->调用成功。如公众号管理员第一次拒绝该IP调用,用户在1个小时内将无法使用该IP再次发起调用,如公众号管理员多次拒绝该IP调用,该IP将可能长期无法发起调用。平台建议开发者在发起调用前主动与管理员沟通确认调用需求,或请求管理员开启IP白名单功能并将该IP加入IP白名单列表。

公众号和小程序均可以使用AppID和AppSecret调用本接口来获取access\_token。AppID和AppSecret可在“微信公众平台-开发-基本配置”页中获得(需要已经成为开发者,且帐号没有异常状态)。**调用接口时,请登录“微信公众平台-开发-基本配置”提前将服务器IP地址添加到IP白名单中,否则将无法调用成功。**小程序无需配置IP白名单。

获取access_token需要调用的接口如下

https请求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant\_type=client\_credential&appid=APPID&secret=APPSECRET

参数是否必须说明
grant\_type获取access\_token填写client\_credential
appid公众号唯一凭证,注册成功后由微信提供
secret公众号唯一凭证密钥,注册成功后由微信提供

正常情况下,微信会返回下述JSON数据包给公众号:

{"access_token":"ACCESS_TOKEN","expires_in":7200}
参数说明
access\_token获取到的凭证
expires\_in凭证有效时间,单位:秒

错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):

{"errcode":40013,"errmsg":"invalid appid"}
返回码说明
-1系统繁忙,此时请开发者稍候再试
0请求成功
40001AppSecret错误或者AppSecret不属于这个公众号,请开发者确认AppSecret的正确性
40002请确保grant\_type字段值为client\_credential
40164调用接口的IP地址不在白名单中,请在接口IP白名单中进行设置。(小程序及小游戏调用不要求IP地址在白名单内。)
89503此IP调用需要管理员确认,请联系管理员
89501此IP正在等待管理员确认,请联系管理员
8950624小时内该IP被管理员拒绝调用两次,24小时内不可再使用该IP调用
895071小时内该IP被管理员拒绝调用一次,1小时内不可再使用该IP调用

在Controller中创建获取access_token的方法,获取成功后将其保存到redis中

@GetMapping("getAccessToken")
public String getAccessToken(){
    String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID +
        "&secret=" + APPSECRET;
    // 利用hutool的http工具类请求获取access_token
    String result = HttpUtil.get(url);
    // 将结果解析为json
    JSONObject jsonObject = JSONUtil.parseObj(result);
    // 获取access_token
    String accessToken = jsonObject.getStr("access_token");
    if (!StringUtils.isEmpty(accessToken)){
        // 将access_token存入redis
        stringRedisTemplate.opsForValue().set("access_token", accessToken);
    }
    return accessToken;
}

3.创建自定义菜单

在Controller中新增方法,发送创建菜单的请求

@GetMapping("createMenu")
public String createMenu(){
    // 从redis中取出access_token
    String accessToken = stringRedisTemplate.opsForValue().get("access_token");
    String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + accessToken;
    // 创建菜单的请求体
    String body = "{\n" +
        "     \"button\":[\n" +
        "     {\t\n" +
        "          \"type\":\"click\",\n" +
        "          \"name\":\"位置\",\n" +
        "          \"key\":\"button_location\"\n" +
        "      }]}";
    return HttpUtil.post(url, body);
}

发送请求后返回结果

{"errcode":0,"errmsg":"ok"}

创建菜单后效果如下

发送模板消息

官方文档:https://developers.weixin.qq.com/doc/offiaccount/Message\_Management/Template\_Message\_Interface.html

在我们的生活中,无论是微商城消费,还是日常生活消费,都可能收到这种提示,比如订单通知,快递状态通知,银行卡支付通知,都属于业务通知,很多公众号也都实现了这种功能,当触发了某种行为或状态改变,就会发送这么一个消息给你,因为这种消息都是按照一定的的格式来编辑,所以也叫模板消息

认证后的服务号才可以申请模板消息的使用权限并获得该权限,否则就只能使用测试号

我们要发送模板消息,第一步是需要创建一个模板,有了模板之后,我们才能填充内容来进行发送。创建模板不需要调用接口,在公众号后台即可设置

现在我们来按照下面案例来新建一个模板。但是模板的内容是有一定的规则的,不能随便添加:

  • 测试模板的模板ID仅用于测试,不能用来给正式帐号发送模板消息
  • 为方便测试,测试模板可任意指定内容,但实际上正式帐号的模板消息,只能从模板库中获得
  • 需为正式帐号申请新增符合要求的模板,需使用正式号登录公众平台,按指引申请
  • 模板内容可设置参数(模板标题不可),供接口调用时使用,参数需以{{开头,以.DATA}}结尾

保存之后,微信会给该模板分配一个ID,待我们要发送模板消息的时候就需要用到这个ID了

发送模板消息需要如下请求:

http请求方式: POST https://api.weixin.qq.com/cgi-bin/message/template/send?access\_token=ACCESS\_TOKEN

请求体示例如下

{
    "touser":"OPENID",
    "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
    "url":"http://weixin.qq.com/download",  
    "miniprogram":{
        "appid":"xiaochengxuappid12345",
        "pagepath":"index?foo=bar"
    },          
    "data":{
        "goodsName":{
            "value":"巧克力",
            "color":"#173177"
        },
        "price": {
            "value":"39.8元",
            "color":"#173177"
        },
        "time": {
            "value":"2014年9月22日",
            "color":"#173177"
        },
        "remark":{
            "value":"欢迎再次购买!",
            "color":"#173177"
        }
    }
}
参数是否必填说明
touser接收者openid
template\_id模板ID
url模板跳转链接(海外帐号没有跳转能力)
miniprogram跳小程序所需数据,不需跳小程序可不用传该数据
appid所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏)
pagepath所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),要求该小程序已发布,暂不支持小游戏
data模板数据
color模板内容字体颜色,不填默认为黑色

在调用模板消息接口后,会返回JSON数据包。正常时的返回JSON数据包示例:

{
    "errcode":0,
    "errmsg":"ok",
    "msgid":200228332
}

1、OpenClaw介绍

OpenClaw(曾用名 Clawdbot、Moltbot)是一款‌本地优先、可自托管的AI自动化代理工具‌,由开发者 Peter Steinberger 于2025年底创建。核心理念是:“The AI that actually does things”——真正能做事的AI。

与传统聊天机器人不同,OpenClaw 不仅能理解自然语言,还能主动规划任务、调用工具、执行操作并反馈结果,形成“‌需求解析 → 任务规划 → 工具调用 → 结果反馈‌”的完整闭环。

2、AI代理工具对比

OpenCode 与 Claude Code 聚焦开发者,OpenWork 与 Claude Cowork 服务办公人群,而OpenClaw 则是面向个人的全场景自动化中枢

工具归属类别开源/闭源核心载体目标用户核心优势
OpenCode编程专用 AI 代理开源(MIT)终端优先,支持 IDE / 桌面终端开发者、极客、小团队多模型兼容、本地优先、高度可定制
Claude Code编程专用 AI 代理闭源终端 / IDE / 桌面专业开发者、企业团队模型强、稳定性高、企业级安全
OpenWork通用办公 AI 代理开源(MIT)桌面 GUI(基于 OpenCode)非技术员工、混合团队可视化工作流、低门槛、本地可控
Claude Cowork通用办公 AI 代理闭源桌面应用全岗位办公人群、企业开箱即用、跨工具联动、沙盒安全
OpenClaw全场景本地自动化AI 代理开源(MIT)本地网关(CLI)个人 / 轻量团队、自动化需求者跨渠道控制、持久记忆、完全自托管

3、OpenClaw部署

3.1、安装系统要求

确认机器环境,需要符合如下要求。

1、Node >=22
2、macOS、Linux 或通过 WSL2 的 Windows
3、pnpm 仅在从源代码构建时需要

3.2、OpenClaw安装

方式1、本机安装

通过 npm 全局安装 openclaw 并运行新手引导。

curl -fsSL https://openclaw.ai/install.sh | bash

初始化配置与验证:安装完成后,自动启动配置向导(或手动执行命令);向导将引导完成以下配置:

  • 认证配置:生成Gateway访问令牌,确保本地和远程客户端需认证连接。
  • 网关设置:配置端口(默认18789)、绑定地址。
  • 通道连接:可选配置WhatsApp、Telegram、Discord等消息平台集成。
  • 后台服务安装:将Gateway安装为系统服务,开机自启。
openclaw onboard

更多参考:Link

方式2、云服务快速安装

云服务快速安装:针对希望快速上线、免本地环境折腾的受众,建议进行云端安装。可提供更稳定的公网访问能力,服务可持续运行、随时随地都能连接使用。如 阿里云、腾讯云 等。

更多可参考各云厂商文档,比如:Link

4、OpenClaw体验

在上述配置完成后,需重启OpenClaw服务使配置生效,OpenClaw将加载新配置的百炼模型,然后可进行后续的验证操作。

img

5、QQ Channel集成

5.1、QQ机器人创建

前往腾讯QQ开放平台官网,在龙虾专用入口单击创建机器人,生成新的QQ机器人,并获取机器人的AppID和AppSecret。

img

5.2、OpenClaw配置集成:

安装QQ Channel,然后登录 OpenClaw 控制台进行QQ渠道配置;填入上文获取的App ID和App Secret,并单击应用生效。

// 安装 QQ Bot 插件(云服务版默认集成QQ插件、且提供工作台设置AppID等信息,可跳过该配置)
openclaw plugins install @sliverp/qqbot

// 执行命令添加账号凭证(将 你的AppID 和 你的AppSecret 替换为真实值):
openclaw channels add --channel qqbot --token "你的AppID:你的AppSecret"

6、OpenClaw体验

依次完成OpenClaw安装、模型配置、QQ Channel配置后,可对话体验OpenClaw:养虾成功,更多Skills及价值研究挖掘中

img

关注我了解更多AI知识!

什么是大模型接入服务商?

我们先说没有七牛云、OpenRouter这种大模型接入服务商时,你想使用OpenClaw或者ClaudeCode调用大模型时,该怎么做?

自己接入大模型-不使用大模型接入服务商的方式

  • 第一,你要先想好你使用哪家公司的大模型API(HTTP)接口,比如:你要先确定好你要使用国内哪家公司的大模型。
  • 第二,假如你已经确定使用国内MiniMax公司的大模型了,这时,你要先访问MiniMax的官网注册账号、创建API Key,获取MiniMax的API(HTTP)接口地址(api.minimaxi.com/anthropic )、充值、然后把MiniMax的API Key接入你的OpenClaw或者ClaudeCode就可以了。注意,这种方式你只能使用MiniMax这家公司的大模型,若中途想切换到其他厂商,你要重新去另外一家公司的官网注册、充值、获取API(http)接口地址。

使用大模型接入服务商的方式

  • 第一,大模型接入服务商它自己对接了很多家大模型公司,你只需要在大模型接入服务商这里注册用户,然后在大模型接入服务商这里获取一个统一的大模型API(http)接口地址、充值、获取API Key就行了。使用大模型接入服务商提供的方式,你可以使用大模型服务接入商背后对接的所有大模型,然后中途你可以随时切换任意大模型服务接入商提供的大模型。
  • 第二,你可以把大模型接入服务商想象成一个中介,你只需要找中介,告诉中介你想接入什么大模型,只要中介手里面有你想要的大模型,你都可以成功接入,而且不用重新更换你的API(http)接口地址,无需重新注册账号、充值等操作。你只需要在你的OpenClaw和ClaudeCode的配置文件里面更换一下你的大模型ID就行了。

下面我们使用七牛云大模型接入服务去调用大模型

七牛云已对接国内外绝大多数主流大模型,包含国外的大模型,国内的就更不用说了,你只需要注册一个七牛云的用户就可以使用了。

使用电脑浏览器访问该链接七牛云官网 https://s.qiniu.com/ey6Fzy,注册账号,新用户可以免费领取1000万的token。

注册账号1000万token.png

注意必须点击我的这个连接七牛云官网 https://s.qiniu.com/ey6Fzy,否则你只能拿到300万的Token,如下截图。

300万token.png

1. 注册成功后先获取API Key

点击上面的连接并注册新用户之后,点击网页右上角的[控制台],进入控制台页面(见下图):

控制台.png

进入控制台页面之后,再点击控制台左上角的[AI 大模型推理] 菜单,如下截图:

AI 大模型推理菜单.png

进入[AI 大模型推理]页面之后,点击网页左上角的[API KEY],然后点击页面上方的[创建API KEY],创建一个API KEY即可。API KEY的名字,你可以随便起。页面右边有大模型的API(http)接口地址,注意这个接口地址只是一个BASE URL,只是基本的大模型接口地址,我们具体使用哪个大模型会在后面通过指定大模型的ID来确定我们到底使用哪家公司的大模型。

创建API Key.png

也可以通过点击七牛云官网七牛云官网的[AI 大模型]菜单去创建API Key,如下截图:

AI大模型.png

2. 访问七牛云的大模型广场搜索你想使用的大模型

点击网页右上角的[模型广场],搜索你想使用的大模型。国内大模型推荐优先选择:

  1. 智谱 GLM-5
  2. Minimax-M2.5
  3. Kimi-K2.5

大模型广场.png

注意,如果你想使用国外的大模型,你可以访问这个链接大模型广场 https://sufy.com/zh-CN/services/ai-inference/models去搜索访国外的大模型,国外的大模型确实比较厉害,但是缺点就是太贵了,根本用不起。我个人觉得,我们使用国内的智谱、Minimax、Kimi就足够了,对我来说真的是绰绰有余。我个人由于暂时没钱并且我的使用场景,我也用不上国外这种昂贵的大模型。你有需要可以使用。

国外大模型广场.png

3. 获取大模型的ID

我们在[模型广场]搜索模型之后,点击模型的详情,可以获取大模型的模型ID,操作步骤如下截图:

查看详情.png

获取大模型ID.png

在上面的截图中,我们可以拿到ModelID(大模型ID):moonshotai/kimi-k2.5、BaseURL(注意:BaseURL我们复制这个Anthropic BaseURL:api.qnaigc.com )

再加上我们之前配置好的API Key,我们就可以给OpenClaw🦞和ClaudeCode接入大模型,让OpenClaw🦞和ClaudeCode给我们干活了。

4. 给OpenClaw接入七牛云大模型调用服务商

目前OpenClaw还不支持通过命令或者界面的方式去配置七牛云的大模型调用方式,所以我们只能通过手工修改OpenClaw的openclaw.json配置文件,去配置七牛云的大模型调用方式。

OpenClaw的配置文件openclaw.json在你的C盘的这个目录:C:\Users\你的电脑用户名\.openclaw,如下截图:

OpenClaw配置文件.png

4.1 编辑OpenClaw的配置文件openclaw.json

先停止你电脑上的OpenClaw,然后打开openclaw.json文件,安全起见:建议你自己先把该配置文件复制一份到别的地方进行备份,以防万一。

4.2 先配置models节点

我们先修改openclaw.json文件中的models节点,这个是配置大模型提供商的,我们添加如下信息:

`"qiniu": {  
    "baseUrl": "https://api.qnaigc.com",  
    "api":"anthropic-messages",  
    "apiKey":"你自己的七牛云APIKEY",  
    "models":[  
        {  
        "id": "moonshotai/kimi-k2.5",  
        "name": "Kimi K2.5",  
        "reasoning": false,  
        "input": ["text"],  
        "contextWindow": 256000,  
        "maxTokens": 8192,  
        "compat": {  
            "supportsStore": false,  
            "supportsDeveloperRole": false,  
            "supportsReasoningEffort": false  
            }  
        }  
    ]  
}

配置Models.png

不要忘了,在"apiKey"那里填上你自己的七牛云API Key。

4.3 再配置agents节点

我们添加如下信息:

"agents": {  
    "defaults": {  
        "model": {  
            "primary": "qiniu/moonshotai/kimi-k2.5"  
        },  
        "models": {  
            "qiniu/moonshotai/kimi-k2.5": {  
                "alias": "Kimi K2.5"  
            }  
        }  
    }  
}

配置Agents.png

4.4 启动OpenClaw

执行以下命令启动 OpenClaw:

openclaw gateway --verbose

启动OpenClaw.png

启动OpenClaw成功.png

可以看到,我们使用七牛云调用大模型的方式已经配置成功了,OpenClaw已经可以成功启动,并正常工作了。

5 给ClaudeCode接入七牛云大模型

ClaudeCode的配置文件settings.json在你的C盘的这个目录:C:\Users\你的电脑用户名\.claude,如下截图:

ClaudeCode配置文件.png

5.1 编辑ClaudeCode的配置文件settings.json

先停止你电脑上的ClaudeCode,然后打开settings.json文件,安全起见:建议你自己先把该配置文件复制一份到别的地方进行备份,以防万一。

我们添加如下信息:

{  
    "env": {  
        "ANTHROPIC_BASE_URL": "[https://api.qnaigc.com"](https://api.qnaigc.com%22),  
        "ANTHROPIC_AUTH_TOKEN": "你自己的七牛云API Key",  
        "API_TIMEOUT_MS": "3000000",  
        "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",  
        "ANTHROPIC_MODEL": "moonshotai/kimi-k2.5",  
        "ANTHROPIC_SMALL_FAST_MODEL": "moonshotai/kimi-k2.5",  
        "ANTHROPIC_DEFAULT_SONNET_MODEL": "moonshotai/kimi-k2.5",  
        "ANTHROPIC_DEFAULT_OPUS_MODEL": "moonshotai/kimi-k2.5",  
        "ANTHROPIC_DEFAULT_HAIKU_MODEL": "moonshotai/kimi-k2.5"  
    },  
    "autoUpdatesChannel": "stable",  
    "companyAnnouncements": ["您正在使用微信公众号:四千岁 AI 大模型推理服务 🚀"]  
}

ClaudeCode配置文件截图.png

不要忘了,在"ANTHROPIC\_AUTH\_TOKEN"那里填上你自己的七牛云API Key。

5.2 启动ClaudeCode

使用如下命令启动Claude

claude

启动ClaudeCode.png

可以看到,我们使用七牛云调用大模型的方式已经配置成功了,ClaudeCode已经可以成功启动,并正常工作了。

6. 无需翻墙-使用Anthropic公司的Opus大模型-只需要更换模型ID即可

访问SUFY网站,搜索Opus,如下截图

SUFY网站.png

6.1 复制Opus的模型ID

我们通过七牛云调用Opus大模型,只需要复制Opus的模型ID即可:claude-4.6-opus

Opus大模型的ID.png

6.2 编辑ClaudeCode的配置文件settings.json

先停止你电脑上的ClaudeCode,然后打开settings.json文件,安全起见:建议你自己先把该配置文件复制一份到别的地方进行备份,以防万一。

我们添加如下信息:

{  
    "env": {  
        "ANTHROPIC_BASE_URL": "https://api.qnaigc.com",  
        "ANTHROPIC_AUTH_TOKEN": "你的七牛云API Key",  
        "API_TIMEOUT_MS": "3000000",  
        "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",  
        "ANTHROPIC_MODEL": "claude-4.6-opus",  
        "ANTHROPIC_SMALL_FAST_MODEL": "claude-4.6-opus",  
        "ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-4.6-opus",  
        "ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-4.6-opus",  
        "ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-4.6-opus"  
    },  
    "autoUpdatesChannel": "stable",  
    "companyAnnouncements": [  
    "您正在使用微信公众号:四千岁 AI 大模型推理服务 🚀"  
    ]  
}

只需要更换大模型ID即可.png

6.3 启动ClaudeCode

使用如下命令启动Claude

claude

再次启动ClaudeCode.png

可以看到,我们使用七牛云调用国外大模型的方式已经配置成功了,ClaudeCode已经可以成功启动,并正常工作了。国外的大模型太贵了,Token 很快就会耗尽,小心一点。

7. 怎么看Token使用量

访问七牛云的控制台,点击[用量统计]菜单,如下截图:

Token使用量.png

或者点击[API Key]菜单,如下截图:

API Key使用量.png

8. 怎么看七牛云给你了多少免费Token

访问七牛云的控制台,点击[模型广场]菜单,查看七牛云送给你的免费Token,如下截图:

免费Token.png

Token资源管理.png

9. 七牛云官方配置文档

七牛云官方配置文档链接官方配置文档,如下截图:

官方配置文档.png

9.1 获取七牛云官方文档方式

访问官网链接官网链接,点击[AI 大模型],如下截图:

AI 大模型服务.png

最佳实践.png

9.2 通过控制台页面中的模型广场访问官方配置文档

具体步骤如下截图:

查看模型详情.png

查看API文档.png

往下拉.png

点击最佳实践.png

欢迎关注我微信公众号:四千岁,关注我了解更多AI知识。欢迎点赞、收藏、关注,一键三连!!!

白嫖的时候,google AI studio 对话 100 万 token,天天用着挺爽。Antigravity 刚出来,用了一下,很惊艳。 后来感觉一般,看它有了 opus 很强,就先充三个月的 AI pro 试试,偶尔用用。2 月份一个 token 都没用。3 月,一次对话就限制一周。也懒得用了,他妈妈的。还是用我的 copilot pro 了。现在 gemini 3 pro 对话像个二逼青年。100 万的额度用了 5 万就限额,傻逼玩意。
屏幕截图 2026-03-14 134144.png
屏幕截图 2026-03-14 134200.png
屏幕截图 2026-03-14 134228.png
屏幕截图 2026-03-14 140054.png

广西电信,1000m 宽带

使用全球网测 APP 测速

南宁电信节点,跑满千兆

南宁联通节点,下行差不多 20mbps ,上行正常

南宁移动节点,下行就惨不忍睹了,5mbps 左右,上行正常

为了确认是不是我家被暗中限速了,我就找个同小区的电信千兆宽带的朋友来测,居然也得到了一样的结果。同省其他地方的电信宽带没试。

不知其他省有这种情况吗?可以用全球网测选自己同省不同运营商的节点测测

Cloudflare 近日宣布支持 ASPA(Autonomous System Provider Authorization)。这一新的加密标准通过验证数据在网络之间传输的路径,使互联网路由更加安全,从而防止流量经过不可靠或不受信任的网络。

ASPA 是一种基于 RPKI 的安全机制。它通过验证 AS_PATH(即路由通告经过的网络路径),来提升互联网路由协议 BGP 的安全性,从而减少路由泄露(route leaks)以及某些类型的路由劫持。这一新兴标准的目标,是提升互联网的整体可靠性,并减少由于意外或恶意行为导致的流量绕行。Cloudflare 首席系统工程师 Mingwei Zhang 和首席网络工程师 Bryton Herdes 解释道:

当数据在互联网上传输时,它会记录自己经过的每一个网络节点。ASPA 为网络运营者提供了一种机制,可以在 RPKI 系统中正式发布其授权的上游提供商列表。这样一来,接收网络就可以查看 AS_PATH,检查相关的 ASPA 记录,并验证数据流是否只经过被授权的网络链路。

边界网关协议(BGP) 是互联网流量路由的核心协议,但它本身缺乏原生的路径验证机制,因此容易受到路由泄露和路由劫持的影响。虽然 RPKI 和 Route Origin Authorization(ROA) 可以加强路由来源的验证,但它们并不会验证端到端路径。ASPA 通过一种加密方式,使网络运营者能够声明其被授权的提供商,从而让接收网络可以验证某个 AS 路径是否符合预期的结构。

ASPA 通过验证互联网路由的预期层级结构来检测路由绕行。在正常的 “valley-free”(无谷)拓扑 中,流量会从客户网络向上游提供商传递,可能在顶层经过一次对等(peer)连接,然后再通过提供商向下传递到目标客户网络。这种 客户 → 提供商的上行路径、可选的对等连接,以及提供商 → 客户的下行路径,共同构成了符合策略的标准路径。

去年,美国国家标准与技术研究院(NIST)发布了开源测试工具和数据集,以促进对新兴 BGP 安全和韧性机制的测试与实验,其中包括评估路由器实现 ASPA 规范的能力。

Cloudflare 还在 Cloudflare Radar 中添加了相关工具,用于跟踪 ASPA 的采用情况,使网络运营者能够查看哪些网络正在使用该技术,以及路径验证的情况。Zhang 和 Herdes 提醒:

随着 ASPA 终于成为现实,我们在互联网路径验证方面迎来了加密层面的升级。不过,对于那些从 RPKI 路由来源验证一开始就参与其中的人来说,他们知道,要让这一技术在互联网上真正产生显著价值仍然需要很长时间。 要真正利用 ASPA 对象并进行路径验证,还需要对多个组件进行改造,包括 RPKI 依赖方(RP)软件包、签名实现、RTR(RPKI-to-Router)软件以及 BGP 实现。

Cloudflare 表示,在最近的委内瑞拉 BGP 路由泄露事件 中,如果部署了 ASPA,网络就可以通过验证观察到的 AS 路径是否符合预期的提供商授权关系,从而检测并拒绝异常的路径通告,而仅依赖来源验证是无法做到这一点的。

Cloudflare 并不是唯一致力于推动这一加密标准的提供商。在去年发布的文章 “AWS secures internet routing with RPKI plus security checks” 中,亚马逊云科技团队写道:

虽然 ASPA 仍处于标准化过程中,但我们致力于使用它以及所有可用工具,让互联网继续成为一个安全可靠的环境。

尽管相关 IETF 标准仍处于草案阶段,Cloudflare 指出 ARIN 和 RIPE NCC 已经支持创建 ASPA 对象,而路由软件 OpenBGPD 和 BIRD 也已经包含 ASPA 验证功能。

原文链接:

https://www.infoq.com/news/2026/03/cloudflare-aspa-standard/

项目介绍

岩石识别系统是一个基于深度学习的图像分类应用,旨在为地质勘查、教育科研等领域提供快速、准确的岩石类型识别服务。系统采用前后端分离架构,前端使用Vue3结合Element Plus构建用户界面,提供直观友好的操作体验;后端基于Flask框架开发RESTful API,实现用户认证、图像识别、历史记录管理等核心功能;算法层面采用TensorFlow深度学习框架,基于ResNet50卷积神经网络模型进行训练,能够准确识别玄武岩、煤、花岗岩、石灰岩、大理石、石英岩、砂岩等7种常见岩石类型。系统支持用户注册登录、图像上传识别、历史记录查询、公告管理等功能,通过JWT认证机制保障数据安全,为用户提供一个便捷、高效的岩石智能识别平台。

图片

图片

图片

选题背景与意义

在地质科学研究、矿产资源勘查、建筑材料鉴定等领域,岩石类型的准确识别是一项基础且重要的工作。传统的岩石识别方法主要依赖专业地质人员通过肉眼观察或显微镜分析,不仅要求识别者具备丰富的专业知识,而且识别效率较低,难以满足大规模快速识别的需求。近年来,随着深度学习技术的快速发展,计算机视觉在图像识别领域取得了突破性进展,为岩石智能识别提供了新的技术路径。

关键技术栈:ResNet50

ResNet50是ResNet(Residual Network,残差网络)系列中的经典模型,由微软研究院于2015年提出,在ImageNet图像识别竞赛中取得优异成绩,成为计算机视觉领域广泛应用的骨干网络之一。ResNet50的核心创新在于引入了残差连接(Residual Connection)结构,通过跨层的恒等映射连接,有效解决了深层神经网络训练过程中的梯度消失和梯度退化问题,使得网络层数可以大幅增加而性能不会下降。ResNet50共包含50层网络,其中包括49个卷积层和1个全连接层,采用瓶颈结构(Bottleneck)设计,在保证网络表达能力的同时控制参数量。

技术架构图

图片

系统功能模块图

图片

演示视频 and 完整代码 and 安装

地址:https://www.yuque.com/ziwu/qkqzd2/aob7gygvoqi64r37

准备送父母去趟深圳姐姐家,在 12306 上买完票后。

顺手查了下本地刚好有趟车可以直接到西九龙,二等座票价 565 元。比只到深圳北的票价贵了差不多 150 元。

觉得深圳北到西九龙也没有多远,不会跨境车这么贵吧。细查深圳北到西九龙的票才 75 元。接着查了同趟车本地到深圳北的无折扣票也就 424.5 元。

两段票价加起来比一段票价便宜了 65 元。

去香港都需要护照或者通行证。可以用身份证买一段票再用护照或通行证买一段票,单趟下来通行证的钱快出来了,往返都够办理护照的钱了。


https://i.imgur.com/a/QtPrSRR.png

项目介绍

本植物识别系统是一款基于深度学习技术的智能植物识别应用,旨在帮助用户快速、准确地识别各类植物。系统采用前后端分离架构,前端使用 Vue3 框架结合 Element Plus 组件库,提供美观、直观的用户界面;后端采用 Flask 轻量级 Web 框架,负责处理业务逻辑和数据交互;核心识别算法基于 TensorFlow 框架实现的 ResNet50 深度学习模型,具备强大的图像特征提取和分类能力。

图片

图片

图片

选题背景与意义

随着生态环境的日益恶化和人们环保意识的不断提高,植物保护和研究工作变得越来越重要。然而,传统的植物识别方法主要依赖于植物学专家的经验和专业知识,效率低下且成本高昂,无法满足普通民众和非专业人员的需求。

近年来,深度学习技术的快速发展为植物识别提供了新的解决方案。卷积神经网络(CNN)在图像识别领域取得了显著的成果,ResNet50 作为其中的经典模型,具有强大的特征提取能力和分类精度。本项目正是基于这一技术背景,开发了一款易用、高效的智能植物识别系统。

关键技术栈:ResNet50

ResNet50 是由微软研究院提出的一种深度残差网络结构,是 ResNet(Residual Neural Network)系列模型中的经典代表。它通过引入残差学习(Residual Learning)机制,有效地解决了深度神经网络训练过程中的梯度消失和梯度爆炸问题,使得网络可以达到更深的层数(50层),同时保持良好的训练效果。

ResNet50 的核心创新点在于残差块(Residual Block)的设计。传统的卷积神经网络在层数增加时会出现退化现象(Degradation),即随着网络深度的增加,训练误差和测试误差都会增加。ResNet 通过在网络中添加跳跃连接(Skip Connection),将输入直接与输出相加,使得网络可以学习残差映射(Residual Mapping),从而避免了退化问题。

技术架构图

图片

系统功能模块图(MindMap)

图片

演示视频 and 完整代码 and 安装

地址:https://www.yuque.com/ziwu/qkqzd2/fh58mo20xzg2tvr1

多智能体系统是 2026 年主流构建方式,Claude 的智能体团队功能、OpenAI 的 Swarm 框架、LangGraph 的编排层以及 CrewAI都指向同一个结论:复杂任务需要协调配合的专家,而非一个万能通才。

为什么单个智能体会失效

一个智能体包揽一切,就像一人创业公司——小规模时凑合,规模上去就垮了。

反复遭遇的失效模式有三种。

上下文窗口污染:当一个智能体挂载十种不同工具时,每份工具 schema、每条 API 响应、每个中间结果都在抢占上下文空间。执行到十步任务的第七步 ,第二步里的关键信息早已被挤出或稀释。

角色混乱:被指示"调研、分析、编写代码、起草摘要"的智能体,往往会在这些角色之间相互干扰:调研没完成就开始写代码,代码没编译就开始起草摘要。系统提示词逐渐变成一堆互相抵触的指令。

故障扩散:单个智能体在第三步出错,第四步到第十步全部受污染,没有隔离机制、没有检查点、没有独立验证。

给每个智能体分配单一职责、少量工具和清晰的系统接口,可以同时解决上述三个问题。

三种行之有效的编排模式

构建了几套多智能体系统之后,几乎所有架构都可以归结为以下三种模式,或它们的混合体。

Supervisor 模式

一个中央 supervisor 智能体接收任务,将其分解为子任务,分配给各专家智能体,收集结果后综合输出最终答案。只有 supervisor 能看到全局。

这是最常见的模式,也是优先考虑的起点。Claude 的智能体团队功能本质上就是该模式的产品化实现:定义专家智能体,编排器负责在它们之间路由工作。

supervisor 通常运行在能力较强的模型上(Claude Opus 4.6、GPT-5.3),专家智能体则可以选用更便宜、更快的模型(Claude Sonnet 4.5、Gemini flash),毕竟它们的任务范围窄得多。

 supervisor = Agent(  
    model="claude-opus-4.6",  
    system_prompt="You are a project coordinator. Decompose tasks and delegate to specialists.",  
    available_agents=["researcher", "coder", "reviewer"]  
)  

researcher = Agent(  
    model="claude-sonnet-4.5",  
    system_prompt="You research technical topics. Return structured findings.",  
    tools=[web_search, doc_lookup, arxiv_search]  
 )

适用场景:子任务边界清晰的复杂任务,例如客户支持、内容生成流水线、代码审查工作流。

需要警惕的是,supervisor 本身容易成为瓶颈。任务分解一旦出错,下游每个智能体都会拿到错误的指令。

Pipeline 模式

各智能体串联成线性链路,每个节点接收上游输出,完成自身的转换处理后传递给下游。

 pipeline = [  
    Agent(name="extractor", task="Extract key entities from raw text"),  
    Agent(name="enricher", task="Enrich entities with database lookups"),  
    Agent(name="analyzer", task="Analyze patterns across enriched entities"),  
    Agent(name="reporter", task="Generate human-readable report"),  
]  

result = input_data  
for agent in pipeline:  
     result = agent.run(result)

适用场景:ETL 式工作流、文档处理,以及任何第 N 阶段输出即为第 N+1 阶段输入的任务。

需要警惕的是,错误传播。第一阶段的失误会级联穿透整条链路,各阶段之间必须设置验证门控。

Swarm 模式

没有中央协调者。各智能体点对点通信,根据当前状态动态移交工作:OpenAI 的 Swarm 框架普及了这一思路。

核心机制是 handoff:某个智能体判断自身已不再适合处理当前状态,便将控制权连同对话上下文一起转交给另一个智能体。

 def triage_agent_instructions(context):  
    return """You handle initial customer contact.  
    If the issue is billing, hand off to billing_agent.  
    If the issue is technical, hand off to tech_agent.  
    If you can resolve it directly, do so."""  
    triage = Agent(  
        name="triage",  
        instructions=triage_agent_instructions,  
        handoffs=[billing_agent, tech_agent]  
     )

适用场景:对话走向难以预测的面向用户系统,以及分类路由场景。

需要警惕的是:无限 handoff 循环,智能体 A 认为应由 B 处理,B 又认为应由 A 处理。所以需要设置最大 handoff 深度。

智能体间通信

多智能体系统里最难的部分不是构建单个智能体,而是让它们有效地协作。

结构化消息传递是不可妥协的前提。智能体之间不能交换自由文本,必须为每个角色的输入输出定义明确的 schema。

 class AgentMessage:  
     sender: str  
     receiver: str  
     task_id: str  
     payload: dict          # 结构化数据  
     confidence: float      # 智能体对自身输出的置信度  
     requires_review: bool  # 人工介入标志

系统中每条消息都携带

confidence

字段。研究智能体返回的结论置信度若低于 0.7,supervisor 不会盲目将其转发给代码智能体,所以要么以更精确的查询重试,要么升级给人工处理。

共享状态与消息传递是第一个绕不开的架构决策。共享状态(所有智能体读写同一个数据库或内存存储)更简单,代价是耦合;消息传递(只通过显式消息通信)更干净,代价是冗长。

实践中倾向于混合方案:用一个共享的任务上下文对象供各方读取,控制流则走显式消息。可以把它想象成一块共享白板,智能体在上面张贴各自的工作产出,再通过点对点消息协调后续动作。

 task_context = {  
     "task_id": "support-4521",  
     "customer": {"id": "C-1234", "tier": "enterprise"},  
     "research_findings": None,   # 由研究智能体填充  
     "proposed_solution": None,   # 由解决方案智能体填充  
     "review_status": None,       # 由审查智能体填充  
 }

不会崩溃的任务分解

supervisor 的分解质量决定了整个系统的天花板。单个智能体再优秀,喂给它的子任务如果划分得一团糟,结果也好不了。

按能力分解,而非按步骤数分解。任务看起来复杂,不代表要拆成十个子任务。应当沿着不同智能体各自擅长的边界来切割:研究智能体、代码智能体、审查智能体是自然的划分;"步骤 1-3 的智能体"和"步骤 4-6 的智能体"不是。

每个子任务应当能够独立验证。研究智能体返回一份 API 端点列表,在将其传给代码智能体之前,就能验证这份列表是否存在、格式是否正确,不必等到后续步骤才发现问题。

在每个子任务里明确退出条件。不要只告诉智能体"调研账单 API",而要说:"调研账单 API,并返回:(a) 端点 URL,(b) 认证方式,© 速率限制,(d) 相关错误码。如有任何字段缺失,返回 INCOMPLETE。"

 subtask = {  
     "agent": "researcher",  
     "objective": "Find the billing API documentation",  
     "required_outputs": ["endpoint_url", "auth_method", "rate_limits", "error_codes"],  
     "exit_criteria": "All four fields populated with verified data",  
     "max_retries": 2,  
     "timeout_seconds": 30  
 }

跨链路的错误处理

单智能体的错误处理思路清晰:重试、回退或失败。多智能体场景要难得多,故障会级联、叠加,有时还隐而不发。

第一层是智能体级别的重试,用于处理短暂性故障,API 超时、速率限制、工具响应格式异常。在向上级报告失败之前,以指数退避策略最多重试三次。

第二层是 supervisor 级别的重新路由。智能体重试耗尽后,supervisor 可以重新分解子任务、切换到其他智能体,或者简化请求。代码智能体曾在一项复杂的重构任务上连续失败,supervisor 将其拆成三个较小的代码变更后顺序派发,三项均顺利完成。

第三层是人工升级。有些故障需要人工判断,系统要知道何时停手。简单的启发式规则:若 supervisor 已尝试三种不同的分解策略且均告失败,则生成一张包含完整尝试上下文的结构化升级工单。

 class EscalationPolicy:  
     max_agent_retries: int = 3  
     max_redecompositions: int = 2  
     confidence_threshold: float = 0.6  
       
     def should_escalate(self, attempts, confidence):  
         return (attempts >= self.max_redecompositions  
                 or confidence < self.confidence_threshold)

还有一种隐性风险:部分成功。研究智能体返回了五个所需数据点中的四个,代码智能体基于不完整的数据写出了可运行的代码,审查智能体因代码能够编译而放行。一切看起来正常,直到上线后那个缺失的字段引发故障。验证完整性,不只是验证正确性。

生产部署与监控

在生产环境运行多智能体系统,所需的可观测性能力远超大多数团队的预期。

Tracing 不可或缺

每次智能体调用、每条消息传递、每次工具调用都需要留存 trace。分布式 tracing 配合贯穿整个执行过程的关联 ID,是最基础的保障。

 trace= {  
    "trace_id": "ma-2026-02-25-a8f3",  
    "total_agents_invoked": 4,  
    "total_llm_calls": 12,  
    "total_tool_calls": 8,  
    "total_tokens": 47_200,  
    "total_cost_usd": 0.34,  
    "total_latency_ms": 18_400,  
    "outcome": "success"  
 }

没有这些在四个智能体和十二次 LLM 调用里定位一个故障,无异于读一本缺了好几章的悬疑小说。

按智能体监控成本

多智能体架构会成倍放大调用成本。一次 supervisor 调用加三次专家调用,意味着至少四次 LLM 请求。按每个智能体、每种任务类型追踪成本,并在单次任务费用超过阈值时触发告警。

核心优化点是只对真正需要强推理的角色(supervisor、代码智能体)使用高端模型,任务范围更窄的智能体(研究智能体、审查智能体)换用成本更低的模型。仅此一项,成本下降了 40%。

延迟预算

多智能体调用默认串行执行。每个智能体耗时三秒,四个智能体的链路就是十二秒——对面向用户的场景来说不可接受。

两种缓解手段:并行化独立子任务(supervisor 同时派发调研任务与代码框架生成任务),以及流式返回中间结果(在代码智能体仍在工作时,先将研究结论呈现给用户)。

总结

  1. 按能力拆分,而非按复杂度拆分。一个智能体配十种工具,不如三个智能体各配三种工具。职责单一的智能体更可靠、成本更低、也更易于调试。
  2. 从 supervisor 模式起步。可预测性和可调试性最好。只有当场景明确需要时,再考虑 Swarm 或 Pipeline。
  3. 结构化通信不可妥协。定义消息 schema,包含置信度分数,每次 handoff 时验证完整性。
  4. 将成本预算设为单智能体的 3-5 倍。多智能体系统能力更强,成本也更高,通过对专家智能体选用更便宜的模型来部分抵消。
  5. 全程留存 Trace。看不见的东西无法调试,跨智能体的分布式 tracing 是最值得投入的运营基础设施。

多智能体系统不是银弹。额外的复杂性、更高的成本、新增的故障模式,这些代都是现实中的问题。但当单个智能体确实力的确无法解决,任务需要多种能力、独立验证或动态路由,精心编排的智能体团队是目前见过的最可靠的解法。

https://avoid.overfit.cn/post/972be117256f4f72b11fb12007f742b7

by Prakash Sharma

1.首先获取key和密钥,这里不再阐述。

2.查询官方文档并没有提供实例,只说明调用方法回调

3.我这里是写好的,拿来即可使用(key和密钥自行申请):

<script type="text/javascript">
  window._AMapSecurityConfig = {
    securityJsCode: "729c83c0211116ffcfcb52a48fff124d909d1",
  };
</script>
<script src="https://webapi.amap.com/subway?v=1.0&amp;key=6bb49d7aa91fe88df2cfb708a7d9d08c111&amp;callback=cbk"></script>
<script type="text/javascript">
    window.cbk = function(){
        var mySubway = subway("hotList", {
            easy: 1
        });
         var allLineList = mySubway.getCityList(function(callback){
             console.log(callback)
         });
         
    };
</script>

4.查询控制台返回:
image.png

日常抽奖、点名、分组、测试数据时,经常会用到随机数。相比手动输入或套公式,这个在线工具打开就能用,电脑和手机都方便。

这个简单随机数生成器是我用 Vue 开发的,重点是上手快、操作清晰。你只需要设置范围和数量,就能马上得到结果,不用注册,也不用下载安装软件。

在线工具网址:https://see-tool.com/dns-query
工具截图:

这个工具能做什么

  • 按区间生成随机整数或小数
  • 一次生成多个结果,适合批量使用
  • 支持去重,避免抽样重复
  • 可选升序或降序,方便直接粘贴到表格
  • 结果支持一键复制,减少手工整理

三步就能完成

  1. 输入最小值、最大值和生成数量。
  2. 按需求勾选去重、排序;如果是小数场景,再设置小数位数。
  3. 点击生成,满意就复制,不满意可立即重抽。

适合哪些场景

  • 学生:随机点名、练习题取值
  • 老师或运营:活动抽取、名单分组
  • 办公人群:测试数据、编号样例
  • 开发与测试:快速生成一批可控数字

使用小提醒

如果开启去重,生成数量不能超过区间内可选值;如果你更关注分布效果,可以多生成几次再观察结果。

我做这个工具的目标很简单:让“生成随机数”这件小事更省时间。希望它能帮你减少重复操作,把精力留给更重要的事情。

OpenClaw 难的不是“功能不会点”,而是“流程没想清楚”。你把这 8 个坑避开,基本就能从“能跑”进阶到“跑得稳”。

大家好,我是饭米粒

很多人刚上手 OpenClaw,第一反应是:

“这工具好强,但我怎么越用越乱?”

我一开始也一样。
看了很多功能,配了很多自动化,结果两天后自己都看不懂自己在干嘛。

后来我发现,新手不是输在技术,而是输在顺序
下面这 8 个坑,是我见过最多、也最容易修的。

坑 1:一上来就追“全自动”

典型表现

  • 想一次性把写作、分发、复盘全打通
  • 没有人工检查点
  • 出错后不知道哪一环炸了

为什么会踩

你把 OpenClaw 当“无人驾驶”,但新手阶段它更像“带辅助驾驶”。

正确做法

先做“半自动流程”:

  1. AI 先生成草稿
  2. 人工确认关键内容
  3. 再自动分发或归档

口诀:先可控,再省事。

坑 2:不写流程图,直接堆功能

典型表现

  • 看见工具就接
  • 触发条件全靠记忆
  • 后面改一处,其他地方连锁崩

为什么会踩

你没有“流程视图”,只有“功能碎片”。

正确做法

开工前先写 4 行流程:

  1. 触发源(谁触发)
  2. 处理逻辑(AI 做什么)
  3. 人工校验点(哪一步必须确认)
  4. 输出目标(发到哪,存到哪)

你可以把它理解成“装修前先画户型图”。
不画图直接开工,后面全是返工。

坑 3:提示词写太长、太抽象

典型表现

  • 一段提示词几百字
  • 要求很多,但没有优先级
  • 输出风格忽左忽右

为什么会踩

你以为“写得越多越准”,实际上模型更吃“结构化指令”。

正确做法

把提示词改成 3 段:

  • 角色:你是谁(例如:公众号结构增强编辑)
  • 任务:你要产出什么(提纲/正文/标题)
  • 约束:字数、风格、禁区

再加一条:
先出提纲,再写正文。

这样稳定性会明显提升。

坑 4:上下文喂太少,AI“失忆”

典型表现

  • 每次都像重新开始
  • 同一主题,前后口径不一致
  • 写作风格飘来飘去

为什么会踩

你没把“长期信息”变成可复用上下文。

正确做法

最少维护 3 类固定信息:

  1. 用户画像(你是谁、读者是谁)
  2. 写作偏好(口语化、步骤化、少术语)
  3. 内容边界(不夸大、不虚构)

把它当成“开场白模板”,每次任务前自动带上。

坑 5:不做错误分层,排障全靠猜

典型表现

  • 一报错就重试
  • 不看是权限问题、格式问题还是网络问题
  • 反复踩同一类错误

为什么会踩

你把所有报错当成一个问题。

正确做法

按 3 层排查:

  1. 输入层:参数、字段、格式是否对
  2. 权限层:token/scopes 是否够
  3. 执行层:超时、网络、服务状态

先分层,再处理,速度会快很多。

坑 6:把“提醒”写成“系统噪音”

典型表现

  • 定时任务会触发,但提醒文案看不懂
  • 过了几天你自己都不知道它在提醒什么

为什么会踩

提醒没有上下文,只有一句“该做事了”。

正确做法

提醒文案至少包含 4 件事:

  1. 这是提醒
  2. 具体任务是什么
  3. 对应哪个项目
  4. 下一步动作是什么

比如:
“【提醒】你 3 天前规划的 OpenClaw 教程选题该复盘了,现在请补 3 条读者问题并更新提纲。”

坑 7:没有“人工兜底”就直接外发

典型表现

  • 自动生成后直接发群/发公众号
  • 出现事实错误、口吻失真
  • 返工成本巨大

为什么会踩

你把 AI 当最终发布者,而不是第一生产者。

正确做法

加一个“发布前 checklist”:

  • 事实是否可验证
  • 观点是否符合你定位
  • 是否有夸大承诺
  • 是否读起来像你本人

检查通过再发布。

坑 8:只盯功能,不做复盘

典型表现

  • 今天搭一个流程,明天换一个
  • 没有衡量标准
  • 做了很多,增长没感觉

为什么会踩

你在“忙自动化”,不是“用自动化拿结果”。

正确做法

每周固定复盘 3 个指标:

  1. 省了多少时间
  2. 错误率是否下降
  3. 内容产出是否更稳定

复盘后只做一件优化,不要一次改 10 个地方。

给新手的最小可行路径(直接照抄)

如果你刚开始,不要贪多,先跑这条线:

  1. 先做一个“选题 → 提纲 → 初稿”的半自动流程
  2. 强制保留人工审核
  3. 每周复盘一次,只优化一个点

你会发现,OpenClaw 真正的价值不是“炫技”,
而是让你稳定产出、减少内耗。

我现在做内容也是这套。
不是最快,但很稳。

写在最后

很多人问我:
“OpenClaw 到底难不难?”

我现在的回答是:
不难,前提是你别一上来就把它当万能机器。

先把流程跑顺,再谈规模化。
先把一个场景做成,再复制到第二个、第三个。

你会比大多数人快很多。

本文由mdnice多平台发布

image.png

本文介绍 OpenClaw 如何构建安全沙箱浏览器环境。在多 Agent 并发时让浏览器也支持并发。通过容器化 Sandbox,可以实现浏览器隔离、并发任务支持以及资源回收等能力。同时分析沙箱工具权限配置的关键细节,并介绍 OpenClaw 源码调试方法。

前言: OpenClaw 到底是真正有产能性价比的 AI 助手,还是只是个 皇帝的新衣 被一众相关利益者吹嘘炒作?虽然我已经研究了两个多月,还不知道答案。不过,作为一个失业码农,我只想研究一下相关的技术和应用。所以本文没有 “功能炸裂、不学就被淘汰、震惊、天花板、刷新了我xyz、省下多少 $$ ” 这类的故事。

以 OpenClaw 为代表的这类个人 AI Agent,如果要真实完成任务,就必须给予其一定的权限和工具。但由于 LLM 的不稳定性以及 Prompt 注入漏洞,这些能力又必须被严格约束。无论是 Guardrail 还是其它防御手段,都无法保证绝对安全。

在这种情况下,容器化 Sandbox 就成为最后一道入侵防线和风险防火墙。

OpenClaw 提供两类 tools 的 sandbox:

  • 运行时 sandbox container
  • 浏览器 sandbox container

使用容器化 Sandbox,除了作为 入侵防线和风险防火墙 外,还能带来一些额外好处:

  • 支持浏览器并发操作

    每个 Session 使用独立容器,资源完全隔离。例如,每个 session 使用自己的 browser sandbox 实例,从而避免并发任务之间的浏览器操作互相干扰。

  • Session 资源使用限制
  • Session 资源统一回收

沙箱浏览器的部署

1. 创建 Agent

openclaw agents add worker --workspace ~/.openclaw/workspace/worker
openclaw agents set-identity --agent worker --name "worker"

2. 配置沙箱

每个 Agent 都可以拥有独立的沙箱配置,其配置位于 ~/.openclaw/openclaw.json 中的 agents.list[].sandbox

agents:
  list:
      {
        "id": "worker",
        "name": "worker",
        "workspace": "~/.openclaw/workspace/worker",
        "agentDir": "~/.openclaw/agents/worker/agent",
        "identity": {
          "name": "worker"
        },
        "subagents": {
          "allowAgents": [
            "main",
            "worker"
          ]
        },
        "sandbox": {
          "mode": "all",
          "workspaceAccess": "rw",
          "scope": "session",
          "docker": {
            "image": "openclaw-sandbox-common:bookworm-slim",
            "containerPrefix": "oc-sbx-worker",
            "workdir": "/workspace",
            "readOnlyRoot": true,
            "tmpfs": [
              "/tmp",
              "/var/tmp",
              "/run"
            ],
            "network": "bridge",
            "user": "1000:1000",
            "capDrop": [
              "ALL"
            ],
            "env": {
              "LANG": "C.UTF-8"
            },
            "memory": "8g",
            "cpus": 2
          },
          "browser": {
            "enabled": true,
            "image": "openclaw-sandbox-browser:bookworm-slim",
            "containerPrefix": "oc-sbx-browser-worker",
            "network": "openclaw-sandbox-browser",
            "headless": false,
            "enableNoVnc": true,
            "allowHostControl": false,
            "autoStart": true,
            "autoStartTimeoutMs": 12000
          },
          "prune": {
            "idleHours": 1,
            "maxAgeDays": 1
          }
        },
        "tools": {
          "profile": "full",
          "allow": [
            "group:messaging",
            "group:runtime",
            "group:fs",
            "group:sessions",
            "group:web",
            "group:ui",
            "group:automation",
            "group:nodes",
            "group:openclaw"
          ],
          "deny": [],
          "elevated": {
            "enabled": true,
            "allowFrom": {
              "webchat": [
                "*"
              ]
            }
          }
        }
      },
 
tools:
  profile: full
  allow:
    - group:messaging
    - group:runtime
    - group:fs
    - group:sessions
    - group:web
    - group:ui
    - group:automation
    - group:nodes
    - group:openclaw
  deny: []
  sessions:
    visibility: all
  sandbox:
    tools:
      allow:
        - group:messaging
        - group:runtime
        - group:fs
        - group:sessions
        - group:web
        - group:ui
        - group:automation
        - group:nodes
        - group:openclaw
      deny: [] 

如果你好奇 deny: [] 的作用,那就对了,请继续往下看。

沙箱浏览器工具权限

这里是本文的重点和难点。我在沙箱工具权限上花了整整两天时间,因为沙箱模式下的 Agent 一直抱怨没有 browser 工具。

OpenClaw 文档中虽然说明了 sandbox browser 的配置,但并没有提供一个完整的配置教程。网上也几乎找不到成功配置的案例。最后我只能通过 Debug OpenClaw 源代码来定位问题。

在 OpenAI Codex 的源码解读以及 VSCode Debug 的帮助下,我终于找到了问题所在。具体过程会在后面的 “Debug” 一节中说明。这里先直接给出结论:

需要在 tools 的权限配置中增加 deny: []

在 Debug 了很久之后,源码中的
src/agents/sandbox/tool-policy.ts 里的 DEFAULT_TOOL_DENY 最终给了我答案。

你也许会说:文档里不是已经写了吗?

确实写了,但 OpenClaw 的文档相对碎片化,没有一个系统的教程示例可以直接参考。因此如果没有完整理解权限体系,很容易忽略这个细节。


题外话:这个空配置的重要性,让我想起“空性”(Śūnyatā)。
在佛教(尤其是禅宗和大乘佛教)中,“空”并不代表什么都没有,而是指一切事物都没有固定、独立、不变的本质(自性)。

3. 构建沙箱浏览器的 Image

参考: https://docs.openclaw.ai/gateway/sandboxing#images-%2B-setup
git clone https://github.com/openclaw/openclaw

cd openclaw

# build agent runtime docker image: openclaw-sandbox-common:bookworm-slim
scripts/sandbox-common-setup.sh

# build agent browser docker image: openclaw-sandbox-browser:bookworm-slim
scripts/sandbox-browser-setup.sh

沙箱浏览器的使用

下面示例:

Now I want to summarize following websites:
- https://news.ycombinator.com/
- https://lobste.rs/

You can use `sessions_spawn` tool to submit task to a worker. With parameters:
- `agentId`=worker
- `label`=$(a_key_word_of_the_website_domain_name)

The `task` parameter you submit should use following template:
> 1. Call browser `navigate` tool with targetUrl=$(the_website_url) , don't call browser `open` tool. Don't use `web fetch` tool or any other shell command if the browser tool fail. Then summarize the webpage content and response it with the screen shoot of your browser.

Try your best to concurrent the tasks.

中文版本:

现在,我希望你对以下网站进行摘要总结:
- https://news.ycombinator.com/
- https://lobste.rs/

你可以使用 `sessions_spawn` 工具将任务提交给一个工作进程(worker)。提交时需包含以下参数:
- `agentId`=worker
- `label`=$(网站域名中的一个关键词)

你提交的 `task` 参数应采用如下模板:
> 1. 调用浏览器的 `navigate` 工具,并指定 `targetUrl=$(网站URL)`;切勿调用浏览器的 `open` 工具。如果浏览器工具执行失败,请勿转而使用 `web fetch` 工具或任何其他 Shell 命令。随后,请对网页内容进行摘要总结,并将总结结果连同浏览器的截图一并作为响应输出。

请尽可能尝试并发执行这些任务。

运行结果:

image.png

Debug OpenClaw (调试并设置断点)

在 VSCode 中调试 OpenClaw 成了排查配置问题的最后手段。很多有经验的开发者都知道:再完善的文档,也比不上直接阅读源代码。

作为一个后端 Java 开发者,调试 TypeScript 项目其实并不容易入手。好在 OpenAI Codex 最终为我 指明了方向

具体过程我准备在另一篇文章中详细说明。这里先给出 VSCode 调试 OpenClaw 的配置示例。

.vscode/launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "OpenClaw: Gateway dev",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "program": "${workspaceFolder}/scripts/run-node.mjs",
      "args": ["gateway", "--port", "18789", "--force"],
      "console": "integratedTerminal",
      "outFiles": ["${workspaceFolder}/dist/**/*.js"],
      "skipFiles": ["<node_internals>/**"]
    },    

随着跨境电商、网络营销以及数据采集需求的增长,日本原生IP的使用越来越受到关注。那么,什么是原生IP,为什么选择日本原生IP购买呢?下面就跟着IPDEEP小编一起来看看吧!

日本原生IP购买

一、什么是原生IP?

原生IP指的是由互联网服务提供商(ISP)分配的真实IP地址,与虚拟或共享IP不同,它是真实存在的、可追踪的网络身份。使用原生IP范文公文网站,可以更好地模拟普通用户的上网行为,降低被封禁或限制的风险。

二、为什么选择日本IP?

1.高访问稳定性

相比普通代理或VPN,日本原生IP通常来自正规ISP,稳定性和访问速度更高,特别适合需要频繁访问日本网站的用户。

2.地域定位精准

日本原生IP能够帮助用户在网络服务上显示为日本本地访问,这对需要获取日本本地内容、进行广告投放或跨境电商运营的用户非常重要。

3.适用场景广泛

无论是做市场调研、自动化抓取数据,还是管理日本亚马逊等平台账号,原生IP都能提供更安全可靠的网络环境。

三、日本原生IP购买建议

1.根据需求选择IP类型

动态IP适合频繁切换IP、避免封禁的场景;静态IP适合长期绑定账号或固定服务器访问。

2.关注价格与套餐

不同供应商提供的IP数量、流量限制和使用时间不同,用户可以根据自身需求选择最适合的套餐。

3.选择正规供应商

在购买前应该选择信誉好、提供实时在线支持的供应商,确保IP的稳定性和安全性。

四、总结

日本原生IP在跨境业务、数据抓取、账号管理登场景中有着非常重要的作用。选择稳定、安全的供应商,并根据实际需求购买合适的IP类型,能够有效提供工作效率和网络安全性。

背景介绍

我日常喜欢用 Claude 来做项目的架构师,而 codex 做代码执行者,不管是 claude 还是 codex ,我对他们的生成的结果都保持怀疑态度的,但我又不可能去看一行行代码看,所以我日常使用的一个最常用方式,就是让他们互相进行 review 。

在这个过程中,我往往充当一个“无情”的复制粘贴者。比如 Claude 做完架构或方案设计后,我就让 Codex 去 review ,再把 Codex 的 review 结果复制粘贴回 Claude 的终端;或者 Codex 写完代码,我就让 Claude 去 review ,再把 review 意见贴回给 Codex 。我成了他们之间沟通的桥梁,这种方式非常低效。

方案介绍

为了解决这个问题,我开发了两个开源的 Skill ,让它们可以自动沟通和 review:

  1. 1. Codex Skill (底层基础)
    首先需要一个把 Codex 封装成"即插即用"接口的基础 Skill 。我参考的是oil-oil/codex,把 Codex 做成一个随时可调用的 Sub-Agent 。不管是 Plan Review 还是 Plan Execute ,Claude 想让它干活就直接调这个接口。整个协作体系都是搭在这个基础之上的。
  2. Plan Review Skill (方案评审层)
    这个项目整体由 Claude 驱动,因为我觉得 Claude 在工具调用( Tool Use )上的表现更好。
    • 流程: 我先跟 Claude 沟通出一版方案,我觉得差不多了,但不确定是否有遗漏或优化空间,此时我会调用 Plan Review
    • 机制: Claude 会调用封装好的 Codex Skill ,让 Codex 进行 review 。
    • 迭代: review 完后,Claude 会自动读取 Codex 反馈的结果并进行修改。这个过程会反复迭代,直到两者达成一致。这完全省去了我日常手动复制粘贴的麻烦。
  3. Plan Execute Skill (方案执行层)
    方案 review 完后进入执行阶段,我会使用这个负责“计划执行”的 Skill 。
    • 角色分工: Claude 依然作为“架构师”或“代码审计员”,它不会亲自动手写代码,而是将任务交给 Codex 。
    • 执行: 它通过调用 Codex Skill ,让 Codex 去完成具体的代码编写。
    • 闭环: Codex 完成后,Claude 会去 review 它的代码。如果发现问题,就让 Codex 继续改,直到两者达成统一。

通过这种“左右互博”的博弈机制,最终产出的结果基本上不会有大问题。这样可以更高效地解放双手,不需要时刻盯着,就能达到满意的结果。

阶段 Skill Claude 角色 Codex 角色
方案打磨 /plan-review 编排 + 改方案 批判性审稿人
代码实施 /plan-execute 编排 + review 代码 写代码 + 修复

补充说明

claude 进入 plan 模式之后默认存储的位置是在 ~/.claude下的,可以在 ~/.claude/settings.json 进行配置 plan 存放的目录,如下所示,便会存放到当前项目中了

{
  "env": {
  },
...
  "plansDirectory": "./plans",
}


工作流

claude_gpt_workflow

安装

方式一:npx add-skill (推荐)

前置条件: 先安装 add-skill CLI:

npm install -g add-skill

然后安装 skills:

npx add-skill longranger2/claude-gpt-workflow

方式二:单独安装

单独安装各个 skill:

npx add-skill longranger2/claude-gpt-workflow/plan-review
npx add-skill longranger2/claude-gpt-workflow/plan-execute
npx add-skill longranger2/claude-gpt-workflow/codex

方式三:手动安装

复制 skills 到你的 Claude Code skills 目录:

cp -r plan-review/ ~/.claude/skills/
cp -r plan-execute/ ~/.claude/skills/
cp -r codex/ ~/.claude/skills/

参考链接

https://github.com/longranger2/claude-gpt-workflow