
2.3.7 你这个bug终于修复了
2.3.7.20191024
- 修复 html 连续编写有默认值的属性时(例如 autocomplete accesskey等), 覆盖位置不对的Bug
2.3.7.20191024
- 修复 html 连续编写有默认值的属性时(例如 autocomplete accesskey等), 覆盖位置不对的Bug

iOS平台HBuilder基座证书过期无法真机运行的说明(return code=-402620395)
原因
HBuilder/HBuilderX真机运行时需在iPhone/iPad手机上安装HBuilder运行基座App,此App非苹果企业证书(iEP)签名发布,通过数据线安装到iOS设备。
因为苹果政策要求,这样的App证书有效期只有1年,过期后将无法安装,需要重新打包基座
故障现象
在HBuilder/HBuilderX上真机运行证书过期的基座,控制台会提示以下错误:
正在建立手机连接...
正在安装手机端HBuilder调试基座...
安装失败 return code=-402620395,请手动安装..\HBuilder\plugins\com.pandora.tools.android_1.0.0.201811231756\base\iPhone_base.ipa到手机上(可使用iTools安装),并重新运行真机调试。
注:这个提示语不太精准,未来HBuilderX会提供更准确的提示。
影响范围
老HBuilder早已停止更新(1年前已经公告过),请开发者及时升级最新版HBuilderX。
HBuilderX在2.3.3.20190923及以下版本也已过期。
但这个只影响HBuilder/HBuilderX的默认运行基座,用户使用自己证书制作的自定义运行基座不受影响,自定义基座的证书过期时间是开发者自己管理的。
已经安装过的运行基座仍然可以使用。
解决方案
- 升级到HBuilderX 2.3.4及以上版本,目前最新版是HBuilder 2.3.7
- 在老版HBuilder或HBuilderX里自行制作自定义基座。运行菜单里有自定义基座的制作选项和教程。
升级注意
老版HBuilder/HBuilderX升级新版,有一些常见的升级注意事项,比如新版默认的webview从UIWebview调整为WKWebview,比如微信SDK升级引发要求通用链接。
请开发者仔细阅读升级公告指南:https://ask.dcloud.net.cn/article/36260
使用自定义调试基座出现此问题
通常出现此问题是提交云端打包自定义基座时使用了提交appstore的证书及profile文件,这种包只能用于提交appstore,不能作为自定义基座。
应该使用开发(Development)证书及profile文件打包生成自定义基座
苹果官方提供的profile类型,分别适用的场景:
Development
- iOS App Development
开发者调试使用,在特定设备上测试使用。可用于制作自定义基座。
Distribution
- Ad Hoc
发布测试时使用,在有限的设备上可安装使用。可以用于制作自定义基座,必须在指定的设备上真机运行 - App Store
发布到AppStore使用,只能用于提交AppStore。不能用于制作自定义基座。 - In House
仅iEP账号可创建,可用于企业内部发行应用使用。可以用于制作自定义基座。
原因
HBuilder/HBuilderX真机运行时需在iPhone/iPad手机上安装HBuilder运行基座App,此App非苹果企业证书(iEP)签名发布,通过数据线安装到iOS设备。
因为苹果政策要求,这样的App证书有效期只有1年,过期后将无法安装,需要重新打包基座
故障现象
在HBuilder/HBuilderX上真机运行证书过期的基座,控制台会提示以下错误:
正在建立手机连接...
正在安装手机端HBuilder调试基座...
安装失败 return code=-402620395,请手动安装..\HBuilder\plugins\com.pandora.tools.android_1.0.0.201811231756\base\iPhone_base.ipa到手机上(可使用iTools安装),并重新运行真机调试。
注:这个提示语不太精准,未来HBuilderX会提供更准确的提示。
影响范围
老HBuilder早已停止更新(1年前已经公告过),请开发者及时升级最新版HBuilderX。
HBuilderX在2.3.3.20190923及以下版本也已过期。
但这个只影响HBuilder/HBuilderX的默认运行基座,用户使用自己证书制作的自定义运行基座不受影响,自定义基座的证书过期时间是开发者自己管理的。
已经安装过的运行基座仍然可以使用。
解决方案
- 升级到HBuilderX 2.3.4及以上版本,目前最新版是HBuilder 2.3.7
- 在老版HBuilder或HBuilderX里自行制作自定义基座。运行菜单里有自定义基座的制作选项和教程。
升级注意
老版HBuilder/HBuilderX升级新版,有一些常见的升级注意事项,比如新版默认的webview从UIWebview调整为WKWebview,比如微信SDK升级引发要求通用链接。
请开发者仔细阅读升级公告指南:https://ask.dcloud.net.cn/article/36260
使用自定义调试基座出现此问题
通常出现此问题是提交云端打包自定义基座时使用了提交appstore的证书及profile文件,这种包只能用于提交appstore,不能作为自定义基座。
应该使用开发(Development)证书及profile文件打包生成自定义基座
苹果官方提供的profile类型,分别适用的场景:
Development
- iOS App Development
开发者调试使用,在特定设备上测试使用。可用于制作自定义基座。
Distribution
- Ad Hoc
发布测试时使用,在有限的设备上可安装使用。可以用于制作自定义基座,必须在指定的设备上真机运行 - App Store
发布到AppStore使用,只能用于提交AppStore。不能用于制作自定义基座。 - In House
仅iEP账号可创建,可用于企业内部发行应用使用。可以用于制作自定义基座。

