HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

uni-app / mui / h5+ 手机底部导航栏(虚拟按键菜单)背景颜色

onLaunch() {

                   //底部导航栏(虚拟按键)背景颜色  
        var color = plus.android.newObject("android.graphics.Color")  
        var ac = plus.android.runtimeMainActivity();  
        var c2int = plus.android.invoke(color, "parseColor", "#161516");  
        console.log("c2int===" + JSON.stringify(c2int))  
        var win = plus.android.invoke(ac, "getWindow");  
        console.log("win===" + JSON.stringify(win))  
        plus.android.invoke(win, "setNavigationBarColor", c2int);  

继续阅读 »

onLaunch() {

                   //底部导航栏(虚拟按键)背景颜色  
        var color = plus.android.newObject("android.graphics.Color")  
        var ac = plus.android.runtimeMainActivity();  
        var c2int = plus.android.invoke(color, "parseColor", "#161516");  
        console.log("c2int===" + JSON.stringify(c2int))  
        var win = plus.android.invoke(ac, "getWindow");  
        console.log("win===" + JSON.stringify(win))  
        plus.android.invoke(win, "setNavigationBarColor", c2int);  

收起阅读 »

如何判断时候安装了微信、qq等

技术分享

案例判断是否安装微信 其他同理
if (plus.runtime.isApplicationExist({ pname: 'com.tencent.mm', action: 'weixin://' })) {
//安装了微信
} else {
//未安装微信
}

继续阅读 »

案例判断是否安装微信 其他同理
if (plus.runtime.isApplicationExist({ pname: 'com.tencent.mm', action: 'weixin://' })) {
//安装了微信
} else {
//未安装微信
}

收起阅读 »

Spark Streaming的优化之路——从Receiver到Direct模式

数据交互


作者:个推数据研发工程师 学长

1 业务背景

随着大数据的快速发展,业务场景越来越复杂,离线式的批处理框架MapReduce已经不能满足业务,大量的场景需要实时的数据处理结果来进行分析、决策。Spark Streaming是一种分布式的大数据实时计算框架,他提供了动态的,高吞吐量的,可容错的流式数据处理,不仅可以实现用户行为分析,还能在金融、舆情分析、网络监控等方面发挥作用。个推开发者服务——消息推送“应景推送”正是应用了Spark Streaming技术,基于大数据分析人群属性,同时利用LBS地理围栏技术,实时触发精准消息推送,实现用户的精细化运营。此外,个推在应用Spark Streaming做实时处理kafka数据时,采用Direct模式代替Receiver模式的手段,实现了资源优化和程序稳定性提升。

本文将从Spark Streaming获取kafka数据的两种模式入手,结合个推实践,带你解读Receiver和Direct模式的原理和特点,以及从Receiver模式到Direct模式的优化对比。

2 两种模式的原理和区别

Receiver模式

1. Receiver模式下的运行架构


1)InputDStream: 从流数据源接收的输入数据。
2)Receiver:负责接收数据流,并将数据写到本地。
3)Streaming Context:代表SparkStreaming,负责Streaming层面的任务调度,生成jobs发送到Spark engine处理。
4)Spark Context: 代表Spark Core,负责批处理层面的任务调度,真正执行job的Spark engine。

2. Receiver从kafka拉取数据的过程


该模式下:
1)在executor上会有receiver从kafka接收数据并存储在Spark executor中,在到了batch时间后触发job去处理接收到的数据,1个receiver占用1个core;
2)为了不丢数据需要开启WAL机制,这会将receiver接收到的数据写一份备份到第三方系统上(如:HDFS);
3)receiver内部使用kafka High Level API去消费数据及自动更新offset。

Direct模式

1. Direct模式下的运行架构

与receiver模式类似,不同在于executor中没有receiver组件,从kafka拉去数据的方式不同。

2. Direct从kafka拉取数据的过程


该模式下:
1)没有receiver,无需额外的core用于不停地接收数据,而是定期查询kafka中的每个partition的最新的offset,每个批次拉取上次处理的offset和当前查询的offset的范围的数据进行处理;
2)为了不丢数据,无需将数据备份落地,而只需要手动保存offset即可;
3)内部使用kafka simple Level API去消费数据, 需要手动维护offset,kafka zk上不会自动更新offset。

Receiver与Direct模式的区别

1.前者在executor中有Receiver接受数据,并且1个Receiver占用一个core;而后者无Receiver,所以不会暂用core;
2.前者InputDStream的分区是 num_receiver *batchInterval/blockInteral,后者的分区数是kafka topic partition的数量。Receiver模式下num_receiver的设置不合理会影响性能或造成资源浪费;如果设置太小,并行度不够,整个链路上接收数据将是瓶颈;如果设置太多,则会浪费资源;
3.前者使用zookeeper来维护consumer的偏移量,而后者需要自己维护偏移量;
4.为了保证不丢失数据,前者需要开启WAL机制,而后者不需要,只需要在程序中成功消费完数据后再更新偏移量即可。

3 Receiver改造成Direct模式

个推使用Spark Streaming做实时处理kafka数据,先前使用的是receiver模式;

receiver有以下特点
1.receiver模式下,每个receiver需要单独占用一个core;
2.为了保证不丢失数据,需要开启WAL机制,使用checkpoint保存状态;
3.当receiver接受数据速率大于处理数据速率,导致数据积压,最终可能会导致程序挂掉。

由于以上特点,receiver模式下会造成一定的资源浪费;使用checkpoint保存状态, 如果需要升级程序,则会导致checkpoint无法使用;第3点receiver模式下会导致程序不太稳定;并且如果设置receiver数量不合理也会造成性能瓶颈在receiver。为了优化资源和程序稳定性,应将receiver模式改造成direct模式。

修改方式如下:

1. 修改InputDStream的创建

将receiver的:

val kafkaStream = KafkaUtils.createStream(streamingContext,  
     [ZK quorum], [consumer group id], [per-topic number of Kafka partitions to consume])

改成direct的:

val directKafkaStream = KafkaUtils.createDirectStream[  
     [key class], [value class], [key decoder class], [value decoder class] ](  
     streamingContext, [map of Kafka parameters], [set of topics to consume])

2. 手动维护offset

receiver模式代码:
(receiver模式不需要手动维护offset,而是内部通过kafka consumer high level API 提交到kafka/zk保存)

kafkaStream.map {  
           ...  
 }.foreachRDD { rdd =>  
    // 数据处理  
    doCompute(rdd)  
 }

direct模式代码:

directKafkaStream.map {  
           ...  
 }.foreachRDD { rdd =>  
    // 获取当前rdd数据对应的offset  
    val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges  
    // 数据处理  
    doCompute(rdd)  
    // 自己实现保存offset  
    commitOffsets(offsetRanges)  
 }

4 其他优化点

1. 在receiver模式下
1)拆分InputDStream,增加Receiver,从而增加接收数据的并行度;
2)调整blockInterval,适当减小,增加task数量,从而增加并行度(在core的数量>task数量的情况下);
3)如果开启了WAL机制,数据的存储级别设置为MOMERY_AND_DISK_SER。

2.数据序列化使用Kryoserializationl,相比Java serializationl 更快,序列化后的数据更小;

3.建议使用CMS垃圾回收器降低GC开销;

4.选择高性能的算子(mapPartitions, foreachPartitions, aggregateByKey等);

5.repartition的使用:在streaming程序中因为batch时间特别短,所以数据量一般较小,所以repartition的时间短,可以解决一些因为topicpartition中数据分配不均匀导致的数据倾斜问题;

6.因为SparkStreaming生产的job最终都是在sparkcore上运行的,所以sparkCore的优化也很重要;

7.BackPressure流控

1)为什么引入Backpressure?
当batch processing time>batchinterval 这种情况持续过长的时间,会造成数据在内存中堆积,导致Receiver所在Executor内存溢出等问题;

2)Backpressure:根据JobScheduler反馈作业的执行信息来动态调整数据接收率;

3)配置使用:

spark.streaming.backpressure.enabled  
含义: 是否启用 SparkStreaming内部的backpressure机制,  
默认值:false ,表示禁用  

spark.streaming.backpressure.initialRate  
含义: receiver 为第一个batch接收数据时的比率  

spark.streaming.receiver.maxRate  
含义: receiver接收数据的最大比率,如果设置值<=0, 则receiver接收数据比率不受限制  

spark.streaming.kafka.maxRatePerPartition  
含义: 从每个kafka partition中读取数据的最大比率

8.speculation机制
spark内置speculation机制,推测job中的运行特别慢的task,将这些task kill,并重新调度这些task执行。
默认speculation机制是关闭的,通过以下配置参数开启:

spark.speculation=true

