HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

开发Android插件,报错 is not accessible from ...TypeModuleFactory 问题解决方案

Android uniapp原生插件 uniapp

最近升级到了Andoroid Studio 4.0,然后开发插件时遇到的一个小问题

本来一切流程照常,和以前创建Android原生插件一样新建了Module,但是跑起来的时候,调用任何原生方法都报这个错
代码、详细报错如下

java.lang.Class"\<"com.fenger.uniplugin.uniplugin_easyprotector.easyProtector> is not accessible from java.lang.Class"\<"com.taobao.weex.common.TypeModuleFactory>

2020-07-15 12:44:26.429 2905-2960/com.dapaoxiaoyingxiong.game E/weex: easy-protector module build instace failed.java.lang.IllegalAccessException: java.lang.Class<com.fenger.uniplugin.uniplugin_easyprotector.easyProtector> is not accessible from java.lang.Class<com.taobao.weex.common.TypeModuleFactory>  
        at java.lang.Class.newInstance(Native Method)  
        at com.taobao.weex.common.TypeModuleFactory.buildInstance(TypeModuleFactory.java:79)  
        at com.taobao.weex.bridge.WXModuleManager.findModule(WXModuleManager.java:284)  
        at com.taobao.weex.bridge.WXModuleManager.callModuleMethod(WXModuleManager.java:213)  
        at com.taobao.weex.bridge.WXBridgeManager.callModuleMethod(WXBridgeManager.java:518)  
        at com.taobao.weex.bridge.WXBridgeManager.callNativeModule(WXBridgeManager.java:700)  
        at com.taobao.weex.bridge.WXBridge.callNativeModule(WXBridge.java:371)  
        at com.taobao.weex.base.SystemMessageHandler.nativeRunWork(Native Method)  
        at com.taobao.weex.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:104)  
        at android.os.Handler.dispatchMessage(Handler.java:106)  
        at android.os.Looper.loop(Looper.java:173)  
        at android.os.HandlerThread.run(HandlerThread.java:65)

看提示以为是继承出了问题
但是修改为继承:com.taobao.weex.common.WXModule 还是 com.taobao.weex.WXSDKEngine.DestroyableModule 都还仍然报这个错

折腾了几个小时后,仔细对比,发现和以前自己开发的插件 只有一个差别:
以前的Module建的Class ide自动使用了public修饰
升级到了Andoroid Studio 4.0后,ide默认没有public修饰Class
遂用public 修饰 Class之后,一切调用运行正常
很隐蔽的一个小坑,记录一下

继续阅读 »

最近升级到了Andoroid Studio 4.0,然后开发插件时遇到的一个小问题

本来一切流程照常,和以前创建Android原生插件一样新建了Module,但是跑起来的时候,调用任何原生方法都报这个错
代码、详细报错如下

java.lang.Class"\<"com.fenger.uniplugin.uniplugin_easyprotector.easyProtector> is not accessible from java.lang.Class"\<"com.taobao.weex.common.TypeModuleFactory>

2020-07-15 12:44:26.429 2905-2960/com.dapaoxiaoyingxiong.game E/weex: easy-protector module build instace failed.java.lang.IllegalAccessException: java.lang.Class<com.fenger.uniplugin.uniplugin_easyprotector.easyProtector> is not accessible from java.lang.Class<com.taobao.weex.common.TypeModuleFactory>  
        at java.lang.Class.newInstance(Native Method)  
        at com.taobao.weex.common.TypeModuleFactory.buildInstance(TypeModuleFactory.java:79)  
        at com.taobao.weex.bridge.WXModuleManager.findModule(WXModuleManager.java:284)  
        at com.taobao.weex.bridge.WXModuleManager.callModuleMethod(WXModuleManager.java:213)  
        at com.taobao.weex.bridge.WXBridgeManager.callModuleMethod(WXBridgeManager.java:518)  
        at com.taobao.weex.bridge.WXBridgeManager.callNativeModule(WXBridgeManager.java:700)  
        at com.taobao.weex.bridge.WXBridge.callNativeModule(WXBridge.java:371)  
        at com.taobao.weex.base.SystemMessageHandler.nativeRunWork(Native Method)  
        at com.taobao.weex.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:104)  
        at android.os.Handler.dispatchMessage(Handler.java:106)  
        at android.os.Looper.loop(Looper.java:173)  
        at android.os.HandlerThread.run(HandlerThread.java:65)