Android平台云端打包 - 公共测试证书
由于公共测试证书的描述信息都是测试数据,并且任何人都可以使用,也可以下载此证书,最近发现有开发者使用此证书发布了一些涉嫌欺诈的APP,被某些安全检测平台将此证书列入黑名单,因此可能会将使用了公共测试证书的应用误报为病毒。HBuilderX3.1.10+版本更新了公共测试证书,避免在部分手机安装测试时提示应用存在风险的问题,已经使用测试证书的应用需尽快更新使用自有证书。另外后续HBuilderX版本将不再支持使用公共测试证书,新增自动生成证书功能来替代公共测试证书。
!!!注意!!!
公共测试证书仅适合应用开发期间体验测试使用
公共测试证书中的描述信息都是测试数据,任何人都能下载使用,存在安全隐患,在部分安全检测平台可能会误报病毒。不要使用公共测试证书正式发布应用!!!
为了避免开发者误用公共测试证书引起安全隐患,公共测试证书于2025年1月1日下线
测试证书更新引出的问题
HBuilderX3.1.10版本更新了测试证书,解决使用功能测试证书在部分机器上安装测试时提示风险的问题
如果之前发布的应用已经使用了测试证书,更新HBuilderX3.1.10后提交云端打包将会使用新的公共测试证书,可能会出现以下情况:
- 无法覆盖安装,这是因为公共测试证书更新了
- 在部分手机安装时可能被系统弹出提示有风险,这是因为系统记录了之前使用的签名证书,重新打包后使用了新证书,因此被系统怀疑为钓鱼App
- 三方SDK相关功能(地图、UniPush、一键登录)无法正常使用,这是因为签名证书改变,需要到对应后台更新配置签名证书信息
解决方案
- 更新证书,注意不要继续使用测试证书发布应用,应该更新使用自有证书,参考:生成Android签名证书。如果已经上架到应用市场,需要先下架再重新提交。
- 继续使用原来测试证书,请在此文章的“HBuilderX3.1.10之前版本公共测试证书”下载,作为自己生成的证书提交云端打包`
关于Android证书的用途
证书是一个开发者的身份标志,对Android系统而言。使用同一个证书签发的App,是属于同一个开发者的App,并确保此证书不被泄露(务必管理好自己的证书)。
举个极端的例子,如果你的应用证书泄露,那么别人可以用这个证书签名一个仿冒App,假如包名和你的包名也一样,就可以覆盖安装安卓手机上你之前的包。
当前仅依赖证书校验是不完善的,所以主流的Android应用市场,通过实名认证开发者信息和著作权,强化了App的唯一性。也就是从主流应用市场点更新,不会发生冒充事件。但是通过其他方式安装apk,仍然会发生冒充的可能性。
如果签名不同,即使包名相同,也无法覆盖安装。此时安卓手机会在安装时报错,需要先卸载老的版本,才能安装新版。
云端打包使用公共测试证书
提交云端打包时在“App云端打包界面”选择“使用公共测试证书”:

HBuilderX3.1.10+版本公共测试证书
可用于测试打包,不要用于正式发布,其信息如下:
MD5: 06:83:8C:C8:40:09:3B:9D:46:89:FC:41:9B:A1:A3:F3
SHA1: 97:C8:41:01:B9:14:1C:13:0D:D7:5D:74:28:A2:92:25:18:C3:6D:CD
SHA256: B0:1D:06:18:0D:00:3E:79:C7:B9:08:89:93:B8:E5:AE:7A:19:B0:DA:11:61:AA:09:7C:7F:39:8A:6F:51:4F:A7
应用签名: 06838cc840093b9d4689fc419ba1a3f3
完整信息如下:
Keystore type: PKCS12
Keystore provider: SUN
Your keystore contains 1 entry
Alias name: android
Creation date: 2021-4-12
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Android Debug, OU=Android, O=Android, L=HD, ST=BJ, C=CN
Issuer: CN=Android Debug, OU=Android, O=Android, L=HD, ST=BJ, C=CN
Serial number: 363bc393
Valid from: Mon Apr 12 16:27:53 CST 2021 until: Wed Mar 19 16:27:53 CST 2121
Certificate fingerprints:
MD5: 06:83:8C:C8:40:09:3B:9D:46:89:FC:41:9B:A1:A3:F3
SHA1: 97:C8:41:01:B9:14:1C:13:0D:D7:5D:74:28:A2:92:25:18:C3:6D:CD
SHA256: B0:1D:06:18:0D:00:3E:79:C7:B9:08:89:93:B8:E5:AE:7A:19:B0:DA:11:61:AA:09:7C:7F:39:8A:6F:51:4F:A7
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3
注意:为了确保应用的安全性,正式发布应用不要使用公共测试证书!!!
HBuilderX3.1.10之前版本公共测试证书
可用于测试打包,不要用于正式发布,其信息如下:
MD5: F9:F6:C8:1F:DB:AB:50:14:7D:6F:2C:4F:CE:E6:0A:A5
SHA1: BB:AC:E2:2F:97:3B:18:02:E7:D6:69:A3:7A:28:EF:D2:3F:A3:68:E7
SHA256: 24:11:7D:E7:36:12:BC:FE:AF:2A:6A:24:BD:04:4F:2E:33:E5:2D:41:96:5F:50:4D:74:17:7F:4F:E2:55:EB:26
应用签名: f9f6c81fdbab50147d6f2c4fcee60aa5
完整信息如下:
Keystore type: PKCS12
Keystore provider: SUN
Your keystore contains 1 entry
Alias name: test
Creation date: 2019-10-28
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Tester, OU=Test, O=Test, L=HD, ST=BJ, C=CN
Issuer: CN=Tester, OU=Test, O=Test, L=HD, ST=BJ, C=CN
Serial number: 7dd12840
Valid from: Fri Jul 26 20:52:56 CST 2019 until: Sun Jul 02 20:52:56 CST 2119
Certificate fingerprints:
MD5: F9:F6:C8:1F:DB:AB:50:14:7D:6F:2C:4F:CE:E6:0A:A5
SHA1: BB:AC:E2:2F:97:3B:18:02:E7:D6:69:A3:7A:28:EF:D2:3F:A3:68:E7
SHA256: 24:11:7D:E7:36:12:BC:FE:AF:2A:6A:24:BD:04:4F:2E:33:E5:2D:41:96:5F:50:4D:74:17:7F:4F:E2:55:EB:26
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3
注意:为了确保应用的安全性,正式发布应用不要使用公共测试证书!!!
由于公共测试证书的描述信息都是测试数据,并且任何人都可以使用,也可以下载此证书,最近发现有开发者使用此证书发布了一些涉嫌欺诈的APP,被某些安全检测平台将此证书列入黑名单,因此可能会将使用了公共测试证书的应用误报为病毒。HBuilderX3.1.10+版本更新了公共测试证书,避免在部分手机安装测试时提示应用存在风险的问题,已经使用测试证书的应用需尽快更新使用自有证书。另外后续HBuilderX版本将不再支持使用公共测试证书,新增自动生成证书功能来替代公共测试证书。
!!!注意!!!
公共测试证书仅适合应用开发期间体验测试使用
公共测试证书中的描述信息都是测试数据,任何人都能下载使用,存在安全隐患,在部分安全检测平台可能会误报病毒。不要使用公共测试证书正式发布应用!!!
为了避免开发者误用公共测试证书引起安全隐患,公共测试证书于2025年1月1日下线
测试证书更新引出的问题
HBuilderX3.1.10版本更新了测试证书,解决使用功能测试证书在部分机器上安装测试时提示风险的问题
如果之前发布的应用已经使用了测试证书,更新HBuilderX3.1.10后提交云端打包将会使用新的公共测试证书,可能会出现以下情况:
- 无法覆盖安装,这是因为公共测试证书更新了
- 在部分手机安装时可能被系统弹出提示有风险,这是因为系统记录了之前使用的签名证书,重新打包后使用了新证书,因此被系统怀疑为钓鱼App
- 三方SDK相关功能(地图、UniPush、一键登录)无法正常使用,这是因为签名证书改变,需要到对应后台更新配置签名证书信息
解决方案
- 更新证书,注意不要继续使用测试证书发布应用,应该更新使用自有证书,参考:生成Android签名证书。如果已经上架到应用市场,需要先下架再重新提交。
- 继续使用原来测试证书,请在此文章的“HBuilderX3.1.10之前版本公共测试证书”下载,作为自己生成的证书提交云端打包`
关于Android证书的用途
证书是一个开发者的身份标志,对Android系统而言。使用同一个证书签发的App,是属于同一个开发者的App,并确保此证书不被泄露(务必管理好自己的证书)。
举个极端的例子,如果你的应用证书泄露,那么别人可以用这个证书签名一个仿冒App,假如包名和你的包名也一样,就可以覆盖安装安卓手机上你之前的包。
当前仅依赖证书校验是不完善的,所以主流的Android应用市场,通过实名认证开发者信息和著作权,强化了App的唯一性。也就是从主流应用市场点更新,不会发生冒充事件。但是通过其他方式安装apk,仍然会发生冒充的可能性。
如果签名不同,即使包名相同,也无法覆盖安装。此时安卓手机会在安装时报错,需要先卸载老的版本,才能安装新版。
云端打包使用公共测试证书
提交云端打包时在“App云端打包界面”选择“使用公共测试证书”:
HBuilderX3.1.10+版本公共测试证书
可用于测试打包,不要用于正式发布,其信息如下:
MD5: 06:83:8C:C8:40:09:3B:9D:46:89:FC:41:9B:A1:A3:F3
SHA1: 97:C8:41:01:B9:14:1C:13:0D:D7:5D:74:28:A2:92:25:18:C3:6D:CD
SHA256: B0:1D:06:18:0D:00:3E:79:C7:B9:08:89:93:B8:E5:AE:7A:19:B0:DA:11:61:AA:09:7C:7F:39:8A:6F:51:4F:A7
应用签名: 06838cc840093b9d4689fc419ba1a3f3
完整信息如下:
Keystore type: PKCS12
Keystore provider: SUN
Your keystore contains 1 entry
Alias name: android
Creation date: 2021-4-12
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Android Debug, OU=Android, O=Android, L=HD, ST=BJ, C=CN
Issuer: CN=Android Debug, OU=Android, O=Android, L=HD, ST=BJ, C=CN
Serial number: 363bc393
Valid from: Mon Apr 12 16:27:53 CST 2021 until: Wed Mar 19 16:27:53 CST 2121
Certificate fingerprints:
MD5: 06:83:8C:C8:40:09:3B:9D:46:89:FC:41:9B:A1:A3:F3
SHA1: 97:C8:41:01:B9:14:1C:13:0D:D7:5D:74:28:A2:92:25:18:C3:6D:CD
SHA256: B0:1D:06:18:0D:00:3E:79:C7:B9:08:89:93:B8:E5:AE:7A:19:B0:DA:11:61:AA:09:7C:7F:39:8A:6F:51:4F:A7
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3
注意:为了确保应用的安全性,正式发布应用不要使用公共测试证书!!!
HBuilderX3.1.10之前版本公共测试证书
可用于测试打包,不要用于正式发布,其信息如下:
MD5: F9:F6:C8:1F:DB:AB:50:14:7D:6F:2C:4F:CE:E6:0A:A5
SHA1: BB:AC:E2:2F:97:3B:18:02:E7:D6:69:A3:7A:28:EF:D2:3F:A3:68:E7
SHA256: 24:11:7D:E7:36:12:BC:FE:AF:2A:6A:24:BD:04:4F:2E:33:E5:2D:41:96:5F:50:4D:74:17:7F:4F:E2:55:EB:26
应用签名: f9f6c81fdbab50147d6f2c4fcee60aa5
完整信息如下:
Keystore type: PKCS12
Keystore provider: SUN
Your keystore contains 1 entry
Alias name: test
Creation date: 2019-10-28
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Tester, OU=Test, O=Test, L=HD, ST=BJ, C=CN
Issuer: CN=Tester, OU=Test, O=Test, L=HD, ST=BJ, C=CN
Serial number: 7dd12840
Valid from: Fri Jul 26 20:52:56 CST 2019 until: Sun Jul 02 20:52:56 CST 2119
Certificate fingerprints:
MD5: F9:F6:C8:1F:DB:AB:50:14:7D:6F:2C:4F:CE:E6:0A:A5
SHA1: BB:AC:E2:2F:97:3B:18:02:E7:D6:69:A3:7A:28:EF:D2:3F:A3:68:E7
SHA256: 24:11:7D:E7:36:12:BC:FE:AF:2A:6A:24:BD:04:4F:2E:33:E5:2D:41:96:5F:50:4D:74:17:7F:4F:E2:55:EB:26
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3
注意:为了确保应用的安全性,正式发布应用不要使用公共测试证书!!!
收起阅读 »
uniapp H5图片上传压缩自动旋转
//H5压终审图片上传
const upload= async (opt) => {
let maxWidth = 500; //压缩图片最大宽度
opt = opt || {};
opt.url = opt.url || '';
opt.success = opt.success || function(){ };
console.log(opt.url)
let Orientation = 1;
//获取图片META信息
await getImageTag(opt.url, 'Orientation', function(e) {
if(e != undefined) Orientation = e;
})
var img = null;
var canvas = null;
await comprossImage(opt.url, maxWidth, function(e) {
img = e.img;
canvas = e.canvas;
})
console.log(Orientation)
let baseStr = '';
//如果方向角不为1,都需要进行旋转
switch(Orientation){
case 6://需要顺时针(向右)90度旋转
console.log('(向右)90度旋转');
baseStr = rotateImg(img,'right',canvas);
break;
case 8://需要逆时针(向左)90度旋转
console.log('向左)90度旋转');
baseStr = rotateImg(img,'left',canvas);
break;
case 3://需要180度旋转 转两次
console.log('需要180度旋转');
baseStr = rotateImg(img,'right',canvas, 2);
break;
default:
baseStr = rotateImg(img,'',canvas);
break;
}
opt.success(baseStr); //公共方法的回调函数
}
const comprossImage = async (imgSrc, maxWidth, func) => {
if(!imgSrc) return 0;
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: imgSrc,
success(res) {
let img = new Image();
img.src = res.path;
console.log(img)
let canvas = document.createElement('canvas');
let obj = new Object();
obj.img = img;
obj.canvas = canvas;
resolve(func(obj));
}
});
})
}
/**
* @desc 获取图片信息,使用exif.js库,具体用法请在github中搜索
* @param {Object} file 上传的图片文件
* @param {String} tag 需要获取的信息 例如:'Orientation'旋转信息
* @return {Promise<Any>} 读取是个异步操作,返回指定的图片信息
*/
const getImageTag = (file, tag, suc) => {
if (!file) return 0;
return new Promise((resolve, reject) => {
/* eslint-disable func-names */
// 箭头函数会修改this,所以这里不能用箭头函数
let imgObj = new Image()
imgObj.src = file
console.log(imgObj)
uni.getImageInfo({
src: file,
success(res) {
Exif.getData(imgObj, function () {
Exif.getAllTags(this);
let or = Exif.getTag(this,'Orientation');//这个Orientation 就是我们判断需不需要旋转的值了,有1、3、6、8
resolve(suc(or))
});
}
})
});
};
//网上提供的旋转function
const rotateImg = (img, direction, canvas, times = 1) => {
console.log('开始旋转')
//最小与最大旋转方向,图片旋转4次后回到原方向
var min_step = 0;
var max_step = 3;
if (img == null)return;
//img的高度和宽度不能在img元素隐藏后获取,否则会出错
var height = img.height;
var width = img.width;
let maxWidth = 500;
let canvasWidth = width; //图片原始长宽
let canvasHeight = height;
let base = canvasWidth/canvasHeight;
console.log(maxWidth);
if(canvasWidth > maxWidth){
canvasWidth = maxWidth;
canvasHeight = Math.floor(canvasWidth/base);
}
width = canvasWidth;
height = canvasHeight;
var step = 0;
if (step == null) {
step = min_step;
}
if (direction == 'right') {
step += times;
//旋转到原位置,即超过最大值
step > max_step && (step = min_step);
} else if(direction == 'left'){
step -= times;
step < min_step && (step = max_step);
} else { //不旋转
step = 0;
}
//旋转角度以弧度值为参数
var degree = step * 90 * Math.PI / 180;
var ctx = canvas.getContext('2d');
console.log(degree)
console.log(step)
switch (step) {
case 1:
console.log('右旋转 90度')
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, 0, -height, width, height);
break;
case 2:
//console.log('旋转 180度')
canvas.width = width;
canvas.height = height;
ctx.rotate(degree);
ctx.drawImage(img, -width, -height, width, height);
break;
case 3:
console.log('左旋转 90度')
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, -width, 0, width, height);
break;
default: //不旋转
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
break;
}
let baseStr = canvas.toDataURL("image/jpeg", 1);
return baseStr;
}
//H5压终审图片上传
const upload= async (opt) => {
let maxWidth = 500; //压缩图片最大宽度
opt = opt || {};
opt.url = opt.url || '';
opt.success = opt.success || function(){ };
console.log(opt.url)
let Orientation = 1;
//获取图片META信息
await getImageTag(opt.url, 'Orientation', function(e) {
if(e != undefined) Orientation = e;
})
var img = null;
var canvas = null;
await comprossImage(opt.url, maxWidth, function(e) {
img = e.img;
canvas = e.canvas;
})
console.log(Orientation)
let baseStr = '';
//如果方向角不为1,都需要进行旋转
switch(Orientation){
case 6://需要顺时针(向右)90度旋转
console.log('(向右)90度旋转');
baseStr = rotateImg(img,'right',canvas);
break;
case 8://需要逆时针(向左)90度旋转
console.log('向左)90度旋转');
baseStr = rotateImg(img,'left',canvas);
break;
case 3://需要180度旋转 转两次
console.log('需要180度旋转');
baseStr = rotateImg(img,'right',canvas, 2);
break;
default:
baseStr = rotateImg(img,'',canvas);
break;
}
opt.success(baseStr); //公共方法的回调函数
}
const comprossImage = async (imgSrc, maxWidth, func) => {
if(!imgSrc) return 0;
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: imgSrc,
success(res) {
let img = new Image();
img.src = res.path;
console.log(img)
let canvas = document.createElement('canvas');
let obj = new Object();
obj.img = img;
obj.canvas = canvas;
resolve(func(obj));
}
});
})
}
/**
* @desc 获取图片信息,使用exif.js库,具体用法请在github中搜索
* @param {Object} file 上传的图片文件
* @param {String} tag 需要获取的信息 例如:'Orientation'旋转信息
* @return {Promise<Any>} 读取是个异步操作,返回指定的图片信息
*/
const getImageTag = (file, tag, suc) => {
if (!file) return 0;
return new Promise((resolve, reject) => {
/* eslint-disable func-names */
// 箭头函数会修改this,所以这里不能用箭头函数
let imgObj = new Image()
imgObj.src = file
console.log(imgObj)
uni.getImageInfo({
src: file,
success(res) {
Exif.getData(imgObj, function () {
Exif.getAllTags(this);
let or = Exif.getTag(this,'Orientation');//这个Orientation 就是我们判断需不需要旋转的值了,有1、3、6、8
resolve(suc(or))
});
}
})
});
};
//网上提供的旋转function
const rotateImg = (img, direction, canvas, times = 1) => {
console.log('开始旋转')
//最小与最大旋转方向,图片旋转4次后回到原方向
var min_step = 0;
var max_step = 3;
if (img == null)return;
//img的高度和宽度不能在img元素隐藏后获取,否则会出错
var height = img.height;
var width = img.width;
let maxWidth = 500;
let canvasWidth = width; //图片原始长宽
let canvasHeight = height;
let base = canvasWidth/canvasHeight;
console.log(maxWidth);
if(canvasWidth > maxWidth){
canvasWidth = maxWidth;
canvasHeight = Math.floor(canvasWidth/base);
}
width = canvasWidth;
height = canvasHeight;
var step = 0;
if (step == null) {
step = min_step;
}
if (direction == 'right') {
step += times;
//旋转到原位置,即超过最大值
step > max_step && (step = min_step);
} else if(direction == 'left'){
step -= times;
step < min_step && (step = max_step);
} else { //不旋转
step = 0;
}
//旋转角度以弧度值为参数
var degree = step * 90 * Math.PI / 180;
var ctx = canvas.getContext('2d');
console.log(degree)
console.log(step)
switch (step) {
case 1:
console.log('右旋转 90度')
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, 0, -height, width, height);
break;
case 2:
//console.log('旋转 180度')
canvas.width = width;
canvas.height = height;
ctx.rotate(degree);
ctx.drawImage(img, -width, -height, width, height);
break;
case 3:
console.log('左旋转 90度')
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, -width, 0, width, height);
break;
default: //不旋转
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
break;
}
let baseStr = canvas.toDataURL("image/jpeg", 1);
return baseStr;
}
收起阅读 »

