HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

技术分享:从崩溃边缘到问题解决 —— SSL证书兼容性问题的实战经历

小程序

引言

作为一名开发者,我们经常会遇到一些令人头疼的技术难题。有时候,这些问题看似简单却异常棘手,让人几乎要放弃。今天,我想分享一次特别的经历,它始于一系列的调试失败,最终却在不经意间找到了解决方案。这个故事围绕着SSL证书的兼容性问题展开,希望能给大家带来一些启示。

问题背景

我负责开发的一款小程序需要与后端API进行安全通信。为了保证数据传输的安全性和平台要求,我们采用了SSL/TLS协议,并为服务器配置了一张免费的SSL证书。起初一切看起来都很顺利,直到我们在进行跨平台测试时遇到了问题。

初步排查

我们的测试团队在不同的设备上进行了测试,大部分情况下一切正常,但有一个特定品牌的手机总是无法成功发起HTTPS请求。这个问题一开始并不明显,因为我们主要依赖于模拟器和主流机型进行测试。然而,随着用户反馈的增多,这个问题变得越来越紧迫。

排查步骤

  1. 日志分析:首先,我们查看了客户端的日志记录,发现了一些关于证书验证失败的错误提示。
  2. 网络监控:使用抓包工具对网络请求进行了监控,发现客户端在尝试建立HTTPS连接时收到了错误响应。
  3. 设备差异对比:我们将有问题的设备与其他设备进行了对比测试,试图找出它们之间的差异。

寻找线索

尽管我们进行了初步的排查,但问题仍然没有得到解决。我们开始怀疑是客户端代码的问题,甚至重写了部分网络请求逻辑,但结果依然不尽人意。正当我们几乎要放弃的时候,一个偶然的机会改变了这一切。

关键转折点

在又一次的调试过程中,由于免费证书到期太麻烦,更换一张一年期的付费的SSL证书进行测试。起初,这只是一种无意间的举措,我们并没有抱希望。然而,就在我们更换了证书之后,在那个一直存在问题的手机上进行测试时,奇迹发生了——请求成功了!

分析原因

这次意外的成功让我们意识到,问题可能出在免费SSL证书的兼容性上。为了验证这一猜想,我们进行了更深入的研究:

  1. 证书类型对比:我们比较了免费证书和付费证书之间的差异,特别是它们的验证方式和兼容性。
  2. 浏览器和操作系统兼容性:我们查阅了相关文档,了解了不同浏览器和操作系统对于SSL证书的要求。
  3. 设备特定问题:进一步研究了那款特定品牌的手机的操作系统版本和其他特性,发现它对于某些类型的证书支持较差。

解决方案

最终,我们确定了问题的根本原因是免费SSL证书在某些设备上的兼容性不佳。为了避免类似问题再次发生,我们采取了以下几个措施:

  1. 升级证书:将免费证书升级为付费证书,确保了更好的兼容性和安全性。
  2. 全面测试:增加了更多样化的设备和环境进行测试,确保应用程序在各种条件下都能正常运行。
  3. 用户教育:通过应用内的提示,告知用户如何检查他们的设备是否支持HTTPS连接。

结语

这次经历教会了我们几个重要的教训:

  • 不要忽视小众设备:即使是小众的设备也可能成为问题的关键。
  • 证书的选择很重要:选择合适的SSL证书对于确保应用的兼容性和安全性至关重要。
  • 持续学习:技术总是在不断进步,持续学习新技术和最佳实践可以帮助我们更好地应对挑战。

希望我的这次经历能对你有所帮助,当你面对类似的问题时,不要轻易放弃,

有时候解决之道可能就在不远处。

继续阅读 »

引言

作为一名开发者,我们经常会遇到一些令人头疼的技术难题。有时候,这些问题看似简单却异常棘手,让人几乎要放弃。今天,我想分享一次特别的经历,它始于一系列的调试失败,最终却在不经意间找到了解决方案。这个故事围绕着SSL证书的兼容性问题展开,希望能给大家带来一些启示。

问题背景

我负责开发的一款小程序需要与后端API进行安全通信。为了保证数据传输的安全性和平台要求,我们采用了SSL/TLS协议,并为服务器配置了一张免费的SSL证书。起初一切看起来都很顺利,直到我们在进行跨平台测试时遇到了问题。

初步排查

我们的测试团队在不同的设备上进行了测试,大部分情况下一切正常,但有一个特定品牌的手机总是无法成功发起HTTPS请求。这个问题一开始并不明显,因为我们主要依赖于模拟器和主流机型进行测试。然而,随着用户反馈的增多,这个问题变得越来越紧迫。

排查步骤

  1. 日志分析:首先,我们查看了客户端的日志记录,发现了一些关于证书验证失败的错误提示。
  2. 网络监控:使用抓包工具对网络请求进行了监控,发现客户端在尝试建立HTTPS连接时收到了错误响应。
  3. 设备差异对比:我们将有问题的设备与其他设备进行了对比测试,试图找出它们之间的差异。

寻找线索