看提示以为是继承出了问题
但是修改为继承:com.taobao.weex.common.WXModule 还是 com.taobao.weex.WXSDKEngine.DestroyableModule 都还仍然报这个错

折腾了几个小时后,仔细对比,发现和以前自己开发的插件 只有一个差别:
以前的Module建的Class ide自动使用了public修饰
升级到了Andoroid Studio 4.0后,ide默认没有public修饰Class
遂用public 修饰 Class之后,一切调用运行正常
很隐蔽的一个小坑,记录一下

收起阅读 »

云账户:个人开发者提现说明

提现 广告联盟 插件市场 云账户

现在插件市场和广告联盟面向个人开发者提供新的提现选项了——云账户(天津)。

云账户介绍

云账户综合服务平台为全国共享经济平台企业和个体经营者提供共享经济综合服务。

1、通过云账户收款完全合法,是按经营所得缴个税后的合法收入,有完税证明,这部分收入是经营所得,无需和工资薪金所得等综合所得进行汇算清缴,个人税赋降低。法律依据:小规模纳税人税收优惠政策。

2、通过云账户收款DCloud将不再代扣代缴劳务个人所得税,而是由云账户代缴1%的税费、并由云账户收取6.1%的服务费(服务费由云账户收取,DCloud 不收取任何费用)后的金额是开发者的实际收入。

如何通过云账户收款

DCloud 已开通线上启用云账户的功能,通过下面页面补充财务信息后会自动启用云账户,选择以下任意一种方式即可

智能节税方案

DCloud在处理个人开发者提现申请时,会根据账单金额自动选择税费最优的打款方式。根据目前的个人劳务所得税计算方法,当月收入低于1200元时,使用银行打款税费最低,超过1200元时使用云账户收款税费更低。

如何开通云账户

当开发者的提现申请满足云账户打款条件时,DCloud会向云账户发起付款申请,如果开发者尚未开通云账户,此时会收到云账户的服务开通短信,开发者按照短信提示开通相关服务即可。使用云账户收款时,开发者需要在云账户 APP 进行线上签约、注册。开发者可以在“应用宝”中搜索“云账户”进行下载安装,或者当使用云账户收款时,云账户会通过短信方式,将下载链接发送到开发者手机号。

使用方法:下载云账户APP,选择线上签约--线上注册--获得收入。如有相关问题,可发送邮件到market@dcloud.io进行咨询。

注意:请务必保证在开发者中心绑定的手机号是有效的手机号,并请留意该手机的短信。以免遗漏短信影响您的收款。

其他问题

Q: 云账户是什么公司的?可靠吗?
A: 云账户是天津的公司,网址:https://www.yunzhanghu.com/,是业内普通使用的兼职服务平台,比如很多直播App的主播打赏,都是通过云账户支付。

Q: 云账户开通个体工商户,影响上班的薪资个税吗?
A: 个体工商户申报的是生产经营所得,和个税没关系

继续阅读 »

现在插件市场和广告联盟面向个人开发者提供新的提现选项了——云账户(天津)。

云账户介绍

云账户综合服务平台为全国共享经济平台企业和个体经营者提供共享经济综合服务。

1、通过云账户收款完全合法,是按经营所得缴个税后的合法收入,有完税证明,这部分收入是经营所得,无需和工资薪金所得等综合所得进行汇算清缴,个人税赋降低。法律依据:小规模纳税人税收优惠政策。

2、通过云账户收款DCloud将不再代扣代缴劳务个人所得税,而是由云账户代缴1%的税费、并由云账户收取6.1%的服务费(服务费由云账户收取,DCloud 不收取任何费用)后的金额是开发者的实际收入。

如何通过云账户收款

DCloud 已开通线上启用云账户的功能,通过下面页面补充财务信息后会自动启用云账户,选择以下任意一种方式即可

智能节税方案

DCloud在处理个人开发者提现申请时,会根据账单金额自动选择税费最优的打款方式。根据目前的个人劳务所得税计算方法,当月收入低于1200元时,使用银行打款税费最低,超过1200元时使用云账户收款税费更低。