注意:在有些情况下,开启speculation反而效果不好,比如:streaming程序消费多个topic时,从kafka读取数据直接处理,没有重新分区,这时如果多个topic的partition的数据量相差较大那么可能会导致正常执行更大数据量的task会被认为执行缓慢,而被中途kill掉,这种情况下可能导致batch的处理时间反而变长;可以通过repartition来解决这个问题,但是要衡量repartition的时间;而在streaming程序中因为batch时间特别短,所以数据量一般较小,所以repartition的时间短,不像spark_batch一次处理大量数据一旦repartition则会特别久,所以最终还是要根据具体情况测试来决定。

5 总结

将Receiver模式改成Direct模式,实现了资源优化,提升了程序的稳定性,缺点是需要自己管理offset,操作相对复杂。未来,个推将不断探索和优化Spark Streaming技术,发挥其强大的数据处理能力,为建设实时数仓提供保障。

继续阅读 »


作者:个推数据研发工程师 学长

1 业务背景

随着大数据的快速发展,业务场景越来越复杂,离线式的批处理框架MapReduce已经不能满足业务,大量的场景需要实时的数据处理结果来进行分析、决策。Spark Streaming是一种分布式的大数据实时计算框架,他提供了动态的,高吞吐量的,可容错的流式数据处理,不仅可以实现用户行为分析,还能在金融、舆情分析、网络监控等方面发挥作用。个推开发者服务——消息推送“应景推送”正是应用了Spark Streaming技术,基于大数据分析人群属性,同时利用LBS地理围栏技术,实时触发精准消息推送,实现用户的精细化运营。此外,个推在应用Spark Streaming做实时处理kafka数据时,采用Direct模式代替Receiver模式的手段,实现了资源优化和程序稳定性提升。

本文将从Spark Streaming获取kafka数据的两种模式入手,结合个推实践,带你解读Receiver和Direct模式的原理和特点,以及从Receiver模式到Direct模式的优化对比。

2 两种模式的原理和区别

Receiver模式

1. Receiver模式下的运行架构


1)InputDStream: 从流数据源接收的输入数据。
2)Receiver:负责接收数据流,并将数据写到本地。
3)Streaming Context:代表SparkStreaming,负责Streaming层面的任务调度,生成jobs发送到Spark engine处理。
4)Spark Context: 代表Spark Core,负责批处理层面的任务调度,真正执行job的Spark engine。

2. Receiver从kafka拉取数据的过程


该模式下:
1)在executor上会有receiver从kafka接收数据并存储在Spark executor中,在到了batch时间后触发job去处理接收到的数据,1个receiver占用1个core;
2)为了不丢数据需要开启WAL机制,这会将receiver接收到的数据写一份备份到第三方系统上(如:HDFS);
3)receiver内部使用kafka High Level API去消费数据及自动更新offset。

Direct模式

1. Direct模式下的运行架构

与receiver模式类似,不同在于executor中没有receiver组件,从kafka拉去数据的方式不同。

2. Direct从kafka拉取数据的过程


该模式下:
1)没有receiver,无需额外的core用于不停地接收数据,而是定期查询kafka中的每个partition的最新的offset,每个批次拉取上次处理的offset和当前查询的offset的范围的数据进行处理;
2)为了不丢数据,无需将数据备份落地,而只需要手动保存offset即可;
3)内部使用kafka simple Level API去消费数据, 需要手动维护offset,kafka zk上不会自动更新offset。

Receiver与Direct模式的区别

1.前者在executor中有Receiver接受数据,并且1个Receiver占用一个core;而后者无Receiver,所以不会暂用core;
2.前者InputDStream的分区是 num_receiver *batchInterval/blockInteral,后者的分区数是kafka topic partition的数量。Receiver模式下num_receiver的设置不合理会影响性能或造成资源浪费;如果设置太小,并行度不够,整个链路上接收数据将是瓶颈;如果设置太多,则会浪费资源;
3.前者使用zookeeper来维护consumer的偏移量,而后者需要自己维护偏移量;
4.为了保证不丢失数据,前者需要开启WAL机制,而后者不需要,只需要在程序中成功消费完数据后再更新偏移量即可。

3 Receiver改造成Direct模式

个推使用Spark Streaming做实时处理kafka数据,先前使用的是receiver模式;

receiver有以下特点
1.receiver模式下,每个receiver需要单独占用一个core;
2.为了保证不丢失数据,需要开启WAL机制,使用checkpoint保存状态;
3.当receiver接受数据速率大于处理数据速率,导致数据积压,最终可能会导致程序挂掉。

由于以上特点,receiver模式下会造成一定的资源浪费;使用checkpoint保存状态, 如果需要升级程序,则会导致checkpoint无法使用;第3点receiver模式下会导致程序不太稳定;并且如果设置receiver数量不合理也会造成性能瓶颈在receiver。为了优化资源和程序稳定性,应将receiver模式改造成direct模式。

修改方式如下:

1. 修改InputDStream的创建

将receiver的:

val kafkaStream = KafkaUtils.createStream(streamingContext,  
     [ZK quorum], [consumer group id], [per-topic number of Kafka partitions to consume])

改成direct的:

val directKafkaStream = KafkaUtils.createDirectStream[  
     [key class], [value class], [key decoder class], [value decoder class] ](  
     streamingContext, [map of Kafka parameters], [set of topics to consume])

2. 手动维护offset

receiver模式代码:
(receiver模式不需要手动维护offset,而是内部通过kafka consumer high level API 提交到kafka/zk保存)

kafkaStream.map {  
           ...  
 }.foreachRDD { rdd =>  
    // 数据处理  
    doCompute(rdd)  
 }

direct模式代码:

directKafkaStream.map {  
           ...  
 }.foreachRDD { rdd =>  
    // 获取当前rdd数据对应的offset  
    val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges  
    // 数据处理  
    doCompute(rdd)  
    // 自己实现保存offset  
    commitOffsets(offsetRanges)  
 }

4 其他优化点

1. 在receiver模式下
1)拆分InputDStream,增加Receiver,从而增加接收数据的并行度;
2)调整blockInterval,适当减小,增加task数量,从而增加并行度(在core的数量>task数量的情况下);
3)如果开启了WAL机制,数据的存储级别设置为MOMERY_AND_DISK_SER。

2.数据序列化使用Kryoserializationl,相比Java serializationl 更快,序列化后的数据更小;

3.建议使用CMS垃圾回收器降低GC开销;

4.选择高性能的算子(mapPartitions, foreachPartitions, aggregateByKey等);

5.repartition的使用:在streaming程序中因为batch时间特别短,所以数据量一般较小,所以repartition的时间短,可以解决一些因为topicpartition中数据分配不均匀导致的数据倾斜问题;

6.因为SparkStreaming生产的job最终都是在sparkcore上运行的,所以sparkCore的优化也很重要;

7.BackPressure流控

1)为什么引入Backpressure?
当batch processing time>batchinterval 这种情况持续过长的时间,会造成数据在内存中堆积,导致Receiver所在Executor内存溢出等问题;

2)Backpressure:根据JobScheduler反馈作业的执行信息来动态调整数据接收率;

3)配置使用:

spark.streaming.backpressure.enabled  
含义: 是否启用 SparkStreaming内部的backpressure机制,  
默认值:false ,表示禁用  

spark.streaming.backpressure.initialRate  
含义: receiver 为第一个batch接收数据时的比率  

spark.streaming.receiver.maxRate  
含义: receiver接收数据的最大比率,如果设置值<=0, 则receiver接收数据比率不受限制  

spark.streaming.kafka.maxRatePerPartition  
含义: 从每个kafka partition中读取数据的最大比率

8.speculation机制
spark内置speculation机制,推测job中的运行特别慢的task,将这些task kill,并重新调度这些task执行。
默认speculation机制是关闭的,通过以下配置参数开启:

spark.speculation=true

注意:在有些情况下,开启speculation反而效果不好,比如:streaming程序消费多个topic时,从kafka读取数据直接处理,没有重新分区,这时如果多个topic的partition的数据量相差较大那么可能会导致正常执行更大数据量的task会被认为执行缓慢,而被中途kill掉,这种情况下可能导致batch的处理时间反而变长;可以通过repartition来解决这个问题,但是要衡量repartition的时间;而在streaming程序中因为batch时间特别短,所以数据量一般较小,所以repartition的时间短,不像spark_batch一次处理大量数据一旦repartition则会特别久,所以最终还是要根据具体情况测试来决定。

5 总结

将Receiver模式改成Direct模式,实现了资源优化,提升了程序的稳定性,缺点是需要自己管理offset,操作相对复杂。未来,个推将不断探索和优化Spark Streaming技术,发挥其强大的数据处理能力,为建设实时数仓提供保障。

收起阅读 »

android 插件开发,插件类 打开 activity ,然后接收 activity 返回值 onActivityResult

activity Android 5 SDK

也是郁闷了好些天才搞定

场景:如题,activity (页面)操作后返回数据到,插件类里面。
不说了,直接上代码:
插件类:

package com.example.chajian;  

