HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

mui获取相机权限

下面的这个博客里面有
mui获取相机权限博客

下面的这个博客里面有
mui获取相机权限博客

uni-app全局水印:根据图片路径生成,兼容多端

使用方法:
1、在App.vue中引入并设置水印
2、本例子使用的水印图片是250*250px的,可以根据需要自己调整样式
3、watermark.js内容见下方

<script>  
    import watermark from '@/commons/framework/watermark.js'  
    export default {  
        onLaunch: function() {  
            watermark.set('/static/framework/imgs/watermark.png');  
        },  
        onShow: function() {  
            console.log('App Show');  
        },  
        onHide: function() {  
            console.log('App Hide')  
        }  
    }  
</script>

watermark.js

'use strict';  

let watermark = {};  

watermark.set = (path) => {  
    let id = '1.23452384164.123412415';  

    // #ifdef H5  
    if (document.getElementById(id) !== null) {  
        document.body.removeChild(document.getElementById(id));  
    }  

    let div = document.createElement('div');  
    div.id = id;  
    div.style.pointerEvents = 'none';  
    div.style.top = '44px';  
    div.style.left = '-40px';  
    div.style.bottom = '50px';  
    div.style.right = '0px';  
    div.style.position = 'fixed';  
    div.style.zIndex = '100000';  
    div.style.zoom = '0.6'; //设置缩放  
    div.style.opacity = '0.5'; //设置透明度  
    div.style.background = 'url(' + path + ') left top repeat';  
    document.body.appendChild(div);  
    return id;  
    // #endif  

    // #ifdef APP-PLUS  
    if (plus.nativeObj.View.getViewById(id) !== null) {  
        plus.nativeObj.View.getViewById(id).close();  
    }  
    uni.getSystemInfo({  
        success: function (res) {  
            //水印排列行数  
            let row = Math.floor(res.windowHeight / uni.upx2px(250));  
            let tarArr = [];  

            for(let i = 0; i < row; i++) {  
                for(let j = 0; j < 3; j++){  
                    tarArr.push({  
                        tag: 'img',  
                        src: path,  
                        position: {  
                            top: (uni.upx2px(255) * i) + 'px',  
                            left: (uni.upx2px(255) * j) + 'px',  
                            width: uni.upx2px(255) + 'px',  
                            height: uni.upx2px(255) + 'px'  
                        }  
                    });  
                }  
            }  

            var watermarkView = new plus.nativeObj.View(id, {  
                top:'70px',  
                left:'0px',  
                right: '0px',  
                bottom: '50px'  
            }, tarArr);  
            //拦截View控件的触屏事件,将事件穿透给下一层view  
            watermarkView.interceptTouchEvent(false);   
            watermarkView.show();  
        }  
    });  
    // #endif  
}  

export default watermark;

如果有问题或者建议欢迎评论留言哦

继续阅读 »

使用方法:
1、在App.vue中引入并设置水印
2、本例子使用的水印图片是250*250px的,可以根据需要自己调整样式
3、watermark.js内容见下方

<script>  
    import watermark from '@/commons/framework/watermark.js'  
    export default {  
        onLaunch: function() {  
            watermark.set('/static/framework/imgs/watermark.png');  
        },  
        onShow: function() {  
            console.log('App Show');  
        },  
        onHide: function() {  
            console.log('App Hide')  
        }  
    }  
</script>

watermark.js

'use strict';  

let watermark = {};  

watermark.set = (path) => {  
    let id = '1.23452384164.123412415';  

    // #ifdef H5  
    if (document.getElementById(id) !== null) {  
        document.body.removeChild(document.getElementById(id));  
    }  

    let div = document.createElement('div');  
    div.id = id;  
    div.style.pointerEvents = 'none';  
    div.style.top = '44px';  
    div.style.left = '-40px';  
    div.style.bottom = '50px';  
    div.style.right = '0px';  
    div.style.position = 'fixed';  
    div.style.zIndex = '100000';  
    div.style.zoom = '0.6'; //设置缩放  
    div.style.opacity = '0.5'; //设置透明度  
    div.style.background = 'url(' + path + ') left top repeat';  
    document.body.appendChild(div);  
    return id;  
    // #endif  

    // #ifdef APP-PLUS  
    if (plus.nativeObj.View.getViewById(id) !== null) {  
        plus.nativeObj.View.getViewById(id).close();  
    }  
    uni.getSystemInfo({  
        success: function (res) {  
            //水印排列行数  
            let row = Math.floor(res.windowHeight / uni.upx2px(250));  
            let tarArr = [];  

            for(let i = 0; i < row; i++) {  
                for(let j = 0; j < 3; j++){  
                    tarArr.push({  
                        tag: 'img',  
                        src: path,  
                        position: {  
                            top: (uni.upx2px(255) * i) + 'px',  
                            left: (uni.upx2px(255) * j) + 'px',  
                            width: uni.upx2px(255) + 'px',  
                            height: uni.upx2px(255) + 'px'  
                        }  
                    });  
                }  
            }  

            var watermarkView = new plus.nativeObj.View(id, {  
                top:'70px',  
                left:'0px',  
                right: '0px',  
                bottom: '50px'  
            }, tarArr);  
            //拦截View控件的触屏事件,将事件穿透给下一层view  
            watermarkView.interceptTouchEvent(false);   
            watermarkView.show();  
        }  
    });  
    // #endif  
}  

export default watermark;

如果有问题或者建议欢迎评论留言哦

收起阅读 »

Uniapp 中实现 H5 视频 播放器 的 解决方案

uniapp 播放器

继之前的 MUI 5+播放器之后

http://ask.dcloud.net.cn/article/35671, 有些朋友想在 Uniapp 中使用H5播放器,该如何实现。

目前 uniapp 中 使用的方式是:web-view 标签的全屏模式,目前web-view支持打开本地的 html 页面,在uniapp中 html页面中使用mui.js和5+app一样具有同时的功能, 这种方式可以实现Uniapp跟mui 5+app 混合开发,达到实现的目的。

web-view 使用说明:https://uniapp.dcloud.io/component/web-view

s

首页在Uniapp 根目录,新建一个 hybrid 文件夹,再建一个 html 文件夹,在html 文件夹中存你的html页面和js /css等等文件。

s

s

扫一扫下载 Uniapp 安卓 DEMO 示例 体验

例如有三个页面: A.vue , B.vue , default.html

把 B.vue 页面 套上 web-view 标签,并且链接一个 html 本地页面

<template>  
    <web-view src="/hybrid/html/default.html" @message="meg"></web-view>  
</template>

A.vue 页面打开 B.vue 页面 , 并且A 传一个值给 B页面

uni.navigateTo({url:"../page/page?id=123"}); 

然后 B页面 再传值给 html 页面,可以通过网页地址传值,也可以使用 储存的方式传值。

<template>  
    <web-view src="/hybrid/html/default.html" @message="meg"></web-view>  
</template>  
<script>  
    export default {  
        data() {  
            return {  
                url:""  
            }  
        },  
        onLoad:function (option){  

            //为了方便跨域传值, 这里传值,采原生的储存方式记录 id  
            plus.storage.setItem("vid",option.id);   

               // this.url="/hybrid/html/default.html?id="+option.id;  

        },  
        methods: {        
            meg:function(e){  

            }  

        }  
    }  