如何开通云账户

当开发者的提现申请满足云账户打款条件时,DCloud会向云账户发起付款申请,如果开发者尚未开通云账户,此时会收到云账户的服务开通短信,开发者按照短信提示开通相关服务即可。使用云账户收款时,开发者需要在云账户 APP 进行线上签约、注册。开发者可以在“应用宝”中搜索“云账户”进行下载安装,或者当使用云账户收款时,云账户会通过短信方式,将下载链接发送到开发者手机号。

使用方法:下载云账户APP,选择线上签约--线上注册--获得收入。如有相关问题,可发送邮件到market@dcloud.io进行咨询。

注意:请务必保证在开发者中心绑定的手机号是有效的手机号,并请留意该手机的短信。以免遗漏短信影响您的收款。

其他问题

Q: 云账户是什么公司的?可靠吗?
A: 云账户是天津的公司,网址:https://www.yunzhanghu.com/,是业内普通使用的兼职服务平台,比如很多直播App的主播打赏,都是通过云账户支付。

Q: 云账户开通个体工商户,影响上班的薪资个税吗?
A: 个体工商户申报的是生产经营所得,和个税没关系

收起阅读 »

动态修改app应用图标

要求:5+SDK离线打包方式。 iOS 10.3及以上。
思路:在5+SDK离线工程中新增应用图标,在工程info.plist文件新增图标内容,新增一个原生页面(ViewControllers),在原生页面中写修改应用图标代码(步骤3),在js中通过跳转原生页面来修改应用图标,跳转原生页面代码(步骤4)。
安卓的思路也大致一样,通过跳转原生页面来操作应用图标修改。

  1. 配置icons,在工程中新增一个icons应用图标文件,并添加要更改的icon。(可添加多个尺寸和多种类型的icon)

  2. 配置info.plist

  1. 通过代码触发事件来修改应用图标。

    
    - (void)changeAppIconWithName:(NSString *)iconName {  
    if (![[UIApplication sharedApplication] supportsAlternateIcons]) {  
        return;  
    }  
    
    if ([iconName isEqualToString:@""]) {  
        iconName = nil;  
    }  
    [[UIApplication sharedApplication] setAlternateIconName:iconName completionHandler:^(NSError * _Nullable error) {  
        if (error) {  
            NSLog(@"更换app图标发生错误了 : %@",error);  
        }  
    }];  
    }  
    [self changeAppIconWithName:@"rain"];  


4.  js跳转原生页面  
/*  
  //ViewControllers为要跳转的原生页面  
            var newVCobj = plus.ios.newObject("ViewControllers");  
            var UIApplicationClass = plus.ios.importClass("UIApplication");  
            var UIAppObj = UIApplicationClass.sharedApplication();  
            var del = plus.ios.invoke(UIAppObj,"delegate");  
            var appWindowObj = plus.ios.invoke(del,"window");  
            var appRootController = plus.ios.invoke(appWindowObj,"rootViewController");  

            //从底部向上弹出方式跳转。  
            plus.ios.invoke(appRootController,"presentViewController:animated:completion:",newVCobj,"YES",null);  

            //带有原生导航的跳转方式,跳转动画为从右到左滑入。  
//          plus.ios.invoke(appRootController,"pushViewController:animated:",newVCobj,"YES");  

//如需传值  
//js注册通知进行js与oc传值  defaultsName为通知名, 1234为需要传的值,  原生实现通知方法进行值接收。  
            SetUserDefault("defaultsName", "1234");  

//注册通知进行js传值到oc界面  
        function SetUserDefault(key, value)    
        {    
            if (typeof value != 'undefined' && typeof key === "string")    
            {    
                var UserDefaultsClass = plus.ios.importClass("NSUserDefaults");    
                var standardUserDefaults = UserDefaultsClass.standardUserDefaults();    
                plus.ios.invoke(standardUserDefaults, "setObject:forKey:", value, key);    
                plus.ios.invoke(standardUserDefaults,"synchronize");    
            }    
        }   

//oc页面实现通知接收传值方法  
NSUserDefaults* pDefDefaults = [NSUserDefaults standardUserDefaults];  
    if (pDefDefaults) {  
        NSString* pString =  [pDefDefaults objectForKey:@"defaultsName"];  
        NSLog(@"这里是js通知所传的值:%@",pString);  
    }  

返过来,oc要给js传值也可以通过发送通知进行传值。  

*/  

