企业微信文本推送接口部署手册(Nginx + PHP-FPM)

一、环境要求

  • 系统:Ubuntu 24.04(其他 Ubuntu 版本同理)
  • Web:Nginx
  • PHP:8.3
  • 网络:服务器可访问 qyapi.weixin.qq.com
  • 用途:内部系统 / 告警 / 自动化调用

二、安装基础环境

1. 更新系统

apt update

2. 安装 Nginx

apt install -y nginx
systemctl enable nginx
systemctl start nginx

3. 安装 PHP-FPM 及必需扩展(一次性装全)

apt install -y \
  php8.3-fpm \
  php8.3-cli \
  php8.3-curl \
  php8.3-mbstring

启动 PHP-FPM:

systemctl enable php8.3-fpm
systemctl start php8.3-fpm

确认运行中:

systemctl status php8.3-fpm

三、部署代码

1. 创建目录

mkdir -p /var/www/go-wecomchan-php

2. 上传代码

最终版 send.php 上传到:

/var/www/go-wecomchan-php/send.php

并确保权限:

chown -R www-data:www-data /var/www/go-wecomchan-php
chmod 755 /var/www/go-wecomchan-php

四、Nginx 配置

1. 新建站点配置

vim /etc/nginx/conf.d/wecom.conf

2. 写入配置(可直接用)

server {
    listen 3960;
    server_name _;

    root /var/www/go-wecomchan-php;

    location = /send.php {

        # 不记录参数,防止 sendkey / touser 泄露
        access_log off;

        # 建议:限制访问来源(按需修改)
        allow 10.0.0.0/8;
        allow 192.168.0.0/16;
        allow 127.0.0.1;
        deny all;

        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /var/www/go-wecomchan-php/send.php;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

    # 禁止访问其他 PHP
    location ~ \.php$ {
        deny all;
    }
}

3. 重载 Nginx

nginx -t
systemctl reload nginx

五、接口调用方式(GET)

基本格式

http://服务器IP:3960/send.php

必须参数

参数 说明
profile 企业 / 应用标识
sendkey 鉴权密钥
touser 接收人(暴露参数)
msg 文本内容

示例 1:指定人员

curl "http://IP:3960/send.php?profile=corp_a_alarm&sendkey=jiaban1&touser=zhangsan|lisi&msg=主变油温超限"

示例 2:@ 所有人

curl "http://IP:3960/send.php?profile=corp_a_alarm&sendkey=jiaban1&touser=@all&msg=系统告警"

注意: shell 中必须给 URL 加引号,否则 & 会被当作后台符号。


六、返回结果说明

  • 成功:
{"errcode":0,"errmsg":"ok"}
  • 常见错误返回(纯文本):
返回内容 含义
bad params 参数缺失
invalid profile profile 未定义
auth failed sendkey 错误
invalid touser touser 不合法
msg too long 消息过长
get token failed 企业微信鉴权失败

七、最小安全要求(已内置)

  • 只支持 GET
  • 不接收 CID / Secret
  • touser 严格正则校验
  • 消息长度限制
  • Nginx 关闭 access_log
  • 建议 IP 白名单

八、快速自检

# PHP 扩展检查
php -m | grep -E 'curl|mbstring'

# FPM socket 是否存在
ls /run/php/php8.3-fpm.sock

# 本地测试
curl "http://127.0.0.1:3960/send.php?profile=corp_a_alarm&sendkey=jiaban1&touser=@all&msg=test"

九、目录结构最终形态

/var/www/go-wecomchan-php/
└── send.php

十、用途定位(一句话)

这是一个:内部系统可控调用的企业微信文本消息网关

可用于:

  • 告警推送
  • 值班 / 运维通知
  • 自动化脚本
  • 老系统 GET 调用

<?php
/**
 * 企业微信文本消息推送接口(最终版)
 *
 * 特性:
 * - 仅支持 GET
 * - profile 控制企业与应用
 * - sendkey 鉴权
 * - touser 参数对外暴露(严格校验)
 * - 仅支持文本消息
 * - 无文件写入、无反序列化、无动态执行
 */

// ==================================================
// 0. 强制 GET
// ==================================================
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
    http_response_code(405);
    exit('Method Not Allowed');
}

// ==================================================
// 1. profile 本地定义(企业 / 应用边界)
// ==================================================
$profiles = [

    'corp_a_alarm' => [
        'cid'     => '企业A_CorpID',
        'secret'  => '企业A_AppSecret',
        'aid'     => '1000002',
        'sendkey' => 'jiaban1',
    ],

    // 你可以继续加
    // 'corp_b_alarm' => [ ... ],
];

// ==================================================
// 2. 读取参数(不容错、不猜)
// ==================================================
$profile = $_GET['profile'] ?? '';
$sendkey = $_GET['sendkey'] ?? '';
$msg     = $_GET['msg'] ?? '';
$touser  = $_GET['touser'] ?? '';

// ==================================================
// 3. 基础参数校验
// ==================================================
if ($profile === '' || $sendkey === '' || $msg === '' || $touser === '') {
    exit('bad params');
}

// 消息长度限制(中文安全)
$len = function_exists('mb_strlen')
    ? mb_strlen($msg, 'UTF-8')
    : strlen($msg);

if ($len > 500) {
    exit('msg too long');
}

// ==================================================
// 4. profile 校验
// ==================================================
if (!isset($profiles[$profile])) {
    exit('invalid profile');
}

$cfg = $profiles[$profile];

// ==================================================
// 5. sendkey 鉴权(防时序攻击)
// ==================================================
if (!hash_equals($cfg['sendkey'], $sendkey)) {
    exit('auth failed');
}

// ==================================================
// 6. touser 校验(你要求暴露的关键点)
//    允许:
//      - @all
//      - user1
//      - user1|user2|user3
// ==================================================

// 长度限制(防 DoS)
if (strlen($touser) > 200) {
    exit('touser too long');
}

// 正则严格锁死形态
if (!preg_match('/^(@all|[a-zA-Z0-9_]+(\|[a-zA-Z0-9_]+)*)$/', $touser)) {
    exit('invalid touser');
}

// ==================================================
// 7. 获取 access_token
// ==================================================
$token_url = sprintf(
    'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s',
    urlencode($cfg['cid']),
    urlencode($cfg['secret'])
);

$token_resp = @file_get_contents($token_url);
$token_json = json_decode($token_resp, true);

if (!isset($token_json['access_token'])) {
    exit('get token failed');
}

$access_token = $token_json['access_token'];

// ==================================================
// 8. 发送文本消息
// ==================================================
$data = [
    'touser'  => $touser,
    'agentid'=> $cfg['aid'],
    'msgtype'=> 'text',
    'text'   => [
        'content' => $msg
    ]
];

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL            => 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=' . urlencode($access_token),
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($data, JSON_UNESCAPED_UNICODE),
    CURLOPT_TIMEOUT        => 5,
    CURLOPT_SSL_VERIFYHOST => 2,
    CURLOPT_SSL_VERIFYPEER => true,
]);

$response = curl_exec($ch);
curl_close($ch);

// ==================================================
// 9. 输出结果
// ==================================================
header('Content-Type: application/json; charset=UTF-8');
echo $response;