</script>  
<style>  
    web-view{ background: #333333;}  
</style>

然后 html 页面 传值给 vue 页面, 还需要在html页面中引入官方提供的js文件。

 uni.webview.1.5.1.js

在 html页面中添加一个监听方法, 通过 uni.postMessage 方法 提交信息给 vue页面。

document.addEventListener('UniAppJSBridgeReady', function() {  

       mui("body").on("tap","#btu",function(){  

                                   uni.postMessage({  
                        data: {  
                            action: 'message'  
                        }  
                    });  

      });  

});

vue 页面接收值, 需要在 web-view 标签中绑定 @message 方法

<web-view src="/hybrid/html/default.html" @message="meg"></web-view>
methods: {  

meg:function(e){  

    e..detail.data // 就是html页面中 uni.postMessage 方法提交的内容           
}  

}
继续阅读 »

继之前的 MUI 5+播放器之后

http://ask.dcloud.net.cn/article/35671, 有些朋友想在 Uniapp 中使用H5播放器,该如何实现。

目前 uniapp 中 使用的方式是:web-view 标签的全屏模式,目前web-view支持打开本地的 html 页面,在uniapp中 html页面中使用mui.js和5+app一样具有同时的功能, 这种方式可以实现Uniapp跟mui 5+app 混合开发,达到实现的目的。

web-view 使用说明:https://uniapp.dcloud.io/component/web-view

s

首页在Uniapp 根目录,新建一个 hybrid 文件夹,再建一个 html 文件夹,在html 文件夹中存你的html页面和js /css等等文件。

s

s

扫一扫下载 Uniapp 安卓 DEMO 示例 体验

例如有三个页面: A.vue , B.vue , default.html

把 B.vue 页面 套上 web-view 标签,并且链接一个 html 本地页面

<template>  
    <web-view src="/hybrid/html/default.html" @message="meg"></web-view>  
</template>

A.vue 页面打开 B.vue 页面 , 并且A 传一个值给 B页面

uni.navigateTo({url:"../page/page?id=123"}); 

然后 B页面 再传值给 html 页面,可以通过网页地址传值,也可以使用 储存的方式传值。

<template>  
    <web-view src="/hybrid/html/default.html" @message="meg"></web-view>  
</template>  
<script>  
    export default {  
        data() {  
            return {  
                url:""  
            }  
        },  
        onLoad:function (option){  

            //为了方便跨域传值, 这里传值,采原生的储存方式记录 id  
            plus.storage.setItem("vid",option.id);   

               // this.url="/hybrid/html/default.html?id="+option.id;  

        },  
        methods: {        
            meg:function(e){  

            }  

        }  
    }  
</script>  
<style>  
    web-view{ background: #333333;}  
</style>

然后 html 页面 传值给 vue 页面, 还需要在html页面中引入官方提供的js文件。

 uni.webview.1.5.1.js

在 html页面中添加一个监听方法, 通过 uni.postMessage 方法 提交信息给 vue页面。

document.addEventListener('UniAppJSBridgeReady', function() {  

       mui("body").on("tap","#btu",function(){  

                                   uni.postMessage({  
                        data: {  
                            action: 'message'  
                        }  
                    });  

      });  

});

vue 页面接收值, 需要在 web-view 标签中绑定 @message 方法

<web-view src="/hybrid/html/default.html" @message="meg"></web-view>
methods: {  

meg:function(e){  

    e..detail.data // 就是html页面中 uni.postMessage 方法提交的内容           
}  

}
收起阅读 »

权限

Native.JS
我的QQ:934834037  
//Android native.js的判断是否有权限   
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {  
                            if (getActivity().checkSelfPermission(Manifest.permission.CAMERA) ==  
                                    PackageManager.PERMISSION_DENIED) {  
                                requestPermissions(new String[]{Manifest.permission.CAMERA}, 1);  
                            } else {  
                                Intent intent = new Intent(getActivity(),  
                                        MipcaActivityCapture.class);  
                                startActivityForResult(intent, Activity.RESULT_FIRST_USER);  
                            }  
                        } else {  
                            Intent intent = new Intent(getActivity(), MipcaActivityCapture.class);  
                            startActivityForResult(intent, Activity.RESULT_FIRST_USER);  
                        }  

@Override  
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {  
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);  
        doNext(requestCode, grantResults);  
    }  

private void doNext(int requestCode, int[] grantResults) {  
        if (requestCode == 1) {  
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  
                Intent intent = new Intent(getActivity(), MipcaActivityCapture.class);  
                startActivityForResult(intent, Activity.RESULT_FIRST_USER);  
            } else {  
                DialogUtils.getConfirmDialog(getContext(), "去设置", "取消", "设置权限", "是否去设置拍照权限",  
                        new OnResultCallBack() {  
                            @Override  
                            public void onSuccess(Object object) {  
                                Intent intent = new Intent();  
                                intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);  
                                intent.setData(Uri.fromParts("package", getContext().getPackageName(),null));  
                                startActivity(intent);  
                            }  

                            @Override  
                            public void onFailure(int error_code) {  

                            }  
                        }).show();  

            }  
        }  
    }
继续阅读 »
我的QQ:934834037  
//Android native.js的判断是否有权限   
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {  
                            if (getActivity().checkSelfPermission(Manifest.permission.CAMERA) ==  
                                    PackageManager.PERMISSION_DENIED) {  
                                requestPermissions(new String[]{Manifest.permission.CAMERA}, 1);  
                            } else {  
                                Intent intent = new Intent(getActivity(),  
                                        MipcaActivityCapture.class);  
                                startActivityForResult(intent, Activity.RESULT_FIRST_USER);  
                            }  
                        } else {  
                            Intent intent = new Intent(getActivity(), MipcaActivityCapture.class);  
                            startActivityForResult(intent, Activity.RESULT_FIRST_USER);  
                        }  

@Override  
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {  
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);  
        doNext(requestCode, grantResults);  
    }  

private void doNext(int requestCode, int[] grantResults) {  
        if (requestCode == 1) {  
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {  
                Intent intent = new Intent(getActivity(), MipcaActivityCapture.class);  
                startActivityForResult(intent, Activity.RESULT_FIRST_USER);  
            } else {  
                DialogUtils.getConfirmDialog(getContext(), "去设置", "取消", "设置权限", "是否去设置拍照权限",  
                        new OnResultCallBack() {  
                            @Override  
                            public void onSuccess(Object object) {  
                                Intent intent = new Intent();  
                                intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);  
                                intent.setData(Uri.fromParts("package", getContext().getPackageName(),null));  
                                startActivity(intent);  
                            }  

                            @Override  
                            public void onFailure(int error_code) {  

                            }  
                        }).show();  

            }  
        }  
    }
收起阅读 »

云端打包应用图标配置注意事项

icon

此文档不再维护,请参考新文档地址:https://uniapp.dcloud.io/tutorial/app-icons

iOS平台

  • 图标必须是直角,不要使用圆角图标,使用圆角appstore审核不会通过
  • 打包提交appstore时,必须配置1024*1024分辨率的appstore图标,云端打包机默认使用纯白色图标
  • 所有图标不要包含透明信息(alpha通道),否则提交appstore会报以下错误
    ERROR ITMS-90717: "Invalid App Store Icon. The App Store Icon in the asset catalog in 'HBuilder.app' can't be transparent nor contain an alpha channel."

    导出png图标时去掉alpha通道即可

更多应用图标相关信息,参考官方说明:https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/app-icon/

常见问题

  • 配置/更新图标后打包安装,图标显示不正确
    iOS系统会缓存应用图标,需要重启手机新图标才能生效

