C++ Web框架Paozhu v1.17.0发布,添加MQTT,网络支付

Paozhu 重构了webpay模块 公网测试了支付宝支付 微信v2, v3版支付,小程序支付。网站使用 微信native支付 无需年费,应用于低频次支付场景。

Paozhu 已经是金丹修为的框架,提供了完善了web开发,而无需依赖第三方。

配置参考 conf/webpay.conf 配置文件

# 演示数据,需要你填入你真正的数据
# 微信支付 V2(jscode2session / access_token / JSAPI / Native XML 接口)

[wxpayv2]
appid = wx21222334444
mch_id = 11145678901
apikey = zI8wF8cI3k78uB8uZ2oB6lZ9gB2bL5bO
secret = 379c869abc23a35849663ea9d5bc7b69
cert_file = conf/apiclient_cert.pem
key_file = conf/apiclient_key.pem
notifyurl = https://www.xxx.net/ordernotify
# 微信支付 V3(Native / JSAPI 等 v3 接口)
# ⚠️ 换商户时 cert_file / key_file / v3_serial_no 三项必须全部替换

[wxpayv3]
appid = wx21222334444
mch_id = 11145678901
api_v3_key = 1122334455_need32char
cert_file = conf/apiclient_cert.pem
key_file = conf/apiclient_key.pem
notifyurl = https://www.xxx.net/ordernotify
v3_serial_no = 6F245450E065994D48A99A6987BE9B54286F2D1A
platform_cert_file =

填上自己的财付通信息

[sitea.alipay]
appid = xxx-sitea-alipay-appid
key_file = conf/sitea应用私钥RSA2048.txt
public_key_file = conf/sitea支付宝公钥RSA2.txt
notifyurl = https://www.site-a.com/alipaynotify
returnurl = https://www.site-a.com/alipayreturn
sandbox = 0

阿里支付宝支付配置信息,支持跳转阿里支付控制台或者本页扫描支付,微信只有本页扫描支付。

// 电脑网站支付的「前置模式」:告诉网关"这一版页面我要嵌在自己页里",让它吐二维码区而不是
// 整页收银台。模式编号语义官方文档页取不到 ⇒ 以网关行为为准,mode 原样透传不校验,
// 并把实际出网的串回显在页面上——"发没发这个键"不该靠翻代码确认。
//   /testalipayfront                  默认档(mode=4 & width 由网关自己定)
//   /testalipayfront?mode=0|1|3|4     四档对照
//   /testalipayfront?mode=4&width=300 模式 4 的宽度
//   /testalipayfront?mode=none        对照档:一个键都不发
//@urlpath(null,testalipayfront)
asio::awaitable<std::string> test_alipay_front(std::shared_ptr<httppeer> peer)
{
    httppeer &client = peer->get_peer();
    client.type("text/html; charset=UTF-8");
#ifdef ENABLE_WEBPAY
    std::string mode  = client.get["mode"].to_string();
    std::string width = client.get["width"].to_string();
    if (mode.empty()) mode = "4";

    const bool front = (mode != "none");
    if (!front) width.clear();

    std::string out_trade_no = get_date("%Y%m%d%H%M%S") + rand_string(4, 4);

    pay::alipay apay;
    std::string cfgmsg;
    if (!webpay::alipay_fill(apay, kPayTag, cfgmsg))
    {
        client.output = cfgmsg;
        co_return "";
    }
    apay.setOutTradeNo(out_trade_no);
    apay.setSubject("测试商品");
    apay.setBody("测试商品描述");
    apay.setTotalAmount("0.01");
    if (front)
    {
        apay.setQrPayMode(mode);
        if (!width.empty()) apay.setQrcodeWidth(width);
    }

    std::string pay_url = apay.createTradePage();
    // buildRequest() 把出网那一串(值已 percent 编码)留在 last_request_body_ 里;
    // qr_pay_mode 这类键名本身是 ASCII,编码前后长得一样,所以直接按字面找就是"发没发"的读数。
    const std::string sent = apay.lastRequestBody();
    const bool sent_mode  = (sent.find("qr_pay_mode") != std::string::npos);
    const bool sent_width = (sent.find("qrcode_width") != std::string::npos);

    std::string sent_note = "前置模式档 " + html_encode(mode)
                          + "  qrcode_width " + html_encode(width.empty() ? "(不发)" : width)
                          + "<br>出网串里含 <b>qr_pay_mode</b>:" + (sent_mode ? "是" : "<b>否</b>")
                          + "  含 <b>qrcode_width</b>:" + (sent_width ? "是" : "否")
                          + "  串长 " + std::to_string(sent.size()) + " 字节"
                          + "<details><summary>出网那一串原文(gateway.do? 之后,含 sign)</summary><pre>"
                          + html_encode(sent) + "</pre></details>";

    std::string insrep;
    unsigned int oid = co_await tal_order_insert_async(out_trade_no, "0.01", "测试商品", "测试商品描述", client.client_ip, &insrep);

    // 高度按档给:前置模式那一版只有二维码区,780px 会剩一大片空白,看着像"没渲染出来";
    // none 档装的是整页收银台,得留够高度。
    std::string frame_style = "width:100%;height:" + std::string(front ? "520px" : "780px")
                            + ";border:1px solid #bbb";

    if (oid == 0)
    {
        // 闸门:库里没这一行 ⇒ 不内嵌收银台(那一格里钱付出去就没账可对)。
        // 出网串读数照打:它是本地签名的结果,跟库连不连得上无关。
        client.output = "<!DOCTYPE html><html><head><meta charset=\"utf-8\">"
                        "<title>支付宝前置模式:未下单</title></head><body>"
                        "<p><b>存根写入失败</b>:订单没写进 cms.orderlist,这一页没有内嵌收银台。</p>"
                        "<p style=\"font-size:13px\">" + sent_note + "</p>"
                        "<p style=\"font-size:12px\">" + insrep + "</p></body></html>";
        co_return "";
    }
    client.output = tal_paypage_embed(pay_url, out_trade_no, oid, insrep, sent_note, frame_style);
#else
    client.output = "{\"code\":\"-1\",\"msg\":\"ENABLE_WEBPAY not enabled\"}";
#endif
    co_return "";
}

