爱发电nginx转发企业微信webhook

问题

由于爱发电的webhook格式,与企业微信的webhook格式不一致,因此我将介绍如何使用nginx来实现中间件修改内容。

使用条件

1、保证你有一台公网可以访问的服务器

2、保证你已经可以登录企业微信,并且可以创建webhook机器人

分析

爱发电的请求格式:

json 复制代码
{
  "ec": 200,
  "em": "ok",
  "data": {
    "type": "order",
    "order": {
      "out_trade_no": "20210",
      "user_id": "adf397fe",
      "plan_id": "a4535",
      "month": 1,
      "total_amount": "5.00",
      "show_amount": "5.00",
      "status": 2,
      "remark": "",
      "redeem_id": "",
      "product_type": 0,
      "discount": "0.00",
      "sku_detail": [],
      "address_person": "",
      "address_phone": "",
      "address_address": ""
    }
  }
}

响应格式:

json 复制代码
{"ec":200,"em":""}

因为爱发电的文档说了跟没说一样,这里就不贴文档链接了。

企业微信的请求格式:
企业微信webhook说明文档

请求格式:

json 复制代码
{
    "msgtype": "markdown",
    "markdown": {
        "content": "test<font color=\"warning\">132例</font>test2\n>类型:<font color=\"comment\">test3</font>\n>test4<font color=\"comment\">test5/font>\n>test6<font color=\"comment\">test7</font>"
    }
}

响应格式:

json 复制代码
{"errcode":0,"errmsg":"ok"}

因此要对请求和响应都要做修改,否则会出现格式不对,导致发送webhook失败。

大致思路是这样的:

开始配置

1、1panel 安装 openresty

OpenResty 是在 Nginx 基础上扩展了一整套 Lua 能力的增强版 Nginx。

安装完成后,点击网站,创建。

选择反向代理。

主域名填服务器的域名,代理地址随便填,因为等下用不上。

2、配置DNS

点击配置

点击配置文件

因为等下使用的lua_resty_http没有自己的DNS,必须要人工设置一个进去。

在server_name下面添加,这里我添加了两个常用的DNS服务器地址

lua 复制代码
    resolver 223.5.5.5 119.29.29.29 valid=300s;

3、添加lua_resty_http

下载仓库:https://github.com/ledgetech/lua-resty-http

提取 lib/resty下的三个lua文件。

接着放到这个目录下,如果你是用docker也是一样的

bash 复制代码
/opt/1panel/apps/openresty/openresty/lualib/resty/http/http_headers.lua
/opt/1panel/apps/openresty/openresty/lualib/resty/http/http_connect.lua
/opt/1panel/apps/openresty/openresty/lualib/resty/http/http.lua

接着映射文件到这个路径,使得nginx可以搜索到lua脚本

bash 复制代码
/usr/local/openresty/lualib/resty/http_headers.lua
/usr/local/openresty/lualib/resty/http_connect.lua
/usr/local/openresty/lualib/resty/http.lua

4、修改location配置

你可以理解为匹配请求路径并定义响应规则

路径在你创建的代理配置中:

/opt/1panel/apps/openresty/openresty/www/sites/xxxxxxxx/proxy/xxxxxx.conf

如果你是用自己的openresty,就要把这个代理配置添加到你的server块中。

说明:

1、该代码块实现了两个功能,爱发电的key使用特殊的逻辑来转换,非爱发电的key就直接透传转发。

2、下面的key == "XXX",对应着企业微信webhook的参数key的内容,只有这里需要你把对应的key填入。