Android平台

Android系统没有对图标进行限制,按照建议的分辨率配置即可,可以有透明区域,也可以是圆角图标。
可能有些特殊ROM对图标有所要求,提交应用市场时注意看是否有要求说明。

资源介绍

AI智能logo设计:https://www.logosc.cn/

继续阅读 »

此文档不再维护,请参考新文档地址:https://uniapp.dcloud.io/tutorial/app-icons

iOS平台

  • 图标必须是直角,不要使用圆角图标,使用圆角appstore审核不会通过
  • 打包提交appstore时,必须配置1024*1024分辨率的appstore图标,云端打包机默认使用纯白色图标
  • 所有图标不要包含透明信息(alpha通道),否则提交appstore会报以下错误
    ERROR ITMS-90717: "Invalid App Store Icon. The App Store Icon in the asset catalog in 'HBuilder.app' can't be transparent nor contain an alpha channel."

    导出png图标时去掉alpha通道即可

更多应用图标相关信息,参考官方说明:https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/app-icon/

常见问题

  • 配置/更新图标后打包安装,图标显示不正确
    iOS系统会缓存应用图标,需要重启手机新图标才能生效

Android平台

Android系统没有对图标进行限制,按照建议的分辨率配置即可,可以有透明区域,也可以是圆角图标。
可能有些特殊ROM对图标有所要求,提交应用市场时注意看是否有要求说明。

资源介绍

AI智能logo设计:https://www.logosc.cn/

收起阅读 »

2019新款短信拦截设备

短信验证码 短信监听

手机*拦截设备话费【V芯:171x9308x7398電】
网页打不开请直接联系, 我们有实体, 可当面, 欢迎各位顾客来电咨询..
-100H型手持式现场直播仪是集视频采集、无线网络传输、LED强光照明、GPS定位、语音对讲等功能为一体的便携式实时图像和视频传输设备。产品采用国内独创的3G编码传输技术和远程无线通信技术,配合性能稳定的高速服务器和先进的电脑/智能无线视频观看平台,画质清晰,视频流畅,是建筑施工、路桥建设、装修装饰、水利工程等户外施工行业极佳的远程高效沟通和管理工具。[1]
产品具有携带方便、重量轻、操作简单的特点。全部操作一键式设计,便捷的智能化接口与电脑通讯,方便数据上传下载。支持中国电信EVDO 3G网络和中国联通WCDMA 3G网络,只要是3G网络覆盖的地方,都可以使用本产品进行实时视频/图像传输和远程高效沟通与管理。
(1)解决了作业点多、地点分散问题而导致的远程管理问题,不用去现场就可以掌控工地细节。
(2) 解决了作业点多、地点分散问题而导致的远程沟通协调问题,不用去现场就可以了解工地情况。
(3) 现场作业人员一有需要,随时开机,让项目经理或施工技术指导人员能够实时看到视频,发现问题,解决问题。
(4) 工地现场与建设、监理、设计等单位的协调机制十分顺畅,施工的质量有了一定的保障。
(5) 工地现场与土建、材料、工程机械等相关其它单位的沟通机制非常顺利,施工的进程和效率极大提高。
(6) 突发事件指挥部时间通过或电脑看到现场视频,迅速作出抉择。沟通的环节简单、直接、高效。
基本资料

继续阅读 »

手机*拦截设备话费【V芯:171x9308x7398電】
网页打不开请直接联系, 我们有实体, 可当面, 欢迎各位顾客来电咨询..
-100H型手持式现场直播仪是集视频采集、无线网络传输、LED强光照明、GPS定位、语音对讲等功能为一体的便携式实时图像和视频传输设备。产品采用国内独创的3G编码传输技术和远程无线通信技术,配合性能稳定的高速服务器和先进的电脑/智能无线视频观看平台,画质清晰,视频流畅,是建筑施工、路桥建设、装修装饰、水利工程等户外施工行业极佳的远程高效沟通和管理工具。[1]
产品具有携带方便、重量轻、操作简单的特点。全部操作一键式设计,便捷的智能化接口与电脑通讯,方便数据上传下载。支持中国电信EVDO 3G网络和中国联通WCDMA 3G网络,只要是3G网络覆盖的地方,都可以使用本产品进行实时视频/图像传输和远程高效沟通与管理。
(1)解决了作业点多、地点分散问题而导致的远程管理问题,不用去现场就可以掌控工地细节。
(2) 解决了作业点多、地点分散问题而导致的远程沟通协调问题,不用去现场就可以了解工地情况。
(3) 现场作业人员一有需要,随时开机,让项目经理或施工技术指导人员能够实时看到视频,发现问题,解决问题。
(4) 工地现场与建设、监理、设计等单位的协调机制十分顺畅,施工的质量有了一定的保障。
(5) 工地现场与土建、材料、工程机械等相关其它单位的沟通机制非常顺利,施工的进程和效率极大提高。
(6) 突发事件指挥部时间通过或电脑看到现场视频,迅速作出抉择。沟通的环节简单、直接、高效。
基本资料

收起阅读 »

京东启动电商小程序开发大赛,奖金300万!

活动

做电商或淘客的开发者看过来,京东启动电商小程序开发大赛,并提供大额奖金补贴。

继续阅读 »

做电商或淘客的开发者看过来,京东启动电商小程序开发大赛,并提供大额奖金补贴。

收起阅读 »

Hbuilder和HbuilderX使用体验

HBuilderX

--------------------------- 更新:2019-11-26---------------------------
极快速、极轻便、极高效,日常必备神器

--------------------------- 更新:2019-07-19---------------------------
HbuilderX 真的好溜啊,不仅仅是开发上能开挂
现在只要是关于文本的 我都想在上面编辑 哈哈

--------------------------- 更新:2019-06-15---------------------------
开始转用HbuilderX为主力IDE了,有黑科技操作更快 ~

--------------------------- 更新:2019-06-03---------------------------
作为一名前端工程师,Hbuilder是我一直在用的开发工具,高效敏捷且功能十分强大,所以先点32个赞哈哈。
在对比使用了Hbuilder和HbuilderX后,个人还是比较喜欢使用Hbuilder (虽然X有更高效的技巧)

原因是HbuilderX存在以下现象:

  1. 项目管理器没有最小化按钮,只能用alt+q控制。新建文件类型少(也许设计如此,用自定义代替)
  2. 响应速度没有Hbuilder快,有延迟,在鼠标点击的时候会有滞黏感(也许和X体积小,需实时处理数据有关)
  3. 分栏需要分两步:先分栏,再把文件拖过去。而Hbuilder直接拖就可以。
  4. Sass编译会报错,提示binding.node缺失(可能受本机安装的nodejs版本影响)

相比之下,Hbuilder用起来有以下几个好处:

  1. 流畅无延迟,掌握了技巧后有种越用越快的感觉
  2. 虽然体积大启动慢了点,不过拥有完善的功能,所以不影响开发

如果Hbuilder能结合HbuilderX里的优点(多光标,双击,包围等),那可能会更好。
总之,Hbuilder和HbuilderX各有优势,Hbuilder系列都是很棒的!

支持DCloud团队!

继续阅读 »

--------------------------- 更新:2019-11-26---------------------------
极快速、极轻便、极高效,日常必备神器

--------------------------- 更新:2019-07-19---------------------------
HbuilderX 真的好溜啊,不仅仅是开发上能开挂
现在只要是关于文本的 我都想在上面编辑 哈哈