demo:下方  
继续阅读 »

要求:5+SDK离线打包方式。 iOS 10.3及以上。
思路:在5+SDK离线工程中新增应用图标,在工程info.plist文件新增图标内容,新增一个原生页面(ViewControllers),在原生页面中写修改应用图标代码(步骤3),在js中通过跳转原生页面来修改应用图标,跳转原生页面代码(步骤4)。
安卓的思路也大致一样,通过跳转原生页面来操作应用图标修改。

  1. 配置icons,在工程中新增一个icons应用图标文件,并添加要更改的icon。(可添加多个尺寸和多种类型的icon)

  2. 配置info.plist

  1. 通过代码触发事件来修改应用图标。

    
    - (void)changeAppIconWithName:(NSString *)iconName {  
    if (![[UIApplication sharedApplication] supportsAlternateIcons]) {  
        return;  
    }  
    
    if ([iconName isEqualToString:@""]) {  
        iconName = nil;  
    }  
    [[UIApplication sharedApplication] setAlternateIconName:iconName completionHandler:^(NSError * _Nullable error) {  
        if (error) {  
            NSLog(@"更换app图标发生错误了 : %@",error);  
        }  
    }];  
    }  
    [self changeAppIconWithName:@"rain"];  


4.  js跳转原生页面  
/*  
  //ViewControllers为要跳转的原生页面  
            var newVCobj = plus.ios.newObject("ViewControllers");  
            var UIApplicationClass = plus.ios.importClass("UIApplication");  
            var UIAppObj = UIApplicationClass.sharedApplication();  
            var del = plus.ios.invoke(UIAppObj,"delegate");  
            var appWindowObj = plus.ios.invoke(del,"window");  
            var appRootController = plus.ios.invoke(appWindowObj,"rootViewController");  

            //从底部向上弹出方式跳转。  
            plus.ios.invoke(appRootController,"presentViewController:animated:completion:",newVCobj,"YES",null);  

            //带有原生导航的跳转方式,跳转动画为从右到左滑入。  
//          plus.ios.invoke(appRootController,"pushViewController:animated:",newVCobj,"YES");  

//如需传值  
//js注册通知进行js与oc传值  defaultsName为通知名, 1234为需要传的值,  原生实现通知方法进行值接收。  
            SetUserDefault("defaultsName", "1234");  

//注册通知进行js传值到oc界面  
        function SetUserDefault(key, value)    
        {    
            if (typeof value != 'undefined' && typeof key === "string")    
            {    
                var UserDefaultsClass = plus.ios.importClass("NSUserDefaults");    
                var standardUserDefaults = UserDefaultsClass.standardUserDefaults();    
                plus.ios.invoke(standardUserDefaults, "setObject:forKey:", value, key);    
                plus.ios.invoke(standardUserDefaults,"synchronize");    
            }    
        }   

//oc页面实现通知接收传值方法  
NSUserDefaults* pDefDefaults = [NSUserDefaults standardUserDefaults];  
    if (pDefDefaults) {  
        NSString* pString =  [pDefDefaults objectForKey:@"defaultsName"];  
        NSLog(@"这里是js通知所传的值:%@",pString);  
    }  

返过来,oc要给js传值也可以通过发送通知进行传值。  

*/  

demo:下方  
收起阅读 »

uni-app使用心得

百度地图

我要吐槽一下。
我接触uni-app是朋友介绍的,去年合伙做了一个项目,我负责app端,朋友说这样开发太费事,就说起了Dcloud.
说实话uni-app开发起来很方便,尤其是对于我这种非科班出身,擅长哪种语言完全取决于项目。
这几天做了个小项目,单纯一个地图操作app,客户要求用百度地图的接口,我也擅长用百度地图。
最后用了map组件,开发很方便,一两天做出来很轻松,最后最后最后发现map只支持gcj02坐标系,百度的坐标是BD09,最后只能找插件转换,转换完成后偏差太大,别说客户接受不了,我这边也没法给客户解释,所以在艰难的考虑后放弃dcloud,转为原生的,想想苹果就头疼。