import android.content.Intent;  
import android.util.Log;  

import org.json.JSONArray;  

import io.dcloud.common.DHInterface.IApp;  
import io.dcloud.common.DHInterface.ISysEventListener;  
import io.dcloud.common.DHInterface.IWebview;  
import io.dcloud.common.DHInterface.StandardFeature;  
import io.dcloud.common.util.JSUtil;  

public class Ceshi extends StandardFeature {  

    public void Test(IWebview pWebview, JSONArray array) {  

        final IWebview iWebview = pWebview;  
        final String CallBackID = array.optString(0);  
        final JSONArray newArray = new JSONArray();  

        newArray.put(array.optString(1));  
        newArray.put(array.optString(2));  
        newArray.put(array.optString(3));  
        newArray.put(array.optString(4));  

         //添加监听  
        final IApp _app = pWebview.obtainFrameView().obtainApp();  
        _app.registerSysEventListener(new ISysEventListener() {  
            @Override  
            public boolean onExecute(SysEventType pEventType, Object pArgs) {  

                Object[] _args = (Object[]) pArgs;  
                int requestCode = (Integer) _args[0];  
                int resultCode = (Integer) _args[1];  

                Intent data = (Intent) _args[2];  

                //[Ljava.lang.Object;@e89a7c8  
                Log.d("aaa", "onExecute: _args " + _args);  
                //1  
                Log.d("aaa", "onExecute: requestCode " + requestCode);  
                //-1  
                Log.d("aaa", "onExecute: resultCode " + resultCode);  
                //Intent { (has extras) }  
                Log.d("aaa", "onExecute: data " + data);  

                if (pEventType == SysEventType.onActivityResult) {  
                    //用完给取消注册监听  
                    _app.unregisterSysEventListener(this, SysEventType.onActivityResult);  

                    //判断请求码  
                    if (requestCode == 1) {  
                        //获取返回值  
                        String returnData = data.getStringExtra("return_data");  
                        //执行 js 回调  
                        JSUtil.execCallback(iWebview, CallBackID, returnData, JSUtil.OK, false);  
                        Log.d("aaa", "返回的数据为:" + returnData);  
                    }  
                }  
                return false;  
            }  
        }, SysEventType.onActivityResult);  

        //打开 activity  
        Intent openItent = new Intent(pWebview.getActivity(), Open.class);  
//        Intent openItent = new Intent(pWebview.getContext(),Open.class);  
        pWebview.getActivity().startActivityForResult(openItent, 1);  
    }  

}  

activity 类:

package com.example.chajian;  

import android.content.Intent;  
import android.os.Bundle;  
import android.support.v7.app.AppCompatActivity;  
import android.util.Log;  
import android.view.View;  
import android.widget.Button;  

public class Open extends AppCompatActivity {  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.open_activity);  

        Button open = findViewById(R.id.open);  

        open.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View view) {  
                Log.d("aaa", "Open.onCreate : open");  

                Intent intent = new Intent();  
                intent.putExtra("return_data","我去它妹的!");  
                setResult(RESULT_OK,intent);  

                finish();  
            }  
        });  
    }  

}  

代码并不复杂,一行一行的看,很好理解,还有注释。
逻辑是,插件类设置事件监听-打开 activity-activity 设置返回值后 finish() (关闭)-插件类监听到返回值处理执行 js 回调。

继续阅读 »

也是郁闷了好些天才搞定

场景:如题,activity (页面)操作后返回数据到,插件类里面。
不说了,直接上代码:
插件类:

package com.example.chajian;  

import android.content.Intent;  
import android.util.Log;  

import org.json.JSONArray;  

import io.dcloud.common.DHInterface.IApp;  
import io.dcloud.common.DHInterface.ISysEventListener;  
import io.dcloud.common.DHInterface.IWebview;  
import io.dcloud.common.DHInterface.StandardFeature;  
import io.dcloud.common.util.JSUtil;  

public class Ceshi extends StandardFeature {  

    public void Test(IWebview pWebview, JSONArray array) {  

        final IWebview iWebview = pWebview;  
        final String CallBackID = array.optString(0);  
        final JSONArray newArray = new JSONArray();  

        newArray.put(array.optString(1));  
        newArray.put(array.optString(2));  
        newArray.put(array.optString(3));  
        newArray.put(array.optString(4));  

         //添加监听  
        final IApp _app = pWebview.obtainFrameView().obtainApp();  
        _app.registerSysEventListener(new ISysEventListener() {  
            @Override  
            public boolean onExecute(SysEventType pEventType, Object pArgs) {  

                Object[] _args = (Object[]) pArgs;  
                int requestCode = (Integer) _args[0];  
                int resultCode = (Integer) _args[1];  

                Intent data = (Intent) _args[2];  

                //[Ljava.lang.Object;@e89a7c8  
                Log.d("aaa", "onExecute: _args " + _args);  
                //1  
                Log.d("aaa", "onExecute: requestCode " + requestCode);  
                //-1  
                Log.d("aaa", "onExecute: resultCode " + resultCode);  
                //Intent { (has extras) }  
                Log.d("aaa", "onExecute: data " + data);  

                if (pEventType == SysEventType.onActivityResult) {  
                    //用完给取消注册监听  
                    _app.unregisterSysEventListener(this, SysEventType.onActivityResult);  

                    //判断请求码  
                    if (requestCode == 1) {  
                        //获取返回值  
                        String returnData = data.getStringExtra("return_data");  
                        //执行 js 回调  
                        JSUtil.execCallback(iWebview, CallBackID, returnData, JSUtil.OK, false);  
                        Log.d("aaa", "返回的数据为:" + returnData);  
                    }  
                }  
                return false;  
            }  
        }, SysEventType.onActivityResult);  

        //打开 activity  
        Intent openItent = new Intent(pWebview.getActivity(), Open.class);  
//        Intent openItent = new Intent(pWebview.getContext(),Open.class);  
        pWebview.getActivity().startActivityForResult(openItent, 1);  
    }  

}  

activity 类:

package com.example.chajian;  

import android.content.Intent;  
import android.os.Bundle;  
import android.support.v7.app.AppCompatActivity;  
import android.util.Log;  
import android.view.View;  
import android.widget.Button;  

public class Open extends AppCompatActivity {  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.open_activity);  

        Button open = findViewById(R.id.open);  

        open.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View view) {  
                Log.d("aaa", "Open.onCreate : open");  

                Intent intent = new Intent();  
                intent.putExtra("return_data","我去它妹的!");  
                setResult(RESULT_OK,intent);  

                finish();  
            }  
        });  
    }  

}  

代码并不复杂,一行一行的看,很好理解,还有注释。
逻辑是,插件类设置事件监听-打开 activity-activity 设置返回值后 finish() (关闭)-插件类监听到返回值处理执行 js 回调。

收起阅读 »

【NEXT学院】uni-app商业级应用实战 一次搞定小程序/安卓/iOS/H5 2人拼团 ¥459.00

学习

【NEXT学院】uni-app商业级应用实战 一次搞定小程序/安卓/iOS/H5

2人拼团 ¥459.00

https://ke.qq.com/course/379043?group_id=403035#term_id=100451746

【NEXT学院】uni-app商业级应用实战 一次搞定小程序/安卓/iOS/H5

2人拼团 ¥459.00

https://ke.qq.com/course/379043?group_id=403035#term_id=100451746

终于搞定了UniApp开发的微信小程序的支付,分享下有关微信支付踩的坑

微信支付

之前用H5+做过微信支付和支付宝支付,是一年前做的,有些细节忘记了,本以为不会费太多时间,结果搞了12个小时

记录一下踩过的坑,包括:调用支付JSAPI缺少参数: total_fee,支付验证签名失败,不识别的参数body!!!等我都碰到了

如何去微信那边申请【公众平台】【商户平台】【开放平台】本来不想讲,但是这几个平台也确实容易把人搞晕,还是大致说说我的理解吧,也算是梳理下自己的思路,方便新手,老手绕道:

最早就一个【公众号平台:mp.weixin.qq.com】,2015-2016年那会儿做微信公众号特别火,所以大家最早接触的是它,此前微信支付还不对公众开放,后来有了服务号之后,可以做公众号商城,有了支付需求,微信推出了【商户平台:pay.weixin.qq.com】,从公众号平台,有一个按钮点击就进入了商户平台,,最早的微信支付就只有JSAPI方式,也就是在微信内置浏览器里支付,后来增加了H5网页支付、Native支付、App支付、付款码支付,见下图:

我们一般开发者大多数用到的是JSAPI和App两种,微信公众号服务号、公众号商城、包括后来的小程序,都是用的JSAPI方式,用HBuilder和HBuilderX开发的跨平台App用的是App方式。