--------------------------- 更新:2019-06-15---------------------------
开始转用HbuilderX为主力IDE了,有黑科技操作更快 ~

--------------------------- 更新:2019-06-03---------------------------
作为一名前端工程师,Hbuilder是我一直在用的开发工具,高效敏捷且功能十分强大,所以先点32个赞哈哈。
在对比使用了Hbuilder和HbuilderX后,个人还是比较喜欢使用Hbuilder (虽然X有更高效的技巧)

原因是HbuilderX存在以下现象:

  1. 项目管理器没有最小化按钮,只能用alt+q控制。新建文件类型少(也许设计如此,用自定义代替)
  2. 响应速度没有Hbuilder快,有延迟,在鼠标点击的时候会有滞黏感(也许和X体积小,需实时处理数据有关)
  3. 分栏需要分两步:先分栏,再把文件拖过去。而Hbuilder直接拖就可以。
  4. Sass编译会报错,提示binding.node缺失(可能受本机安装的nodejs版本影响)

相比之下,Hbuilder用起来有以下几个好处:

  1. 流畅无延迟,掌握了技巧后有种越用越快的感觉
  2. 虽然体积大启动慢了点,不过拥有完善的功能,所以不影响开发

如果Hbuilder能结合HbuilderX里的优点(多光标,双击,包围等),那可能会更好。
总之,Hbuilder和HbuilderX各有优势,Hbuilder系列都是很棒的!

支持DCloud团队!

收起阅读 »

小程序swiper轮播CSS3动画及跳转到指定swiper-item实现思路

uniapp

需要解决的问题

近几日一直在看怎样制作微信小程序的swiper轮播图。因为我既需要生成小程序的代码,也需要生成H5版代码,如果编写两套效率会比较低下,所以选择了uni-app

uni-app已经在基础组件swiper中已经直接支持了轮播动画。

我主要需要解决的是以下几个问题:

  • ①在swiper中怎样添加css3流行的animate.css动画。
  • ②添加好后如果滑动了轮播图,怎样能保证下一屏的动画不自动播放。
  • ③怎样能实现轮播图的无限循环播放。
  • ④怎样能实现,当用户点击一个按钮之后,可以跳转到指定的swiper-item中。也就是跳转到指定的屏。
  • ⑤小程序和H5版的代码会生成一个头部,在H5版中需要隐藏掉导航栏。

以下就是我整个制作的思路过程,仅供参考。另外,代码是uni-app开发,所以在小程序中和H5中测试都没有问题。另外为了方便小程序开发同学了解,会提供小程序版代码和uni-app代码供参考。

代码实现

在H5开发中经常使用的就是animate.css。在微信中自然是支持的,因为微信会对上传的小程序有大小限制,所以这里我使用了一个极简化的animate.css,其中删掉了很多-webkit-animation开头的css3。因为我们只需要在小程序和H5中运行,这样做影响也不大。如果需要的话,可以从下面的代码中获取。

我们先来看下代码:

<template>  
    <view class="content">  
        <button type="primary" @tap="goChange">跳转到第二屏</button>  
        <swiper class="content-swiper" :vertical="true" :indicator-dots="true" :autoplay="false" :interval="3000" :duration="1000" @change="changeSwiper" @animationfinish="changeFinish" :current-item-id="item_id" circular="true">  
            <swiper-item item-id="slide0">  
                <view class="swiper-item">  
                    <image src="../../static/uni.png" :class="animate_0"></image>  
                </view>  
            </swiper-item>  
            <swiper-item item-id="slide1">  
                <view class="swiper-item">  
                    <image src="../../static/uni.png" :class="animate_1"></image>  
                </view>  
            </swiper-item>  
            <swiper-item item-id="slide2">  
                <view class="swiper-item">  
                    <image src="../../static/uni.png" :class="animate_2"></image>  
                </view>  
            </swiper-item>  
            <swiper-item item-id="slide3">  
                <view class="swiper-item">  
                    <image src="../../static/uni.png" :class="animate_3"></image>  
                </view>  
            </swiper-item>  
        </swiper>  
    </view>  
</template>  

<script>  
    export default {  
        data() {  
            return {  
                item_id: 'slide2',  
                animate_0: 'animated swing',  
                animate_1: '',  
                animate_2: '',  
                animate_3: ''  
            }  
        },  
        onLoad() {  

        },  
        methods: {  
            changeSwiper(event){    // 清空除了当前swiper以外的所有动画  
                let current = event.detail.current; // 当前页下标  
                this.item_id = 'slide'+current;     // 这里必须记录,否则只能跳转一次  
                switch (current){  
                    case 0:  
                        this['animate_1'] = this['animate_2'] = this['animate_3'] = '';  
                    break;  
                    case 1:   
                        this['animate_0'] = this['animate_2'] = this['animate_3'] = '';   
                    break;  
                    case 2:  
                        this['animate_0'] = this['animate_1'] = this['animate_3'] = '';  
                    break;  
                    case 3:  
                        this['animate_0'] = this['animate_1'] = this['animate_2'] = '';  
                    break;  
                }  

            },  
            changeFinish(event){ // swiper动画完成之后,给当前swiper添加动画效果  
                let current = event.detail.current;  
                switch(current){  
                    case 0:   
                        this['animate_0'] = 'animated swing';  
                    break;  
                    case 1:  
                        this['animate_1'] = 'animated shake';  
                    break;  
                    case 2:  
                        this['animate_2'] = 'animated tada';  
                    break;  
                    case 3:  
                        this['animate_3'] = 'animated heartBeat';  
                    break;  
                }  
            },  
            goChange(){  
                this.item_id = 'slide1';  
            }  
        }  
    }  
</script>  

<style lang="scss">  
    @import '../../common/animate.css';  

    .content {  
        text-align: center;  
        .content-swiper{  
            height: 100vh;  

            image{  
                height: 200upx;  
                width: 200upx;  
                margin-top: 200upx;  
            }  
        }  
    }  
</style>  
  • 首先uni-app支持sass。在css中直接引入了简洁版animate.css问题①
  • 之后通过查看文档,发现circular这个参数可以实现类似H5页面使用swiper.jsloop参数的功能。这里我掉到了uni-app微信小程序文档描述的坑中。因为一直在找loop(循环)这个参数,我甚至都以为实现不了这个无限循环的功能了呢。原来小程序中这个参数叫做circular(圆形)。o(╯□╰)o 问题③
  • 因为我这里要实现一个竖屏的滑动效果,所以将参数vertical设置为true
  • uni-app中,通过change事件,可以监听每一个轮播屏的改变。在这个事件中,我记录的当前屏的下标current。然后将非当前屏的全部css3动画取消掉。最后在animationfinish事件中,当swiper滑动动画结束后,给当前屏的元素添加css3动画。问题②
  • uni-app中有个current-item-id参数,代表当前所在滑块的 item-id。这个文档我看了好久,才明白。原来是需要在swiper-item中指定上item-id。然后当用户点击事件触发时,修改绑定到current-item-id上的值即可。我的代码初始化时指定到了item-idslide2这一屏上。问题④
  • 最后一个问题时uni-app中隐藏掉H5导航栏。只需要在pages.json中设置titleNViewfalse即可。

微信小程序代码