uni-app在APP端实现截屏分享功能
目标
点击分享按钮,截取APP屏,在分享到第三方,如微信,朋友圈。
截屏
方法一:html2canvas.js
- 这种方法需要获取DOM节点,app端获取不到。如果可以获取DOM节点,可以尝试使用此方法。
方法二:5+api
let pages = getCurrentPages();
let page = pages[pages.length - 1];
let ws = page.$getAppWebview();
let bitmap = new plus.nativeObj.Bitmap('drawScreen');
// 将webview内容绘制到Bitmap对象中
ws.draw(bitmap, () => {
// 保存图片到本地
bitmap.save("_doc/drawScreen.jpg", {
overwrite: true
}, res => {
console.log(res.target); // 图片地址
bitmap.clear(); // 清除Bitmap对象
}, error => {
console.log(JSON.stringify(error)); // 保存失败信息
bitmap.clear(); // 清除Bitmap对象
});
// bitmap.clear(); // 清除Bitmap对象
}, error => {
console.log(JSON.stringify(error)); // 绘制失败
}, {
check: true, // 设置为检测白屏
});
注意
- 血的教训:之前能用,后面突然发现返回的路径一直是file://null,原来是 bitmap.save和 bitmap.clear的时间差的问题,一定要把bitmap.clear放在成功回调函数或者失败函数里面。
- 在获取本地对象时,如果使用plus.webview.currentWebview()获取本地对象,有可能截屏时出现黑屏现象。
let ws= plus.webview.currentWebview();
- 在APP端使用app端获取方法
// 因为这里只做app端,所以没有加上条件编译,如果多端的话加上APP的条件编译
let pages = getCurrentPages();
let page = pages[pages.length - 1];
let ws = page.$getAppWebview();
分享
- 使用uni-app的方法uni.share(OBJECT)
目标
点击分享按钮,截取APP屏,在分享到第三方,如微信,朋友圈。
截屏
方法一:html2canvas.js
- 这种方法需要获取DOM节点,app端获取不到。如果可以获取DOM节点,可以尝试使用此方法。
方法二:5+api
let pages = getCurrentPages();
let page = pages[pages.length - 1];
let ws = page.$getAppWebview();
let bitmap = new plus.nativeObj.Bitmap('drawScreen');
// 将webview内容绘制到Bitmap对象中
ws.draw(bitmap, () => {
// 保存图片到本地
bitmap.save("_doc/drawScreen.jpg", {
overwrite: true
}, res => {
console.log(res.target); // 图片地址
bitmap.clear(); // 清除Bitmap对象
}, error => {
console.log(JSON.stringify(error)); // 保存失败信息
bitmap.clear(); // 清除Bitmap对象
});
// bitmap.clear(); // 清除Bitmap对象
}, error => {
console.log(JSON.stringify(error)); // 绘制失败
}, {
check: true, // 设置为检测白屏
});
注意
- 血的教训:之前能用,后面突然发现返回的路径一直是file://null,原来是 bitmap.save和 bitmap.clear的时间差的问题,一定要把bitmap.clear放在成功回调函数或者失败函数里面。
- 在获取本地对象时,如果使用plus.webview.currentWebview()获取本地对象,有可能截屏时出现黑屏现象。
let ws= plus.webview.currentWebview();
- 在APP端使用app端获取方法
// 因为这里只做app端,所以没有加上条件编译,如果多端的话加上APP的条件编译
let pages = getCurrentPages();
let page = pages[pages.length - 1];
let ws = page.$getAppWebview();
分享
- 使用uni-app的方法uni.share(OBJECT)

