乐于分享
好东西不私藏

OpenClaw 只能命令行触发?自研企业微信实现“发消息即执行“

OpenClaw 只能命令行触发?自研企业微信实现“发消息即执行“

系列: SmartClaw × OpenClaw:企业级浏览器自动化实战(第⑤篇)日期: 2026-05-04标签: OpenClaw, 企业微信, Webhook, Spring Boot, AES 解密, 消息触发适合谁看: Spring Boot 开发、企业微信集成、后端工程师


前言

OpenClaw 需要通过命令行或 API 触发任务,非技术人员无法使用。

场景: 门店员工需要通过微信发起设备报修,但她们不会用命令行,只会用微信。

SmartClaw 的做法: 接入企业微信 Webhook,员工发送”报修 空调不制冷 3楼302 张三”,系统自动提取信息并创建工单,完成后回复”✅ 工单已创建”。

本文是系列第⑤篇,完整讲解 Spring Boot 如何接入企业微信 Webhook,实现”发消息即执行”的自动化流程。


一、OpenClaw 的交互局限

1.1 需要 CLI/API 触发

OpenClaw 的典型使用方式:

# 命令行触发openclaw run --prompt "登录系统,填写表单"# 或者调用 APIcurl -X POST http://localhost:3000/api/run \  -d '{"prompt""登录系统,填写表单"}'

问题:

  • 非技术人员无法使用
  • 无法与现有工作流集成
  • 移动端操作不便

1.2 对比表格

维度
OpenClaw
SmartClaw + 企业微信
触发方式
CLI/API
微信消息
用户门槛
高(需懂技术)
低(会发微信即可)
移动支持
工作流集成

二、业务场景:门店报修自动派单

2.1 背景

某连锁零售集团有 50 个门店,每个门店每天需要:

  • 将设备报修信息自动创建到工单系统
  • 人工操作耗时 3 分钟/条
  • 每天约 100 条,总耗时 5 小时

2.2 消息格式约定

前台通过企业微信发送:

报修 空调不制冷 3楼302 张三

2.3 执行流程


三、核心技术实现

3.1 企业微信配置

步骤 1:创建应用

  1. 登录 企业微信管理后台
  2. 进入「应用管理」→「创建应用」
  3. 获取以下参数: 
    • corp_id
      :企业 ID
    • agent_id
      :应用 ID
    • agent_secret
      :应用密钥