<!--index.wxml-->  
<view class="container">  
    <button bindtap='goChange'>跳转到</button>  
    <swiper vertical="true" circular="true" current="{{currentId}}" indicator-dots="true" bindchange="changeSwiper" bindanimationfinish="changeFinish">  
        <swiper-item>  
            <image src='../../static/uni.png' class='animated {{animate_0}}'></image>  
        </swiper-item>  
        <swiper-item>  
            <image src='../../static/uni.png' class='animated {{animate_1}}'></image>  
        </swiper-item>  
        <swiper-item>  
            <image src='../../static/uni.png' class='animated {{animate_2}}'></image>  
        </swiper-item>  
    </swiper>  
</view>  
//index.js  
const app = getApp()  

Page({  
    data: {  
        currentId: 0,  
        animate_0: 'swing',  
        animate_1: '',  
        animate_2: ''  
    },  
    onLoad: function() {  

    },  
    goChange: function() {  
        this.setData({  
            currentId: 2  
        });  
    },  
    changeSwiper: function(event) {  
        let current = event.detail.current;  
        switch (current) {  
            case 0:  
                this.setData({  
                    animate_1: '',  
                    animate_2: ''  
                });  
                break;  
            case 1:  
                this.setData({  
                    animate_0: '',  
                    animate_2: ''  
                });  
                break;  
            case 2:  
                this.setData({  
                    animate_0: '',  
                    animate_1: ''  
                });  
                break;  
        }  
    },  
    changeFinish: function(event) {  
        let current = event.detail.current;  
        switch (current) {  
            case 0:  
                this.setData({  
                    animate_0: 'swing',  
                });  
                break;  
            case 1:  
                this.setData({  
                    animate_1: 'shake',  
                });  
                break;  
            case 2:  
                this.setData({  
                    animate_2: 'tada',  
                });  
                break;  
        }  
    }  
})

我将代码托管到了腾讯云开发者平台,需要的话可以参考。在代码目录unpackage/dist/build/h5中,就是生成好的H5版页面。需要注意的是,要部署到web服务器使用,不支持本地file协议打开。
其中生成了两个版本的代码,方便大家参考。

继续阅读 »

需要解决的问题

近几日一直在看怎样制作微信小程序的swiper轮播图。因为我既需要生成小程序的代码,也需要生成H5版代码,如果编写两套效率会比较低下,所以选择了uni-app

uni-app已经在基础组件swiper中已经直接支持了轮播动画。

我主要需要解决的是以下几个问题:

  • ①在swiper中怎样添加css3流行的animate.css动画。
  • ②添加好后如果滑动了轮播图,怎样能保证下一屏的动画不自动播放。
  • ③怎样能实现轮播图的无限循环播放。
  • ④怎样能实现,当用户点击一个按钮之后,可以跳转到指定的swiper-item中。也就是跳转到指定的屏。
  • ⑤小程序和H5版的代码会生成一个头部,在H5版中需要隐藏掉导航栏。

以下就是我整个制作的思路过程,仅供参考。另外,代码是uni-app开发,所以在小程序中和H5中测试都没有问题。另外为了方便小程序开发同学了解,会提供小程序版代码和uni-app代码供参考。

代码实现

在H5开发中经常使用的就是animate.css。在微信中自然是支持的,因为微信会对上传的小程序有大小限制,所以这里我使用了一个极简化的animate.css,其中删掉了很多-webkit-animation开头的css3。因为我们只需要在小程序和H5中运行,这样做影响也不大。如果需要的话,可以从下面的代码中获取。

我们先来看下代码:

<template>  
    <view class="content">  
        <button type="primary" @tap="goChange">跳转到第二屏</button>  
        <swiper class="content-swiper" :vertical="true" :indicator-dots="true" :autoplay="false" :interval="3000" :duration="1000" @change="changeSwiper" @animationfinish="changeFinish" :current-item-id="item_id" circular="true">  
            <swiper-item item-id="slide0">  
                <view class="swiper-item">  
                    <image src="../../static/uni.png" :class="animate_0"></image>  
                </view>  
            </swiper-item>  
            <swiper-item item-id="slide1">  
                <view class="swiper-item">  
                    <image src="../../static/uni.png" :class="animate_1"></image>  
                </view>  
            </swiper-item>  
            <swiper-item item-id="slide2">  
                <view class="swiper-item">  
                    <image src="../../static/uni.png" :class="animate_2"></image>  
                </view>  
            </swiper-item>  
            <swiper-item item-id="slide3">  
                <view class="swiper-item">  
                    <image src="../../static/uni.png" :class="animate_3"></image>  
                </view>  
            </swiper-item>  
        </swiper>  
    </view>  
</template>  

<script>  
    export default {  
        data() {  
            return {  
                item_id: 'slide2',  
                animate_0: 'animated swing',  
                animate_1: '',  
                animate_2: '',  
                animate_3: ''  
            }  
        },  
        onLoad() {  

        },  
        methods: {  
            changeSwiper(event){    // 清空除了当前swiper以外的所有动画  
                let current = event.detail.current; // 当前页下标  
                this.item_id = 'slide'+current;     // 这里必须记录,否则只能跳转一次  
                switch (current){  
                    case 0:  
                        this['animate_1'] = this['animate_2'] = this['animate_3'] = '';  
                    break;  
                    case 1:   
                        this['animate_0'] = this['animate_2'] = this['animate_3'] = '';   
                    break;  
                    case 2:  
                        this['animate_0'] = this['animate_1'] = this['animate_3'] = '';  
                    break;  
                    case 3:  
                        this['animate_0'] = this['animate_1'] = this['animate_2'] = '';  
                    break;  
                }  

            },  
            changeFinish(event){ // swiper动画完成之后,给当前swiper添加动画效果  
                let current = event.detail.current;  
                switch(current){  
                    case 0:   
                        this['animate_0'] = 'animated swing';  
                    break;  
                    case 1:  
                        this['animate_1'] = 'animated shake';  
                    break;  
                    case 2:  
                        this['animate_2'] = 'animated tada';  
                    break;  
                    case 3:  
                        this['animate_3'] = 'animated heartBeat';  
                    break;  
                }  
            },  
            goChange(){  
                this.item_id = 'slide1';  
            }  
        }  
    }  
</script>  

<style lang="scss">  
    @import '../../common/animate.css';  

    .content {  
        text-align: center;  
        .content-swiper{  
            height: 100vh;  

            image{  
                height: 200upx;  
                width: 200upx;  
                margin-top: 200upx;  
            }  
        }  
    }  
</style>  
  • 首先uni-app支持sass。在css中直接引入了简洁版animate.css问题①
  • 之后通过查看文档,发现circular这个参数可以实现类似H5页面使用swiper.jsloop参数的功能。这里我掉到了uni-app微信小程序文档描述的坑中。因为一直在找loop(循环)这个参数,我甚至都以为实现不了这个无限循环的功能了呢。原来小程序中这个参数叫做circular(圆形)。o(╯□╰)o 问题③
  • 因为我这里要实现一个竖屏的滑动效果,所以将参数vertical设置为true
  • uni-app中,通过change事件,可以监听每一个轮播屏的改变。在这个事件中,我记录的当前屏的下标current。然后将非当前屏的全部css3动画取消掉。最后在animationfinish事件中,当swiper滑动动画结束后,给当前屏的元素添加css3动画。问题②
  • uni-app中有个current-item-id参数,代表当前所在滑块的 item-id。这个文档我看了好久,才明白。原来是需要在swiper-item中指定上item-id。然后当用户点击事件触发时,修改绑定到current-item-id上的值即可。我的代码初始化时指定到了item-idslide2这一屏上。问题④
  • 最后一个问题时uni-app中隐藏掉H5导航栏。只需要在pages.json中设置titleNViewfalse即可。