关于使用HBuilder开发APP时,icon图标不能正常显示,只显示为一个白方块问题的解决
我是新手。网上也搜不到解决方法,至于官方能给你什么解决方法,呵呵。我提个问题还要扣积分。虽然是官方原本就有的。呵呵哒。果然不是AS 和VS。从来没有见过提个问题还要扣分。真的恶心。
对于mui.ttf文件配置在那的。我不知道,也没去学习过。但是就是这个文件导致了图标变成方框。无法加载。按道理字体和图标无关。教程上似乎也没说。反正就是这个文件最好放在根目录下。不要移动到其他目录。否则会导致图标无法加载。
我是新手。网上也搜不到解决方法,至于官方能给你什么解决方法,呵呵。我提个问题还要扣积分。虽然是官方原本就有的。呵呵哒。果然不是AS 和VS。从来没有见过提个问题还要扣分。真的恶心。
对于mui.ttf文件配置在那的。我不知道,也没去学习过。但是就是这个文件导致了图标变成方框。无法加载。按道理字体和图标无关。教程上似乎也没说。反正就是这个文件最好放在根目录下。不要移动到其他目录。否则会导致图标无法加载。
收起阅读 »
iOS App上架内购问题3.1.1条款被拒解决思路
1.如何选择苹果内购买项目?
如果开发者想在 App 中解锁如订阅、游戏内货币、游戏关卡等访问权限,则必须使用 App 内购买项目。App 内购买项目主要分为 4 种展现形式,如下图:
在产品页面上,开发者可一次性推广多达 20 个 App 内购买项目,并同时为其他通过审核的 App 内购买项目做好推广准备。这样开发者可以灵活地根据业务需求(比如,当开发者打算推出限时促销或提供独家内容的时候)来更换产品页面上展示的 App 内购买项目。
需要注意的是,如果你的产品是需要定期续费的,那需要选择订阅类型;但如果是一次性消费不要再次订阅的,则选择非订阅类型。
此次,我们公司用到的苹果内购产品有两种会员:一种是订阅自动续费的,一种是非订阅产品,不会自动续费的。
2.内购产品上架需与 App 上架的包一起提交
这里需要重点注意一下,内购产品上架的时候一定要和 App 上架的包一起提交审核,不可以分开提交。如果你选择分开提交了,那内购永远不可能上架成功的。亲测过,一定要注意了。
3.App 内购买项目多地区上架,选择本地化适配或可增加过审几率
在 App Store 审核条款中,也指出“如果我们无法理解 App 的工作方式,或者 App 内购买项目不是那么一目了然,则审核会有所延误,并可能会导致 App 被拒绝。”所以,如果你的产品要是在多个地区上架的话,建议开发者要做好 App 内购买项目本地化工作,以免审核被拒。
4.四种声明条款
①用户协议和隐私条款
在 App 登录页面和产品购买页面,一定要有用户协议和隐私条款这两个东西。
②连续包月协议说明和自动续费声明
如果你的产品中有自动订阅产品类型,则需要加入连续包月协议说明。同时,还要在 App 产品页面写上自动续费声明。
5.App 应用描述中加入自动订阅描述
如果还是有些担心 App 内购买项目审核被拒,则可以选择在 App 应用描述当中加入自动订阅的内容描述,让苹果审核人员能够更加直观、清楚的看到 App 内购买项目相关情况。
6.虚拟产品不要加入第三方支付的 SDK
以上内容都准备好了的话,那最后就需要注意一下:如果你的 App 内购买项目是虚拟产品的话,不包括实物,请不要加入支付第三方的 SDK,不然检测出来可能会被拒掉。
Tips:由于 App 内购买项目的产品 ID 具有唯一性,即生成一次后就无法再次生成同一个 ID 的产品,包括删除后也无法使用同一个 ID。因此在选择产品类型,填写产品 ID 时,需要注意避免填错或误删了。
1.如何选择苹果内购买项目?
如果开发者想在 App 中解锁如订阅、游戏内货币、游戏关卡等访问权限,则必须使用 App 内购买项目。App 内购买项目主要分为 4 种展现形式,如下图:
在产品页面上,开发者可一次性推广多达 20 个 App 内购买项目,并同时为其他通过审核的 App 内购买项目做好推广准备。这样开发者可以灵活地根据业务需求(比如,当开发者打算推出限时促销或提供独家内容的时候)来更换产品页面上展示的 App 内购买项目。
需要注意的是,如果你的产品是需要定期续费的,那需要选择订阅类型;但如果是一次性消费不要再次订阅的,则选择非订阅类型。
此次,我们公司用到的苹果内购产品有两种会员:一种是订阅自动续费的,一种是非订阅产品,不会自动续费的。
2.内购产品上架需与 App 上架的包一起提交
这里需要重点注意一下,内购产品上架的时候一定要和 App 上架的包一起提交审核,不可以分开提交。如果你选择分开提交了,那内购永远不可能上架成功的。亲测过,一定要注意了。
3.App 内购买项目多地区上架,选择本地化适配或可增加过审几率
在 App Store 审核条款中,也指出“如果我们无法理解 App 的工作方式,或者 App 内购买项目不是那么一目了然,则审核会有所延误,并可能会导致 App 被拒绝。”所以,如果你的产品要是在多个地区上架的话,建议开发者要做好 App 内购买项目本地化工作,以免审核被拒。
4.四种声明条款
①用户协议和隐私条款
在 App 登录页面和产品购买页面,一定要有用户协议和隐私条款这两个东西。
②连续包月协议说明和自动续费声明
如果你的产品中有自动订阅产品类型,则需要加入连续包月协议说明。同时,还要在 App 产品页面写上自动续费声明。
5.App 应用描述中加入自动订阅描述
如果还是有些担心 App 内购买项目审核被拒,则可以选择在 App 应用描述当中加入自动订阅的内容描述,让苹果审核人员能够更加直观、清楚的看到 App 内购买项目相关情况。
6.虚拟产品不要加入第三方支付的 SDK
以上内容都准备好了的话,那最后就需要注意一下:如果你的 App 内购买项目是虚拟产品的话,不包括实物,请不要加入支付第三方的 SDK,不然检测出来可能会被拒掉。
Tips:由于 App 内购买项目的产品 ID 具有唯一性,即生成一次后就无法再次生成同一个 ID 的产品,包括删除后也无法使用同一个 ID。因此在选择产品类型,填写产品 ID 时,需要注意避免填错或误删了。
收起阅读 »
unipush 厂商推送配置踩坑指南
1,如果应用使用的是Dcloud公共证书,
华为厂商sha256签名指纹需使用,
D7:5C:1F:A2:B9:AE:86:7C:E6:88:A8:AD:C6:DE:AC:7C:D6:BA:96:F4:3A:75:1F:D1:0A:20:0F:A5:97:4A:C6:36
(1)华为厂商不需上架应用到其应用平台,如果离线消息无法收到,请把华为移动服务更新至最新版本,并清除华为移动服务的缓存数据
参考帖子如下:DCloud公用证书信息(将过期不要再使用)
(2) Dcloud开发这平台应用配置sha1签名请使用
2,如果使用了自定义应用包名,包名尽量全部用小写
3,Dcloud开发平台发送透传消息格式示例:
其中消息内容格式为:{"title":"消息格式示例","content":"消息示例内容","payload":"100"}
intent格式:intent:#Intent;action=android.intent.action.oppopush;component=com.xxx.包名/io.dcloud.PandoraEntry;S.title=消息格式示例;S.content=消息示例内容;S.payload=100;end
4,希望小小分享可以帮到需要的小伙伴们!!!
1,如果应用使用的是Dcloud公共证书,
华为厂商sha256签名指纹需使用,
D7:5C:1F:A2:B9:AE:86:7C:E6:88:A8:AD:C6:DE:AC:7C:D6:BA:96:F4:3A:75:1F:D1:0A:20:0F:A5:97:4A:C6:36
(1)华为厂商不需上架应用到其应用平台,如果离线消息无法收到,请把华为移动服务更新至最新版本,并清除华为移动服务的缓存数据
参考帖子如下:DCloud公用证书信息(将过期不要再使用)
(2) Dcloud开发这平台应用配置sha1签名请使用
2,如果使用了自定义应用包名,包名尽量全部用小写
3,Dcloud开发平台发送透传消息格式示例:
其中消息内容格式为:{"title":"消息格式示例","content":"消息示例内容","payload":"100"}
intent格式:intent:#Intent;action=android.intent.action.oppopush;component=com.xxx.包名/io.dcloud.PandoraEntry;S.title=消息格式示例;S.content=消息示例内容;S.payload=100;end
4,希望小小分享可以帮到需要的小伙伴们!!!
收起阅读 »
ios 运行失败问题
ios 运行失败,更新后重启电脑,,,再去运行,为了防止问题,最好把原先基座卸载
ios 运行失败,更新后重启电脑,,,再去运行,为了防止问题,最好把原先基座卸载

安卓集成极光推送
第一.集成极光推送,首先要学会离线打包 官方教程 :https://ask.dcloud.net.cn/article/508
离线打包中出现的问题:
1.启动图片不展示问题:
需要把启动图片放入 src/main/res/drawable 文件夹中 名称splash.png
第二.极光推送官方教程: https://github.com/jpush/jpush-hbuilder-demo/tree/master
注意事项:
极光官方缺少一步,需要把jpush.js引入app首页 <script type="text/javascript" src="js/jpush.js"></script>
第一.集成极光推送,首先要学会离线打包 官方教程 :https://ask.dcloud.net.cn/article/508
离线打包中出现的问题:
1.启动图片不展示问题:
需要把启动图片放入 src/main/res/drawable 文件夹中 名称splash.png
第二.极光推送官方教程: https://github.com/jpush/jpush-hbuilder-demo/tree/master
注意事项:
极光官方缺少一步,需要把jpush.js引入app首页 <script type="text/javascript" src="js/jpush.js"></script>