尽管我们进行了初步的排查,但问题仍然没有得到解决。我们开始怀疑是客户端代码的问题,甚至重写了部分网络请求逻辑,但结果依然不尽人意。正当我们几乎要放弃的时候,一个偶然的机会改变了这一切。

关键转折点

在又一次的调试过程中,由于免费证书到期太麻烦,更换一张一年期的付费的SSL证书进行测试。起初,这只是一种无意间的举措,我们并没有抱希望。然而,就在我们更换了证书之后,在那个一直存在问题的手机上进行测试时,奇迹发生了——请求成功了!

分析原因

这次意外的成功让我们意识到,问题可能出在免费SSL证书的兼容性上。为了验证这一猜想,我们进行了更深入的研究:

  1. 证书类型对比:我们比较了免费证书和付费证书之间的差异,特别是它们的验证方式和兼容性。
  2. 浏览器和操作系统兼容性:我们查阅了相关文档,了解了不同浏览器和操作系统对于SSL证书的要求。
  3. 设备特定问题:进一步研究了那款特定品牌的手机的操作系统版本和其他特性,发现它对于某些类型的证书支持较差。

解决方案

最终,我们确定了问题的根本原因是免费SSL证书在某些设备上的兼容性不佳。为了避免类似问题再次发生,我们采取了以下几个措施:

  1. 升级证书:将免费证书升级为付费证书,确保了更好的兼容性和安全性。
  2. 全面测试:增加了更多样化的设备和环境进行测试,确保应用程序在各种条件下都能正常运行。
  3. 用户教育:通过应用内的提示,告知用户如何检查他们的设备是否支持HTTPS连接。

结语

这次经历教会了我们几个重要的教训:

  • 不要忽视小众设备:即使是小众的设备也可能成为问题的关键。
  • 证书的选择很重要:选择合适的SSL证书对于确保应用的兼容性和安全性至关重要。
  • 持续学习:技术总是在不断进步,持续学习新技术和最佳实践可以帮助我们更好地应对挑战。

希望我的这次经历能对你有所帮助,当你面对类似的问题时,不要轻易放弃,

有时候解决之道可能就在不远处。

收起阅读 »

使用了推送功能怎样生成推送证书

推送

使用了推送功能后,要生成推送证书,不过这个推送证书并不是用来打包的,推送证书的作用是放在推送平台里用来发送消息用的,生成了推送证书后,依然还需要生成打包证书。

我们这篇文章来说下,使用了推送功能后,证书和证书profile文件如何修改。

假如你用了推送功能,在苹果创建应用的时候,需要勾选上支持推送,创建identifiers(appId)的时候,要注意勾选下图的选项。

因此修改了苹果开发者中心的应用的属性,所以要重新生成证书,然后参考这篇文章先创建打包证书:https://www.yunedit.com/xueyuan/jx/uniappcreatecert

appId勾选了支持push的时候,是需要绑定一个推送证书的,所以你需要创建一个推送证书。

注意这个推送证书并不是用来打包的,创建推送证书的步骤跟上面链接的教程的流程几乎一样,可以先在香蕉云编生成一个csr文件,然后再去苹果开发者中心的certificates菜单,创建一个cer证书,然后回到香蕉云编上传这个cer文件,生成一个p12。(具体图文教程参考上面发的教程链接),和教程不同的是,创建cer文件的时候,需要选择推送类型,不是其他的类型。

这样,你会生成两个p12证书文件,一个是打包用的证书,一个是推送用的证书,还有一个证书profile文件。总共三个文件。其中打包证书p12文件和证书profile文件,是用来在uniapp打包用的,推送证书p12文件是用来放在推送平台发消息的。这里比较重要,不要用推送证书来打包哦。

继续阅读 »

使用了推送功能后,要生成推送证书,不过这个推送证书并不是用来打包的,推送证书的作用是放在推送平台里用来发送消息用的,生成了推送证书后,依然还需要生成打包证书。

我们这篇文章来说下,使用了推送功能后,证书和证书profile文件如何修改。

假如你用了推送功能,在苹果创建应用的时候,需要勾选上支持推送,创建identifiers(appId)的时候,要注意勾选下图的选项。

因此修改了苹果开发者中心的应用的属性,所以要重新生成证书,然后参考这篇文章先创建打包证书:https://www.yunedit.com/xueyuan/jx/uniappcreatecert

appId勾选了支持push的时候,是需要绑定一个推送证书的,所以你需要创建一个推送证书。

注意这个推送证书并不是用来打包的,创建推送证书的步骤跟上面链接的教程的流程几乎一样,可以先在香蕉云编生成一个csr文件,然后再去苹果开发者中心的certificates菜单,创建一个cer证书,然后回到香蕉云编上传这个cer文件,生成一个p12。(具体图文教程参考上面发的教程链接),和教程不同的是,创建cer文件的时候,需要选择推送类型,不是其他的类型。

这样,你会生成两个p12证书文件,一个是打包用的证书,一个是推送用的证书,还有一个证书profile文件。总共三个文件。其中打包证书p12文件和证书profile文件,是用来在uniapp打包用的,推送证书p12文件是用来放在推送平台发消息的。这里比较重要,不要用推送证书来打包哦。

收起阅读 »