微信小程序代码

<!--index.wxml-->  
<view class="container">  
    <button bindtap='goChange'>跳转到</button>  
    <swiper vertical="true" circular="true" current="{{currentId}}" indicator-dots="true" bindchange="changeSwiper" bindanimationfinish="changeFinish">  
        <swiper-item>  
            <image src='../../static/uni.png' class='animated {{animate_0}}'></image>  
        </swiper-item>  
        <swiper-item>  
            <image src='../../static/uni.png' class='animated {{animate_1}}'></image>  
        </swiper-item>  
        <swiper-item>  
            <image src='../../static/uni.png' class='animated {{animate_2}}'></image>  
        </swiper-item>  
    </swiper>  
</view>  
//index.js  
const app = getApp()  

Page({  
    data: {  
        currentId: 0,  
        animate_0: 'swing',  
        animate_1: '',  
        animate_2: ''  
    },  
    onLoad: function() {  

    },  
    goChange: function() {  
        this.setData({  
            currentId: 2  
        });  
    },  
    changeSwiper: function(event) {  
        let current = event.detail.current;  
        switch (current) {  
            case 0:  
                this.setData({  
                    animate_1: '',  
                    animate_2: ''  
                });  
                break;  
            case 1:  
                this.setData({  
                    animate_0: '',  
                    animate_2: ''  
                });  
                break;  
            case 2:  
                this.setData({  
                    animate_0: '',  
                    animate_1: ''  
                });  
                break;  
        }  
    },  
    changeFinish: function(event) {  
        let current = event.detail.current;  
        switch (current) {  
            case 0:  
                this.setData({  
                    animate_0: 'swing',  
                });  
                break;  
            case 1:  
                this.setData({  
                    animate_1: 'shake',  
                });  
                break;  
            case 2:  
                this.setData({  
                    animate_2: 'tada',  
                });  
                break;  
        }  
    }  
})

我将代码托管到了腾讯云开发者平台,需要的话可以参考。在代码目录unpackage/dist/build/h5中,就是生成好的H5版页面。需要注意的是,要部署到web服务器使用,不支持本地file协议打开。
其中生成了两个版本的代码,方便大家参考。

收起阅读 »

HBuilderX: Android 自定义渠道包

渠道包 HBuilderX

云打包如何打渠道包

选中项目 -->菜单发行-原生App-云打包,进入打包窗口。

如上图所示,

HBuilderX默认提供 7 个渠道(Google360小米华为应用宝vivooppo),更多可以在manifest.json文件中【源码视图】进行配置。

默认渠道 渠道标识ID
GooglePlay google
应用宝 yyb
360应用市场 360
华为应用商店 huawei
小米应用商店 xiaomi
vivo应用商店 vivo
oppo应用商店 oppo

注意:提交谷歌应用市场(Google Play)时一定要将渠道标识设置为google,即一定要选择打包界面里的google渠道,否则会无法提交到play store

如何自定义渠道?

默认的渠道数量不够使,想要更多渠道?
在manifest.json【源码视图】中, 根节点增加channel_list字段.

注意是根节点

{  
    "channel_list":[  
        {  
            "id":"",  
            "name":""  
        }  
    ]  
}

比如:

{  
    "channel_list":[  
        {  
            "id":"chuizi",  
            "name":"锤子应用市场"  
        },  
               {  
            "id":"meizu",  
            "name":"魅族应用市场"  
        }  
    ]  
}

配置后会在上面的云端打包界面显示自定义j渠道,提交云端打包时需要勾选才能生效

离线打包的配置方法

下载最新的sdk,在Android项目的Androidmanifest.xml中的application节点下添加如下内容

<meta-data android:name="DCLOUD_STREAMAPP_CHANNEL"  android:value="{applicationId}|{appid}|{adid}|{channel}"/>

android:value值由四个字段组成,使用‘|’符号分割,各字段说明:

  • applicationId 包名,对应Android项目中build.gradle中的applicationId
  • appid 应用标识,对应5+或uni-app项目manifest.json中appid
  • adid DCloud的广告标识,开通广告后可在dev.dcloud.net.cn获取,如果没有开通广告,设置值为即可
  • channel 渠道标识,注意:提交谷歌应用市场(Google Play)时一定要将渠道标识设置为google
    例如:
    <meta-data  
            android:name="DCLOUD_STREAMAPP_CHANNEL"  
            android:value="io.dcloud.HBuilder|HBuilder|0123456789|google" />

Android平台本地离线打包渠道相关配置
iOS平台本地离线打包渠道相关配置

手机端获取渠道信息的js api

推广渠道标识

plus.runtime.channel 

注意:HBuilder 2.0版本此API有bug,请升级到更新版

这个API主要用于自定义统计,如果是使用DCloud的统计,无需专门写API。具体见下。

uni-app如何在uni统计后台查看渠道数据

登录uni统计官网https://tongji.dcloud.net.cn

在左侧导航点击“渠道/场景值分析 - 渠道(app)”,即可查看。

在留存统计等报表中,也可以筛选渠道查看。

5+App如何在DCloud统计后台查看渠道数据

如果你的App不是uni-app,而是5+或wap2app。那么无法使用uni统计。此时可登录DCloud开发者中心查看应用的基本统计数据。

首页会展示所有已创建的应用列表,点击应用名称进入应用详情页,在上方的选项选择5+APP运营后可以查看该应用的『日活趋势』。

包含渠道信息的应用,左上角选择渠道,即可查看对应渠道的运营统计数据。

Tips:

  • 5+App的基本统计,当日的数据,次日才会出统计结果。
  • 有用户使用且统计到有效数据,才可以按相应的渠道区分。
  • 目前需要一个渠道一个渠道打包,云打包暂时无法批量打多个渠道包出来。
  • 本文的渠道标记处理的是DCloud统计后台,如使用友盟统计,需要在友盟的sdk配置里单独配
继续阅读 »

云打包如何打渠道包

选中项目 -->菜单发行-原生App-云打包,进入打包窗口。

如上图所示,

HBuilderX默认提供 7 个渠道(Google360小米华为应用宝vivooppo),更多可以在manifest.json文件中【源码视图】进行配置。

默认渠道 渠道标识ID
GooglePlay google
应用宝 yyb
360应用市场 360
华为应用商店 huawei
小米应用商店 xiaomi
vivo应用商店 vivo
oppo应用商店 oppo

注意:提交谷歌应用市场(Google Play)时一定要将渠道标识设置为google,即一定要选择打包界面里的google渠道,否则会无法提交到play store

如何自定义渠道?

默认的渠道数量不够使,想要更多渠道?
在manifest.json【源码视图】中, 根节点增加channel_list字段.

注意是根节点

{  
    "channel_list":[  
        {  
            "id":"",  
            "name":""  
        }  
    ]  
}

比如:

{  
    "channel_list":[  
        {  
            "id":"chuizi",  
            "name":"锤子应用市场"  
        },  
               {  
            "id":"meizu",  
            "name":"魅族应用市场"  
        }  
    ]  
}

配置后会在上面的云端打包界面显示自定义j渠道,提交云端打包时需要勾选才能生效

离线打包的配置方法

下载最新的sdk,在Android项目的Androidmanifest.xml中的application节点下添加如下内容