lua 复制代码
location ^~ /cgi-bin/webhook/send {
    content_by_lua_block {
        local cjson = require "cjson"
        local ngx = ngx
        local http = require "resty.http"
        -- 获取查询参数 'key'
        local args = ngx.req.get_uri_args()
        local key = args.key

        -- 检查是否需要转换
        if key == "XXX" then
            -- 获取请求的 JSON 数据
            ngx.req.read_body()
            local req_body = ngx.req.get_body_data()

            -- 如果请求体为空,直接返回 400 错误
            if not req_body then
                ngx.status = 400
                ngx.say('{"error": "no request body"}')
                return
            end

            -- 解析 JSON 数据
            local data, err = cjson.decode(req_body)
            if not data then
                ngx.status = 400
                ngx.say('{"error": "invalid json format"}')
                return
            end

            -- 转换数据为企业微信 Webhook 接口需要的格式
local msg = {
    msgtype = "markdown",
    markdown = {
        content = "### 新订单通知\n\n" ..
                  "**订单号**:<font color=\"warning\">" .. data.data.order.out_trade_no .. "</font>\n\n" ..
                  "**用户ID**:<font color=\"comment\">" .. data.data.order.user_id .. "</font>\n\n" ..
                  "**计划ID**:<font color=\"comment\">" .. data.data.order.plan_id .. "</font>\n\n" ..
                  "**订单状态**:<font color=\"comment\">" .. (data.data.order.status == 2 and "待支付" or "已支付") .. "</font>\n\n" ..
                  "**金额**:<font color=\"comment\">" .. data.data.order.total_amount .. "</font>\n\n" ..
                  "**折扣**:<font color=\"comment\">" .. data.data.order.discount .. "</font>\n\n" ..
                  "**产品类型**:<font color=\"comment\">" .. (data.data.order.product_type == 0 and "普通商品" or "VIP商品") .. "</font>\n\n" ..
                  "**用户备注**:<font color=\"comment\">" .. (data.data.order.remark == "" and "无" or data.data.order.remark) .. "</font>\n\n" ..
                  "### 地址信息\n\n" ..
                  "**收货人**:<font color=\"comment\">" .. (data.data.order.address_person == "" and "无" or data.data.order.address_person) .. "</font>\n\n" ..
                  "**电话**:<font color=\"comment\">" .. (data.data.order.address_phone == "" and "无" or data.data.order.address_phone) .. "</font>\n\n" ..
                  "**地址**:<font color=\"comment\">" .. (data.data.order.address_address == "" and "无" or data.data.order.address_address) .. "</font>\n\n"
    }
}
            -- 调用企业微信 Webhook
            local httpc = http.new()
            local full_url = "https://qyapi.weixin.qq.com" .. ngx.var.request_uri
            local res, err = httpc:request_uri(full_url, {
                method = "POST",
                body = cjson.encode(msg),
                headers = {
                    ["Content-Type"] = "application/json"
                },
                ssl_verify = false 
            })

            -- 如果请求失败,返回错误信息
            if not res then
                ngx.status = 500
                ngx.say('{"error": "failed to forward request", "info": "' .. (err or "unknown error") ..'"}')
                ngx.log(ngx.ERR, "Failed to forward request to WeChat: " .. (err or "unknown error"))
                ngx.log(ngx.ERR, "body : " .. cjson.encode(msg))
                return
            end

            -- 解析企业微信返回的响应
            local response = cjson.decode(res.body)

            -- 根据企业微信返回的响应构建新的响应格式
            local response_msg = {
                ec = 200,
                em = "",  -- em 字段为空
            }

            -- 如果响应成功,更新 em 字段为 "",如果有错误,返回相应的 errmsg
            if response.errcode ~= 0 then
                response_msg.em = response.errmsg or "unknown error"
            end

            -- 发送修改后的响应
            ngx.header.content_type = "application/json"
            ngx.say(cjson.encode(response_msg))
        else
            -- 直接转发请求到企业微信
            local httpc = http.new()
            
            -- 读取原始请求体
            ngx.req.read_body()
            local req_body = ngx.req.get_body_data()
            
            -- 构建完整的 URL
            local full_url = "https://qyapi.weixin.qq.com" .. ngx.var.request_uri
            
            -- 转发请求
            local res, err = httpc:request_uri(full_url, {
                method = ngx.var.request_method,
                body = req_body,
                headers = ngx.req.get_headers(),
                ssl_verify = false 
            })
            
            if not res then
                ngx.status = 502
                ngx.say('{"error": "Bad Gateway"}')
                return
            end
            
            -- 返回原始响应
            ngx.status = res.status
            for k, v in pairs(res.headers) do
                ngx.header[k] = v
            end
            ngx.say(res.body)
        end
    }
}

5、测试

在爱发电的网站上有测试按钮,可以直接使用它进行测试。

因为我的服务器已经支持了 https协议,所以我只需要把 企业微信的webhook URL中的域名,替换成我自己的域名,就可以直接拿来使用。

不建议直接使用 http协议,有安全隐患。

相关推荐
星光一影11 小时前
【OA办公系统】神点企业OA办公助手/全开源
mysql·nginx·开源·php·源代码管理
matlab的学徒16 小时前
nginx+springboot+redis+mysql+elfk
linux·spring boot·redis·nginx
NicolasCage1 天前
解决苍穹外卖WebSocket连接失败的问题
nginx
维尔切2 天前
Nginx 反向代理与负载均衡
运维·nginx·负载均衡
Justin_193 天前
nginx反向代理与缓存功能
运维·nginx
苹果醋33 天前
数据结构其一 线性表
java·运维·spring boot·mysql·nginx
java干货3 天前
我用Nginx做了负载均衡,还需要API网关吗?
运维·nginx·负载均衡
東雪蓮☆3 天前
LNMP 环境部署 WordPress
linux·运维·mysql·nginx·php
-dcr4 天前
22.Nginx 服务器 LNMP项目
运维·服务器·nginx·php·lnmp