$ node > .help .break Sometimes you get stuck, this gets you out .clear Alias for .break .editor Enter editor mode .exit Exit the repl .help Print this help message .load Load JS from a file into the REPL session .save Save all evaluated commands in this REPL session to a file > .editor // Entering editor mode (^D to finish, ^C to cancel) let a = 2 b = a console.log(b)
2 undefined > .editor // Entering editor mode (^D to finish, ^C to cancel) 'use strict'; let a = 2 b = a console.log(b)
Thrown: SyntaxError: Identifier 'a' has already been declared > b = a 2 > .editor // Entering editor mode (^D to finish, ^C to cancel) 'use strict'
$ node Welcome to Node.js v12.18.1. Type ".help"for more information. > process.version 'v12.18.1' > os.type() 'Darwin' > os.arch() 'x64' > (To exit, press ^C again or ^D or type .exit) >
很明显的是进入命令行之后,多了
1 2
Welcome to Node.js v12.18.1. Type ".help"for more information.
$ node Welcome to Node.js v12.18.1. Type ".help"for more information. > .help .break Sometimes you get stuck, this gets you out .clear Alias for .break .editor Enter editor mode .exit Exit the repl .help Print this help message .load Load JS from a file into the REPL session .save Save all evaluated commands in this REPL session to a file
Press ^C to abort current expression, ^D to exit the repl > .editor // Entering editor mode (^D to finish, ^C to cancel) let a = 1; let b = a; console.log(b);
$ node Welcome to Node.js v12.18.1. Type ".help"for more information. > .load Failed to load: > .load /Users/durban/nodejs/test.js lethash = "83108adff87ba432cd03d15f9f82db16"; function enHash(key) { key = key instanceof Buffer ? key : new Buffer(key); var p = 16777619; // 32 var hash = 0x811C9DC5;
for (var i = 0; i < key.length; i++) { console.log(hash * p + '======' + key[i]); console.log((hash * p) ^ key[i]); hash = (hash * p) ^ key[i]; }
这里需要注意的是产品 ID 具有唯一性,建议使用项目的 Bundle Identidier 作为前缀后面拼接自定义的唯一的商品名或者 ID(字母、数字),这里有个坑:一旦新建一个内购商品,它的产品ID将永远被占用,即使该商品已经被删除,已创建的内购商品除了产品 ID 之外的所有信息都可以修改,如果删除了一个内购商品,将无法再创建一个相同产品 ID 的商品,也意味着该产品 ID 永久失效,一般来说产品ID有特定的命名规则,如果命名规则下有某个产品 ID 永久失效,可能会导致整个产品ID命名规则都要修改,这里千万要注意!
另外内购商品的定价只能从苹果提供的价格等级去选择,这个价格等级是固定的,同一价格等级会对应各个国家的货币,也就是说内购商品的价格是根据 Apple ID 所在区域的货币进行结算的,比如:一个内购商品你选择等级1,那么这个商品在美区是 0.66 美元,在中区是 6 元人民币,在香港去是 8 港币,这些价格一般是固定的,除非某些货币出现大的变动(印象中有过一次卢布大跌,苹果调整过俄区的价格),价格等级表可以点击上图右边的 所有价格和货币 查看
iOS 11 用户可以在 App Store 内 App 的下载页面内直接购买应用的内购商品,这项功能苹果称作做 Promoting In-App Purchases,如果你的 App 需要在 App Store 推广自己的内购商品,则需要在上图的 App Store 推广 里上传推广用的图像,另外苹果也在 iOS11 SDK 里面新增了从 App Store 购买内购项目跳转到 App 的新方法
配置沙箱测试账号
内购也是需要测试的,但是内购涉及到钱,所以苹果为内购测试提供了 沙箱测试账号 的功能,Apple Pay 推出之后 沙箱测试账号 也可以用于 Apple Pay 支付的测试,沙箱测试账号 简单理解就是:只能用于内购和 Apple Pay 测试功能的 Apple ID,它并不是真实的 Apple ID,下面看如何创建 沙箱测试账号
Itunes Connect 后台选择 用户和职能,选择 + 添加测试账号
填写沙箱测试账号信息需要注意以下几点:
电子邮件不能是别人已经注册过 AppleID 的邮箱
电子邮箱可以不是真实的邮箱,但是必须符合邮箱格式
App Store 地区的选择,测试的时候弹出的提示框以及结算的价格会按照沙箱账号选择的地区来,建议测试的时候新建几个不同地区的账号进行测试
配置好测试账号之后,看一下沙箱账号测试的时候如何使用:
首先沙箱测试账号必须在真机环境下进行测试,并且是 adhoc 证书或者 develop 证书签名的安装包,沙盒账号不支持直接从 App Store 下载的安装包
去真机的 App Store 退出真实的 Apple ID 账号,退出之后并不需要在App Store 里面登录沙箱测试账号
然后去 App 里面测试购买商品,会弹出登录框,选择 使用现有的 Apple ID,然后登录沙箱测试账号,登录成功之后会弹出购买提示框,点击 购买,然后会弹出提示框完成购买
case .purchasing: transcationPurchasing(transcation) case .purchased: transcationPurchased(transcation) case .failed: transcationFailed(transcation) case .restored: transcationRrestored(transcation) case .deferred: transcationDeferred(transcation) } } }
实际上即使把上面的问题全部考虑到了依然还可能出问题,比如用户切换了 Apple ID 这个时候钥匙串里持久化的订单信息也会发生改变、或者验证成功之前用户换新的手机登录或者用户其他的不可预知的操作行为依然会导致错漏单的问题,但是我觉得内购大部分时候是用户一种强意识的行为,上面这些操作只能是认为用户在阻止自己购买成功,所以我们最终是无法百分之百避免错漏单的,手动去处理这些订单是非常有必要的
而且苹果在上线审核的时候也是使用沙盒账号测试的,那如何识别App端发过来的收据是沙盒测试还是正式环境用户的购买呢?这里服务端就要采用双重验证,即先把收据拿到正式环境的验证地址去验证,如果苹果的正式环境验证服务器返回的状态码 status 为 21007,则说明当前收据是沙盒环境产生,则再连接一次沙盒环境服务器进行验证,这样不管是我们自己采用沙盒账号测试还是苹果审核人员采用沙盒账号进行审核、或者用户购买都可以保证收据正常的验证成功
沙盒测试遇到的其他问题
在进行沙盒测试的时候,遇到过 IAP 沙盒服务器出现异常,购买过程非常的慢,而且总是超时,返回购买失败,谷歌到有同行也出现过沙盒购买缓慢的问题(iTunes Sandbox Extremely Slow),所以如果确定代码没有问题,多半就是 IAP 的沙盒服务器出现了问题,建议换个时间再测试,可以通过 System Status - Apple Developer 查看苹果公司开发者相关的服务的服务状态
CREATEPROCEDURE update_records(IN activityid INTEGER ,ids text) BEGIN UPDATE wall_records SET activity_id = activityid WHERE FIND_IN_SET (cid,ids); # 等于 where ids in ('1,2,3') END;
functionhandleFiles(file) { var orientation = null; EXIF.getData(file, function () { EXIF.getAllTags(this); orientation = EXIF.getTag(this, 'Orientation'); }); var t = this; var reader = newFileReader(); reader.readAsDataURL(file);
reader.onload = function () { var result = this.result; var img = newImage(); img.src = result;
img.onload = function () { var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); canvas.width = t.getImage.width; canvas.height = t.getImage.height; ctx.drawImage(this, 0, 0, t.getImage.width, t.getImage.height);