如果我说的哪个地方不对,希望大家批评,最好能解决我上面说的问题。谢谢

继续阅读 »

我要吐槽一下。
我接触uni-app是朋友介绍的,去年合伙做了一个项目,我负责app端,朋友说这样开发太费事,就说起了Dcloud.
说实话uni-app开发起来很方便,尤其是对于我这种非科班出身,擅长哪种语言完全取决于项目。
这几天做了个小项目,单纯一个地图操作app,客户要求用百度地图的接口,我也擅长用百度地图。
最后用了map组件,开发很方便,一两天做出来很轻松,最后最后最后发现map只支持gcj02坐标系,百度的坐标是BD09,最后只能找插件转换,转换完成后偏差太大,别说客户接受不了,我这边也没法给客户解释,所以在艰难的考虑后放弃dcloud,转为原生的,想想苹果就头疼。

如果我说的哪个地方不对,希望大家批评,最好能解决我上面说的问题。谢谢

收起阅读 »

发布微信小程序失败.

uniapp

麻烦各位大神帮忙看看这个要怎么解决

麻烦各位大神帮忙看看这个要怎么解决

接单,iOS+Android原生插件开发,价格美丽

Android iOS 插件开发

接单,iOS+Android原生插件开发,价格美丽,也有现成的插件

QQ:1037606624

接单,iOS+Android原生插件开发,价格美丽,也有现成的插件

QQ:1037606624

html2canvas截屏并保存到手机相册

截屏
<div ref="imageWrapper">  
   <p>要截屏的内容</p>  
</div>
// import html2canvas from "html2canvas"; // 当前vue文件引入html2canvas  
// components: {html2canvas}  

 // 点击保存图片  
    justDoIt() {  
      var that = this;  
      if (navigator.userAgent.indexOf("Html5Plus") >= 0) {  
        that.saveCodeBtnHide(1);  
        // 5+app环境  
        console.log("点击保存图片");  
        that.$nextTick(() => {  
          that.toImage();  
        });  
      }  
    },  
    // 网页截图  
    toImage() {  
      var that = this;  
      setTimeout(() => {  
        html2canvas(this.$refs.imageWrapper, {  
          backgroundColor: null,  
          useCORS: true, // 开启跨域配置  
          allowTaint: true, // 允许跨域图片  
          taintTest: true // 是否在渲染前测试图片  
        }).then(canvas => {  
          let dataURL = canvas.toDataURL("image/png");  
          console.log("网页截图");  
          //   console.log(dataURL);  
          that.savePicture(dataURL);  
        });  
      }, 300);  
    },  
    // 保存图片到相册中  
    savePicture(imgurl) {  
      var that = this;  
      var b = new plus.nativeObj.Bitmap("bitblmap");  
      console.log("☆☆☆☆☆☆ 保存图片到相册中");  
      console.log(b);  
      b.loadBase64Data(  
        imgurl,  
        function() {  
          console.log("图片创建成功");  
          var fileName = "_doc/img1.png";  
          b.save(  
            fileName,  
            { overwrite: true },  
            object => {  
              plus.gallery.save(  
                fileName,  
                () => {  
                  console.log("保存图片到相册成功");  
                },  
                () => {  
                  console.log("保存图片到相册失败");  
                }  
              );  
            },  
            () => {  
              console.log("保存失败");  
            }  
          );  
        },  
        function() {  
          console.log("图片创建失败");  
        }  
      );  
    }
继续阅读 »
<div ref="imageWrapper">  
   <p>要截屏的内容</p>  