uni.getLocation偶而获取不到,使用原生代码替换

uni.getlocation

在APP中一直使用uni.getLocation的系统定位模块获取模糊位置,用以查询同城文章。偶然发现最近升级代码后,ANDROID下uni.getLocation无法正常工作,返回error:2错误。在仔细检查后,怀疑是hbuilderx升级后的BUG。写了一段android原生代码工作正常,并在运行一次此代码获取到位置后,再使用uni.getLocation即能正常。有理由怀疑是uni.getLocation使用了getLastKnownLocation导致的,查android文档,getLastKnownLocation返回最近一次位置缓存,并不保证时效及是否有值。所以在运行这段代码后,缓存了位置值,此后uni.getLocation可以正常动作。
这是一段试验代码,并未加入超时、错误和权限检查等功能,只用于临时替换及检验uni.getLocation是否正常工作。
PS:时间原故未做更多研究,不确定是否是BUG,厂家有结果请予以告知。

        __getLocation = (options) => {  
                const p = new Promise((resolve, reject) => {  
                    const isNull = value => !value && reject({  
                        errMsg: "_getLocation fail."  
                    });  
                    isNull(options);  
                    isNull(options.success);  
                    isNull(typeof options.success === "function");  
                    const mainActivity = plus.android.runtimeMainActivity();  
                    isNull(mainActivity);  
                    const LOCATION_SERVICE = plus.android.getAttribute(mainActivity, "LOCATION_SERVICE");  
                    const locationManager = plus.android.invoke(mainActivity, "getSystemService",  
                        LOCATION_SERVICE);  
                    plus.android.autoCollection(mainActivity);  
                    isNull(locationManager);  
                    const criteria = plus.android.newObject("android.location.Criteria");  
                    isNull(criteria);  
                    const ACCURACY_COARSE = criteria.plusGetAttribute("ACCURACY_COARSE");  
                    plus.android.invoke(criteria, "setAccuracy", ACCURACY_COARSE);  
                    plus.android.invoke(criteria, "setAltitudeRequired", false);  
                    plus.android.invoke(criteria, "setBearingRequired", false);  
                    plus.android.invoke(criteria, "setCostAllowed", false);  
                    const POWER_LOW = criteria.plusGetAttribute("POWER_LOW");  
                    plus.android.invoke(criteria, "setPowerRequirement", POWER_LOW);  
                    const provider = plus.android.invoke(locationManager, "getBestProvider", criteria, true);  
                    plus.android.autoCollection(criteria);  
                    isNull(provider);  
                    const location = plus.android.invoke(locationManager, "getLastKnownLocation", provider);  
                    ////怀疑是此处无值导致原函数错误  
                    if (location) {  
                        const longitude = plus.android.invoke(location, "getLongitude");  
                        const latitude = plus.android.invoke(location, "getLatitude");  
                        plus.android.autoCollection(location);  
                        resolve({  
                            longitude,  
                            latitude  
                        });  
                    } else {  
                    ////location无值则进行监听至有值返回  
                        let instLocationListener = null;  
                        const locationListener = {  
                            "onLocationChanged": function(location) {  
                                if (location) {  
                                    const longitude = plus.android.invoke(location, "getLongitude");  
                                    const latitude = plus.android.invoke(location, "getLatitude");  
                                    plus.android.invoke(locationManager, "removeUpdates",  
                                        instLocationListener);  
                                    plus.android.autoCollection(instLocationListener);  
                                    plus.android.autoCollection(location);  
                                    resolve({  
                                        longitude,  
                                        latitude  
                                    });  
                                }  
                            },  
                            "onStatusChanged": function(provider, status, extras) {  
                                console.log(provider, status);  
                            },  
                            "onProviderEnabled": function(provider) {  
                                console.log(provider + " 启用了");  
                            },  
                            "onProviderDisabled": function(provider) {  
                                console.log(provider + " 关闭了");  
                            }  
                        }  
                        instLocationListener = plus.android.implements("android.location.LocationListener",  
                            locationListener);  
                        isNull(instLocationListener);  
                        plus.android.invoke(locationManager, "requestLocationUpdates", provider,  
                            3000, 10,  
                            instLocationListener);  
                    }  
                });  
                p.then(res => options.success(res)).catch(err => options.fail && options.fail(err));  
            };  
    __getLocation({  
        success: res => console.log(res)  
    });
继续阅读 »

