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/