</div>
// import html2canvas from "html2canvas"; // 当前vue文件引入html2canvas  
// components: {html2canvas}  

 // 点击保存图片  
    justDoIt() {  
      var that = this;  
      if (navigator.userAgent.indexOf("Html5Plus") >= 0) {  
        that.saveCodeBtnHide(1);  
        // 5+app环境  
        console.log("点击保存图片");  
        that.$nextTick(() => {  
          that.toImage();  
        });  
      }  
    },  
    // 网页截图  
    toImage() {  
      var that = this;  
      setTimeout(() => {  
        html2canvas(this.$refs.imageWrapper, {  
          backgroundColor: null,  
          useCORS: true, // 开启跨域配置  
          allowTaint: true, // 允许跨域图片  
          taintTest: true // 是否在渲染前测试图片  
        }).then(canvas => {  
          let dataURL = canvas.toDataURL("image/png");  
          console.log("网页截图");  
          //   console.log(dataURL);  
          that.savePicture(dataURL);  
        });  
      }, 300);  
    },  
    // 保存图片到相册中  
    savePicture(imgurl) {  
      var that = this;  
      var b = new plus.nativeObj.Bitmap("bitblmap");  
      console.log("☆☆☆☆☆☆ 保存图片到相册中");  
      console.log(b);  
      b.loadBase64Data(  
        imgurl,  
        function() {  
          console.log("图片创建成功");  
          var fileName = "_doc/img1.png";  
          b.save(  
            fileName,  
            { overwrite: true },  
            object => {  
              plus.gallery.save(  
                fileName,  
                () => {  
                  console.log("保存图片到相册成功");  
                },  
                () => {  
                  console.log("保存图片到相册失败");  
                }  
              );  
            },  
            () => {  
              console.log("保存失败");  
            }  
          );  
        },  
        function() {  
          console.log("图片创建失败");  
        }  
      );  
    }
收起阅读 »

android 离线打包,video组件提示未勾选videoPlayer模块

VideoPlayer App离线打包

android离线打包,路途非常曲折,首次尝试,会出现各种问题。
首先,参照官网给的离线打包文档(Android平台App本地离线打包),一定要按照官网文档上写的来做,不要自由发挥,否则都是坑。。


build.gradle中compileSdkVersion写30会白屏(Android版本10.0),换用下面的配置就好了。。