在APP中一直使用uni.getLocation的系统定位模块获取模糊位置,用以查询同城文章。偶然发现最近升级代码后,ANDROID下uni.getLocation无法正常工作,返回error:2错误。在仔细检查后,怀疑是hbuilderx升级后的BUG。写了一段android原生代码工作正常,并在运行一次此代码获取到位置后,再使用uni.getLocation即能正常。有理由怀疑是uni.getLocation使用了getLastKnownLocation导致的,查android文档,getLastKnownLocation返回最近一次位置缓存,并不保证时效及是否有值。所以在运行这段代码后,缓存了位置值,此后uni.getLocation可以正常动作。
这是一段试验代码,并未加入超时、错误和权限检查等功能,只用于临时替换及检验uni.getLocation是否正常工作。
PS:时间原故未做更多研究,不确定是否是BUG,厂家有结果请予以告知。

        __getLocation = (options) => {  
                const p = new Promise((resolve, reject) => {  
                    const isNull = value => !value && reject({  
                        errMsg: "_getLocation fail."  
                    });  
                    isNull(options);  
                    isNull(options.success);  
                    isNull(typeof options.success === "function");  
                    const mainActivity = plus.android.runtimeMainActivity();  
                    isNull(mainActivity);  
                    const LOCATION_SERVICE = plus.android.getAttribute(mainActivity, "LOCATION_SERVICE");  
                    const locationManager = plus.android.invoke(mainActivity, "getSystemService",  
                        LOCATION_SERVICE);  
                    plus.android.autoCollection(mainActivity);  
                    isNull(locationManager);  
                    const criteria = plus.android.newObject("android.location.Criteria");  
                    isNull(criteria);  
                    const ACCURACY_COARSE = criteria.plusGetAttribute("ACCURACY_COARSE");  
                    plus.android.invoke(criteria, "setAccuracy", ACCURACY_COARSE);  
                    plus.android.invoke(criteria, "setAltitudeRequired", false);  
                    plus.android.invoke(criteria, "setBearingRequired", false);  
                    plus.android.invoke(criteria, "setCostAllowed", false);  
                    const POWER_LOW = criteria.plusGetAttribute("POWER_LOW");  
                    plus.android.invoke(criteria, "setPowerRequirement", POWER_LOW);  
                    const provider = plus.android.invoke(locationManager, "getBestProvider", criteria, true);  
                    plus.android.autoCollection(criteria);  
                    isNull(provider);  
                    const location = plus.android.invoke(locationManager, "getLastKnownLocation", provider);  
                    ////怀疑是此处无值导致原函数错误  
                    if (location) {  
                        const longitude = plus.android.invoke(location, "getLongitude");  
                        const latitude = plus.android.invoke(location, "getLatitude");  
                        plus.android.autoCollection(location);  
                        resolve({  
                            longitude,  
                            latitude  
                        });  
                    } else {  
                    ////location无值则进行监听至有值返回  
                        let instLocationListener = null;  
                        const locationListener = {  
                            "onLocationChanged": function(location) {  
                                if (location) {  
                                    const longitude = plus.android.invoke(location, "getLongitude");  
                                    const latitude = plus.android.invoke(location, "getLatitude");  
                                    plus.android.invoke(locationManager, "removeUpdates",  
                                        instLocationListener);  
                                    plus.android.autoCollection(instLocationListener);  
                                    plus.android.autoCollection(location);  
                                    resolve({  
                                        longitude,  
                                        latitude  
                                    });  
                                }  
                            },  
                            "onStatusChanged": function(provider, status, extras) {  
                                console.log(provider, status);  
                            },  
                            "onProviderEnabled": function(provider) {  
                                console.log(provider + " 启用了");  
                            },  
                            "onProviderDisabled": function(provider) {  
                                console.log(provider + " 关闭了");  
                            }  
                        }  
                        instLocationListener = plus.android.implements("android.location.LocationListener",  
                            locationListener);  
                        isNull(instLocationListener);  
                        plus.android.invoke(locationManager, "requestLocationUpdates", provider,  
                            3000, 10,  
                            instLocationListener);  
                    }  
                });  
                p.then(res => options.success(res)).catch(err => options.fail && options.fail(err));  
            };  
    __getLocation({  
        success: res => console.log(res)  
    });
收起阅读 »

找一个懂安卓原生+uniapp插件包兼职开发

需要做一个安卓原生插件包,供uniapp原生代码调用;

主要需求: 自定义扫条码样式功能、自定义扫二维码样式功能、个别特定原生实现需求提供API供uniapp调用。

需要做一个安卓原生插件包,供uniapp原生代码调用;

主要需求: 自定义扫条码样式功能、自定义扫二维码样式功能、个别特定原生实现需求提供API供uniapp调用。

