- 【已成功】只用云开发,如何发送公众号模板消息?
下发统一消息接口回收后,如果你只有云开发,没有服务器和域名,不妨尝试使用本文的方法来发送公众号模板消息。 前提: 小程序和公众号同主体且已绑定到同一个微信开放平台账号。 思路推理: 小程序用户访问任意云函数都可以拿到小程序openid和unionid,提前将它们保存到云数据库用户集合中。 发送模板消息: 使用云函数A发送模板消息,需要调用公众号发送模板消息的接口,调用接口需要用到公众号的access_token。 获取和保存access_token: 使用云函数B获取access_token,推荐使用Stable Access token接口获取,减少出错率,云函数B可固定IP。因接口有日调用限制且access_token默认2小时过期,获取到access_token之后需要保存到云数据库中。为防止access_token过期,推荐云函数B设置定时触发,每隔1小时执行一次去重新获取access_token保存到云数据库中。 使用最新access_token: 每次发模板消息时从云数据库查询最新的access_token记录,发送模板消息需要用到公众号的openid。 获取公众号openid: 将小程序的云环境共享给公众号,小程序可使用云函数C来获取公众号的openid和unionid。如何操作呢? 1、使用云开发静态网站制作一个授权页面D,在该页面中访问云函数C,使用静默授权方式访问在云函数C即可获取到访问用户的公众号openid和unionid。 2、小程序使用webview来访问页面D,授权成功后在页面显示公众号的二维码,提示用户关注公众号获取通知功能。 将小程序和公众号用户关联: 通过上述引导用户获取到的公众号unionid来查询云数据库中的用户信息,保存公众号的openid到用户信息中。 经历以上安排之后,小程序云数据库的用户集合中,用户信息已经包含了小程序openid、unionid、公众号openid,到发消息的时机就可以发了。
2023-09-24 - 微信支付APIv3的Nodejs版SDK,让开发变得简单不再繁琐
在向云端推送这个 [代码]wechatpay-axios-plugin[代码] 业务实现时,发现0.1系列还不够好用,还需要进行更多层级的包裹包装,遂再次做了重大更新,让SDK使用起来更简单、飘逸。 先看官方文档,每一个接口,文档都至少标示了[代码]请求URL[代码] [代码]请求方式[代码] [代码]请求参数[代码] [代码]返回参数[代码] 这几个要素,[代码]URL[代码] 可以拆分成 [代码]Base[代码] 及 [代码]URI[代码],按照这种思路,封装SDK其实完全就可以不用动脑,即,对[代码]URI[代码]资源的 [代码]POST[代码] 或 [代码]GET[代码] 请求(条件带上[代码]参数[代码]),取得[代码]返回参数[代码]。 更近一步,我们设想一下,如果把众多接口的[代码]URI[代码]按照斜线([代码]/[代码] [代码]slash[代码])分割,然后组织在一起,是不是就可以构建出一颗树,这颗树的每个节点(实体[代码]Entity[代码])都存在有若干个方法([代码]HTTP METHODs[代码]),这是不是就能把接口[代码]SDK实现[代码]更简单化了?! 例如: /v3/certificates /v3/bill/tradebill /v3/ecommerce/fund/withdraw /v3/ecommerce/profitsharing/orders /v3/marketing/busifavor/users/{openid}/coupons/{coupon_code}/appids/{appid} 树形化即: [代码]v3 ├── certificates ├── bill │ └── tradebill ├── ecommerce │ ├── fund │ │ └── withdraw │ └── profitsharing │ └── orders └── marketing └── busifavor └── users └── {openid} └── coupons └── {coupon_code} └── appids └── {appid} [代码] 按照这种树形构想,我们来看下需要做的[代码]封装实现[代码]工作: 把实体对象,按照实体的排列顺序,映射出请求的URI; 每个对象实体,包含有若干操作方法,其中可选带参数发起RPC请求; 随官方放出更多的接口,SDK需要能够弹性扩容; wechatpay-axios-plugin~0.2.0 版本实现了上述这3个目标,代码包如下截屏: [图片] 我们用伪代码来校验看一下这个[代码]封装实现[代码]: [代码]require('util').inspect.defaultOptions.depth = 10; const { Wechatpay } = require('wechatpay-axios-plugin'); const wxpay = new Wechatpay({mchid: '1', serial: '2', privateKey: '3', certs: {'4': '5'}}); wxpay.v3.certificates; wxpay.v3.bill.tradebill; wxpay.v3.ecommerce.fund.withdraw; wxpay.v3.marketing.busifavor.users['{openid}'].coupons.$coupon_code$.appids['wx233544546545989']; console.info(wxpay); //以下是输出内容 { entities: [], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], v3: { entities: [ 'v3' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], certificates: { entities: [ 'v3', 'certificates' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload] }, bill: { entities: [ 'v3', 'bill' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], tradebill: { entities: [ 'v3', 'bill', 'tradebill' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload] } }, ecommerce: { entities: [ 'v3', 'ecommerce' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], fund: { entities: [ 'v3', 'ecommerce', 'fund' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], withdraw: { entities: [ 'v3', 'ecommerce', 'fund', 'withdraw' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload] } } }, marketing: { entities: [ 'v3', 'marketing' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], busifavor: { entities: [ 'v3', 'marketing', 'busifavor' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], users: { entities: [ 'v3', 'marketing', 'busifavor', 'users' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], '{openid}': { entities: [ 'v3', 'marketing', 'busifavor', 'users', '{openid}' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], coupons: { entities: [ 'v3', 'marketing', 'busifavor', 'users', '{openid}', 'coupons' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], '$coupon_code$': { entities: [ 'v3', 'marketing', 'busifavor', 'users', '{openid}', 'coupons', '{coupon_code}' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], appids: { entities: [ 'v3', 'marketing', 'busifavor', 'users', '{openid}', 'coupons', '{coupon_code}', 'appids' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], wx233544546545989: { entities: [ 'v3', 'marketing', 'busifavor', 'users', '{openid}', 'coupons', '{coupon_code}', 'appids', 'wx233544546545989' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload] } } } } } } } } } } [代码] 注: API树实体节点,存储在每个 [代码]entities[代码] 属性上,方便后续的[代码]get[代码], [代码]post[代码] 抑或 [代码]upload[代码] 方法调用调用前,反构成最终请求的[代码]URI[代码];特别地,对于动态树实体节点来说,每个实体节点均提供了 [代码]withEntities[代码] 方法,用来在最终请求前,把动态实体节点替换成实际的值。 正常用法示例如下: [代码]const {Wechatpay} = require('wechatpay-axios-plugin'); const wxpay = new Wechatpay({/*初始化参数,README有*/}, {/*可选调整axios的参数*/}); //拿证书 wxpay.v3.certificates.get(); //带参申请交易账单 wxpay.v3.bill.tradebill.get({params: {bill_date}}); //带参发起账户余额提现 wxpay.v3.ecommerce.fund.withdraw.post({sub_mchid, out_request_no, amount, remark, bank_memo}); //查询用户单张券详情 wxpay.v3.marketing.busifavor.users['{openid}'].coupons.$coupon_code$.appids['wx233544546545989'].withEntities({openid, coupon_code}).get(); [代码] 请求APIv3是不是就“丧心病狂”般的简单了?! 详细功能说明及用法示例,npmjs及github的README均有。 如果喜欢,就给来个 赞 及 Star 吧。
2020-07-17 - PHP微信支付类V3接口
不知不觉微信支付也更新了,接口版本也升级到了V3, 跟着微信的升级,将个人使用微信支付类也进行了升级, V3微信支付文档:https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml (相关方法已经过测试,不保证完全没问题,仅供参考,如测试有问题,可联系作者修改----查看原文) 使用方法还和之前的一样(V2微信支付),直接传递参数就可使用: 新版新增了[代码]composer[代码]安装,便于集成框架使用(Github地址): [代码]composer require fengkui/pay [代码] 首先把配置文件填写完整(细心不要填错,否则会导致签名错误): [代码]# 微信支付配置 $wechatConfig = [ 'xcxid' => '', // 小程序 appid 'appid' => '', // 微信支付 appid 'mchid' => '', // 微信支付 mch_id 商户收款账号 'key' => '', // 微信支付 apiV3key(尽量包含大小写字母,否则验签不通过) 'appsecret' => '', // 公众帐号 secert (公众号支付获取 code 和 openid 使用) 'notify_url' => '', // 接收支付状态的连接 改成自己的回调地址 'redirect_url' => '', // 公众号支付,调起支付页面 'serial_no' => '', // 证书序列号 'cert_client' => './cert/apiclient_cert.pem', // 证书(退款,红包时使用) 'cert_key' => './cert/apiclient_key.pem', // 商户私钥(Api安全中下载) 'public_key' => './cert/public_key.pem', // 平台公钥(调动证书列表,自动生成,注意目录读写权限) ]; [代码] 支付类封装相关方法: method 描述 js JSAPI下单 app APP支付 h5 H5支付 scan Navicat支付 xcx 小程序支付 query 查询订单 close 关闭订单 refund 申请退款 notify 支付结果通知 使用方法(这里已小程序支付为示例): [代码]<?php require_once('./vendor/autoload.php'); $config = []; // 支付配置 $order = [ 'order_sn' => time(), // 订单编号 'total_amount' => 1, // 订单金额(分) 'body' => '测试商品', // 商品名称 'openid' => '', // 用户openid // 'type' => 'Wap', ]; $wechat = new fengkui\Pay\Wechat($config); $re = $wechat->xcx($order); die(json_encode($re)); // JSON化直接返回小程序客户端 [代码] 如下代码是封装好的完整支付类文件(Wechat.php), 可以根据自己需求随意修改,详细的使用方法后期会有文档: [代码]<?php /** * @Author: [FENG] <1161634940@qq.com> * @Date: 2019-09-06 09:50:30 * @Last Modified by: [FENG] <1161634940@qq.com> * @Last Modified time: 2021-07-12 18:24:18 */ namespace fengkui\Pay; use Exception; use RuntimeException; use fengkui\Supports\Http; /** * Wechat 微信支付 * 新版(V3)接口(更新中) */ class Wechat { const AUTH_TAG_LENGTH_BYTE = 16; // 新版相关接口 // GET 获取平台证书列表 private static $certificatesUrl = 'https://api.mch.weixin.qq.com/v3/certificates'; // 统一下订单管理 private static $transactionsUrl = 'https://api.mch.weixin.qq.com/v3/pay/transactions/'; // 申请退款 private static $refundUrl = 'https://api.mch.weixin.qq.com/v3/refund/domestic/refunds'; // 静默授权,获取code private static $authorizeUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize'; // 通过code获取access_token以及openid private static $accessTokenUrl = 'https://api.weixin.qq.com/sns/oauth2/access_token'; // 支付完整配置 private static $config = array( 'xcxid' => '', // 小程序 appid 'appid' => '', // 微信支付 appid 'mchid' => '', // 微信支付 mch_id 商户收款账号 'key' => '', // 微信支付 apiV3key(尽量包含大小写字母,否则验签不通过) 'appsecret' => '', // 公众帐号 secert (公众号支付获取 code 和 openid 使用) 'notify_url' => '', // 接收支付状态的连接 改成自己的回调地址 'redirect_url' => '', // 公众号支付,调起支付页面 'serial_no' => '', // 证书序列号 'cert_client' => './cert/apiclient_cert.pem', // 证书(退款,红包时使用) 'cert_key' => './cert/apiclient_key.pem', // 商户私钥(Api安全中下载) 'public_key' => './cert/public_key.pem', // 平台公钥(调动证书列表,自动生成,注意目录读写权限) ); /** * [__construct 构造函数] * @param [type] $config [传递微信支付相关配置] */ public function __construct($config=NULL, $referer=NULL){ $config && self::$config = array_merge(self::$config, $config); } /** * [unifiedOrder 统一下单] * @param [type] $order [订单信息(必须包含支付所需要的参数)] * @param boolean $type [区分是否是小程序,是则传 true] * @return [type] [description] * $order = array( * 'body' => '', // 产品描述 * 'order_sn' => '', // 订单编号 * 'total_amount' => '', // 订单金额(分) * ); */ public static function unifiedOrder($order, $type=false) { $config = array_filter(self::$config); // 获取配置项 $params = array( 'appid' => $type ? $config['xcxid'] : $config['appid'], // 由微信生成的应用ID 'mchid' => $config['mchid'], // 直连商户的商户号 'description' => $order['body'], // 商品描述 'out_trade_no' => (string)$order['order_sn'], // 商户系统内部订单号 'notify_url' => $config['notify_url'], // 通知URL必须为直接可访问的URL 'amount' => ['total' => (int)$order['total_amount'], 'currency' => 'CNY'], // 订单金额信息 ); !empty($order['attach']) && $params['attach'] = $order['attach']; // 附加数据 if (!empty($order['time_expire'])) { // 订单失效时间 preg_match('/[年\/-]/', $order['time_expire']) && $order['time_expire'] = strtotime($order['time_expire']); $time = $order['time_expire'] > time() ? $order['time_expire'] : $order['time_expire'] + time(); $params['time_expire'] = date(DATE_ATOM, $time); } if (!in_array($order['type'], ['native'])) { !empty($order['openid']) && $params['payer'] = ['openid' => $order['openid']]; $params['scene_info'] = ['payer_client_ip' => self::get_ip()]; } if (in_array($order['type'], ['iOS', 'Android', 'Wap'])) { $params['scene_info']['h5_info'] = ['type' => $order['type']]; $url = self::$transactionsUrl . 'h5'; // 拼接请求地址 } else { $url = self::$transactionsUrl . strtolower($order['type']); // 拼接请求地址 } $header = self::createAuthorization($url, $params, 'POST'); $response = Http::post($url, json_encode($params, JSON_UNESCAPED_UNICODE), $header); $result = json_decode($response, true); if (isset($result['code']) && isset($result['message'])) { throw new \Exception("[" . $result['code'] . "] " . $result['message']); } return $result; } /** * [query 查询订单] * @param [type] $orderSn [订单编号] * @param boolean $type [微信支付订单编号,是否是微信支付订单号] * @return [type] [description] */ public static function query($orderSn, $type = false) { $config = self::$config; $url = self::$transactionsUrl . ($type ? 'id/' : 'out-trade-no/') . $orderSn . '?mchid=' . $config['mchid']; $params = ''; $header = self::createAuthorization($url, $params, 'GET'); $response = Http::get($url, $params, $header); $result = json_decode($response, true); return $result; } /** * [close 关闭订单] * @param [type] $orderSn [微信支付订单编号] * @return [type] [description] */ public static function close($orderSn) { $config = self::$config; $url = self::$transactionsUrl . 'out-trade-no/' . $orderSn . '/close'; $params['mchid'] = $config['mchid']; $header = self::createAuthorization($url, $params, 'POST'); $response = Http::post($url, json_encode($params, JSON_UNESCAPED_UNICODE), $header); $result = json_decode($response, true); return true; } /** * [js 获取jssdk需要用到的数据] * @param [type] $order [订单信息数组] * @return [type] [description] */ public static function js($order=[]){ $config = self::$config; if (!is_array($order) || count($order) < 3) die("订单数组信息缺失!"); if (count($order) == 4 && !empty($order['openid'])) { $data = self::xcx($order, false, false); // 获取支付相关信息(获取非小程序信息) return $data; } $code = !empty($order['code']) ? $order['code'] : ($_GET['code'] ?? ''); $redirectUri = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . rtrim($_SERVER['REQUEST_URI'], '/') . '/'; // 重定向地址 $params = ['appid' => $config['appid']]; // 如果没有get参数没有code;则重定向去获取code; if (empty($code)) { $params['redirect_uri'] = $redirectUri; // 返回的url $params['response_type'] = 'code'; $params['scope'] = 'snsapi_base'; $params['state'] = $order['order_sn']; // 获取订单号 $url = self::$authorizeUrl . '?'. http_build_query($params) .'#wechat_redirect'; } else { $params['secret'] = $config['appsecret']; $params['code'] = $code; $params['grant_type'] = 'authorization_code'; $response = Http::get(self::$accessTokenUrl, $params); // 进行GET请求 $result = json_decode($response, true); $order['openid'] = $result['openid']; // 获取到的openid $data = self::xcx($order, false, false); // 获取支付相关信息(获取非小程序信息) if (!empty($order['code'])) { return $data; } $url = $config['redirect_url'] ?? $redirectUri; $url .= '?data=' . json_encode($data, JSON_UNESCAPED_UNICODE); } header('Location: '. $url); die; } /** * [app 获取APP支付需要用到的数据] * @param [type] $order [订单信息数组] * @return [type] [description] */ public static function app($order=[], $log=false) { if(empty($order['order_sn']) || empty($order['total_amount']) || empty($order['body'])){ die("订单数组信息缺失!"); } $order['type'] = 'app'; // 获取订单类型,用户拼接请求地址 $result = self::unifiedOrder($order, true); if (!empty($result['prepay_id'])) { $data = array ( 'appId' => self::$config['appid'], // 微信开放平台审核通过的移动应用appid 'timeStamp' => (string)time(), 'nonceStr' => self::get_rand_str(32, 0, 1), // 随机32位字符串 'prepayid' => $result['prepay_id'], ); $data['paySign'] = self::makeSign($data); $data['partnerid'] = $config['mchid']; $data['package'] = 'Sign=WXPay'; return $data; // 数据小程序客户端 } else { return $log ? $result : false; } } /** * [h5 微信H5支付] * @param [type] $order [订单信息数组] * @return [type] [description] */ public static function h5($order=[], $log=false) { if(empty($order['order_sn']) || empty($order['total_amount']) || empty($order['body']) || empty($order['type']) || !in_array(strtolower($order['type']), ['ios', 'android', 'wap'])){ die("订单数组信息缺失!"); } $result = self::unifiedOrder($order); if (!empty($result['h5_url'])) { return $result['h5_url']; // 返回链接让用户点击跳转 } else { return $log ? $result : false; } } /** * [xcx 获取jssdk需要用到的数据] * @param [type] $order [订单信息数组] * @param boolean $log [description] * @param boolean $type [区分是否是小程序,默认 true] * @return [type] [description] */ public static function xcx($order=[], $log=false, $type=true) { if(empty($order['order_sn']) || empty($order['total_amount']) || empty($order['body']) || empty($order['openid'])){ die("订单数组信息缺失!"); } $order['type'] = 'jsapi'; // 获取订单类型,用户拼接请求地址 $config = self::$config; $result = self::unifiedOrder($order, $type); if (!empty($result['prepay_id'])) { $data = array ( 'appId' => $type ? $config['xcxid'] : $config['appid'], // 由微信生成的应用ID 'timeStamp' => (string)time(), 'nonceStr' => self::get_rand_str(32, 0, 1), // 随机32位字符串 'package' => 'prepay_id='.$result['prepay_id'], ); $data['paySign'] = self::makeSign($data); $data['signType'] = 'RSA'; return $data; // 数据小程序客户端 } else { return $log ? $result : false; } } /** * [scan 微信扫码支付] * @param [type] $order [订单信息数组] * @return [type] [description] */ public static function scan($order=[], $log=false) { if(empty($order['order_sn']) || empty($order['total_amount']) || empty($order['body'])){ die("订单数组信息缺失!"); } $order['type'] = 'native'; // Native支付 $result = self::unifiedOrder($order); if (!empty($result['code_url'])) { return urldecode($result['code_url']); // 返回链接让用户点击跳转 } else { return $log ? $result : false; } } /** * [notify 回调验证] * @return [array] [返回数组格式的notify数据] */ public static function notify($server = [], $response = []) { $config = self::$config; $server = $server ?? $_SERVER; $response = $response ?: file_get_contents('php://input', 'r'); if (empty($response) || empty($server['HTTP_WECHATPAY_SIGNATURE'])) { return false; } $body = [ 'timestamp' => $server['HTTP_WECHATPAY_TIMESTAMP'], 'nonce' => $server['HTTP_WECHATPAY_NONCE'], 'data' => $response, ]; // 验证应答签名 $verifySign = self::verifySign($body, trim($server['HTTP_WECHATPAY_SIGNATURE']), trim($server['HTTP_WECHATPAY_SERIAL'])); if (!$verifySign) { die("签名验证失败!"); } $result = json_decode($response, true); if (empty($result) || $result['event_type'] != 'TRANSACTION.SUCCESS' || $result['summary'] != '支付成功') { return false; } // 加密信息 $associatedData = $result['resource']['associated_data']; $nonceStr = $result['resource']['nonce']; $ciphertext = $result['resource']['ciphertext']; $data = $result['resource']['ciphertext'] = self::decryptToString($associatedData, $nonceStr, $ciphertext); return json_decode($data, true); } /** * [refund 微信支付退款] * @param [type] $order [订单信息] * @param [type] $type [是否是小程序] */ public static function refund($order) { $config = self::$config; if(empty($order['refund_sn']) || empty($order['refund_amount']) || (empty($order['order_sn']) && empty($order['transaction_id']))){ die("订单数组信息缺失!"); } $params = array( 'out_refund_no' => (string)$order['refund_sn'], // 商户退款单号 'funds_account' => 'AVAILABLE', // 退款资金来源 'amount' => [ 'refund' => $order['refund_amount'], 'currency' => 'CNY', ] ); if (!empty($order['transaction_id'])) { $params['transaction_id'] = $order['transaction_id']; $orderDetail = self::query($order['transaction_id'], true); } else { $params['out_trade_no'] = $order['order_sn']; $orderDetail = self::query($order['order_sn']); } $params['amount']['total'] = $orderDetail['amount']['total']; !empty($order['reason']) && $params['reason'] = $order['reason']; $url = self::$refundUrl; $header = self::createAuthorization($url, $params, 'POST'); $response = Http::post($url, json_encode($params, JSON_UNESCAPED_UNICODE), $header); $result = json_decode($response, true); return $result; } /** * [queryRefund 查询退款] * @param [type] $refundSn [退款单号] * @return [type] [description] */ public static function queryRefund($refundSn, $type = false) { $url = self::$refundUrl . '/' . $refundSn; $params = ''; $header = self::createAuthorization($url, $params, 'GET'); $response = Http::get($url, $params, $header); $result = json_decode($response, true); return $result; } /** * [success 通知支付状态] */ public static function success() { $str = ['code'=>'SUCCESS', 'message'=>'成功']; die(json_encode($str, JSON_UNESCAPED_UNICODE)); } /** * [createAuthorization 获取接口授权header头信息] * @param [type] $url [请求地址] * @param array $data [请求参数] * @param string $method [请求方式] * @return [type] [description] */ //生成v3 Authorization protected static function createAuthorization($url, $data=[], $method='POST'){ $config = self::$config; //商户号 $mchid = $config['mchid']; // 证书序列号 if (empty($config['serial_no'])) { $certFile = @file_get_contents($config['cert_client']); $certArr = openssl_x509_parse($publicStr); $serial_no = $certArr['serialNumberHex']; } else { $serial_no = $config['serial_no']; } // 解析url地址 $url_parts = parse_url($url); //生成签名 $body = [ 'method' => $method, 'url' => ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : "")), 'time' => time(), // 当前时间戳 'nonce' => self::get_rand_str(32, 0, 1), // 随机32位字符串 'data' => (strtolower($method) == 'post' ? json_encode($data, JSON_UNESCAPED_UNICODE) : $data), // POST请求时 需要 转JSON字符串 ]; $sign = self::makeSign($body); //Authorization 类型 $schema = 'WECHATPAY2-SHA256-RSA2048'; //生成token $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $mchid, $body['nonce'], $body['time'], $serial_no, $sign); $header = [ 'Content-Type:application/json', 'Accept:application/json', 'User-Agent:*/*', 'Authorization: '. $schema . ' ' . $token ]; return $header; } /** * [makeSign 生成签名] * @param [type] $data [加密数据] * @return [type] [description] */ public static function makeSign($data) { $config = self::$config; if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) { throw new \RuntimeException("当前PHP环境不支持SHA256withRSA"); } // 拼接生成签名所需的字符串 $message = ''; foreach ($data as $value) { $message .= $value . "\n"; } // 商户私钥 $private_key = self::getPrivateKey($config['cert_key']); // 生成签名 openssl_sign($message, $sign, $private_key, 'sha256WithRSAEncryption'); $sign = base64_encode($sign); return $sign; } /** * [verifySign 验证签名] * @param [type] $data [description] * @param [type] $sign [description] * @param [type] $serial [description] * @return [type] [description] */ public static function verifySign($data, $sign, $serial) { $config = self::$config; if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) { throw new \RuntimeException("当前PHP环境不支持SHA256withRSA"); } $sign = \base64_decode($sign); // 拼接生成签名所需的字符串 $message = ''; foreach ($data as $value) { $message .= $value . "\n"; } // 获取证书相关信息 self::certificates($serial); // 平台公钥 $public_key = self::getPublicKey($config['public_key']); //平台公钥 // 验证签名 $recode = \openssl_verify($message, $sign, $public_key, 'sha256WithRSAEncryption'); return $recode == 1 ? true : false; } //获取私钥 public static function getPrivateKey($filepath) { return openssl_pkey_get_private(file_get_contents($filepath)); } //获取公钥 public static function getPublicKey($filepath) { return openssl_pkey_get_public(file_get_contents($filepath)); } /** * [certificates 获取证书] * @return [type] [description] */ public static function certificates($serial) { $config = self::$config; $publicStr = @file_get_contents($config['public_key']); if ($publicStr) { // 判断证书是否存在 $openssl = openssl_x509_parse($publicStr); if ($openssl['serialNumberHex'] == $serial) { // 是否是所需证书 // return self::getPublicKey($config['public_key']); //平台公钥 return ''; } } $url = self::$certificatesUrl; $params = ''; $header = self::createAuthorization($url, $params, 'GET'); $response = Http::get($url, $params, $header); $result = json_decode($response, true); if (empty($result['data'])) { throw new RuntimeException("[" . $result['code'] . "] " . $result['message']); } foreach ($result['data'] as $key => $certificate) { if ($certificate['serial_no'] == $serial) { $publicKey = self::decryptToString( $certificate['encrypt_certificate']['associated_data'], $certificate['encrypt_certificate']['nonce'], $certificate['encrypt_certificate']['ciphertext'] ); file_put_contents($config['public_key'], $publicKey); break; // 终止循环 } // self::$publicKey[$certificate['serial_no']] = $publicKey; } // return self::getPublicKey($config['public_key']); //平台公钥 } /** * [decryptToString 证书和回调报文解密] * @param [type] $associatedData [附加数据包(可能为空)] * @param [type] $nonceStr [加密使用的随机串初始化向量] * @param [type] $ciphertext [Base64编码后的密文] * @return [type] [description] */ public static function decryptToString($associatedData, $nonceStr, $ciphertext) { $config = self::$config; $ciphertext = base64_decode($ciphertext); if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) { return false; } // ext-sodium (default installed on >= PHP 7.2) if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) { return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $config['key']); } // ext-libsodium (need install libsodium-php 1.x via pecl) if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) { return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $config['key']); } // openssl (PHP >= 7.1 support AEAD) if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) { $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE); $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE); return \openssl_decrypt($ctext, 'aes-256-gcm', $config['key'], \OPENSSL_RAW_DATA, $nonceStr, $authTag, $associatedData); } throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php'); } /** fengkui.net * [get_rand_str 获取随机字符串] * @param integer $randLength [长度] * @param integer $addtime [是否加入当前时间戳] * @param integer $includenumber [是否包含数字] * @return [type] [description] */ public static function get_rand_str($randLength=6, $addtime=0, $includenumber=1) { if ($includenumber) $chars='abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789'; $chars='abcdefghijklmnopqrstuvwxyz'; $len = strlen($chars); $randStr = ''; for ($i=0; $i<$randLength; $i++){ $randStr .= $chars[rand(0, $len-1)]; } $tokenvalue = $randStr; $addtime && $tokenvalue = $randStr . time(); return $tokenvalue; } /** fengkui.net * [get_ip 定义一个函数get_ip() 客户端IP] * @return [type] [description] */ public static function get_ip() { if (getenv("HTTP_CLIENT_IP")) $ip = getenv("HTTP_CLIENT_IP"); else if(getenv("HTTP_X_FORWARDED_FOR")) $ip = getenv("HTTP_X_FORWARDED_FOR"); else if(getenv("REMOTE_ADDR")) $ip = getenv("REMOTE_ADDR"); else $ip = "Unknow"; if(preg_match('/^((?:(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(?:25[0-5]|2[0-4]\d|((1\d{2})|([1 -9]?\d))))$/', $ip)) return $ip; else return ''; } } [代码] 本文参考文档: 1、微信支付 小程序 (v3)- PHP 完整后端代码 2、PHP 微信小程序 微信支付 v3 3、微信支付V3版本小程序支付 php签名,验签,数据解密代码分享(完整方法主参考) 4、微信支付 API v3 回调通知 签名验证 PHPdemo有嘛?
2022-02-21 - 小程序音视频合成初探
小程序音视频 最近使用了一下微信音视频相关 api, 这绝对是一件振奋人心的事, 让视频的合成在小程序端就能完成, 以下是我使用这些 api 的一些记录。 以下代码片段可以直接在开发者工具中预览 视频合成 音视频合成主要用到 [代码]wx.createMediaContainer()[代码] 方法, 该方法会返回一个 [代码]MediaContainer[代码] 对象, 可以将视频源传入到容器中, 对视频轨和音频轨进行一些操作。 下面以合成视频为例: [代码]const mediaContainer = wx.createMediaContainer(); const { tempFilePath } = this.data; // 本地视频文件地址 // 将视频源传入轨道当中 mediaContainer.extractDataSource({ source: tempFilePath, success: (res) => { // 返回的结果中的 tracks 是一个类数组对象, 第一项是音频轨道, 第二项是视频轨道 const [audioTrack, mediaTrack] = res.tracks; mediaContainer.addTrack(mediaTrack); // 将视频轨道加入到待合成容器中 mediaTrack.slice(1000, 5000); // 截取视频轨道中第 1-5 秒的视频 mediaContainer.addTrack(audioTrack); // 将音频轨道加入到待合成容器中 audioTrack.slice(1000, 5000); // 截取音频轨道中第 1-5 秒的视频 // 导出合成容器中的音频和视频 mediaContainer.export({ success: (res) => { // 拿到导出之后的视频 console .log(res.tempFilePath); }, }); }, }); [代码] 视频解码和录制 如果想要给视频加滤镜和贴图可以采用, [代码]VideoDecoder[代码] + [代码]MediaRecorder[代码] + [代码]WebGL[代码] 的方式, 通过 [代码]VideoDecoder[代码] 将视频解码, 获取视频的每一帧画面, 再绘制到 [代码]canvas[代码] 上, 再通过 [代码]glsl[代码] 着色器给画面加滤镜。同时用 [代码]MediaRecorder[代码] 去录制 [代码]canvas[代码] 上的画面, 最后可以导出一段视频。 以下是将一个视频的前十秒加上黑白滤镜合成出来的主要代码: [代码]let w = 300 let h = 200 const vs = ` attribute vec3 aPos; attribute vec2 aVertexTextureCoord; varying highp vec2 vTextureCoord; void main(void){ gl_Position = vec4(aPos, 1); vTextureCoord = aVertexTextureCoord; } ` const fs = ` varying highp vec2 vTextureCoord; uniform sampler2D uSampler; #ifdef GL_ES precision lowp float; #endif void main(void) { vec4 color = texture2D(uSampler, vTextureCoord); float gray = 0.2989*color.r + 0.5870*color.g + 0.1140*color.b; gl_FragColor = vec4(gray, gray, gray, color.a); } ` const vertex = [ -1, -1, 0.0, 1, -1, 0.0, 1, 1, 0.0, -1, 1, 0.0 ] const vertexIndice = [ 0, 1, 2, 0, 2, 3 ] const texCoords = [ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 ] function createShader(gl, src, type) { const shader = gl.createShader(type) gl.shaderSource(shader, src) gl.compileShader(shader) if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error('Error compiling shader: ' + gl.getShaderInfoLog(shader)) } return shader } function createRenderer(canvas, width, height) { const gl = canvas.getContext("webgl") if (!gl) { console.error('Unable to get webgl context.') return } const info = wx.getSystemInfoSync() gl.canvas.width = width //info.pixelRatio * width gl.canvas.height = height // info.pixelRatio * height gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight) const vertexShader = createShader(gl, vs, gl.VERTEX_SHADER) const fragmentShader = createShader(gl, fs, gl.FRAGMENT_SHADER) const program = gl.createProgram() gl.attachShader(program, vertexShader) gl.attachShader(program, fragmentShader) gl.linkProgram(program) if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('Unable to initialize the shader program.') return } gl.useProgram(program) const texture = gl.createTexture() gl.activeTexture(gl.TEXTURE0) gl.bindTexture(gl.TEXTURE_2D, texture) gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) gl.bindTexture(gl.TEXTURE_2D, null) buffers.vertexBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, buffers.vertexBuffer) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertex), gl.STATIC_DRAW) buffers.vertexIndiceBuffer = gl.createBuffer() gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.vertexIndiceBuffer) gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(vertexIndice), gl.STATIC_DRAW) const aVertexPosition = gl.getAttribLocation(program, 'aPos') gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0) gl.enableVertexAttribArray(aVertexPosition) buffers.trianglesTexCoordBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, buffers.trianglesTexCoordBuffer) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW) const vertexTexCoordAttribute = gl.getAttribLocation(program, "aVertexTextureCoord") gl.enableVertexAttribArray(vertexTexCoordAttribute) gl.vertexAttribPointer(vertexTexCoordAttribute, 2, gl.FLOAT, false, 0, 0) const samplerUniform = gl.getUniformLocation(program, 'uSampler') gl.uniform1i(samplerUniform, 0) return (arrayBuffer, width, height) => { gl.bindTexture(gl.TEXTURE_2D, texture) gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, arrayBuffer) gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0) } } wx.createSelectorQuery().select('#video').context(res => { const video = this.video = res.context // 获取 VideoContext video.pause() const decoder = wx.createVideoDecoder() // 创建解码器 const dpr = 1 const webgl = wx.createOffscreenCanvas() const render = createRenderer(webgl, w, h) console.log('webgl', webgl.id, w, h, dpr, webgl.width, webgl.height) const fps = 25 const recorder = wx.createMediaRecorder(webgl, { fps, videoBitsPerSecond: 3000, timeUpdateInterval: 30 }) const startTime = Date.now() let counter = 0 let loopStopped = false let timeCnt = 0 setTimeout(() => { recorder.stop(); decoder.stop(); }, 3000); function loop() { const renderTime = (counter + 1) * (1000 / fps) if (loopStopped || renderTime > 10100) { console.log('recorder stop.', timeCnt, Date.now() - startTime) recorder.stop() return } const ts = Date.now() const imageData = decoder.getFrameData() if (imageData) { render(new Uint8Array(imageData.data), w * dpr, h * dpr) timeCnt += Date.now() - ts counter++ } console.log('render end', counter, Date.now() - ts); recorder.requestFrame(loop) } recorder.on('start', () => { console.log('start render') loopStopped = false loop() }) recorder.on('timeupdate', ({ currentTime }) => { console.log('timeupdate', currentTime) }) recorder.on('stop', (res) => { console.log('recorder finished.', timeCnt, Date.now() - startTime, res) this.setData({ distSrc: res.tempFilePath }) recorder.destroy() }) decoder.on('start', () => { console.log('decoder start 2', decoder.seek) decoder.on('seek', () => { console.log('decoder seeked') recorder.start() }) decoder.seek(0) }) decoder.start({ source: this.data.src, // 这里是一个视频的本地路径, 可通过 wx.chooseVideo 获取 }) }).exec() [代码] 不过此时录制出来的视频是没有声音, 需要通过上面讲到的 [代码]MediaContainer[代码] 截取前 10s 的音频, 将录制出的视频和音频合成得到一段完整的视频。[代码]CanvasRenderingContext2D[代码] 的 [代码]drawImage[代码] 方法 2.10.0 起支持传入通过 [代码]SelectorQuery[代码] 获取的 video 对象, 所以对视频的操作可以先使用 canvas 预览, 再使用 [代码]MediaRecorder[代码] 进行录制。 小程序音视频相关 api 就介绍到这儿, 更多具体的文档, 可以参考微信官方文档。这些 api 大大方便了开发者对音视频的处理, 也期待更多小程序音视频 api 的开放。
2020-07-01 - 拎包哥回归之作,wifi打印机对接 [拎包入住,即抄即用]
[图片] 距离上一次冒泡已经是两个月前了,上一篇文章还是挂人的水帖,这次拎包哥带来的是市面上已经成熟存在的wifi打印机。 我浏览了一遍社区,但却没啥教程,为啥大家都藏着掖着,难道。。。这里有啥大咪咪? 其实不然,代码真的很简单(看上去有点长),大家耐心走个流程(粘贴)就完事儿了。 ------------------------------------------------------------------------------------ 0.首先,买部飞鹅云的wifi打印机(没广告费),大概300RMB左右(不清楚wifi打印机对比蓝牙打印机优势的可以先百度一下) [图片] 这里吐槽一下智联云wifi打印机 不要被他们的噱头:“多送10卷纸”所蒙蔽了双眼(这就是我踩过的坑哈哈)。服务态度。。。真的一言难尽,每次打服务热线过去都是播完一整首的广场舞歌曲后,机器人告诉我无人接听做结束。下载api发现也不能ctrl c+ ctrl v(菜)直接使用,然后我进QQ群去咨询,半天也没人鸟我。综上所述我才退的货去买的飞鹅云,粘贴api代码,机器立马(一两天)就跑起来了。 1.打开网址:http://admin.feieyun.com/,注册,然后添加打印机 [图片] [图片] 填入这两个信息(在打印机底部)就够了 [图片][图片] 2.在飞鹅开放平台注册开发者账号(放心,不要钱的) 打开个人中心,进行个人实名认证(不需要企业认证) [图片] 认证成功后你会得到自己的开发者信息 [图片] 3.粘贴官方的api到自己的小程序(我直接粘贴过来了,方便讲解) 要改的只有这几个地方: 3.1认证通过后得到的开发者信息 + 打印机底部的SN编号 var USER = "xxxxxxxxxxxxxxxxx"; //必填,飞鹅云 http://admin.feieyun.com/ 管理后台注册的账号名 var UKEY = "xxxxxxxxxxxxxxxxx"; //必填,这个不是填打印机的key,是在飞鹅云后台注册账号后生成的UKEY var SN = 'xxxxxxxxxxxxxxxxx'; //必填,打印机编号,打印机必须要在管理后台先添加 USER和UKEY在这里 [图片] SN在这里(密钥不用管) [图片] 3.2 去掉注释 这里的1代表一次打印1张,如此类推。。。 // print_r(SN,orderInfo,1); 3.3 修改orderInfo里的数据到自己想要的格式 ps. 记得勾选这个 [图片] 官方算法 或者你可以直接跑起来先爽一下,但自己的数据还是要有算法来适应打印纸。 var orderInfo; orderInfo = '小程序测试打印'; orderInfo += '名称 单价 数量 金额'; orderInfo += '--------------------------------'; orderInfo += '饭 10.0 10 10.0'; orderInfo += '炒饭 10.0 10 10.0'; orderInfo += '蛋炒饭 10.0 100 100.0'; orderInfo += '鸡蛋炒饭 100.0 100 100.0'; orderInfo += '西红柿炒饭 1000.0 1 100.0'; orderInfo += '西红柿蛋炒饭 100.0 100 100.0'; orderInfo += '西红柿鸡蛋炒饭 15.0 1 15.0'; orderInfo += '备注:加辣'; orderInfo += '--------------------------------'; 我的算法 orderInfo += '名称 单价 数量 金额';orderInfo += '--------------------------------';for (var i in list) { if (list[i].name.length < 15) { aLength = list[i].name.match(reg).length * 2 a = list[i].name.concat(' '.repeat(15 - aLength)) } if (list[i].price.toString().length < 6) { b = list[i].price.toString().concat(' '.repeat(6 - list[i].price.toString().length)) } if (list[i].num.toString().length < 5) { c = list[i].num.toString().concat(' '.repeat(5 - list[i].num.toString().length)) } if (list[i].sinTtlPrice.toString().length < 5) { d = list[i].sinTtlPrice.toString().concat(' '.repeat(5 - list[i].sinTtlPrice.toString().length)) } orderInfo += a + b + c + d + ''} 完整官方小程序api 只是一个方法,不用node xx包,我等手残党福音! [图片] [图片] //微信小程序https请求实例,根据自己的需求条件触发函数,推送订单打印test(e) { //USER和UKEY在飞鹅云( http://admin.feieyun.com/ )管理后台的个人中心可以查看 var USER = "xxxxxxxxxxxxxxxxx"; //必填,飞鹅云 http://admin.feieyun.com/ 管理后台注册的账号名 var UKEY = "xxxxxxxxxxxxxxxxx"; //必填,这个不是填打印机的key,是在飞鹅云后台注册账号后生成的UKEY var SN = 'xxxxxxxxxxxxxxxxx'; //必填,打印机编号,打印机必须要在管理后台先添加 //以下URL参数不需要修改 var HOST = "api.feieyun.cn"; //域名 var PATH = "/Api/Open/"; //接口路径 var STIME = new Date().getTime();//请求时间,当前时间的秒数 var SIG = hex_sha1(USER + UKEY + STIME);//获取签名 //标签说明: //单标签: //"<BR>"为换行,"<CUT>"为切刀指令(主动切纸,仅限切刀打印机使用才有效果) //"<LOGO>"为打印LOGO指令(前提是预先在机器内置LOGO图片),"<PLUGIN>"为钱箱或者外置音响指令 //成对标签: //"<CB></CB>"为居中放大一倍,"<B></B>"为放大一倍,"<C></C>"为居中,<L></L>字体变高一倍 //<W></W>字体变宽一倍,"<QR></QR>"为二维码,"<BOLD></BOLD>"为字体加粗,"<RIGHT></RIGHT>"为右对齐 //拼凑订单内容时可参考如下格式 //根据打印纸张的宽度,自行调整内容的格式,可参考下面的样例格式 var orderInfo; orderInfo = '<CB>小程序测试打印</CB><BR>'; orderInfo += '名称 单价 数量 金额<BR>'; orderInfo += '--------------------------------<BR>'; orderInfo += '饭 10.0 10 10.0<BR>'; orderInfo += '炒饭 10.0 10 10.0<BR>'; orderInfo += '蛋炒饭 10.0 100 100.0<BR>'; orderInfo += '鸡蛋炒饭 100.0 100 100.0<BR>'; orderInfo += '西红柿炒饭 1000.0 1 100.0<BR>'; orderInfo += '西红柿蛋炒饭 100.0 100 100.0<BR>'; orderInfo += '西红柿鸡蛋炒饭 15.0 1 15.0<BR>'; orderInfo += '备注:加辣<BR>'; orderInfo += '--------------------------------<BR>'; orderInfo += '合计:xx.0元<BR>'; orderInfo += '送货地点:广州市南沙区xx路xx号<BR>'; orderInfo += '联系电话:13888888888888<BR>'; orderInfo += '订餐时间:2014-08-08 08:08:08<BR>'; orderInfo += '<QR>http://www.dzist.com</QR>';//把二维码字符串用标签套上即可自动生成二维码 // ***接口返回值说明*** //正确例子:{"msg":"ok","ret":0,"data":"123456789_20160823165104_1853029628","serverExecutedTime":6} //错误:{"msg":"错误信息.","ret":非零错误码,"data":null,"serverExecutedTime":5} // console.log(orderInfo); //打开注释可测试 // print_r(SN,orderInfo,1); var hexcase = 0; var chrsz = 8; function hex_sha1(s) { return binb2hex(core_sha1(AlignSHA1(s))); } function core_sha1(blockArray) { var x = blockArray; var w = Array(80); var a = 1732584193; var b = -271733879; var c = -1732584194; var d = 271733878; var e = -1009589776; for (var i = 0; i < x.length; i += 16) { var olda = a; var oldb = b; var oldc = c; var oldd = d; var olde = e; for (var j = 0; j < 80; j++) { if (j < 16) w[j] = x[i + j]; else w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1); var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), safe_add(safe_add(e, w[j]), sha1_kt(j))); e = d; d = c; c = rol(b, 30); b = a; a = t; } a = safe_add(a, olda); b = safe_add(b, oldb); c = safe_add(c, oldc); d = safe_add(d, oldd); e = safe_add(e, olde); } return new Array(a, b, c, d, e); } function sha1_ft(t, b, c, d) { if (t < 20) return (b & c) | ((~b) & d); if (t < 40) return b ^ c ^ d; if (t < 60) return (b & c) | (b & d) | (c & d); return b ^ c ^ d; } function sha1_kt(t) { return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514; } function safe_add(x, y) { var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); } function rol(num, cnt) { return (num << cnt) | (num >>> (32 - cnt)); } function AlignSHA1(str) { var nblk = ((str.length + 8) >> 6) + 1, blks = new Array(nblk * 16); for (var i = 0; i < nblk * 16; i++) blks[i] = 0; for (i = 0; i < str.length; i++) blks[i >> 2] |= str.charCodeAt(i) << (24 - (i & 3) * 8); blks[i >> 2] |= 0x80 << (24 - (i & 3) * 8); blks[nblk * 16 - 1] = str.length * 8; return blks; } function binb2hex(binarray) { var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; var str = ""; for (var i = 0; i < binarray.length * 4; i++) { str += hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) + hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF); } return str; } // 打印订单方法:Open_printMsg function print_r(SN, orderInfo, TIMES) { wx.request({ url: 'https://' + HOST + PATH, data: { user: USER,//账号 stime: STIME,//当前时间的秒数,请求时间 sig: SIG,//签名 apiname: "Open_printMsg",//不需要修改 sn: SN,//打印机编号 content: orderInfo,//打印内容 times: TIMES,//打印联数,默认为1 }, method: "POST", header: { "content-type": "application/x-www-form-urlencoded" }, success: function (res) { console.log(res.data) } }) }} 好了,大咪咪披露完了。可能还有什么纰漏,没关系,评论里见吧! [图片] -=====================成果图========================= 应大家要求放上成果,图有点歪,当是治治大家的颈椎病吧。 [图片] [图片] ================更新于2020/7/22=========================
2022-03-10 - 小程序蓝牙打印爬坑之旅
因为公司要在小程序上加蓝牙打印标签功能,所以就开始接触小程序的蓝牙打印,看文档还是蛮详细的,而且还有demo,顺着demo,一步一步下来还是蛮顺畅的,原以为很快就能完成。没想到坑来了,由于demo中writeBLECharacteristicValue只是写入了一个16进制的数据,而现实中是需要发送字符串的,而且小程序必须要是arrayBuffer,就必须将字符串转arrayBuffer了,好,网上搜下,准备打印了吱吱吱咦,怎么有乱码啊,怎么中文都乱码了。。这下可糟了!于是就去各种找答案。最后知道问题了:原来是因为我们公司用的打印机是智能支持GB2312编码格式的二进制的,但是字符串是utf-8,诶,又得爬坑。经过一天的努力,终于找到解决方法啦,感谢csdn的大大们。实现的代码如下 //计算arraybuffer的长度 sumStrLength(str) { var length = 0; var data = str.toString(); for (var i = 0; i < data.length; i++) { if (this.isCN(data[i])) { //是中文 length += 2; } else { length += 1; } } return length; }, //混杂 hexStringToBuff(str) { //str=‘中国:WXHSH’ const buffer = new ArrayBuffer((this.sumStrLength(str)) + 1); const dataView = new DataView(buffer) var data = str.toString(); var p = 0; //ArrayBuffer 偏移量 for (var i = 0; i < data.length; i++) { if (this.isCN(data[i])) { //是中文 //调用GBK 转码 var t = gbk.$URL.encode(data[i]); for (var j = 0; j < 2; j++) { var temp = parseInt(t[j * 2] + t[j * 2 + 1], 16) dataView.setUint8(p++, temp) } } else { var temp = parseInt(data.charCodeAt(i).toString(16), 16) dataView.setUint8(p++, temp) } } console.log(String.fromCharCode.apply(null, new Uint8Array(buffer))); return buffer; }, //js正则验证中文 isCN(str) { if (/[1]+$/.test(str)) { return true; } else { return false; } }, 将中文转化为GB2312编码格式再转成arrayBuffer就大功告成啦,把这个文章记录下来,希望可以帮助到其他小程程们。如有需要,加我Q:786914253 \u3220-\uFA29 ↩︎
2019-03-19