企业微信文本推送接口部署手册(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;