步骤 2:配置回调 URL

  1. 进入「应用管理」→ 选择应用 →「接收消息」
  2. 设置回调 URL:https://your-domain.com/api/wechat/work/callback
  3. 设置 Token(自定义,如 SmartClaw2026
  4. 设置 EncodingAESKey(随机生成 43 位字符)

步骤 3:配置 IP 白名单

将 SmartClaw Server 的公网 IP 加入白名单。

3.2 Spring Boot 配置

# application.ymlsmartclaw:  wechat:    work:      enabled: true      corp-id: wwxxxxxxxxxxxxx      agent-id1000001      agent-secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx      token: SmartClaw2026      encoding-aes-key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

3.3 回调接收控制器

@RestController@RequestMapping("/api/wechat/work")@Slf4jpublic class WechatWorkCallbackController {    @Autowired    private WxMsgDecryptService decryptService;    @Autowired    private WechatMessageDispatcher dispatcher;    /**     * 企业微信验证 URL 有效性(GET 请求)     */    @GetMapping("/callback")    public String verify(            @RequestParam String msg_signature,            @RequestParam String timestamp,            @RequestParam String nonce,            @RequestParam String echostr) {        log.info("Verifying URL: timestamp={}, nonce={}", timestamp, nonce);        try {            // 验签 + 解密 echostr            String decryptedEchostr = decryptService.verifyUrl(                msg_signature, timestamp, nonce, echostr            );            log.info("URL verification successful");            return decryptedEchostr;  // 必须原样返回        } catch (Exception e) {            log.error("URL verification failed", e);            return "fail";        }    }    /**     * 接收企业微信推送消息(POST 请求)     */    @PostMapping("/callback")    public String callback(            @RequestBody String rawXml,            @RequestParam String msg_signature,            @RequestParam String timestamp,            @RequestParam String nonce) {        log.info("Received message: timestamp={}", timestamp);        try {            // 1. 验签 + 解密            WxWorkMessage message = decryptService.decryptMessage(                rawXml, msg_signature, timestamp, nonce            );            log.info("Decrypted message: from={}, content={}"                     message.getFromUserName(), message.getContent());            // 2. 只处理文本消息            if ("text".equalsIgnoreCase(message.getMsgType())) {                // 3. 异步 dispatch,避免阻塞企业微信回调                dispatcher.dispatchAsync(message);            }            // 4. 必须返回 success,否则企业微信会重试            return "success";        } catch (Exception e) {            log.error("Callback processing failed", e);            // 即使失败也要返回 success,避免企业微信无限重试            return "success";        }    }}

3.4 AES 解密服务

企业微信使用 AES-256-CBC 加密消息体,需要官方提供的加解密库。

引入依赖

<!-- pom.xml --><dependency>    <groupId>com.github.binarywang</groupId>    <artifactId>weixin-java-cp</artifactId>    <version>4.5.0</version></dependency>

实现解密逻辑

@Service@Slf4jpublic classWxMsgDecryptService{    @Value("{smartclaw.wechat.work.encoding-aes-key}")    private String encodingAesKey;    @Value("',    'store-repair-ticket-v1',    '["issue","location","reporter"]',    100);

规则匹配服务

@Service@Slf4jpublic class MessageRuleService {    @Autowired    private MessageRuleRepository ruleRepository;    @Autowired    private TaskDispatchService dispatchService;    /**     * 匹配消息并触发任务     */    public Optional<DispatchResultmatchAndDispatch(WxWorkMessage message) {        String content = message.getContent().trim();        String sender = message.getFromUserName();        log.info("Matching message: content={}, sender={}", content, sender);        // 1. 从数据库加载启用的规则(按优先级排序)        List<MessageRule> rules = ruleRepository.findEnabledRulesOrderByPriorityDesc();        for (MessageRule rule : rules) {            // 2. 正则匹配            Matcher matcher = Pattern.compile(rule.getPattern()).matcher(content);            if (matcher.matches()) {                log.info("Rule matched: ruleName={}", rule.getName());                // 3. 提取命名捕获组作为变量                Map<StringString> variables = extractNamedGroups(matcher, rule);                // 4. 注入发送者信息                variables.put("_senderUserId", sender);                variables.put("_msgId", message.getMsgId());                // 5. 构建 dispatch 请求                DispatchRequest request = DispatchRequest.builder()                    .templateId(rule.getTemplateId())                    .variables(variables)                    .idempotencyKey("wechat_work:" + message.getMsgId())                    .source("WECHAT_WORK")                    .requesterOpenId(sender)                    .build();                // 6. 下发任务                DispatchResult result = dispatchService.dispatch(request);                log.info("Task dispatched: runId={}", result.getRunId());                return Optional.of(result);            }        }        log.warn("No rule matched for message: {}", content);        // 无匹配规则:发送默认回复        sendDefaultReply(sender, content);        return Optional.empty();    }    /**     * 提取正则命名捕获组     */    private Map<StringStringextractNamedGroups(Matcher matcher, MessageRule rule) {        Map<StringString> vars = new HashMap<>();        // 解析 variable_names JSON 数组        List<String> varNames = parseJsonArray(rule.getVariableNames());        for (String groupName : varNames) {            try {                String value = matcher.group(groupName);                vars.put(groupName, value);                log.debug("Extracted variable: {}={}", groupName, value);            } catch (IllegalArgumentException e) {                log.warn("Group not found: {}", groupName);            }        }        return vars;    }    private List<StringparseJsonArray(String jsonArray) {        // 使用 Jackson 或 Gson 解析 JSON        try {            return objectMapper.readValue(                jsonArray,                 new TypeReference<List<String>>() {}            );        } catch (Exception e) {            log.error("Failed to parse JSON array", e);            return Collections.emptyList();        }    }}

3.6 消息调度器

@Service@Slf4jpublic class WechatMessageDispatcher {    @Autowired    private MessageRuleService ruleService;    @Autowired    private WechatWorkApiClient wechatApiClient;    /**     * 异步 dispatch(避免阻塞企业微信回调)     */    @Async("wechatExecutor")    public void dispatchAsync(WxWorkMessage message) {        try {            Optional<DispatchResult> result = ruleService.matchAndDispatch(message);            if (result.isPresent()) {                log.info("Message processed successfully: msgId={}", message.getMsgId());            } else {                log.warn("Message not matched: msgId={}", message.getMsgId());            }        } catch (Exception e) {            log.error("Message dispatch failed: msgId={}", message.getMsgId(), e);            // 发送错误回复            wechatApiClient.sendText(                message.getFromUserName(),                "❌ 处理失败:" + e.getMessage()            );        }    }}

3.7 回复消息 API 客户端

@Service@Slf4jpublic class WechatWorkApiClient {    private static final String TOKEN_URL =         "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={}&corpsecret={}";    private static final String SEND_URL =         "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={}";    @Value("{smartclaw.wechat.work.agent-secret}")    private String agentSecret;    @Value("// 提取变量issue = "空调不制冷"location = "3302"reporter = "张三"

4.4 下发任务

DispatchRequest request = DispatchRequest.builder()    .templateId("store-repair-ticket-v1")    .variables(Map.of(        "issue""空调不制冷",        "location""3楼302",        "reporter""张三"    ))    .idempotencyKey("wechat_work:1234567890")    .source("WECHAT_WORK")    .requesterOpenId("ZhangSan")    .build();dispatchService.dispatch(request);

4.5 Agent 执行 DSL

steps:  - stepId: s1    action: navigate    params:      url: "https://support.example.com/ticket/new"  - stepId: s2    action: fill    params:      selector: "input[name='name']"      value"{issue}"  # 空调不制冷  - stepId: s4    action: fill    params:      selector: "input[name='room']"      value"${location}"  # 3楼302  - stepId: s5    action: clickRole    params:      role: "button"      name: "提交工单"  - stepId: s6    action: waitText    params:      text: "工单已创建"      timeoutMs: 8000

4.6 回复前台

✅ **自动化任务完成**> 任务:门店报修派单> 流水号:c6efed0a> 耗时:4823ms> 时间:04-27 14:32:11

五、性能优化建议

5.1 AccessToken 缓存

企业微信 AccessToken 有效期 7200 秒,不要每次请求都重新获取:

LoadingCache<StringString> tokenCache = CacheBuilder.newBuilder()    .expireAfterWrite(7000TimeUnit.SECONDS)  // 7000 秒过期    .build(CacheLoader.from(this::fetchAccessToken));

5.2 异步处理消息

企业微信要求 5 秒内响应,因此消息处理必须异步:

@Async("wechatExecutor")public void dispatchAsync(WxWorkMessage message) {    // 异步处理,立即返回 success}

线程池配置:

@Configurationpublic class WechatExecutorConfig {    @Bean("wechatExecutor")    public Executor wechatExecutor() {        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();        executor.setCorePoolSize(5);        executor.setMaxPoolSize(10);        executor.setQueueCapacity(100);        executor.setThreadNamePrefix("wechat-");        executor.initialize();        return executor;    }}

5.3 幂等保护

通过消息 ID 保证同一条消息不会重复触发任务:

.idempotencyKey("wechat_work:" + message.getMsgId())

六、OpenClaw 做不到的事

6.1 自然语言交互

OpenClaw 需要手写 Prompt,而 SmartClaw 可以通过微信消息触发,用户门槛更低。

6.2 工作流集成

SmartClaw 可以与企业微信深度集成,实现:

  • 消息触发任务
  • 任务完成回复
  • 群聊 @机器人触发
  • 审批流程集成

6.3 移动端支持

企业微信支持 iOS/Android,用户可以随时随地触发自动化任务。


七、总结

OpenClaw 展示了 AI 操作浏览器的可能性,但在企业落地场景下,还需要解决交互入口的问题。

SmartClaw 通过接入企业微信 Webhook,实现了”发消息即执行”的自动化流程,将用户门槛降到最低。


相关资源

💬 互动交流

如果你在学习和使用过程中遇到问题,欢迎:1. 在评论区留言讨论2.如果觉得有帮助,点赞👍收藏📌关注➕,后续会持续分享SpringAI和AI工程的实战经验!

你的支持是我持续创作的最大动力!