四个演示和简单订单管理再controller/src目录,订单管理文件在libs/webpay目录

controller/src/test_alipay.cpp
controller/src/test_wxpay.cpp
自己写业务代码时候可以按需剪裁移植。

这次狂添加了 MQTT 模块

方便物联网场景使用,其它也可以使用MQTT。

mqtt目录上服务器端程序 ,客户端在 controller/src目录 test_mqtt_client.cpp 文件

//@urlpath(null, test_mqtt_client)
asio::awaitable<std::string> test_mqtt_client(std::shared_ptr<httppeer> peer)
{
    httppeer &cp = peer->get_peer();

    mqtt_client_config_t cfg1;
    cfg1.client_id       = "mydevice@@@group-device";
    cfg1.clean_start     = true;
    cfg1.keepalive       = 60;
    cfg1.receive_maximum = 65535;

    auto r1 = co_await run_one_case("Case1_B4_no_local", cfg1);

    mqtt_client_config_t cfg2 = cfg1;
    cfg2.client_id            = "mydevice@@@";       // reg_key=mydevice group=empty device=empty
    auto r2                   = co_await run_one_case("Case2_parse_client_id_reg_only", cfg2);

    mqtt_client_config_t cfg3 = cfg1;
    cfg3.client_id            = "mydevice@@@rm-client";
    cfg3.receive_maximum      = 8;                  // less than server policy 16 → server still advertises its own policy 16 (no longer min with client value)
    auto r3                   = co_await run_one_case("Case3_S1_rm8", cfg3);

    auto dump = [](const case_result &r) -> std::string
    {
        std::string s = std::string("=== ") + r.name + " ===\n";
        s += "  connected_=" + std::to_string((int)r.connected_) +
             "  rc=" + std::to_string(static_cast<int>(r.connack_rc)) +
             "  err=" + std::to_string((int)r.iserror);
        if (!r.error_msg.empty())
            s += " (" + r.error_msg + ")";
        s += "\n  session_present=" + std::to_string((int)r.session_present);
        s += "  server_receive_maximum=" + std::to_string(r.server_receive_maximum);
        s += "\n  run_loop_fun=" + std::to_string(r.recv.size());
        if (!r.recv.empty())
            for (auto &p : r.recv) s += " [" + p.topic + "]";
        s += "\n  received_window_empty=" + std::to_string((int)r.received_window_empty) +
             " (hook installed, should be 1)";
        s += "\n";
        return s;
    };

    std::string summary = dump(r1) + dump(r2) + dump(r3);

    summary += "\n=== Verification Summary ===\n";
    summary += "B3 empty Client ID: " + std::string((int)r2.connack_rc == 0 ? "PASS" : "FAIL") +
               "  (rc=" + std::to_string(static_cast<int>(r2.connack_rc)) + ")\n";
    summary += "S1 receive_max:    " + std::string(r3.server_receive_maximum == 16 ? "PASS (server advertises its own policy 16)" : "FAIL") +
               "  (server_rm=" + std::to_string(r3.server_receive_maximum) + ", expect 16)\n";
    summary += "B4 No Local:       " + std::string(r1.recv.empty() ? "PASS" : "FAIL (loopbacks received)") +
               "  (" + std::to_string(r1.recv.size()) + " loopbacks)\n";
    // All three cases install run_loop_fun and the "no message should land"
    // semantics of no_local makes the empty window check toothless here.
    // For real evidence see /test_mqtt_tick_push, which installs the same hook
    // but actually receives 5 messages and checks received in the callback.
    summary += "hook installed, no window writes: — (this group produces zero delivered messages, reading is meaningless; see /test_mqtt_tick_push)\n";

    cp.output = make_html("MQTT 5 Client Test — B3 + S1 + B4", summary);
    co_return "";
}

目前MQTT支持V5版本
http2流量控制目前已经是第四次重构了
ORM添加原生rawsql语句查询支持。
其它可以参考官方地址
https://github.com/hggq/paozhu/

posted @ 2026-09-28 14:57  游水小鸡  阅读(6)  评论(0)    收藏  举报