android {  
  compileSdkVersion 28  
  defaultConfig {  
      applicationId "com.example.myapplication"  
      minSdkVersion 19  
      targetSdkVersion 28  
      versionCode 1  
      versionName "1.0"  
      multiDexEnabled true  
  }

其次,如果打包带有vedio组件,要在manifest.json中勾选videoPlayer选项。并且需要使用官方的云打包才能正常播放视频。
如果要离线打包,即使manifest.json已做了video的配置,运行仍然会报模块未配置的错。官方说是需要自己在工程中配置,但具体如何配置,仍未找到相关说明。
希望官方能给出明确的离线打包,video配置相关说明,既然要做成开源的,就不要总是往付费方向引导。

继续阅读 »

android离线打包,路途非常曲折,首次尝试,会出现各种问题。
首先,参照官网给的离线打包文档(Android平台App本地离线打包),一定要按照官网文档上写的来做,不要自由发挥,否则都是坑。。


build.gradle中compileSdkVersion写30会白屏(Android版本10.0),换用下面的配置就好了。。

android {  
  compileSdkVersion 28  
  defaultConfig {  
      applicationId "com.example.myapplication"  
      minSdkVersion 19  
      targetSdkVersion 28  
      versionCode 1  
      versionName "1.0"  
      multiDexEnabled true  
  }

其次,如果打包带有vedio组件,要在manifest.json中勾选videoPlayer选项。并且需要使用官方的云打包才能正常播放视频。
如果要离线打包,即使manifest.json已做了video的配置,运行仍然会报模块未配置的错。官方说是需要自己在工程中配置,但具体如何配置,仍未找到相关说明。
希望官方能给出明确的离线打包,video配置相关说明,既然要做成开源的,就不要总是往付费方向引导。

收起阅读 »

uni小程序SDK Module调用问题

class LoginModule : WXModule() {

@JSMethod  
fun login(data: JsonObject, mCallback: JSCallback) {  
    mCallback.invoke("OK")  
}  

}

testFun() {
loginModule.login(params,
(ret) => {
console.log(ret)
this.setNativeMsg(ret)
})
}

控制台提示

console: [ERROR]TypeError: undefined is not an object (evaluating 'o.login')

继续阅读 »

class LoginModule : WXModule() {

@JSMethod  
fun login(data: JsonObject, mCallback: JSCallback) {  
    mCallback.invoke("OK")  
}  

}

testFun() {
loginModule.login(params,
(ret) => {
console.log(ret)
this.setNativeMsg(ret)
})
}

控制台提示

console: [ERROR]TypeError: undefined is not an object (evaluating 'o.login')

收起阅读 »

微服务下基于sleuth的参数透传功能探索

  1. 需求
    微服务环境,有A,B,C,D四个服务,调用关系为:A->B->C->D。用户在A的页面选择当前“语言”环境为“英文”,在某些业务场景下,其它几个服务需获取到这个“语言”信息。

  2. 分析
    这个需求还是很简单的,类似于“击鼓传花”:当前服务从上一个服务中获取参数,并传给下一个服务。个人感觉基本上所有的RPC框架都会遇到这个问题,只是以前SOA架构下,服务层级比较少,将“语言”、“登陆”等附加信息放在参数列表中并不会带来太多工作量,所以这个问题并不是太突出。而引入了微服务架构思想后,服务调用层级急剧增长,这就需要一个更加优雅的方式来解决附加信息的传递问题。

3.方案探索
3.1 方案一:参数放在接口参数列表中
3.1.1 优点:
思路简单,开发没有学习成本

3.1.2 缺点:
代码高度耦合:附加信息却要每个接口都显式维护
升级困难:如果将来再加一个参数,所有层级的接都要改动
引起迷惑:如果B服务的逻辑不需要“语言“参数,但是因为D需要,它也必须维护
太傻了,Big不够
3.1.3 思考:
微服务之间绝大多数情况是通过HTTP调用的,HTTP的header中也可以放参数信息。这样,接口参数中就不用维护这些附加信了。

3.2 方案二:参数放在httpRequest的header中
3.2.1 实现:
自定义一个Filter,获取Request中自己需要的附加信息
将这些信息放入ThreadLocal中
实现feign.Client(这里先忽略RestTemplate)的execute()方法,将附件信息在调用下一层服务前塞入request的header中
3.2.2 优点:
参数解耦

3.2.3 缺点:
如果B在获取到附加信息后,新起了一个线程”T1“来调用服务C,这时T1就无法从ThreadLocal拿到附加信息了

3.2.4 思考:
如果我知道怎么用无侵入的方式,在当前线程”T”创建子孙线程”T1”、”T1-1”时,将数据传给后代,就能解决这个问题了
微服务调用链框架Sleuth的核心功能即是跟踪一次请求从A到D的全过程,它肯定支持多线程调用下的traceId的传递。因此,我可以复用Sleuth的相关功能夹带私货
3.3 方案三:修改Sleuth源码,将附加信息跟着TraceId一起往后传递
3.3.1 优点:
原理简单,不用考虑底层实现
不用考虑兼容性等问题,Sleuth都已经实现好
快(对,就是这一个字)
3.3.2 缺点:
维护困难,很容易忘记以前修改了哪些地方,更别提移交给别人维护了
升级困难,以后每次Spring或者Sleuth升级,都要重新下载源码修改
3.3.3 思考:
目前获取参数的问题解决了,用Filter,只剩下保存并传给下一层的问题
既然Sleuth已经解决了多线程下traceId的传递问题,那我就直接用traceId来解决我的问题
3.4 方案四:充分利用traceId
3.4.1 实现:
自定义Filter(优先级要低于TraceFilter,因为你要获取TraceFilter里的traceId),拿到traceId和附加信息后,将它们存在本地缓存中,traceId为key,附加信息为value
参考方案二的实现3。重写execute()方法,获取当前线程的traceId(这个Sleuth有接口,不再介绍),然后再通过traceId去本地缓存中拿到附加信息,放进Request的header中
3.4.2 优点:
拥有上述方案所有的优点
解决上述方案所有缺点
3.4.3 缺点:http://springcloud.cn/view/41

继续阅读 »
  1. 需求
    微服务环境,有A,B,C,D四个服务,调用关系为:A->B->C->D。用户在A的页面选择当前“语言”环境为“英文”,在某些业务场景下,其它几个服务需获取到这个“语言”信息。

  2. 分析
    这个需求还是很简单的,类似于“击鼓传花”:当前服务从上一个服务中获取参数,并传给下一个服务。个人感觉基本上所有的RPC框架都会遇到这个问题,只是以前SOA架构下,服务层级比较少,将“语言”、“登陆”等附加信息放在参数列表中并不会带来太多工作量,所以这个问题并不是太突出。而引入了微服务架构思想后,服务调用层级急剧增长,这就需要一个更加优雅的方式来解决附加信息的传递问题。

3.方案探索
3.1 方案一:参数放在接口参数列表中
3.1.1 优点:
思路简单,开发没有学习成本

3.1.2 缺点:
代码高度耦合:附加信息却要每个接口都显式维护
升级困难:如果将来再加一个参数,所有层级的接都要改动
引起迷惑:如果B服务的逻辑不需要“语言“参数,但是因为D需要,它也必须维护
太傻了,Big不够
3.1.3 思考:
微服务之间绝大多数情况是通过HTTP调用的,HTTP的header中也可以放参数信息。这样,接口参数中就不用维护这些附加信了。

3.2 方案二:参数放在httpRequest的header中
3.2.1 实现:
自定义一个Filter,获取Request中自己需要的附加信息
将这些信息放入ThreadLocal中
实现feign.Client(这里先忽略RestTemplate)的execute()方法,将附件信息在调用下一层服务前塞入request的header中
3.2.2 优点:
参数解耦

3.2.3 缺点:
如果B在获取到附加信息后,新起了一个线程”T1“来调用服务C,这时T1就无法从ThreadLocal拿到附加信息了

3.2.4 思考:
如果我知道怎么用无侵入的方式,在当前线程”T”创建子孙线程”T1”、”T1-1”时,将数据传给后代,就能解决这个问题了
微服务调用链框架Sleuth的核心功能即是跟踪一次请求从A到D的全过程,它肯定支持多线程调用下的traceId的传递。因此,我可以复用Sleuth的相关功能夹带私货
3.3 方案三:修改Sleuth源码,将附加信息跟着TraceId一起往后传递
3.3.1 优点:
原理简单,不用考虑底层实现
不用考虑兼容性等问题,Sleuth都已经实现好
快(对,就是这一个字)
3.3.2 缺点:
维护困难,很容易忘记以前修改了哪些地方,更别提移交给别人维护了
升级困难,以后每次Spring或者Sleuth升级,都要重新下载源码修改
3.3.3 思考:
目前获取参数的问题解决了,用Filter,只剩下保存并传给下一层的问题
既然Sleuth已经解决了多线程下traceId的传递问题,那我就直接用traceId来解决我的问题
3.4 方案四:充分利用traceId
3.4.1 实现:
自定义Filter(优先级要低于TraceFilter,因为你要获取TraceFilter里的traceId),拿到traceId和附加信息后,将它们存在本地缓存中,traceId为key,附加信息为value
参考方案二的实现3。重写execute()方法,获取当前线程的traceId(这个Sleuth有接口,不再介绍),然后再通过traceId去本地缓存中拿到附加信息,放进Request的header中
3.4.2 优点:
拥有上述方案所有的优点
解决上述方案所有缺点
3.4.3 缺点:http://springcloud.cn/view/41

收起阅读 »

flex-direction row在v-for命令下不生效

uni-app新手,请教大神们一个问题,我想在一个view中添加 25个子view(5行5列),父view属性设置如下:
.game-area{
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
子View属性设置:
.light-block{
width: 100rpx;
height: 100rpx;
}
模板如下:

<view class="game-area" v-for="(light, index) in lights" :key = "index">  
    <view class="light-block"></view>  
</view>

lights是一个有25个对象元素的数组。

显示后25个view排成了一列,row属性不起任何作用。如果不用v-for指令,手动添加,是没有问题的。这是为什么,有什么解决方案吗,先行谢过!

继续阅读 »

uni-app新手,请教大神们一个问题,我想在一个view中添加 25个子view(5行5列),父view属性设置如下:
.game-area{
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
子View属性设置:
.light-block{
width: 100rpx;
height: 100rpx;
}
模板如下:

<view class="game-area" v-for="(light, index) in lights" :key = "index">  
    <view class="light-block"></view>  
</view>

lights是一个有25个对象元素的数组。

显示后25个view排成了一列,row属性不起任何作用。如果不用v-for指令,手动添加,是没有问题的。这是为什么,有什么解决方案吗,先行谢过!

收起阅读 »