【开放平台:open.weixin.qq.com】又是什么呢?如果我们想做一个App或微信小程序要集成微信支付功能,需要注册哪些呢?需要注册【微信开放平台】,在开放平台里注册开发者账号,把自己的应用(就是我们的manifest.json里的那个appid)登记上去,然后要申请支付功能,申请支付功能就等于是开通了【商户平台】,因为钱要去这里,如果已经有了【商户平台】就可不用再申请了,见下图:

当然开放平台不单单包括微信支付,还包括其他接入服务,包括(移动应用,网站应用,公众账号,第三方平台)

题外话:微信公众号平台是要年审的,费用300,,开放平台也是要年审的,也是300。

小程序是要单独注册的,但是也是在微信公众平台的页面mp.weixin.qq.com注册,不过要另外注册账号,不能使用已有的微信公众号账号,但是申请成功后,可以挂靠/绑定在某个已有的【公众号】上,同时可以关联已有的【商户平台】。

我个人感觉,小程序是微信公众号的JSSDK可以调用的一大堆接口+Vue的框架的一种集成,过去的微信公众号(服务号)的开发基本上纯后台的,前端技术的不断发展,尤其是Vue这类前端框架,让前端可以做更多的事,包括一些复杂的界面成为可能。

以上梳理了微信几大平台的关系,啰啰嗦嗦说了一堆自己的感受,还回到文章的题目上,说说微信小程序支付的坑。


正是因为有几大平台,所以好多知识点分散在各个平台的文档里,而且不断的版本迭代,文档之间也有所差异,之前做App支付,比较顺利,这次做小程序支付,就因为之前没把所有这些知识点贯通起来,所以碰到了点问题:

整个支付流程:
App或小程序,创建自己的订单------>后台访问统一下单接口:https://api.mch.weixin.qq.com/pay/unifiedorder,生成预支付订单,同时提供回调地址--------->预支付订单返回到App或小程序,调起支付,前台支付成功,跳转到相关页面-------->微信支付平台会异步给回调地址发送支付成功的结果通知,通知地址的程序接收到微信的通知,根据返回参数修改自己的订单的支付状态

0.【微信支付商户平台】设置支付目录:

  1. 先说uni-app的支付:官方文档链接:https://uniapp.dcloud.io/api/plugins/payment,这个接口只是唤起支付的接口,在此之前要做些准备的,以上的几大平台,需要注册开通,该注册的注册,该关联的关联,技术上最关键一点是要先创建预支付订单(创建预支付订单在App和微信小程序是一样的,支付宝也类似,也要先创建预支付订单);

  2. 再说H5+也就是App的支付:官方文档链接:http://www.html5plus.org/doc/zh_cn/payment.html,这个接口只是在App端唤起支付,前提条件依然是创建预支付订单,得到返回值,然后被这个接口调用,以唤起支付。

  3. 如何创建预支付订单,我借用的是DCloud提供的示例程序:https://github.com/dcloudio/H5P.Server/tree/master/payment/wxpayv3,PHP的示例代码,这部分代码的更新时间是2年前和4年前了,但是依然能用,不过有些小地方要做点修改,因为微信那边有些参数的名字有所改变,后面会提到。

首先在WxPay.Config.php里配置自己的各项参数:

    const APPID = 'wxb1马赛克c90a';  
    const MCHID = '150马赛克021';  
    const KEY   = 'ET5g6马赛克E3dxMb7iUxL';     //这个KEY就是  
    const NOTIFY_URL = "https://www.马赛克.cn/order/WxPay/notify.php";

然后在index.php里填写自己app或小程序传来的参数,这个index.php也就是自己的app或小程序访问的入口:

    ……  
    不管是用Post还是Get方法,总之要获取从app或小程序传来的自己的参数和生成预支付订单所需要的参数  
    ……  

    /////////////////////////  
    //填写生成统一下单的各项参数  
    /////////////////////////  

    // 商品或支付单简要描述  
    $subject = 'XXX-订单号:' . $request_json['jxcbillnumber']; //自己的商城或平台的商品名或订单号  
    //之前有乱码问题,现在好像只要是utf-8就不会乱码,所以不用转成iso-8859-1了,至少php平台大家都会用utf-8来做开发,java平台对编码问题很敏感,可能要注意  
    //$subject = iconv("utf-8","iso-8859-1",$subject);    

    // 内部订单号,示例代码使用时间值作为唯一的订单ID号,如果使用自己创建的订单号,如果取消支付了,再次支付时会发生错误,所以可能要每次生成不同的内部订单号  
    //$out_trade_no = date('YmdHis', time());  

    //订单金额,以分为单位  
    $total = 1;  

    $unifiedOrder = new WxPayUnifiedOrder();  
    $unifiedOrder->SetBody($subject);               //商品或支付单简要描述  
    $unifiedOrder->SetOut_trade_no($out_trade_no);      //支付订单号  
    $unifiedOrder->SetTotal_fee($total);                //订单金额,也就支付金额,以分为单位  

    ////////////////////////////////////////////////////////////////////////////////////  
    //APP只要一个参数  
    //$unifiedOrder->SetTrade_type("APP");  

    //JSAPI需要再设置openid  
    $unifiedOrder->SetTrade_type("JSAPI");  
    $unifiedOrder->SetOpenid( $request_json['openid'] );    //openid是什么不用多解释了吧,做过微信公众号的都知道  
    ///////////////////////////////////////////////////////////////////////////////////  

    //查看生成订单前的各项数据  
    console_log_wx( 'unifiedOrder前::' . json_encode( $unifiedOrder->GetValues() ) );  

    $result1 = WxPayApi::unifiedOrder($unifiedOrder);  

    //查看生成订单后的各项数据  
    console_log_wx( 'unifiedOrder后::' . json_encode($result1) );  

    //生成的统一下单结果发回app或小程序  
    if (is_array($result1)) {  
        echo json_encode($result1);  
    }  

    //查询生成的统一下单情况,可以看到生成的订单有什么错误,但是这个错误也不准确,很不靠谱  
    $result2 = WxPayApi::orderQuery($unifiedOrder);  
    console_log_wx( 'orderQuery::'.json_encode($result2) );

生成了统一下单的参数,返回给App或小程序,App用plus.payment.request(), 小程序用uni.requestPayment(),可以唤起支付窗口,但是得到一个-1的提示,大家遇到的最多了,如果查询统一下单查询的日志:

$result2 = WxPayApi::orderQuery($unifiedOrder);  
console_log_wx( 'orderQuery::'.json_encode($result2) );

可能就会看到:调用支付JSAPI缺少参数: total_fee,支付验证签名失败,不识别的参数body!!!等奇怪错误,然后开始折腾total_fee,折腾utf-8转iso-8859-1,都没用,原因是微信那边在生成预支付订单后,不同的平台,对参数的个数和名字有所不同:

再仔细读一下这两个平台的流程和相关参数的说明:

========================
APP
========================
App的业务流程:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_3
App的参数说明:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12

没时间看文档,我们就直接贴图吧,这是供:plus.payment.request()在App唤起支付调用的参数,注意这里变量名都是小写的,而且没有下划线,没有驼峰命名
并且prepayid的内容就是prepayid的内容,不需要写成prepay_id=xxxxxx

========================
JSAPI
========================
JSAPI也就是公众号或小程序的业务流程:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4
JSAPI也就是公众号或小程序的参数说明:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6

***这些参数也就是供uni.requestPayment()在公众号或小程序里唤起支付调用的参数,注意这里的变量名跟App有所不同,个数也不同,而且package要写成prepay_id=xxxxxx的格式,真是个大坑啊!!!如果这里有一个字符不对或者大小写不对,就会支付失败,然后给出莫名其妙的错误


直接上图:

两种方式里都提到了签名,签名又是什么呢?看官方的说明,其实二者是一样的算法,只是参数个数不同,参数的名字也不同,参数的内容细微差别,两个文档里都提到:注意:签名方式一定要与统一下单接口使用的一致:

APP的签名说明:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
JSAPI的签名说明:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3