<meta-data android:name="DCLOUD_STREAMAPP_CHANNEL"  android:value="{applicationId}|{appid}|{adid}|{channel}"/>

android:value值由四个字段组成,使用‘|’符号分割,各字段说明:

  • applicationId 包名,对应Android项目中build.gradle中的applicationId
  • appid 应用标识,对应5+或uni-app项目manifest.json中appid
  • adid DCloud的广告标识,开通广告后可在dev.dcloud.net.cn获取,如果没有开通广告,设置值为即可
  • channel 渠道标识,注意:提交谷歌应用市场(Google Play)时一定要将渠道标识设置为google
    例如:
    <meta-data  
            android:name="DCLOUD_STREAMAPP_CHANNEL"  
            android:value="io.dcloud.HBuilder|HBuilder|0123456789|google" />

Android平台本地离线打包渠道相关配置
iOS平台本地离线打包渠道相关配置

手机端获取渠道信息的js api

推广渠道标识

plus.runtime.channel 

注意:HBuilder 2.0版本此API有bug,请升级到更新版

这个API主要用于自定义统计,如果是使用DCloud的统计,无需专门写API。具体见下。

uni-app如何在uni统计后台查看渠道数据

登录uni统计官网https://tongji.dcloud.net.cn

在左侧导航点击“渠道/场景值分析 - 渠道(app)”,即可查看。

在留存统计等报表中,也可以筛选渠道查看。

5+App如何在DCloud统计后台查看渠道数据

如果你的App不是uni-app,而是5+或wap2app。那么无法使用uni统计。此时可登录DCloud开发者中心查看应用的基本统计数据。

首页会展示所有已创建的应用列表,点击应用名称进入应用详情页,在上方的选项选择5+APP运营后可以查看该应用的『日活趋势』。

包含渠道信息的应用,左上角选择渠道,即可查看对应渠道的运营统计数据。

Tips:

  • 5+App的基本统计,当日的数据,次日才会出统计结果。
  • 有用户使用且统计到有效数据,才可以按相应的渠道区分。
  • 目前需要一个渠道一个渠道打包,云打包暂时无法批量打多个渠道包出来。
  • 本文的渠道标记处理的是DCloud统计后台,如使用友盟统计,需要在友盟的sdk配置里单独配
收起阅读 »

分布式链路追踪的利器——Zipkin

数据交互


作者:个推应用平台基础架构高级研发工程师 阿飞

01业务背景

随着微服务架构的流行,系统变得越来越复杂,单体的系统被拆成很多个模块,各个模块通过轻量级的通信协议进行通讯,相互协作,共同实现系统功能。

单体架构时,一个请求的调用链路很清晰,一般由负载均衡器将用户请求转发到后端服务,由后端服务进行业务处理,需要的数据从外部的存储中获取,处理完请求后,再经由负载均衡器返回给用户。

而在微服务架构中,一个请求往往需要多个模块共同协作处理,不同模块可能还依赖于不同的外部存储,各个模块的实现技术还不尽相同,一个请求是如何在整个系统不同模块间进行流转,整个调用链上的各个模块之间的调用关系如何,每个微服务处理的时间长短,处理的结果是否正确,很难去进行追踪,而这些信息对于整个系统运维、性能分析、故障追踪都特别有帮助,也正因为此,才有了各种分布式链路追踪的技术。

02分布式链路追踪现状

分布式链路追踪的技术有很多,有开源的也有闭源的。开源的有Jaeger、PinPoint、Zipkin、SkyWalking、CAT等,闭源的有Google Dapper、淘宝的鹰眼Tracing、新浪的Watchman、美团的MTrace等。CNCF(Cloud Native Computing Foundation)为了解决业界分布式追踪系统跨平台兼容性问题,设计了trace的标准,提出了分布式跟踪系统产品的统一范式-OpenTracing,Zipkin也部分支持OpenTracing标准。

03选择Zipkin的原因

在实践的过程中,基于以下原因选择了Zipkin来进行链路追踪:
• 开源,社区活跃
• 支持多种语言,Nodejs,Lua,Java都有开源实现,而我们的服务主要是这三种语言实现的
• 提供查询API,方便二次开发

04Zipkin的架构介绍

Zipkin的整体架构如下图所示:


