新年伊始,终于把完成了搬家,将blog迁移到腾讯云上来,庆祝一下

–
上周开发上线了一个活动:从第三方点链接进入我们的站点,然后领取一个优惠券。使用微信oauth登录,每个用户可以领取一张券
周一,对方说,领取券后,需要给他们一个通知,使用URL上带的encrypt_code参数,调他们的一个接口~~三分钟,通知接口加进去了。当时也没多想:不就是调一个接口嘛
可是昨天看领取优惠券的数据时,发现有好多用户领取了不止一张,多的有三、四张。。。
分析问题,发现程序是先到mysql检查用户活动记录,如果没参加过,则新建优惠券、通知第三方、活动记录保存到数据库
逻辑很简单,怎么可能出问题。
再看活动记录:重复领取的记录,间隔的都很近,往往在1、2秒内
呵呵,猜到问题了吧~~给通知第三方加了日志,记录响应时间,。。。x,基本都在1秒以上
也就是说,用户进入页面、领券时,如果活动记录保存到数据库之前,再次进入活动页面,第二次进入的线程就查不到活动记录,还会继续发券~~不仅如此,这个接口本身,也拖慢了页面的响应时间,一般人看到时白页面,肯定会多刷几次
发现问题,基本也就解决了问题:
简单解决,将第三方通知放在保存活动记录之后
但是这样,页面响应时间慢的问题依然没有解决
所以,最终的解决方案是,将第三方通知改成异步进行~~
还是去年入手的海伯利安套装四部曲,因为太懒,今年才开始看;基本就是每天的地铁时间,一个小时左右,四本书前后看了近半年~~
读翻译书就是这样,第一部的时候读的还很慢,慢慢适应的了文风和剧情看的就快起来了
书归正传,说一下整部小说的情况
第一部,是故事切入,也是全系列最精彩的一部
通过六个朝圣者的视角,小的方面是在讲述每个人的经历,而大的方面,就是在向读者介绍霸主、环网、内核组成的整个世界体系。
关于朝圣者的故事,实话说,这第一个故事就被震撼到:为了不被十字形“附体”,神父将自己钉在特斯拉树上,被反复电击、活劈,仍然不放弃自己的信仰,最终“命享真死”(说句题外话,三体1拿了雨果奖,而在国人看来更精彩的三体2居然没有入围,我想原因之一,三体2里将宗教写的太“弱”太浅薄了,这和我们的文化没有冲突,但是到了欧美受基督教影响的文化圈里,就大不一样了)
学者的故事,老学者眼看着自己的女儿一天天“回到”从前,从成年,变成少年、童年,再到婴儿,做了父母的人,才能体验到那种束手无策的绝望心情
其他几个故事也都自成体系,又各具特色,同时逐步将你带你入作者的世界体系
对,就是世界体系,科幻小说,最吸引人的地方,并不全是故事情节,更重要的,是它所构建的“世界体系”
太空歌剧类科幻,距离/时间是个硬伤。
基地系列、星际迷航,用跃迁解决问题,甭管多少光年,一跳了事;三体,则是冬眠,用时间战胜距离,更具可行性
而海伯利安的前两部,用的是更高端、方便的“传送门”:用不同的传送门连接起若干世界的几个房间,吃饭在一个星球,睡觉在另一个,甚至拉屎也要在一个专门的星球上~~当然,这套系统,随着悦石一声令下,基本全完蛋了,到了第三、四部,可以理解成女主伊妮娅构建新体系的故事了
继续说回第一部,科幻小说的故事总是这样,作者往往会在开始时想你展示各种脑洞,然后后面慢慢填这些坑,应该说,在这方面,海伯利安做的一般—-有些坑填的很牵强,比如第一部的故事线索:光阴冢和伯劳,也可能是本人理解能力不够,看完了四部,也不甚明白这两样“神物”的来龙去脉
第二部,人工智能“内核”的阴谋逐渐逐渐明晰,驱逐者对故事的影响逐渐增大,到了后半部的高潮,内核灭绝人类的阴谋被发现,驱逐者成功洗白,霸主体系与内核同归于尽,残存人类退回到没有远距离传输手段的“前太空”时代。
看评论,很多人说,海伯利安系列前两部是经典,后两部完全多此一举
我部分同意这个观点
后两部的主人公,有点“神棍化”、“完人化”,而且情节太拖沓
不过抛开这两点,后两部的叙事更流畅,情节类似公路电影—-满宇宙秀恩爱大逃亡
如果后两部能把追击戏“打薄”一点、合成一部,或许会更好些
抛开主人公神棍情节不谈,第三、四部有几个情节还是堪称科幻脑洞经典的
宗教里的哲学体系
教外别传,不立文字,直指人心,见性成佛,伊妮娅借佛教禅宗来讲解她的“大彻大悟”
十字形
可以理解成超级生物电脑,记录一个人所有的人格、记忆,人类通过它实现“永生”
相对第一部提到的十字形,这里是改良型的,复活的人并不会变傻
大天使信使舰
一种可以短时间星系内跃迁工具,但由于跃迁时加速度过大,里面的人会全被“压死”。但是搭配上面的“十字形”一起使用,简直是绝配
环恒星生物圈
基于牛逼的基因改造技术,环绕恒星生长的大树生物圈,完全不需要行星支持
缔之虚
即所谓“缔结的虚空”,全书最形而上的神物,一种跨越时空的“媒介”。整部书里,超广义、霍金驱动等所有的“超科技”都基于对此的应用
ok,再说就剧透太多了,整体来讲,海伯利安四部曲整的不失为比肩基地三部曲的史诗级经典
基于微信的开发,用户信息是基础,甭管是关注后,还是强制授权,都需要调取“sns/userinfo”接口,提取用户消息
很多事情,我们觉得理所当然,比如,sns/userinfo返回的nickname和headimgurl,这俩不都该成对出现吗?要么都有、要么都没有
不过,事与愿违,到库里去看用户消息,还真有不少nickname、headimgurl只有一个,另一个是空的;特别是nickname,很多都是空的
发现问题,就要寻找原因、解决问题,首先看nickname
由于项目里对外部资源调用的日志记录比较齐全,通过openid,很容易就能发现,比如nickname是下面早这类值时:
存到库就是空的。。。字符集的问题
库里varchar用的是utf8,请教运维,可以不可通过修改字符集,来保存这类支付,无果。。。
无奈关键用户表就不要胡乱做测试,解决方案是加了一个字段,专门保存urlencode(nickname),提取用户信息、需要显示昵称的时候,加个简单判断,再把urldecode显示出来
然后是headimgurl为空的情况
这个再查日志就没啥办法了:就是空的
猜测是用户本来就没上传头像,微信客户端里显示的默认头像,但默认头像既然非用户上传,就认为用户无头像,所以userinfo接口就不给喽
没限制的微信号做这类实际测试,还好headimgurl为空只是极少数
——补充。。。上面说的昵称中的表情符太毒了,wordrpess居然也没能兼容,无奈只能用图
冰火第六季,今天终于播完了,一如往常:前八集拖沓絮叨,最后两集突然爆发
只不过,少了原著的支撑,第六季的很多细节显得很单薄
随着超长的第十集的结束,一堆悬而未决的问题也都尘埃落定了
虽然马大爷和剧集的编剧都说:剧集的剧情不代表原著,但是,相信大的情节走势,二者仍然是一致的
首先,小说第五部和剧集第五季的结尾,共同的悬念,雪诺到底死没死~~~不仅没有死,剧集最后还暴漏了另一个重要的、众多粉丝猜测已久的情节:雪诺是个塔格利安
史塔克家的第一对重逢,相信感动了N多发粉丝
私生子之战,史塔克夺回临冬城,三傻这次终于没犯傻,小指头的如意算盘没能如愿,北境再次回归到史塔克的手下
然后是龙母和她的弥林城,一方面开挂的龙女不仅搞来了多斯拉克的卡拉萨、而且驯服了她的龙;然后顺势救了岌岌可危的弥林城
好啦,预计第七季的主题:《塔格利安的回归》。。。
然后,总结一下第六季被灭门的家族~~下一季可以省一大笔经费了
拜拉席恩。。。其实上一季就已经死完了,这一季连名义上的托曼也死翘翘了。。。不要说大牛,估计大牛以后不会再出来了
波顿~~这个是众望所归,波顿父子的死法也都很到位
马泰尔,(这里估计会和原著有较大的出入)随着亲王父子被杀,多恩还剩一帮沙蛇,后面就安心做龙母的帮手吧
大麻雀~~这个不算家族,不过这个满嘴仁义道德的老头,相信大家早就看他不顺眼了
然后,是几个名存实亡的家族
佛雷~~~和波顿父子一样死的众望所归,虽然二丫似乎只杀了老佛雷和他的两个儿子,号称自己生了一支军队的佛雷,还有一大票的儿子孙子,但是从前面原著对佛雷家的描写看,离开了老佛雷,佛雷家必将大乱
徒利。。。原著里艾德慕开城投降钱,好歹放走了老黑鱼,剧集里居然把老黑鱼给搞死了。。。
提利尔~~和马泰尔类似,提利尔家也是的差不多了,不过剩下了一个最有头脑的老太太,看最后自己的形式,提利尔也将彻底倒向龙母
然后,预计下一季的两对主要对抗势力
兰尼斯特 Vs 塔格利安(+马泰尔+提利尔)
史塔克(+野人+谷地) Vs 异鬼
整体来讲,第六季,似乎已经把大部分悬念消耗完了,旁枝末节的角色们死的也差不多了
第七季,几个令人期待的悬念
异鬼如何突破长城
猎狗是否会单挑魔山
色后最终会是在谁的手里,二丫?半人?还是杀蛇?
然后,就是史塔克家能否如愿团聚。。。
很多时候,我们需要判断一个字符串扔到页面上显示时的宽度,这里,按照我自己的认知顺序说一下都有哪些判断方法
初级:str.length
这个不用说,大家都明白,最简单,也最不靠谱
但是有一个地方要注意:一个中文字符,js里的length是1,而php里,根据编码不同,可以是3或2
中级:正则判断支付类型,按不同的长度处理
1 2 3 4 5 6 7 8 9 10 11 | //判断字符串的长度,全角返回2,半角返回1 var b2reg = /[\u0000-\u00ff|\uff61-\uff9f|\uffe8-\uffee]+$/; function byteLen(s){ var c = 0; if(s && s.length > 0){ var a = s.split(""); for(i in a){ if(!b2reg.test(a[i])) c += 2; else c++; } } |
一般要求不严格的情况,我都是这么处理
但是,这个也不靠谱,中文没问题,但是英文字符的显示宽度,会受字体、浏览器的影响,比如“i”和“w”,很难确定这俩显示宽度是否相同
终结:实际显示宽度
有些复杂,不过思路也很简单:在页面的视口之外,做一个同样的显示区域,字体、样式什么的都一样,然后把文本写进去,再判断宽度;如果过宽,再截
这种方法还有一个经典的应用:输入框跟踪提示。
比如,在一个很大的输入框里,输入多个邮箱地址,要随着用户的输入,在输入点下方位置显示不同邮箱地址的提示,这里的一个难点就是:js里无法获得当前输入点的位置。于是就可以用上面的方法,时时获取显示宽度,然后用复杂的计算近似得到当前输入点的位置…
———
(好久不写技术文章了,一方面是懒,另外很关键的一点是…近半年都在做售货机屏幕的东西,缺少普适性)
小时候一直梦想有一支神秘马良的笔,后来才懂得,神笔只存在于童话中,想把大脑构思千百遍的东西做出来,只能靠自己的双手和合适的工具
于是开始发掘各种各样的工具:从剪刀、各种改锥,到烙铁、各种钳子扳手,再到电锯、电钻
相信每一个男孩子都或多或少的接触过这些工具,并且用它们来编制自己的梦想
用捡来的小马达、铁皮罐子剪出来的铁皮,做成小风扇
用撑温室大棚用的竹条、废旧的木条,做成弩弓
用拆开的自行车链条、车条、剪成条的内胎皮条,做成的火柴枪
自己在纸箱上画一个表盘,然后配上捡来的机芯、表针,重新拼成挂钟~~初中时做的,一直到我上大学,老妈都拿这个钟看时间
后来,开始接触电脑里的各种建模工具:flash、cad、3dmax,建模、然后几行代码的脚本生成动画
再后来,工作了,偶尔需要做些小游戏,和设计师一起,用更复杂的代码,完成更炫的动画
然而,电脑里建出模型和上面动手做的东西,却有一个不可逾越的鸿沟:虚拟与现实
即便时下流行的AR、VR,也只是把虚拟做的更具现实感
你不可能拿pc软件画出来的杯子喝水,更不可能用画出的大饼来填饱你的肚皮
但是,随着一件事物的出现,这一切似乎有了转机,这就是3D打印
简单来说,3D打印就是可以把虚拟建模出来的东西转变成现实的工具
—-对,它可以部分的完成神笔的功能~~虽然这种能力还很弱
以前的3D打印机价格都挺高,最少的也要三五千,所以限于囊中羞涩,一直没有出手购买
直到偶然在淘宝发现micromake“三角洲”3D打印机,相对于普通的方盒子机,这种打印机结构更简单,成本也更低,淘宝售价低配版¥999,虽然精度较低,但是做入门用也够了
到手的就是上图的这堆散件,把这些东西拼装起来,大约用了我一个下午的时间
现成的各类小玩意的3D模型并不难取得,搜一下一大堆
使用工具软件“cura”来连接打印机,导入“.stl”3d模型文件,直接打印,或者生成“.gcode”文件,放在sd卡再打印
使用3dmax建模,可以导出成stl文件,即可再用curl来完成后续的打印动作
整个过程很简单,但其中也有不少的坑:
1. 提前准备“704硅橡胶”(淘宝买,或者让打印机卖家送,五金店什么的很难找的,更不要用牙膏什么的替代…),新手使用不熟练,喷头、喉管的连接位置都很容易堵,这时就需要拆开清理,然后再组装时就需要特别注意:照教程吐足够的硅橡胶、两个螺口都要拧好晾干以后再做下一步的组装动作,否则打印时会漏的一塌糊涂。。。别问我怎么知道的,这个过程我已经做过五次了,最近一次才算弄利索
2. 3dmax和cura配合,长度单位协调有问题,都是“毫米”单位,3dmax到处的stl模型,导入到cura里,需要放大“25.4”倍~~~对,1英寸=25.4毫米,应该是3dmax到处时单位搞混了
3. 昨天刚刚发现的问题,建好的模型,或者下载的模型到了cura里,打印出可能会有问题,空心的东西莫名其妙会被封口,所以打印前一定要检查Layers分层
4. 打印机校准一次、校准对就行了,不用重复校准
5. 要有耐心,打印速度不要太快,30足够了,否则打印出来会特别粗糙~~这种速度,打印一个杯子预计需要五个小时以上
6. 打印材料方面,同样价钱、同样卖家的PLA材料,白色的最好用,红色的其次,黑色的最差
7. 建模方面,有个45度原则,就是倾斜的部分,不要超过45度,否则就需要加支架
8. 最后一点。。。不要抱太大希望,真的只是入门而已:打印出的东西很粗糙,表面是水波纹的,精度也就0.5mm
ok,基本就这么多,这个打印机做“神笔”确实有些吃力,也就是入门,打印一些精度不需要太高的东西,或者,搁家里做摆设也不错
↓↓–2016年05月28日 新加–↓↓
之前一直在试着用3dmax做建模工具,除了上面的单位问题,发现3dmax建模最大的问题是,稍微复杂一点的模型,打出来就会乱,变形、或分层错误
无奈,只能使用更专业的工具,solidWorks
最近一周在学solidWorks,很棒的工具
(很久没写东西,这里写一个自己熟悉的内容做2016的开篇吧)
相对于其他语言,js似乎很少有需要打印调用堆栈的时候,反正我是做了这么多年了前端,第一次用到,其他情况,基本都可以通过更好的方式来调试
今天面对的问题是这样的,有一个“ad_list”事件,在不停的触发,触发它的地方有N个,触发条件也不一样,靠简单的“console.log”,太多,可读性很差,所以想到了调用堆栈
不过,由于js里大部分是匿名函数,打印出来的堆栈并不像java、php一样简介。。。,打印名字不靠谱,而是,直接打印function内容,然后靠内容反查。。。。这就是很少有人用这一招的原因
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | if(action == 'ad_list'){//打印特定条件的堆栈 var caller = arguments.callee.caller, j = 0, s; while (caller) { j++; if(typeof caller == 'function' || typeof caller == 'object'){ s = (caller + '');//function变成字符串 //排除系统函数、框架函数 if(s.indexOf('function (event)') < 0 && s.indexOf('function (packet)') < 0 && s.indexOf('fn.apply') < 0){ console.log('【function】-----stack-----------', j, ': ', (caller + '').substr(0, 200));//打印funciton的前半截 } } caller = caller && caller.caller;//取下一级 if(j > 500){//限定数量 --- 这个一定不要漏了 return; } } } |
————
查了半天(真是半天)的问题,结果是自己给自己挖的坑:默认的图片/活动名称是按规约命名的,但是测试图省事没照这个规则做,才造成上面说的不停的刷新同一个东西。。。。。。
自从开始使用laravel,虽然没觉得这个框架本身有多好用,多“优雅”,但是还是从框架代码里学到了不少东西
首先是php命名空间的使用。php的namespace,说实话,相对于其他语言的并没有什么特色,不算好用,尤其是你的项目要include其他低版本的代码时会很麻烦
然后是数组字面量的定义方式,也就是用”[]”替代array(),可以让代码稍微简洁一点
最后值得说的,比较有价值的,就是函数的多返回值的使用
个人比较喜欢golang的一个比较大的理由就是go函数允许返回多个返回值,而php也可以利用list()函数来实现这一点
1 2 3 4 5 6 | function abc($a, $b){ //返回数组 return [$a+$b, $a-$b]; } list($x, $y) = abc(12, 3);//$x、$y即分别为abc()返回数组的两个值 echo $x, ', ', $y; |
运行结果:15, 9
~~如上,虽然比golang稍微麻烦些,但是还是比使用&传参带回值,或者手动分解返回数组,要“优雅”的多
做树莓派,为什么还要用nodejs?
没啥特别原因,主要是自己对nodejs+webscoket这套玩的比较熟,而对python的web开发没啥研究
所以,定下这样一个技术方案:python负责硬件驱动和底层逻辑,nodejs负责上面的“好玩”的东西,也就是一般说的“业务逻辑”,web/webscoket服务服务由nodejs提供~
那么问题来了,如何让python和nodejs可以方便的互相调用哪?
按照自己的知识储备,大概有三个方案,按照优先级:
1. 双向的webscoket
2. 单向的thrift,python和nodejs各建一个server端和一个client端,实现双向通信
3. 单向的http,和上面的思路类似,但是速度会比thrift慢一个数量级
webscoket最好用,可以和页面通讯实现极速统一,而且一个连接就可以实现双向通讯,~~但是协议相对复杂,本来想使用nodejs的socket.io,让nodejs做server,python做client,但是试了几种方案都连不上,如果手动实现webscoket通讯协议。。。算了吧,还是下一个吧
第2个方案其实也不顺利,由于对python不熟悉,thrift的库文件导了几次总是失败,拖了几天仔细研究了python的import方式才算成功
这样,整个系统的方案也就是这样
(我的初步打算是做一个能自主行走、壁障,又能用手机网页遥控的机器人,所以硬件部分是直流电机控制芯片、超声测距、红外感应)
下面就来分享一下nodejs与python基于thrift做互联的代码,根据这个方案,对于不熟悉python又想在树莓派上实现较复杂业务逻辑的人,完全可以将nodejs换成自己更熟悉的语言,对于thrift不熟悉的朋友,可以自己学习:http://thrift.apache.org
和一般的thrift的思路不同,为了简化代码逻辑,这里其实类似http接口,action类似url,post一坨键值对,然会返回一坨信息,之所以这么设计,是因为一直觉得thrift太啰嗦,每次添加新接口还要替换定义文件
thrift定义文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /** * 统一的返回值 */ struct Retu{ 1: i32 code, //返回码 2: string msg, //返回错误时的详细信息 3: map<string, string> data//map/字典 类型的返回值 } /** * 异常信息 */ exception UException{ 1:i32 errorCode, //错误码 1=参数错误,2=没有结果 99=系统异常 2:string errorMessage //错误描述 } service PiMessService{ Retu c(1:string action, 2:map<string, string> params) throws (1:UException ex), } |
python端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | #coding:utf-8 import sys, glob, os import event #这里是自己封装的python事件中心,可以订阅、触发事件,以后有机会单独给大家分享 rootPath = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(rootPath + '/lib/gen-py') #thrift库文件位置 from thrift.transport import TSocket from thrift.transport import TTransport from thrift.protocol import TBinaryProtocol from thrift.server import TServer import piMess.PiMessService as PiMessService import piMess.ttypes as ttypes #####server start##### class PiMessHandler: def __init__(self): self.log = {} #会被client调用的方法 def c(self, action, params): #收到一次调用,其实是触发一次特定事件,程序其他部分可以去订阅这个事件,返回给出返回值 rets = event.trigger('tele_' + action, params) msg = '' data = {} if(len(rets) > 0): #以第一个绑定的返回值为准,后续可以根据实际需求在扩展 if(type(rets[0]) == type('')):#只使用字符串和字典值 msg = rets[0] elif(type(rets[0]) == type({})): data = rets[0] return ttypes.Retu(200, msg, data) #启动服务 def serviceStart(): print 'PiMessService start on 9001 ...' handler = PiMessHandler() processor = PiMessService.Processor(handler) transport = TSocket.TServerSocket(port=9001) #python server端的端口 tfactory = TTransport.TBufferedTransportFactory() pfactory = TBinaryProtocol.TBinaryProtocolFactory() server = TServer.TSimpleServer(processor, transport, tfactory, pfactory) server.serve() print 'done~' #####server end####### #####client start##### __transport = TSocket.TSocket('localhost', 9002) #nodejs server端的端口 __transport = TTransport.TBufferedTransport(__transport) __protocol = TBinaryProtocol.TBinaryProtocol(__transport) __client = PiMessService.Client(__protocol) def clientOpen(): __transport.open() #供外部调用的向nodejs端发送消息的方法 def send(action, params): r = __client.c(action, params) print('call nodejs:', action, params, r) return r def clientClose(): __transport.close() #####client end####### if __name__ == "__main__": clientOpen() send('py-client', {'QQQ': 'ZZZ', 'abc':'PPP'}) serviceStart() |
nodejs端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | var thrift = require('thrift'); var Thrift = thrift.Thrift; var piMessService = require('../gen-nodejs/PiMessService'), ttypes = require('../gen-nodejs/piMess_types'); /***server start***/ var server = thrift.createServer(piMessService, { c: function(action, params, result){ //server,可以根据action值,调用对应的外部方法 console.log('piMessService.c:', action, params); retu = new ttypes.Retu({code:200, msg:'ok', data:null}); result(null, retu); } }, {}); server.listen(9002); /***server end*****/ /***client start***/ var connection = thrift.createConnection('localhost', 9001), client = thrift.createClient(piMessService, connection); //对外暴漏一个send方法 exports.send = function(action, params, callback){ console.log('exports.send:', action, params); client.c(action, params, function(err, response){ if(response){ console.log('response:', response); } callback && callback(err, response); }); }; exports.clientEnd = function(){ connection.end(); }; /***client end*****/ |
over
———
转载请注明出处:昆仑的山头