是时候回去再看:https://github.com/dcloudio/H5P.Server/tree/master/payment/wxpayv3里的代码了,在WxPay.Api.php这个文件里,有关签名的部分:

    public static function unifiedOrder($inputObj, $timeOut = 6)  
    {  
        $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";  
        //检测必填参数  
        if(!$inputObj->IsOut_trade_noSet()) {  
            throw new WxPayException("缺少统一支付接口必填参数out_trade_no!");  
        }else if(!$inputObj->IsBodySet()){  
            throw new WxPayException("缺少统一支付接口必填参数body!");  
        }else if(!$inputObj->IsTotal_feeSet()) {  
            throw new WxPayException("缺少统一支付接口必填参数total_fee!");  
        }else if(!$inputObj->IsTrade_typeSet()) {  
            throw new WxPayException("缺少统一支付接口必填参数trade_type!");  
        }  

        //关联参数  
        if($inputObj->GetTrade_type() == "JSAPI" && !$inputObj->IsOpenidSet()){  
            throw new WxPayException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!");  
        }  
        if($inputObj->GetTrade_type() == "NATIVE" && !$inputObj->IsProduct_idSet()){  
            throw new WxPayException("统一支付接口中,缺少必填参数product_id!trade_type为JSAPI时,product_id为必填参数!");  
        }  

        //异步通知url未设置,则使用配置文件中的url  
        if(!$inputObj->IsNotify_urlSet()){  
            $inputObj->SetNotify_url(WxPayConfig::NOTIFY_URL);//异步通知url  
        }  

        $inputObj->SetAppid(WxPayConfig::APPID);//公众账号ID  
        $inputObj->SetMch_id(WxPayConfig::MCHID);//商户号  
        $inputObj->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//终端ip      
        //$inputObj->SetSpbill_create_ip("1.1.1.1");          
        $inputObj->SetNonce_str(self::getNonceStr());//随机字符串  

        //签名  
        $inputObj->SetSign();  
        $xml = $inputObj->ToXml();  

        $startTimeStamp = self::getMillisecond();//请求开始时间  
        $response = self::postXmlCurl($xml, $url, false, $timeOut);  
        $result = WxPayResults::Init($response);  
        // 统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。  
        // 参与签名的字段名为appId,partnerId,prepayId,nonceStr,timeStamp,package。注意:package的值格式为Sign=WXPay  
        $time_stamp = time();  
        $pack   = 'Sign=WXPay';  

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
        ///重点在下面:这段代码是App的签名用的,小程序的要改成小程序的参数格式,根据自己的环境来选用哪一段  
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////  

        //////////////////////////////////////////////////////  
        // App输出参数列表  
        $prePayParams =array();  
        $prePayParams['appid']      =$result['appid'];  
        $prePayParams['partnerid']  =$result['mch_id'];  
        $prePayParams['prepayid']   =$result['prepay_id'];  
        $prePayParams['noncestr']   =$result['nonce_str'];  //这里要改成 WxPayApi::getNonceStr();   
        $prePayParams['package']    =$pack;  
        $prePayParams['timestamp']  =$time_stamp;  
        //////////////////////////////////////////////////////  

        //////////////////////////////////////////////////////  
        //JSAPI小程序输出参数列表  
        //输出参数列表,与App的参数有所不同,请参考最新的文档来确定参数的个数和大小写,注意!!!  
        $prePayParams =array();  
        $prePayParams['appId']      = $result['appid'];  
        $prePayParams['timeStamp']  = time();  
        $prePayParams['nonceStr']   = WxPayApi::getNonceStr();  
        $prePayParams['package']    = 'prepay_id=' . $result['prepay_id'];  
        $prePayParams['signType']   = 'MD5';  

        //echo json_encode($prePayParams);  
        $result = WxPayResults::InitFromArray($prePayParams,true)->GetValues();  
        self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间  
        //////////////////////////////////////////////////////  

        return $result;  
    }

另外notify.php也要做一些修改,这是接受微信支付成功之后的异步回传的接口,大家发挥自己的创造力吧,啰嗦了一大堆,只是记录自己踩过的坑,以及浪费的12个小时。

继续阅读 »

之前用H5+做过微信支付和支付宝支付,是一年前做的,有些细节忘记了,本以为不会费太多时间,结果搞了12个小时

记录一下踩过的坑,包括:调用支付JSAPI缺少参数: total_fee,支付验证签名失败,不识别的参数body!!!等我都碰到了

如何去微信那边申请【公众平台】【商户平台】【开放平台】本来不想讲,但是这几个平台也确实容易把人搞晕,还是大致说说我的理解吧,也算是梳理下自己的思路,方便新手,老手绕道:

最早就一个【公众号平台:mp.weixin.qq.com】,2015-2016年那会儿做微信公众号特别火,所以大家最早接触的是它,此前微信支付还不对公众开放,后来有了服务号之后,可以做公众号商城,有了支付需求,微信推出了【商户平台:pay.weixin.qq.com】,从公众号平台,有一个按钮点击就进入了商户平台,,最早的微信支付就只有JSAPI方式,也就是在微信内置浏览器里支付,后来增加了H5网页支付、Native支付、App支付、付款码支付,见下图:

我们一般开发者大多数用到的是JSAPI和App两种,微信公众号服务号、公众号商城、包括后来的小程序,都是用的JSAPI方式,用HBuilder和HBuilderX开发的跨平台App用的是App方式。

【开放平台:open.weixin.qq.com】又是什么呢?如果我们想做一个App或微信小程序要集成微信支付功能,需要注册哪些呢?需要注册【微信开放平台】,在开放平台里注册开发者账号,把自己的应用(就是我们的manifest.json里的那个appid)登记上去,然后要申请支付功能,申请支付功能就等于是开通了【商户平台】,因为钱要去这里,如果已经有了【商户平台】就可不用再申请了,见下图:

当然开放平台不单单包括微信支付,还包括其他接入服务,包括(移动应用,网站应用,公众账号,第三方平台)

题外话:微信公众号平台是要年审的,费用300,,开放平台也是要年审的,也是300。

小程序是要单独注册的,但是也是在微信公众平台的页面mp.weixin.qq.com注册,不过要另外注册账号,不能使用已有的微信公众号账号,但是申请成功后,可以挂靠/绑定在某个已有的【公众号】上,同时可以关联已有的【商户平台】。

我个人感觉,小程序是微信公众号的JSSDK可以调用的一大堆接口+Vue的框架的一种集成,过去的微信公众号(服务号)的开发基本上纯后台的,前端技术的不断发展,尤其是Vue这类前端框架,让前端可以做更多的事,包括一些复杂的界面成为可能。

以上梳理了微信几大平台的关系,啰啰嗦嗦说了一堆自己的感受,还回到文章的题目上,说说微信小程序支付的坑。


正是因为有几大平台,所以好多知识点分散在各个平台的文档里,而且不断的版本迭代,文档之间也有所差异,之前做App支付,比较顺利,这次做小程序支付,就因为之前没把所有这些知识点贯通起来,所以碰到了点问题:

整个支付流程:
App或小程序,创建自己的订单------>后台访问统一下单接口:https://api.mch.weixin.qq.com/pay/unifiedorder,生成预支付订单,同时提供回调地址--------->预支付订单返回到App或小程序,调起支付,前台支付成功,跳转到相关页面-------->微信支付平台会异步给回调地址发送支付成功的结果通知,通知地址的程序接收到微信的通知,根据返回参数修改自己的订单的支付状态

0.【微信支付商户平台】设置支付目录:

  1. 先说uni-app的支付:官方文档链接:https://uniapp.dcloud.io/api/plugins/payment,这个接口只是唤起支付的接口,在此之前要做些准备的,以上的几大平台,需要注册开通,该注册的注册,该关联的关联,技术上最关键一点是要先创建预支付订单(创建预支付订单在App和微信小程序是一样的,支付宝也类似,也要先创建预支付订单);

  2. 再说H5+也就是App的支付:官方文档链接:http://www.html5plus.org/doc/zh_cn/payment.html,这个接口只是在App端唤起支付,前提条件依然是创建预支付订单,得到返回值,然后被这个接口调用,以唤起支付。

  3. 如何创建预支付订单,我借用的是DCloud提供的示例程序:https://github.com/dcloudio/H5P.Server/tree/master/payment/wxpayv3,PHP的示例代码,这部分代码的更新时间是2年前和4年前了,但是依然能用,不过有些小地方要做点修改,因为微信那边有些参数的名字有所改变,后面会提到。

首先在WxPay.Config.php里配置自己的各项参数:

    const APPID = 'wxb1马赛克c90a';  
    const MCHID = '150马赛克021';  
    const KEY   = 'ET5g6马赛克E3dxMb7iUxL';     //这个KEY就是  
    const NOTIFY_URL = "https://www.马赛克.cn/order/WxPay/notify.php";