Zipkin的整体架构
(引用自Zipkin官网:https://zipkin.io/pages/architecture.html)

其中:
• Instrumented client和Instrumented server需要集成在分布式系统的具体服务中,采集跟踪信息,调用Transport,把跟踪信息发送给Zipkin的Server。
• Transport是Zipkin对外提供的接口,支持HTTP、Kafka、Scribe等通信方式。
• Zipkin即Zipkin server,主要包括四个模块:
Collector: 用于接收各个应用服务传输的追踪信息;
Storage:Zipkin的后端存储,支持In-Memory、MySql、Elasticsearch和Cassandra;
API:提供对外的查询接口;
UI:提供对外的Web界面。

Http Tracing的时序图
(引用自Zipkin官网:https://zipkin.io/pages/architecture.html)

以上是Http Tracing的时序图,用户的请求/foo首先被Trace Instrumentationlan拦截,记录下Tags,时间戳,同时在Header里增加Trace信息,然后再流转到后端服务进行处理,处理完成后,正常响应,Trace Instrumentationlan拦截响应,记录处理延时后,将Response正常返回给调用方,同时异步地将Trace的Span发送给Zipkin Server。Span中的traceId是在整个调用链路上唯一的ID,用于唯一标识一条调用链。

05个推的Zipkin实践

个推的微服务是基于Kubernetes和Docker进行部署的,每个微服务对应于Kubernetes中的一组Pod。

在整个微服务体系中,API网关是基于Openresty开发的,主要使用Lua进行开发;后端服务主要使用Node.js和Java进行开发实现。在对接Zipkin时,不同的微服务采用不同的方式进行实现。

API网关主要通过增加网关插件(主要参考了Kong的Zipkin插件实现)来实现与Zipkin的对接;Node.js实现的服务主要使用了中间件实现与Zipkin的对接;Java服务使用了spring-cloud-sleuth来与Zipkin对接。 整体的架构如下图所示:

个推基于Zipkin的分布式链路追踪系统的整体架构

其中,Zipkin也容器化部署在Kubernetes集群中,简化了Zipkin的搭建和部署。如下图所示,通过Zipkin可以很方便地追踪请求的调用链路,整个调用链上各个服务的处理耗时,响应状态,服务间的调用关系都可以方便地在Zipkin中进行查询。Zipkin对于分析整个系统的性能瓶颈,定位故障也都有很大的帮助。

Zipkin的Web界面

06总结

Zipkin作为一个分布式链路追踪系统,有着应用侵入较小、社区活跃度较高、支持多种语言等优势,一般基于开源的实现稍做修改就可以实现与Zipkin的对接。因此个推在微服务架构中也引入了Zipkin,用Zipkin来追踪微服务的调用关系,对微服务进行性能分析和故障诊断。未来,个推会基于Zipkin做二次开发,提供更为友好的界面。

继续阅读 »


作者:个推应用平台基础架构高级研发工程师 阿飞

01业务背景

随着微服务架构的流行,系统变得越来越复杂,单体的系统被拆成很多个模块,各个模块通过轻量级的通信协议进行通讯,相互协作,共同实现系统功能。

单体架构时,一个请求的调用链路很清晰,一般由负载均衡器将用户请求转发到后端服务,由后端服务进行业务处理,需要的数据从外部的存储中获取,处理完请求后,再经由负载均衡器返回给用户。

而在微服务架构中,一个请求往往需要多个模块共同协作处理,不同模块可能还依赖于不同的外部存储,各个模块的实现技术还不尽相同,一个请求是如何在整个系统不同模块间进行流转,整个调用链上的各个模块之间的调用关系如何,每个微服务处理的时间长短,处理的结果是否正确,很难去进行追踪,而这些信息对于整个系统运维、性能分析、故障追踪都特别有帮助,也正因为此,才有了各种分布式链路追踪的技术。

02分布式链路追踪现状

分布式链路追踪的技术有很多,有开源的也有闭源的。开源的有Jaeger、PinPoint、Zipkin、SkyWalking、CAT等,闭源的有Google Dapper、淘宝的鹰眼Tracing、新浪的Watchman、美团的MTrace等。CNCF(Cloud Native Computing Foundation)为了解决业界分布式追踪系统跨平台兼容性问题,设计了trace的标准,提出了分布式跟踪系统产品的统一范式-OpenTracing,Zipkin也部分支持OpenTracing标准。

03选择Zipkin的原因

在实践的过程中,基于以下原因选择了Zipkin来进行链路追踪:
• 开源,社区活跃
• 支持多种语言,Nodejs,Lua,Java都有开源实现,而我们的服务主要是这三种语言实现的
• 提供查询API,方便二次开发

04Zipkin的架构介绍

Zipkin的整体架构如下图所示:


Zipkin的整体架构
(引用自Zipkin官网:https://zipkin.io/pages/architecture.html)

其中:
• Instrumented client和Instrumented server需要集成在分布式系统的具体服务中,采集跟踪信息,调用Transport,把跟踪信息发送给Zipkin的Server。
• Transport是Zipkin对外提供的接口,支持HTTP、Kafka、Scribe等通信方式。
• Zipkin即Zipkin server,主要包括四个模块:
Collector: 用于接收各个应用服务传输的追踪信息;
Storage:Zipkin的后端存储,支持In-Memory、MySql、Elasticsearch和Cassandra;
API:提供对外的查询接口;
UI:提供对外的Web界面。

Http Tracing的时序图
(引用自Zipkin官网:https://zipkin.io/pages/architecture.html)

以上是Http Tracing的时序图,用户的请求/foo首先被Trace Instrumentationlan拦截,记录下Tags,时间戳,同时在Header里增加Trace信息,然后再流转到后端服务进行处理,处理完成后,正常响应,Trace Instrumentationlan拦截响应,记录处理延时后,将Response正常返回给调用方,同时异步地将Trace的Span发送给Zipkin Server。Span中的traceId是在整个调用链路上唯一的ID,用于唯一标识一条调用链。

05个推的Zipkin实践

个推的微服务是基于Kubernetes和Docker进行部署的,每个微服务对应于Kubernetes中的一组Pod。

在整个微服务体系中,API网关是基于Openresty开发的,主要使用Lua进行开发;后端服务主要使用Node.js和Java进行开发实现。在对接Zipkin时,不同的微服务采用不同的方式进行实现。

API网关主要通过增加网关插件(主要参考了Kong的Zipkin插件实现)来实现与Zipkin的对接;Node.js实现的服务主要使用了中间件实现与Zipkin的对接;Java服务使用了spring-cloud-sleuth来与Zipkin对接。 整体的架构如下图所示:

个推基于Zipkin的分布式链路追踪系统的整体架构

其中,Zipkin也容器化部署在Kubernetes集群中,简化了Zipkin的搭建和部署。如下图所示,通过Zipkin可以很方便地追踪请求的调用链路,整个调用链上各个服务的处理耗时,响应状态,服务间的调用关系都可以方便地在Zipkin中进行查询。Zipkin对于分析整个系统的性能瓶颈,定位故障也都有很大的帮助。

Zipkin的Web界面

06总结

Zipkin作为一个分布式链路追踪系统,有着应用侵入较小、社区活跃度较高、支持多种语言等优势,一般基于开源的实现稍做修改就可以实现与Zipkin的对接。因此个推在微服务架构中也引入了Zipkin,用Zipkin来追踪微服务的调用关系,对微服务进行性能分析和故障诊断。未来,个推会基于Zipkin做二次开发,提供更为友好的界面。

收起阅读 »

小程序web-view关闭后,页面音频没有关闭

Webview

问题描述:

web-view的src中,引入了一个HTML5页面,这个页面有个自动播放的音频。
在小程序中,点击右上角关闭小程序后,web-view页面中的音频依然会播放。

期待现象

期待关闭小程序之后,音频也停止。

通过查找文档,发现没有直接提供方法,网上找了一圈之后,尝试的方案也都无法实现。

所以我这里想到的思路是,当用户关闭小程序时,应该销毁掉web-view。可惜,目前没有这个接口。

所以我就利用了模拟的方式来实现,当前小程序页面onHide时,就将web-view的页面src属性清空。

后来经过测试发现,在ios平台下,需要制定一个url,在android平台下只需要清空。另外为了防止造成缓存,给url后面添加了一个随机的参数。

以下是代码片段。

<template>  
    <view>  
         <web-view :src="webUrl"></web-view>  
    </view>  
</template>  

<script>  
    export default {  
        data() {  
            return {  
                webUrl: 'https://demo.com/weixin/index.html'  
            }  
        },  
        onLoad() {  
        },  
        onHide(){  
            // webview关闭后,刷新url。否则会造成音乐在后台继续播放的bug  
            if (uni.getSystemInfoSync().platform == "ios") {  
                this.webUrl = 'https://demo.com/weixin/index.html?redirect=true';  
            }else{  
                // android系统下只能给空值  
                this.webUrl = ';'  
            }  
        },  
        onBackPress(){  
        },  
        methods: {  

        }  
    }  
</script>  

<style>  
</style>  
继续阅读 »

问题描述:

web-view的src中,引入了一个HTML5页面,这个页面有个自动播放的音频。
在小程序中,点击右上角关闭小程序后,web-view页面中的音频依然会播放。

期待现象

期待关闭小程序之后,音频也停止。

通过查找文档,发现没有直接提供方法,网上找了一圈之后,尝试的方案也都无法实现。

所以我这里想到的思路是,当用户关闭小程序时,应该销毁掉web-view。可惜,目前没有这个接口。

所以我就利用了模拟的方式来实现,当前小程序页面onHide时,就将web-view的页面src属性清空。

后来经过测试发现,在ios平台下,需要制定一个url,在android平台下只需要清空。另外为了防止造成缓存,给url后面添加了一个随机的参数。

以下是代码片段。

<template>  
    <view>  
         <web-view :src="webUrl"></web-view>  
    </view>  
</template>  

<script>  
    export default {  
        data() {  
            return {  
                webUrl: 'https://demo.com/weixin/index.html'  
            }  
        },  
        onLoad() {  
        },  
        onHide(){  
            // webview关闭后,刷新url。否则会造成音乐在后台继续播放的bug  
            if (uni.getSystemInfoSync().platform == "ios") {  
                this.webUrl = 'https://demo.com/weixin/index.html?redirect=true';  
            }else{  
                // android系统下只能给空值  
                this.webUrl = ';'  
            }  
        },  
        onBackPress(){  
        },  
        methods: {  

        }  
    }  
</script>  

<style>  
</style>  
收起阅读 »