如何在GitHub免费搭建个人博客网站?

  如何在GitHub免费搭建个人博客网站?当你想要开始自己的博客之旅,但又不想花费金钱购买服务器和域名时,还有一些免费的平台可供你选择。以下是一种无需服务器和域名的方法,利用GitHub Pages和Jekyll搭建个人博客网站(http://m.bokequ.com/list/22-0.html)

  步骤一:准备 GitHub 账户

  如果你还没有 GitHub 账户,首先需要注册一个。GitHub 提供免费的代码托管服务,同时也支持通过 GitHub Pages 托管静态网站。

  步骤二:创建 GitHub 仓库

  登录你的 GitHub 账户,点击右上角的加号按钮,选择 "New repository"。在 "Repository name" 栏中输入你的用户名(或者你希望的博客地址),比如 yourusername.github.io(注意替换成你的用户名)。勾选 "Initialize this repository with a README" 选项,并点击 "Create repository"。

  步骤三:下载 Jekyll 主题

  Jekyll 是一个简单易用的静态网站生成器,GitHub Pages 支持使用 Jekyll 搭建个人网站。你可以在 Jekyll 官方网站(https://jekyllrb.com/)或 GitHub 上找到各种免费的 Jekyll 主题。选择一个你喜欢的主题,将其下载并解压缩到本地。

  步骤四:上传文件到 GitHub 仓库

  将 Jekyll 主题文件夹中的所有文件上传到你在步骤二中创建的 GitHub 仓库中。你可以使用 GitHub Desktop、Git 命令行或者直接通过 GitHub 网站上传文件。

  步骤五:访问你的博客网站

  等待一段时间,GitHub 会自动构建你的网站,并将其托管在bokequ.github.io这个地址上。你可以在浏览器中输入这个地址,访问你的个人博客网站。

  步骤六:定制你的博客

  编辑 Jekyll 主题文件夹中的配置文件和内容文件,定制你的个人博客。你可以修改页面布局、添加新的页面和文章,以及调整样式和颜色。

  通过 GitHub Pages 和 Jekyll,你可以免费搭建个人博客网站,无需购买服务器和域名。这是一个简单且经济高效的方式,让你能够开始你的博客之旅,并与世界分享你的想法和创作。

继续阅读 »

  如何在GitHub免费搭建个人博客网站?当你想要开始自己的博客之旅,但又不想花费金钱购买服务器和域名时,还有一些免费的平台可供你选择。以下是一种无需服务器和域名的方法,利用GitHub Pages和Jekyll搭建个人博客网站(http://m.bokequ.com/list/22-0.html)

  步骤一:准备 GitHub 账户

  如果你还没有 GitHub 账户,首先需要注册一个。GitHub 提供免费的代码托管服务,同时也支持通过 GitHub Pages 托管静态网站。

  步骤二:创建 GitHub 仓库

  登录你的 GitHub 账户,点击右上角的加号按钮,选择 "New repository"。在 "Repository name" 栏中输入你的用户名(或者你希望的博客地址),比如 yourusername.github.io(注意替换成你的用户名)。勾选 "Initialize this repository with a README" 选项,并点击 "Create repository"。

  步骤三:下载 Jekyll 主题

  Jekyll 是一个简单易用的静态网站生成器,GitHub Pages 支持使用 Jekyll 搭建个人网站。你可以在 Jekyll 官方网站(https://jekyllrb.com/)或 GitHub 上找到各种免费的 Jekyll 主题。选择一个你喜欢的主题,将其下载并解压缩到本地。

  步骤四:上传文件到 GitHub 仓库

  将 Jekyll 主题文件夹中的所有文件上传到你在步骤二中创建的 GitHub 仓库中。你可以使用 GitHub Desktop、Git 命令行或者直接通过 GitHub 网站上传文件。

  步骤五:访问你的博客网站

  等待一段时间,GitHub 会自动构建你的网站,并将其托管在bokequ.github.io这个地址上。你可以在浏览器中输入这个地址,访问你的个人博客网站。

  步骤六:定制你的博客

  编辑 Jekyll 主题文件夹中的配置文件和内容文件,定制你的个人博客。你可以修改页面布局、添加新的页面和文章,以及调整样式和颜色。

  通过 GitHub Pages 和 Jekyll,你可以免费搭建个人博客网站,无需购买服务器和域名。这是一个简单且经济高效的方式,让你能够开始你的博客之旅,并与世界分享你的想法和创作。

收起阅读 »

favicon.ico图片在线制作PHP源码分享

【在线ICO图标制作】Favicon.ico图片在线制作网站。favicon.ico一般用于作为缩略的网站标志,它显示位于浏览器的地址栏或者在标签上,用于显示网站的logo,如图红圈的位置, 目前主要的浏览器都支持favicon.ico图标http://www.bokequ.com/580.html

继续阅读 »

【在线ICO图标制作】Favicon.ico图片在线制作网站。favicon.ico一般用于作为缩略的网站标志,它显示位于浏览器的地址栏或者在标签上,用于显示网站的logo,如图红圈的位置, 目前主要的浏览器都支持favicon.ico图标http://www.bokequ.com/580.html

收起阅读 »

uni.getSystemInfoSync() 返回参数:locationEnabled,总是true

uniapp 教程

文档说是地理位置的系统开关是否打开。但用户在关闭定位权限情况下也返回true。

文档说是地理位置的系统开关是否打开。但用户在关闭定位权限情况下也返回true。

uniCloud 外部系统联登 注册功能 C# 完整示例

uniCloud

APP端

this.$http  
        .post(`/uniCloudRegister`, {  
            clientInfo:JSON.stringify(uni.getSystemInfoSync())  
        })  
        .then(res =>{  
            uni.stopPullDownRefresh()  
            uni.hideNavigationBarLoading()  
            console.info(res)  
        }).catch(err => {  
            console.error(err)  
            uni.stopPullDownRefresh()  
            uni.hideNavigationBarLoading()  
        })

后台接口 UserController:ApiController

ConstantsConstants//业务系统登录后才需要联登到 uniCloud,所以不需要在注册时执行,而是单独给出了注册功能的接口  
[HttpPost]  
[Route("uniCloudRegister")]  
public async Task<HttpResponseMessage> uniCloudRegister([FromBody] UniClient client)  
{  
    HttpResponseMessage response = null;  
    try  
    {  
        //此为验证当前系统token并转为 user类的工具,这里就不给出详细示例了  
        var ui = JwtHelper.AnalysisToken(HttpContext.Current.Request);  
        //见 Utils  
        string uniAppUserName = Utils.GetExternalUid(ui);  
        string nonce = Utils.GetNonce();  
        // 获取当前时间戳(精确到毫秒)  
        long timestamp = Utils.GetNowTimeStamp();  
        Dictionary<string, string> paramsDictionary = new Dictionary<string, string>  
        {  
            { "externalUid", uniAppUserName},  
            { "nickname", ui.Name},  
        };  
        //签名算法见 Utils  
        string signature = Utils.GetSignature(paramsDictionary, nonce, timestamp);  
        //client.clientInfo 为JSON.stringify(uni.getSystemInfoSync()) 这里再转回json对象  
        var cliInfo = JsonConvert.DeserializeObject<JObject>(client.clientInfo);  
        Dictionary<string, object> param = new Dictionary<string, object>();  
        param.Add("clientInfo", cliInfo);  
        param.Add("uniIdToken", "");  
        param.Add("params", paramsDictionary);  

        // 将对象序列化为JSON字符串  
        string jsonContent = JsonConvert.SerializeObject(param);  
        Dictionary<string, string> headers = new Dictionary<string, string>()  
        {  
            {"uni-id-nonce",nonce },  
            {"uni-id-timestamp",timestamp + ""},  
            {"uni-id-signature",signature}  
        };  
        var res = await HttpHelper.SendPostAsync(Constants.PushRegister, jsonContent, headers);  
        if (res.Count > 0)  
        {  
            response = Request.CreateResponse(System.Net.HttpStatusCode.OK, res);  
        }  
        else  
        {  
            response = Request.CreateResponse(System.Net.HttpStatusCode.BadRequest, "error");  
        }  
    }  
    catch (Exception ex)  
    {  
        response = Request.CreateErrorResponse(System.Net.HttpStatusCode.InternalServerError, "error");  
    }  
    return response;  
}

Constants

  //uniapp 外部系统联登 https://doc.dcloud.net.cn/uniCloud/uni-id/cloud-object.html#external  
  public static readonly string PushUrl = "you url";  
  //uniapp 注册  
  public static readonly string PushRegister = PushUrl + "externalRegister";  
  //uniapp 登录  
  public static readonly string PushLogin = PushUrl + "externalLogin";  
  //uniapp 修改信息  
  public static readonly string PushUpdateUser = PushUrl + "updateUserInfoByExternal ";

UniClient

 public class UniClient  
  {  
      public string clientInfo { get; set; }  
      public string uniIdToken { get; set; }  
  }

Utils

 /// <summary>  
  /// 根据用户信息 获取uniapp的uid, 这个就根据自己的业务来处理  
  /// </summary>  
  /// <param name="ui"></param>  
  /// <returns></returns>  
  public static string GetExternalUid(UserInfo ui)  
  {  
    //分别为角色ID、 用户ID和用户账号  
    return  ui.Role + "_" + ui.Id + "_" + ui.userName;  
  }  

  //获取随机字符串 这里其实可以固定返回一组字符串  
  public static string GetNonce()  
  {  
      return GenerateRandomStringLinq(8);  
  }  

  private static string GenerateRandomStringLinq(int length)  
  {  
      const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";  
      Random random = new Random();  
      return new string(Enumerable.Repeat(chars, length)  
        .Select(s => s[random.Next(s.Length)]).ToArray());  
  }  

    /// <summary>  
    /// uniapp 鉴权签名算法  
    /// </summary>  
    /// <param name="parameters"></param>  
    /// <param name="nonce"></param>  
    /// <param name="timestamp"></param>  
    /// <returns></returns>  
    public static string GetSignature(Dictionary<string, string> parameters, string nonce, long timestamp)  
    {  
        string paramsStr = GetParamsString(parameters);  
        using (HMACSHA256 hmacSha256 = new HMACSHA256(Encoding.UTF8.GetBytes(Constants.RequestAuthSecret + nonce)))  
        {  
            string message = timestamp.ToString() + paramsStr;  
            byte[] messageBytes = Encoding.UTF8.GetBytes(message);  
            byte[] hashBytes = hmacSha256.ComputeHash(messageBytes);  

            return ByteArrayToHexString(hashBytes).ToUpper();  
        }  
    }  

  /// <summary>  
  /// 获取当前时间戳 单位毫秒  
  /// </summary>  
  /// <returns></returns>  
  public static long GetNowTimeStamp()  
  {  
      return (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;  
  }  

  private static string GetParamsString(Dictionary<string, string> parameters)  
  {  
      var keys = new List<string>(parameters.Keys);  
      keys.Sort();  

      StringBuilder sb = new StringBuilder();  

      for (int i = 0; i < keys.Count; i++)  
      {  
          if (i != 0)  
          {  
              sb.Append("&");  
          }  

          sb.Append(keys[i]).Append("=").Append(parameters[keys[i]]);  
      }  

      return sb.ToString();  
  }  

  private static string ByteArrayToHexString(byte[] bytes)  
  {  
      StringBuilder sb = new StringBuilder();  

      foreach (byte b in bytes)  
      {  
          string hex = b.ToString("x2");  
          sb.Append(hex);  
      }  

      return sb.ToString();  
  }  

HttpHelper

  /// <summary>  
  /// POST异步请求  
  ///   
  /// </summary>  
  /// <param name="url">请求url</param>  
  /// <param name="jsonContent"></param>  
  /// <param name="headers"></param>  
  /// <returns></returns>  
  // 发送POST请求的函数    
  public static async Task<JObject> SendPostAsync(string url, string jsonContent, Dictionary<string, string> headers = null)  
  {  
      using (var client = new HttpClient())  
      {  
          var jsonObject = new JObject();  
          if (headers != null)  
          {  
              foreach (KeyValuePair<string, string> header in headers)  
              {  
                  client.DefaultRequestHeaders.Add(header.Key, header.Value);  
              }  
          }  
          try  
          {  
              // 创建一个HttpContent对象,用于发送JSON数据    
              var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");  

              // 发送POST请求    
              HttpResponseMessage response = await client.PostAsync(url, httpContent);  

              // 确保HTTP请求成功    
              //response.EnsureSuccessStatusCode();  

              // 读取响应内容    
              var responseBody = await response.Content.ReadAsStringAsync();  

              //LoggerHelper.Info("请求:" + url + ",参数:" + jsonContent + ",结果:" + responseBody);  
              jsonObject = JsonConvert.DeserializeObject<JObject>(responseBody);  
          }  
          catch (HttpRequestException e)  
          {  
              LoggerHelper.Error("请求失败!",e);  
          }  
          return jsonObject;  
      }  
  }

tips: 出现了 "clientInfo.uniPlatform" is required. 是没有传参数clientInfo

参考 官方文档

适配URL化

外部系统联登

URL化请求鉴权签名

云端配置config.json的说明

继续阅读 »

APP端

this.$http  
        .post(`/uniCloudRegister`, {  
            clientInfo:JSON.stringify(uni.getSystemInfoSync())  
        })  
        .then(res =>{  
            uni.stopPullDownRefresh()  
            uni.hideNavigationBarLoading()  
            console.info(res)  
        }).catch(err => {  
            console.error(err)  
            uni.stopPullDownRefresh()  
            uni.hideNavigationBarLoading()  
        })

后台接口 UserController:ApiController

ConstantsConstants//业务系统登录后才需要联登到 uniCloud,所以不需要在注册时执行,而是单独给出了注册功能的接口  
[HttpPost]  
[Route("uniCloudRegister")]  
public async Task<HttpResponseMessage> uniCloudRegister([FromBody] UniClient client)  
{  
    HttpResponseMessage response = null;  
    try  
    {  
        //此为验证当前系统token并转为 user类的工具,这里就不给出详细示例了  
        var ui = JwtHelper.AnalysisToken(HttpContext.Current.Request);  
        //见 Utils  
        string uniAppUserName = Utils.GetExternalUid(ui);  
        string nonce = Utils.GetNonce();  
        // 获取当前时间戳(精确到毫秒)  
        long timestamp = Utils.GetNowTimeStamp();  
        Dictionary<string, string> paramsDictionary = new Dictionary<string, string>  
        {  
            { "externalUid", uniAppUserName},  
            { "nickname", ui.Name},  
        };  
        //签名算法见 Utils  
        string signature = Utils.GetSignature(paramsDictionary, nonce, timestamp);  
        //client.clientInfo 为JSON.stringify(uni.getSystemInfoSync()) 这里再转回json对象  
        var cliInfo = JsonConvert.DeserializeObject<JObject>(client.clientInfo);  
        Dictionary<string, object> param = new Dictionary<string, object>();  
        param.Add("clientInfo", cliInfo);  
        param.Add("uniIdToken", "");  
        param.Add("params", paramsDictionary);  

        // 将对象序列化为JSON字符串  
        string jsonContent = JsonConvert.SerializeObject(param);  
        Dictionary<string, string> headers = new Dictionary<string, string>()  
        {  
            {"uni-id-nonce",nonce },  
            {"uni-id-timestamp",timestamp + ""},  
            {"uni-id-signature",signature}  
        };  
        var res = await HttpHelper.SendPostAsync(Constants.PushRegister, jsonContent, headers);  
        if (res.Count > 0)  
        {  
            response = Request.CreateResponse(System.Net.HttpStatusCode.OK, res);  
        }  
        else  
        {  
            response = Request.CreateResponse(System.Net.HttpStatusCode.BadRequest, "error");  
        }  
    }  
    catch (Exception ex)  
    {  
        response = Request.CreateErrorResponse(System.Net.HttpStatusCode.InternalServerError, "error");  
    }  
    return response;  
}

Constants

  //uniapp 外部系统联登 https://doc.dcloud.net.cn/uniCloud/uni-id/cloud-object.html#external  
  public static readonly string PushUrl = "you url";  
  //uniapp 注册  
  public static readonly string PushRegister = PushUrl + "externalRegister";  
  //uniapp 登录  
  public static readonly string PushLogin = PushUrl + "externalLogin";  
  //uniapp 修改信息  
  public static readonly string PushUpdateUser = PushUrl + "updateUserInfoByExternal ";

UniClient

 public class UniClient  
  {  
      public string clientInfo { get; set; }  
      public string uniIdToken { get; set; }  
  }

Utils

 /// <summary>  
  /// 根据用户信息 获取uniapp的uid, 这个就根据自己的业务来处理  
  /// </summary>  
  /// <param name="ui"></param>  
  /// <returns></returns>  
  public static string GetExternalUid(UserInfo ui)  
  {  
    //分别为角色ID、 用户ID和用户账号  
    return  ui.Role + "_" + ui.Id + "_" + ui.userName;  
  }  

  //获取随机字符串 这里其实可以固定返回一组字符串  
  public static string GetNonce()  
  {  
      return GenerateRandomStringLinq(8);  
  }  

  private static string GenerateRandomStringLinq(int length)  
  {  
      const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";  
      Random random = new Random();  
      return new string(Enumerable.Repeat(chars, length)  
        .Select(s => s[random.Next(s.Length)]).ToArray());  
  }  

    /// <summary>  
    /// uniapp 鉴权签名算法  
    /// </summary>  
    /// <param name="parameters"></param>  
    /// <param name="nonce"></param>  
    /// <param name="timestamp"></param>  
    /// <returns></returns>  
    public static string GetSignature(Dictionary<string, string> parameters, string nonce, long timestamp)  
    {  
        string paramsStr = GetParamsString(parameters);  
        using (HMACSHA256 hmacSha256 = new HMACSHA256(Encoding.UTF8.GetBytes(Constants.RequestAuthSecret + nonce)))  
        {  
            string message = timestamp.ToString() + paramsStr;  
            byte[] messageBytes = Encoding.UTF8.GetBytes(message);  
            byte[] hashBytes = hmacSha256.ComputeHash(messageBytes);  

            return ByteArrayToHexString(hashBytes).ToUpper();  
        }  
    }  

  /// <summary>  
  /// 获取当前时间戳 单位毫秒  
  /// </summary>  
  /// <returns></returns>  
  public static long GetNowTimeStamp()  
  {  
      return (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;  
  }  

  private static string GetParamsString(Dictionary<string, string> parameters)  
  {  
      var keys = new List<string>(parameters.Keys);  
      keys.Sort();  

      StringBuilder sb = new StringBuilder();  

      for (int i = 0; i < keys.Count; i++)  
      {  
          if (i != 0)  
          {  
              sb.Append("&");  
          }  

          sb.Append(keys[i]).Append("=").Append(parameters[keys[i]]);  
      }  

      return sb.ToString();  
  }  

  private static string ByteArrayToHexString(byte[] bytes)  
  {  
      StringBuilder sb = new StringBuilder();  

      foreach (byte b in bytes)  
      {  
          string hex = b.ToString("x2");  
          sb.Append(hex);  
      }  

      return sb.ToString();  
  }  

HttpHelper

  /// <summary>  
  /// POST异步请求  
  ///   
  /// </summary>  
  /// <param name="url">请求url</param>  
  /// <param name="jsonContent"></param>  
  /// <param name="headers"></param>  
  /// <returns></returns>  
  // 发送POST请求的函数    
  public static async Task<JObject> SendPostAsync(string url, string jsonContent, Dictionary<string, string> headers = null)  
  {  
      using (var client = new HttpClient())  
      {  
          var jsonObject = new JObject();  
          if (headers != null)  
          {  
              foreach (KeyValuePair<string, string> header in headers)  
              {  
                  client.DefaultRequestHeaders.Add(header.Key, header.Value);  
              }  
          }  
          try  
          {  
              // 创建一个HttpContent对象,用于发送JSON数据    
              var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");  

              // 发送POST请求    
              HttpResponseMessage response = await client.PostAsync(url, httpContent);  

              // 确保HTTP请求成功    
              //response.EnsureSuccessStatusCode();  

              // 读取响应内容    
              var responseBody = await response.Content.ReadAsStringAsync();  

              //LoggerHelper.Info("请求:" + url + ",参数:" + jsonContent + ",结果:" + responseBody);  
              jsonObject = JsonConvert.DeserializeObject<JObject>(responseBody);  
          }  
          catch (HttpRequestException e)  
          {  
              LoggerHelper.Error("请求失败!",e);  
          }  
          return jsonObject;  
      }  
  }

tips: 出现了 "clientInfo.uniPlatform" is required. 是没有传参数clientInfo

参考 官方文档

适配URL化

外部系统联登

URL化请求鉴权签名

云端配置config.json的说明

收起阅读 »

低版本 vue 无法安装 pinia 的解决方案

vue3

1 使用 pnpm 安装,安装时需锁定 pinia 版本
2 使用 yarn 安装,安装时需锁定 pinia 版本

1 使用 pnpm 安装,安装时需锁定 pinia 版本
2 使用 yarn 安装,安装时需锁定 pinia 版本