然后在index.php里填写自己app或小程序传来的参数,这个index.php也就是自己的app或小程序访问的入口:

    ……  
    不管是用Post还是Get方法,总之要获取从app或小程序传来的自己的参数和生成预支付订单所需要的参数  
    ……  

    /////////////////////////  
    //填写生成统一下单的各项参数  
    /////////////////////////  

    // 商品或支付单简要描述  
    $subject = 'XXX-订单号:' . $request_json['jxcbillnumber']; //自己的商城或平台的商品名或订单号  
    //之前有乱码问题,现在好像只要是utf-8就不会乱码,所以不用转成iso-8859-1了,至少php平台大家都会用utf-8来做开发,java平台对编码问题很敏感,可能要注意  
    //$subject = iconv("utf-8","iso-8859-1",$subject);    

    // 内部订单号,示例代码使用时间值作为唯一的订单ID号,如果使用自己创建的订单号,如果取消支付了,再次支付时会发生错误,所以可能要每次生成不同的内部订单号  
    //$out_trade_no = date('YmdHis', time());  

    //订单金额,以分为单位  
    $total = 1;  

    $unifiedOrder = new WxPayUnifiedOrder();  
    $unifiedOrder->SetBody($subject);               //商品或支付单简要描述  
    $unifiedOrder->SetOut_trade_no($out_trade_no);      //支付订单号  
    $unifiedOrder->SetTotal_fee($total);                //订单金额,也就支付金额,以分为单位  

    ////////////////////////////////////////////////////////////////////////////////////  
    //APP只要一个参数  
    //$unifiedOrder->SetTrade_type("APP");  

    //JSAPI需要再设置openid  
    $unifiedOrder->SetTrade_type("JSAPI");  
    $unifiedOrder->SetOpenid( $request_json['openid'] );    //openid是什么不用多解释了吧,做过微信公众号的都知道  
    ///////////////////////////////////////////////////////////////////////////////////  

    //查看生成订单前的各项数据  
    console_log_wx( 'unifiedOrder前::' . json_encode( $unifiedOrder->GetValues() ) );  

    $result1 = WxPayApi::unifiedOrder($unifiedOrder);  

    //查看生成订单后的各项数据  
    console_log_wx( 'unifiedOrder后::' . json_encode($result1) );  

    //生成的统一下单结果发回app或小程序  
    if (is_array($result1)) {  
        echo json_encode($result1);  
    }  

    //查询生成的统一下单情况,可以看到生成的订单有什么错误,但是这个错误也不准确,很不靠谱  
    $result2 = WxPayApi::orderQuery($unifiedOrder);  
    console_log_wx( 'orderQuery::'.json_encode($result2) );

生成了统一下单的参数,返回给App或小程序,App用plus.payment.request(), 小程序用uni.requestPayment(),可以唤起支付窗口,但是得到一个-1的提示,大家遇到的最多了,如果查询统一下单查询的日志:

$result2 = WxPayApi::orderQuery($unifiedOrder);  
console_log_wx( 'orderQuery::'.json_encode($result2) );

可能就会看到:调用支付JSAPI缺少参数: total_fee,支付验证签名失败,不识别的参数body!!!等奇怪错误,然后开始折腾total_fee,折腾utf-8转iso-8859-1,都没用,原因是微信那边在生成预支付订单后,不同的平台,对参数的个数和名字有所不同:

再仔细读一下这两个平台的流程和相关参数的说明:

========================
APP
========================
App的业务流程:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_3
App的参数说明:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12

没时间看文档,我们就直接贴图吧,这是供:plus.payment.request()在App唤起支付调用的参数,注意这里变量名都是小写的,而且没有下划线,没有驼峰命名
并且prepayid的内容就是prepayid的内容,不需要写成prepay_id=xxxxxx

========================
JSAPI
========================
JSAPI也就是公众号或小程序的业务流程:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4
JSAPI也就是公众号或小程序的参数说明:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6

***这些参数也就是供uni.requestPayment()在公众号或小程序里唤起支付调用的参数,注意这里的变量名跟App有所不同,个数也不同,而且package要写成prepay_id=xxxxxx的格式,真是个大坑啊!!!如果这里有一个字符不对或者大小写不对,就会支付失败,然后给出莫名其妙的错误


直接上图:

两种方式里都提到了签名,签名又是什么呢?看官方的说明,其实二者是一样的算法,只是参数个数不同,参数的名字也不同,参数的内容细微差别,两个文档里都提到:注意:签名方式一定要与统一下单接口使用的一致:

APP的签名说明:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
JSAPI的签名说明:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3

是时候回去再看:https://github.com/dcloudio/H5P.Server/tree/master/payment/wxpayv3里的代码了,在WxPay.Api.php这个文件里,有关签名的部分:

    public static function unifiedOrder($inputObj, $timeOut = 6)  
    {  
        $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";  
        //检测必填参数  
        if(!$inputObj->IsOut_trade_noSet()) {  
            throw new WxPayException("缺少统一支付接口必填参数out_trade_no!");  
        }else if(!$inputObj->IsBodySet()){  
            throw new WxPayException("缺少统一支付接口必填参数body!");  
        }else if(!$inputObj->IsTotal_feeSet()) {  
            throw new WxPayException("缺少统一支付接口必填参数total_fee!");  
        }else if(!$inputObj->IsTrade_typeSet()) {  
            throw new WxPayException("缺少统一支付接口必填参数trade_type!");  
        }  

        //关联参数  
        if($inputObj->GetTrade_type() == "JSAPI" && !$inputObj->IsOpenidSet()){  
            throw new WxPayException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!");  
        }  
        if($inputObj->GetTrade_type() == "NATIVE" && !$inputObj->IsProduct_idSet()){  
            throw new WxPayException("统一支付接口中,缺少必填参数product_id!trade_type为JSAPI时,product_id为必填参数!");  
        }  

        //异步通知url未设置,则使用配置文件中的url  
        if(!$inputObj->IsNotify_urlSet()){  
            $inputObj->SetNotify_url(WxPayConfig::NOTIFY_URL);//异步通知url  
        }  

        $inputObj->SetAppid(WxPayConfig::APPID);//公众账号ID  
        $inputObj->SetMch_id(WxPayConfig::MCHID);//商户号  
        $inputObj->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//终端ip      
        //$inputObj->SetSpbill_create_ip("1.1.1.1");          
        $inputObj->SetNonce_str(self::getNonceStr());//随机字符串  

        //签名  
        $inputObj->SetSign();  
        $xml = $inputObj->ToXml();  

        $startTimeStamp = self::getMillisecond();//请求开始时间  
        $response = self::postXmlCurl($xml, $url, false, $timeOut);  
        $result = WxPayResults::Init($response);  
        // 统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。  
        // 参与签名的字段名为appId,partnerId,prepayId,nonceStr,timeStamp,package。注意:package的值格式为Sign=WXPay  
        $time_stamp = time();  
        $pack   = 'Sign=WXPay';  

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
        ///重点在下面:这段代码是App的签名用的,小程序的要改成小程序的参数格式,根据自己的环境来选用哪一段  
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////  

        //////////////////////////////////////////////////////  
        // App输出参数列表  
        $prePayParams =array();  
        $prePayParams['appid']      =$result['appid'];  
        $prePayParams['partnerid']  =$result['mch_id'];  
        $prePayParams['prepayid']   =$result['prepay_id'];  
        $prePayParams['noncestr']   =$result['nonce_str'];  //这里要改成 WxPayApi::getNonceStr();   
        $prePayParams['package']    =$pack;  
        $prePayParams['timestamp']  =$time_stamp;  
        //////////////////////////////////////////////////////  

        //////////////////////////////////////////////////////  
        //JSAPI小程序输出参数列表  
        //输出参数列表,与App的参数有所不同,请参考最新的文档来确定参数的个数和大小写,注意!!!  
        $prePayParams =array();  
        $prePayParams['appId']      = $result['appid'];  
        $prePayParams['timeStamp']  = time();  
        $prePayParams['nonceStr']   = WxPayApi::getNonceStr();  
        $prePayParams['package']    = 'prepay_id=' . $result['prepay_id'];  
        $prePayParams['signType']   = 'MD5';  

        //echo json_encode($prePayParams);  
        $result = WxPayResults::InitFromArray($prePayParams,true)->GetValues();  
        self::reportCostTime($url, $startTimeStamp, $result);//上报请求花费时间  
        //////////////////////////////////////////////////////  

        return $result;  
    }

另外notify.php也要做一些修改,这是接受微信支付成功之后的异步回传的接口,大家发挥自己的创造力吧,啰嗦了一大堆,只是记录自己踩过的坑,以及浪费的12个小时。

收起阅读 »

HBuilderX开发uniapp实现多平台小程序异样点

使用HBuilderX开发uniapp项目发现,实行多平台编译运行小程序。

目前在微信小程序开发编译运行效果良好,在支付宝小程序运行效果与开发效果差异较大。导致使用”支付宝小程序“开发工作重构 uniapp项目

啥时候优化HBuilderX开发的uniapp项目能更好的移植到支付宝小程序中,从而真正达到”一行code,多平台运行“效果无差异!

期待!期待!期待!

继续阅读 »

使用HBuilderX开发uniapp项目发现,实行多平台编译运行小程序。

目前在微信小程序开发编译运行效果良好,在支付宝小程序运行效果与开发效果差异较大。导致使用”支付宝小程序“开发工作重构 uniapp项目

啥时候优化HBuilderX开发的uniapp项目能更好的移植到支付宝小程序中,从而真正达到”一行code,多平台运行“效果无差异!

期待!期待!期待!

收起阅读 »

关于解决在安卓(华为,荣耀)等部分机型上的显示白屏或错乱的问题

白屏

【现象】
在页面切换时,随机性出现显示白屏或者错乱。js代码能够正常运行,控制台能正常输出。

【代码写法】
使用rem作为单位,并且使用类似以下代码来实现在不同机型上的大小问题。

document.documentElement.style.fontSize = document.documentElement.clientWidth / 7.5 + 'px'

【原因】
显示白屏或错乱的原因,是因为上面这段代码计算出来的fontSize可能为0,导致元素无法正常显示。这个在其他浏览器上是从未出现过的,建议官方修复。

【解决办法】
在官方没有修复之前,可以使用以下代码替换上述代码来解决。

(function setFontSize() {  
    document.documentElement.style.fontSize = document.documentElement.clientWidth / 7.5 + 'px'  
    if(document.documentElement.clientWidth === 0) {  
      setTimeout(setFontSize, 100)  
    }  
  })()
继续阅读 »

【现象】
在页面切换时,随机性出现显示白屏或者错乱。js代码能够正常运行,控制台能正常输出。

【代码写法】
使用rem作为单位,并且使用类似以下代码来实现在不同机型上的大小问题。

document.documentElement.style.fontSize = document.documentElement.clientWidth / 7.5 + 'px'

【原因】
显示白屏或错乱的原因,是因为上面这段代码计算出来的fontSize可能为0,导致元素无法正常显示。这个在其他浏览器上是从未出现过的,建议官方修复。

【解决办法】
在官方没有修复之前,可以使用以下代码替换上述代码来解决。

(function setFontSize() {  
    document.documentElement.style.fontSize = document.documentElement.clientWidth / 7.5 + 'px'  
    if(document.documentElement.clientWidth === 0) {  
      setTimeout(setFontSize, 100)  
    }  
  })()
收起阅读 »

uniwebview传参到html5网页

uniapp

需求就是,在uniapp 下 打开一个h5网页 并且携带登录信息

具体信息可以参考:http://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.create

var webviewId = "__uniapp__service_browser";  
var style = {}  

var params = {  
    preload:{  
        data:"jalon8888",  
        timestamp: new Date().getTime(),  
    }  
}  
var url = "你的网址哟~"  
plus.webview.create(url,webviewID,style,params)

然后在HTML5网页下,使用如下方法获取

先参考文档 引入uni-webview.js ( https://uniapp.dcloud.io/component/web-view )

开始获取参数

var data = plus.webview.getWebviewById( "__uniapp__service_browser" ).preload;  
console.log( JSON.stringify( data ) )

这种不是实时的,只是利用了5+创建webview时的参数(extras) 而已 。还不知道有啥弊端不,一般来说 可以传参 比如 登录信息、设备信息 这种基本无需实时获取的参数

继续阅读 »

需求就是,在uniapp 下 打开一个h5网页 并且携带登录信息

具体信息可以参考:http://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.create

var webviewId = "__uniapp__service_browser";  
var style = {}  

var params = {  
    preload:{  
        data:"jalon8888",  
        timestamp: new Date().getTime(),  
    }  
}  
var url = "你的网址哟~"  
plus.webview.create(url,webviewID,style,params)

然后在HTML5网页下,使用如下方法获取

先参考文档 引入uni-webview.js ( https://uniapp.dcloud.io/component/web-view )

开始获取参数

var data = plus.webview.getWebviewById( "__uniapp__service_browser" ).preload;  
console.log( JSON.stringify( data ) )

这种不是实时的,只是利用了5+创建webview时的参数(extras) 而已 。还不知道有啥弊端不,一般来说 可以传参 比如 登录信息、设备信息 这种基本无需实时获取的参数

收起阅读 »

ThorUI组件库分享,含微信小程序原生版

模板 ui组件 组件 小程序 uniapp 技术分享 源码分享

ThorUI组件库分享,含微信小程序原生版

ThorUI uni版本插件市场
ThorUI 小程序版本插件市场

更新日志

V1.5.1(2020-06-06)

1.日历组件支持农历(古历)。

2.sticky吸顶组件新增wxs实现(tui-sticky-wxs)。

3.新增圆形进度条组件renderjs实现(App、H5端)与canvas 2d实现(小程序端)以及nuve版本。

4.图片裁剪组件优化,手势事件改为wxs实现。

5.级联选择器组件优化,支持设置默认选中。

6.新增图片颜色分析器组件,传入图片分析图片色彩。

7.新增中文转拼音组件,可处理多音字。

8.新增update页面(App端资源更新页面)。

9.解决H5端异步请求数据后复制文本(clipboard)报错bug。
解决方案:
①.新增参数event,调用方法时传入此参数;
②.异步请求转同步。

10.修复其它已知bug。

注:自1.4.2版本之后,ThorUI只对uni-app版本进行开源,小程序原生版付费会员才可使用(¥29.99),1.4.2版本之前不受影响。
如若需要,请联系QQ:3168647172。

部分功能截图

V1.5.0 (2020-05-12)

1.新增日历组件 。

2.新增图片裁剪组件。

3.新增头像、图片组合组件。

4.新增顶部NavationBar导航组件。

5.新增圆形进度条组件。

6.新增底部导航组件。

7.新增级联选择组件cascade-selection。

8.新增步骤条组件。

9.新增气泡框组件。

10.countdown倒计时组件优化,可只显示秒数倒计时。

11.回到顶部组件优化,新增回首页,分享按钮,可自定义控制显示。

12.日期时间选择组件优化,新增秒数选择,单位可置顶展示。

13.新增垂直分类菜单左右联动联动案例。

14.地图支持H5。

15.表单验证优化:非必填情况下,如果值为空,则不进行校验,不为空则进行校验。

16.发布H5、QQ小程序以及Android App。H5地址:https://www.thorui.cn/h5/#/ ,QQ小程序搜索ThorUI,apk前往https://www.thorui.cn/ 下载。

17.针对支付宝小程序做了部分优化调整。

18.nvue支持uni-app编译模式。

19.新增自定义tabbar使用模板,小程序参考:https://github.com/dingyong0214/AfterSale ,uni-app参考:https://github.com/dingyong0214/tabbar-uniapp。

20.引入优化uParse。

21.搜索页面新增关键词高亮显示。

22.新增navbar上拉加载下拉刷新案例。

23.v3编译支持。

24.支持easycom组件模式,直接在页面中使用组件,无需引入注册。

25.优化sticky组件,新增普通案例和异步加载案例。

26.整体优化调整,项目重构,部分模板完善。

部分功能截图

V1.4.2

1.modal组件优化,宽高以及padding值可设置

2.rate评分组件支持小数,如4星半,4.6星等

3.tabs组件优化

4.商城模板新增优惠券页面

5.商城模板新增订单详情页面

6.分包,解决小程序预览时包体积超出限制的问题

功能截图

V1.4.1

1.新增文档相关链接信息。

2.新增骨架屏组件(extend=>骨架屏)。

3.新增网络请求示例。

4.调整sticky组件,支持索引列表吸顶效果。

5.新增吸顶&索引列表,非scroll-view实现(code[首页]=>索引列表=>索引&吸顶效果)。

6.已知问题修复以及优化。

功能截图

V1.4.0

1.新增日期时间选择器组件。

2.H5新增复制文本功能。

3.新增悬浮按钮组件。

4.新增Tabbar组件。

5.新增tabs标签页组件。

6.新增折叠面板组件。

7.新增图片上传组件。

8.NumberBox组件优化调整。

9.Modal组件优化调整。

10.sticky组件优化调整。

11.countdown组件优化调整。

12.商城模板新增购物车、我的、提交订单、支付成功、我的订单、地址列表、新增地址、设置、用户信息等页面。

13.修改已知bug。

功能截图

V1.3.2

1.修复H5端发行报错的问题。

2.扩展基础组件(保留了之前版本):alert、tips、button、toast。

3.新增表单验证功能,只需配置相应验证即可。

4.新增返回顶部组件,nvue返回顶部看首页[nvue商品列表]。

5.优化部分页面,修复已知bug。

功能截图

V1.3.0

1.新增倒计时组件:时分秒倒计时,支持设置大小,颜色等。

2.新增分隔符组件:Divider分隔符,可设置占据高度,线条宽度,颜色等。

3.新增卡片轮播:包含顶部轮播,秒杀商品轮播等。

4.nvue下拉刷新优化。

5.修复已知bug。

V1.2.2

1.新增组件Modal弹框:可设置按钮数,按钮样式,提示文字样式等,还可自定义弹框内容。

2.修复部分已知bug。

V1.2.1

1.修复部分兼容性问题。

2.修复部分已知bug。

V1.2.0

1.新增组件NumberBox数字框:可设置步长,支持浮点数,支持调整样式(可单独设置)。

2.新增组件Rate评分:可设置星星数,可设置大小颜色。

3.新增聊天模板,包含:消息列表,好友列表,聊天界面等。

4.新增商城模板,包含:商城首页,商城列表,商城详情等。

5.优化部分体验。

V1.1.0

1.将基础组件移出扩展,单独出来。

2.扩展改为单独tab bar选项extend。

3.新增滚动消息(extend=>滚动消息):包括顶部通告栏,滚动新闻,以及搜索框中出现的热搜产品。

4.新增弹层下拉选择(extend=>弹层下拉选择):包含顶部下拉选择列表、输入框下拉选择以及底部分享弹层。

5.新增ActionSheet操作菜单(extend=>ActionSheet):可加入提示信息,可单独设置字体样式。

6.新增新闻模板(extend=>新闻模板):包含新闻列表,新闻详情,评论等。

7.部分功能优化,修复已知bug。

V1.0.0

1.【地图】新增拖拽定位功能

2.【扩展】新增基础组件,包括:字体图标,按钮,Grid宫格,List列表,Card卡片...

3.【扩展】新增数字键盘

4.【扩展】新增时间轴

5.完善thor(个人中心)功能,包括:关于Thor UI,模拟登录,GitHub地址复制,赞赏,反馈,更新日志等

6.已知bug修复,以及部分功能优化

商城模板部分截图

商城首页

新闻模板部分截图

聊天模板截图

组件功能部分截图

消息提示 吸顶容器 数字键盘 锁频键盘 时间轴
消息滚动 弹层,下拉选择 抽奖转盘 抽屉 滑动菜单
二维码生成 地图-拖拽定位 地图-周边地点 基础组件 索引列表-城市选择
索引列表 顶部选项卡 rate评分 数字输入框
继续阅读 »

ThorUI组件库分享,含微信小程序原生版

ThorUI uni版本插件市场
ThorUI 小程序版本插件市场

更新日志

V1.5.1(2020-06-06)

1.日历组件支持农历(古历)。

2.sticky吸顶组件新增wxs实现(tui-sticky-wxs)。

3.新增圆形进度条组件renderjs实现(App、H5端)与canvas 2d实现(小程序端)以及nuve版本。

4.图片裁剪组件优化,手势事件改为wxs实现。

5.级联选择器组件优化,支持设置默认选中。

6.新增图片颜色分析器组件,传入图片分析图片色彩。

7.新增中文转拼音组件,可处理多音字。

8.新增update页面(App端资源更新页面)。

9.解决H5端异步请求数据后复制文本(clipboard)报错bug。
解决方案:
①.新增参数event,调用方法时传入此参数;
②.异步请求转同步。

10.修复其它已知bug。

注:自1.4.2版本之后,ThorUI只对uni-app版本进行开源,小程序原生版付费会员才可使用(¥29.99),1.4.2版本之前不受影响。
如若需要,请联系QQ:3168647172。

部分功能截图

V1.5.0 (2020-05-12)

1.新增日历组件 。

2.新增图片裁剪组件。

3.新增头像、图片组合组件。

4.新增顶部NavationBar导航组件。

5.新增圆形进度条组件。

6.新增底部导航组件。

7.新增级联选择组件cascade-selection。

8.新增步骤条组件。

9.新增气泡框组件。

10.countdown倒计时组件优化,可只显示秒数倒计时。

11.回到顶部组件优化,新增回首页,分享按钮,可自定义控制显示。

12.日期时间选择组件优化,新增秒数选择,单位可置顶展示。

13.新增垂直分类菜单左右联动联动案例。

14.地图支持H5。

15.表单验证优化:非必填情况下,如果值为空,则不进行校验,不为空则进行校验。

16.发布H5、QQ小程序以及Android App。H5地址:https://www.thorui.cn/h5/#/ ,QQ小程序搜索ThorUI,apk前往https://www.thorui.cn/ 下载。

17.针对支付宝小程序做了部分优化调整。

18.nvue支持uni-app编译模式。

19.新增自定义tabbar使用模板,小程序参考:https://github.com/dingyong0214/AfterSale ,uni-app参考:https://github.com/dingyong0214/tabbar-uniapp。

20.引入优化uParse。

21.搜索页面新增关键词高亮显示。

22.新增navbar上拉加载下拉刷新案例。

23.v3编译支持。

24.支持easycom组件模式,直接在页面中使用组件,无需引入注册。

25.优化sticky组件,新增普通案例和异步加载案例。

26.整体优化调整,项目重构,部分模板完善。

部分功能截图

V1.4.2

1.modal组件优化,宽高以及padding值可设置

2.rate评分组件支持小数,如4星半,4.6星等

3.tabs组件优化

4.商城模板新增优惠券页面

5.商城模板新增订单详情页面

6.分包,解决小程序预览时包体积超出限制的问题

功能截图

V1.4.1

1.新增文档相关链接信息。

2.新增骨架屏组件(extend=>骨架屏)。

3.新增网络请求示例。

4.调整sticky组件,支持索引列表吸顶效果。

5.新增吸顶&索引列表,非scroll-view实现(code[首页]=>索引列表=>索引&吸顶效果)。

6.已知问题修复以及优化。

功能截图

V1.4.0

1.新增日期时间选择器组件。

2.H5新增复制文本功能。

3.新增悬浮按钮组件。

4.新增Tabbar组件。

5.新增tabs标签页组件。

6.新增折叠面板组件。

7.新增图片上传组件。

8.NumberBox组件优化调整。

9.Modal组件优化调整。

10.sticky组件优化调整。

11.countdown组件优化调整。

12.商城模板新增购物车、我的、提交订单、支付成功、我的订单、地址列表、新增地址、设置、用户信息等页面。

13.修改已知bug。

功能截图

V1.3.2

1.修复H5端发行报错的问题。

2.扩展基础组件(保留了之前版本):alert、tips、button、toast。

3.新增表单验证功能,只需配置相应验证即可。

4.新增返回顶部组件,nvue返回顶部看首页[nvue商品列表]。

5.优化部分页面,修复已知bug。

功能截图

V1.3.0

1.新增倒计时组件:时分秒倒计时,支持设置大小,颜色等。

2.新增分隔符组件:Divider分隔符,可设置占据高度,线条宽度,颜色等。

3.新增卡片轮播:包含顶部轮播,秒杀商品轮播等。

4.nvue下拉刷新优化。

5.修复已知bug。

V1.2.2

1.新增组件Modal弹框:可设置按钮数,按钮样式,提示文字样式等,还可自定义弹框内容。

2.修复部分已知bug。

V1.2.1

1.修复部分兼容性问题。

2.修复部分已知bug。

V1.2.0

1.新增组件NumberBox数字框:可设置步长,支持浮点数,支持调整样式(可单独设置)。

2.新增组件Rate评分:可设置星星数,可设置大小颜色。

3.新增聊天模板,包含:消息列表,好友列表,聊天界面等。

4.新增商城模板,包含:商城首页,商城列表,商城详情等。

5.优化部分体验。

V1.1.0

1.将基础组件移出扩展,单独出来。

2.扩展改为单独tab bar选项extend。

3.新增滚动消息(extend=>滚动消息):包括顶部通告栏,滚动新闻,以及搜索框中出现的热搜产品。

4.新增弹层下拉选择(extend=>弹层下拉选择):包含顶部下拉选择列表、输入框下拉选择以及底部分享弹层。

5.新增ActionSheet操作菜单(extend=>ActionSheet):可加入提示信息,可单独设置字体样式。

6.新增新闻模板(extend=>新闻模板):包含新闻列表,新闻详情,评论等。

7.部分功能优化,修复已知bug。

V1.0.0

1.【地图】新增拖拽定位功能

2.【扩展】新增基础组件,包括:字体图标,按钮,Grid宫格,List列表,Card卡片...

3.【扩展】新增数字键盘

4.【扩展】新增时间轴

5.完善thor(个人中心)功能,包括:关于Thor UI,模拟登录,GitHub地址复制,赞赏,反馈,更新日志等

6.已知bug修复,以及部分功能优化

商城模板部分截图

商城首页

新闻模板部分截图

聊天模板截图

组件功能部分截图

消息提示 吸顶容器 数字键盘 锁频键盘 时间轴
消息滚动 弹层,下拉选择 抽奖转盘 抽屉 滑动菜单
二维码生成 地图-拖拽定位 地图-周边地点 基础组件 索引列表-城市选择
索引列表 顶部选项卡 rate评分 数字输入框
收起阅读 »

showToast 提示 ios端闪退,不支持长内容!

uni_app iOS App

uni.showToast({title: res.data.err,icon: "none",position: "bottom"});

提示内容: 不能回复自己发布的问题,你可以修改问题内容

ios端会闪退
安卓端正常支持

继续阅读 »

uni.showToast({title: res.data.err,icon: "none",position: "bottom"});

提示内容: 不能回复自己发布的问题,你可以修改问题内容

ios端会闪退
安卓端正常支持

收起阅读 »

Hbuilder雅蓝主题

主题

自制Hbuilder雅蓝主题,需要的拿走

自制Hbuilder雅蓝主题,需要的拿走