接上篇
使用socket.io开发微信点餐并在后台蓝牙打印小票功能(一)/)
我们现在已经能在app里打印了,但是我们想要的功能是在用户端点击下单,然后商家后台的手机要能根据用户下的订单打印出对应的票据出来,普通的ajax请求已经不能满足我们的需求了,用轮询的话性能太差,这时候就该轮到websocket协议出场了. websocket是长连接的,完全能满足我们的需求
所以第一步就是搭建websocket服务器,我是用nodejs开发,然后选择了express和socket.io这两个库.

使用socket.io这个库的原因是因为兼容性强,Socket.io将Websocket和轮询 (Polling)机制以及其它的实时通信方式封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码,用的人也多
由于手机和打印设备众多,如何让用户下单后就能让对应商家手机通知打印机打印呢?

我想到的是商家手机上安装的app存有唯一的id,每个用户通过扫描携带商家id的二维码,然后下单后再emit一个事件,携带id和要打印的数据到服务器,服务器再广播一个事件,把这个id广播出去,商家端app接收后再对比这个id和本地id是否相同,相同则打印服务端携带的数据
整体思路就是这样

说干就干,服务端代码简单


核心功能就是监听sendData事件,接收携带id和打印内容的数据,然后接收完毕就广播print事件,发送id和打印内容
商家客户端方面也不难,监听print事件,接受id和打印内容,如果接收到的id和本地设备id相同,则控制打印机进行打印
ps:要注意的是之前点击这个按钮是直接打印的,现在变成监听print事件,接收完数据再打印接收到的内容

用户网页端方面,我简单做了个demo,只有简单的html


如上图所示每次点击发送按钮的时候,都会把商家id和打印内容发送到服务器,然后服务器把id和打印内容广播到每台商家端app上,app接收到信息检测接收到的id是否和本地设备id相等,相等则打印接收到的内容
可以通过这个视频感受一下

ps:需要注意的是cordova打包后会出现无法连接socket,但是网页正常的现象,这是因为cordova默认只允许”file”协议,这时候应该装个cordova-plugin-whitelist,让cordova允许其他协议
即:

1
cordova plugin add cordova-plugin-whitelist

官网文档提到
By default, navigations only to file:// URLs, are allowed. To allow others URLs, you must add tags to your config.xml:

cordova默认只允许file协议,所以下面需要修改config.xml,让cordova允许http协议,添加下面内容

1
2
3
4
5
6
7
8
9
<!-- A wildcard can be used to whitelist the entire network,
over HTTP and HTTPS.
*NOT RECOMMENDED* -->
<allow-navigation href="*" />
<!-- The above is equivalent to these three declarations -->
<allow-navigation href="http://*/*" />
<allow-navigation href="https://*/*" />
<allow-navigation href="data:*" />

这样就没问题了

消息队列

我们现在的是实时打印的,万一商家端手机网络中断,蓝牙关闭,不小心把app关了,这样就会错过要打印的内容,这可不能容忍,所以我们还得给加上消息队列的功能
思路很简单:
在服务器端创建一个空数组,然后监听打印成功事件,如果没数据传回来就把sendData事件传过来的id和打印内容的 json 字符串 push 到这个数组里,遍历这个数组,如果成功了,就从这个数组剔除打印成功的元素
然后给客户端的打印成功的回调那里加一个emit事件,emit打印成功这个事件
这样一个消息队列就搞定了,原理简单,但是还有很多要注意的,比如隔多长时间遍历一次数组,多长时间取消打印回收内存等等

2016/08/05 更新

最后发现用数组很难维护,最后我使用mongoDB解决的,我的做法如下:
设置一个保存数据到数据库的函数,status(是否已经打印)都为false

每次接收到sendData事件就把数据存到数据库,同时监听hadPrint事件,商家app每次完成打印都会触发hadPrint事件



hadPrint被触发时,会根据orderId来查找对应订单,同时设置该订单的status(是否已经打印)为true

最后设置一个定时函数,定时检测数据库里status为false的订单,找到了就把它打印,这样我们的项目基本雏形就完成了,由于商业项目,不能具体描述代码详情,只能简单介绍下思路,抛砖引玉,希望对读者有所帮助

最后附上成果:




还有又被我浪费的一卷打印纸

(完)

近期公司要开发个能连接蓝牙,并用蓝牙打印的一个app,由于遇到的坑比较多,特此纪录
也许你会问,为啥前端还要研究硬件?
前端为啥就不能研究硬件,前端工程师首先是工程师,不能把眼界局限在前端.而且通过cordova,用JS的语法就能开发硬件了,还能提高自己JS姿势,何乐而不为?
我们选择的打印机是主流的芯烨XP-58IIH热敏蓝牙打印机,厂家也提供了足够多的文档,编程手册,和开发实例.
我选择了 vue 和 cordova 来开发这个app,然后就是蓝牙插件的选择了,然后这里就遇到了第一个坑,一开始使用了 cordova-plugin-ble-central 这个蓝牙插件,也能扫描出来打印机的蓝牙信号,就是死活连不上,在查了资料,折腾了几个钟后才恍然大悟,我用的是低功耗蓝牙的插件,于是就选择了另外一个蓝牙插件 BluetoothSerial ,总算是能连上了,连接代码如下:


连上了就该打印了,打印英文还是很简单的


但是,在打印中文的时候就出问题了,


这肯定不行,下意识查看厂家提供的资料
厂家提供的是安卓源码,首先查找核心打印部分的代码,很快就找到了

然后开始分析这段代码:
这段代码主要作用是获取输入框内容,然后进行字符串拼接,最后通过getBytes("GBK")来转换成gbk码的字节数组
JAVA代码简单,到JS这里就不是一回事了,首先就卡在gbk编码上了,花了好长时间查询JS怎么转gbk编码,发现js原生不支持gbk转换,最后才锁定一个库,不过年久失修,原地址失效了,找了好久才下载到,于是我把它用module.exports包装了下,然后放到github上了
传送门
这个库能把中文转换成gbk编码的十六进制字符串,格式是%xx%yy形式的,比如转换”哈”这个字,是转换成”%B9%FE”的,然而我通过一番搜索发现打印机需要的格式是十六进制字节数组的,即类似[‘0xB9’,’0xFE’]格式的,这个好办,我写了个转换函数,用 replace加正则去掉第一个”%”,然后用split(“%”)转换成数组,再用map加上”0x”就行.不过最后也发现,这个库不会转换数字和字母,于是我又用 str.charCodeAt(0).toString(16)转换成对应的ASCII码,再加上”0x”,就OK了,整个转换函数也写好了,不知道还有没有更好的方法

整个转换源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const $ = require('./gbk.js')
module.exports=function(str) {
const arr = []
for (let i = 0; i < str.length; i++) {
const result = $.encode(str[i])
const length = result.length
switch (length) {
case 1:
const c = result.charCodeAt(0).toString(16)
arr.push("0x" +c)
break
case 3:
arr.push("0x" + result.replace(/%/, ''))
break
case 6:
const tmp = result.replace(/%/, '').split('%')
const newArr = tmp.map(item => "0x" + item)
arr.push(...newArr)
break
}
}
arr.push('0x0a')
return arr
}


然后在组件处引入

1
const encode =require('../encode.js')

再把这里要打印的内容用encode函数包起来

最后就可以愉快的打印中英文啦

如果需要打印表单内容,把encode里面内容换成this.content就行

app原始界面:

ps:文章简单,填坑艰辛,望能为后来人提供有用的帮助信息

为什么要学正则表达式

虽然很多web开发者在忽视正则表达式后,还可以顺利工作,但在javascript中还存在一些问题,如果不用正则表达式,是没办法进行很好的解决的.

当然,也许还有其他的方式能够解决相同的问题.但通常,用一句正确的正则表达式很有可能就可以省略半屏幕的代码.

为什么正则表达式很牛

假设我们要验证一个字符串是否为格式正确的手机号码.手机号码开头都是1,然后后面都是数字,然后长度都为11位
让我们创建一个函数,对一个输入内容进行验证

1
2
3
4
5
6
7
8
9
function isPhoneNumber(candidate){
if(typeof candidate !== "string" || candidate.length!=11){
return false; //过滤明显不符合条件的输入内容
}
if(isNaN(candidate)){
return false; //过滤非纯数字的输入内容
}
return true;
}

虽然这段代码实现的很合理,检查了输入内容的类型和长度,但是对于一个简单的手机号码检查,看起来依然有太多的代码
现在考虑这样一种方式

1
2
3
function isPhoneNumber(candidate){
return /^1[0-9]{10}$/.test(candidate)
}

除了函数体内的一些深奥的语法以外,这种方式看起来更简洁,更优雅,不是吗?
这就是正则表达式的威力,而且这只是它冰山的一角.如果语法看起来像键盘上爬行的蜥蜴的话,也不要担心.

正则表达式进阶

让我们开始对正则表达式进行深挖,要学习正则表达式,就先了解正则表达式出现的由来

正则表达式解释

“正则表达式(regular expression)” 这个词源于中世纪的数学,当时,一个名叫Stephen Kleene的数学家,使用了一个名为”正则集合”的数学符号描述自动计算模式.但这不会帮助我们了解任何关于正则表达式的内容,那么让我们把它简单化,正则表达式通常被称为一个模式(pattern),是一个用简单方式描述或者匹配一系列符合某个语法规则的字符串.表达式本身包含了允许定义这些模式的术语和操作符.我们很快就会看到这些术语和操作符.

在 javascript 中,与大多数其他对象类型一样,有两种方式可以创建正则表达式:通过正则表达式字面量,或者通过构造 RegExp 对象的实例.

例如,如果要创建一个正则表达式 (或简称为正则(regex)),用于精确匹配字符串 “test” ,可以使用正则字面量:

1
var pattern = /test/;

正斜杠可能看起来有点奇怪,但是就像字符串使用引号进行界定的一样,正则字面量是用正斜杠进行界定的.

或者,我们可以构造一个 RegExp 实例,将正则作为字符串传入:

1
var pattern = new RexExp('test');

这两种格式在pattern变量中创建的正则表达式都是一样的.

在开发过程中,如果正则是已知的,则优先选择字面量语法,而构造器方式则是用于在运行时,通过动态创建字符串来构建正则表达式.

字面量语法优先于用字符串构建正则表达式的其中一个原因是反斜杠字符在正则表达式中发挥重要的作用(很快就能看到).但由于反斜杠字符在普通字符串中也是一个转义字符,所以,如果要在字符串内表示反斜杠,我们就要使用 \\ (两个反斜杠).这回让本来语法就很神秘的正则表达式变得更加怪异了.

除了表达本身,还有三个标志可以与正则表达式进行关联.

  • i 让正则表达式不区分大小写,所以/test/i不仅可以匹配 “test” ,还可以匹配 “Test” “TEST” “tEsT” 等.
  • g 匹配模式中的所有实例,而不是默认的只匹配第一次出现的结果
  • m 允许匹配多个行,比如可以匹配文本区元素(textarea)中的字

    这些标志将附加到字面量尾部(例如: /test/ig) 或者作为 RegExp 构造器的第二个字符参数 (new RegExp("test","ig")) .

阅读全文 »

「梦断代码」中对软件工程所面临的种种困难与艰难的描述,即便再过5年读也许都不过时。因为正如原作者所说,书中描写的是一队人马并肩扛起代码大石,虽历经磨难仍欲将其推上山顶的故事,而正是这种故事成就着今天全世界亿万台服务器和PC机上运行的各种软件,成就着人类不断超越实现更伟大的梦想。


当人们梦想把软件变成流水线式的工作,他们常会期盼标准化的插件.新西兰学者詹姆斯.诺博尔和罗伯特.毕多有时用’后现代程序员’的笔名共同协作,他们把这梦想叫做”乐高假设”:”未来,程序将由可服用的部件组合而成.软件部件将在全球范围内提供.软件工程将从编程的窠臼解放出来.” 从架子上取几样零件,拼在一起,马上就能得到可用的软件–不用在痛苦的编码了!


一帮牛人,不缺技术不缺钱,最终的结果却不如人愿。刚开始看的时候,还是很轻松很调侃的在看老外大牛们的囧事。可是越看越发现这本书里的很多事情其实每天都发生在自己的身边,让人后怕.

想要走向这种编程乌托邦之路的程序员大多都发现此路不通.诺博尔和毕多的研究指出了最大的路障.他们同另外两名同事一起研究了采用面向对象技术的真实程序中的大量软件对象,发现这些构建快完全不像是乐高积木.如果软件组件像乐高积木块,那么它们就该细小、不能再分、可被替代;它们互相之间应该更为相像;它们应该”只能与有有限相邻组件拼合.”然而,当诺博尔和毕多观察真实程序时,他们却发现,真实程序中的组件在尺寸上,功能上以及与其他组件的可拼合数量上差异甚大.它们”大小不一,就像不规则的形体,不像乐高积木.”诺博尔和毕多发现了它们称之为”普遍多样性”的现象:目力所及之处,有常者惟无常. 想想看一套乐高积木,其中一些积木块只有半英寸长,而其他积木块则长达半英里:有些用硬塑料制成,有些则是液态或气态;有些积木块藉由大家熟悉的凹凸就够相互连接,而另一些则用上了焊接,胶水或绳索


「梦断代码」虽然是2008年出版的书,但是也反映了很多现实中开发的问题,比如比较火的React JS的开发模式正是组件化开发,写好组件后就可以像搭积木一样拼在一起使用,看起来很美,但是实际开发工作中,由于React 生态并不完善,组件库不多,比如轮播图组件在社区里都找不到,还得自己造轮子.而且由于不同组件之间需要通信,组件多了通信就容易变得复杂,又不得不引入flux这种架构模式来管理状态和处理不同组件间的通信,个人感觉这种方式给组件增加了耦合度


可复用软件之梦有一个悖论:几乎总能找到一段满足大部分需要的代码。但这些拿来的代码所不能做到的部分,恰恰是项目与众不同的创新之处—-也是创建这个项目的出发点。


这些程序组件仓库可能是软件世界中最接近乐高之梦的部分了.但它却远不及最热心的贡献者们所期盼的那样有用并且被采用.在同一类组件库中,往往有许多种不同选择,每种选择还有许多不同版本.可用的代码以及每种代码包所能做的事如此之多,多到连老手们有时都会忘记有哪些可用代码;他们停止了四处找寻,转而从头开始编写某些其实是现成的功能.


尽管有github这个开源社区,但是很多前端er往往会因为各种理由选择造社区已经很成熟的轮子,比如因为性能或体积问题,选择自己造轮子,问题是这些轮子往往质量很差,不能在其他人的项目中使用

生产出通用构造块式软件包并不容易,这显而易见.尽管屡遭失败,乐高之梦仍然在现代编程史上投射下长长的影子.对于路上的每一个障碍,一代又一代程序员总能找到借力之法,避免自行开拓之苦.


模块化和组件化是软件人员的梦想,谁都想把几个模块插到一起就可以完美的运行并完成任务,但现实却相当残酷,可以运行的模块通常不能与自己想写的程序配合工作,好的源代码由于商业利益也不太容易找到,程序员只能自己另起炉灶,搭建自己的模块,但结果还是一样,做出来的东西难以让他人共享,这个现象周而复始,不断地在多个程序员身上上演,让人深思.

要打造一个产品,远比最初估计的难得多 不要过度设计,重造车轮,框架,顶层设计,从可行的简单方案开干;

前言:

React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。

此教程的demo基于阮一峰的React入门实例教程的demo使用ES6语法重写而来(原博地址)

安装

React 的安装包,可以到官网下载。不过,React Demos已经自带React源码,不用另外安装,只需把这个库拷贝到你的硬盘就行了。

1
$ git clone git@github.com:binaryify/react-demos.git


12个例子在各个 Demo 子目录,每个目录都有一个 index.html 文件,由于是使用了src=app.jsx的形式,所以需要在服务器运行,可以使用http-server这个工具来打开demo

http-server

1
$ npm install -g http-server

安装后在dmeo文件夹下执行http-server,然后打开’127.0.0.1:8080’就行了

本教程只涉及浏览器。一方面是为了尽量保持简单,另一方面React的语法是一致的,服务器的用法与浏览器差别不大。Demo13 是服务器首屏渲染的例子,有兴趣的朋友可以自己去看源码。

关于不成长

看了CFF前端交流会winter老师的分享,感觉收获还是挺多的,尤其是讲到不成长这段.
winter老师讲到了他刚加入阿里无线前端的时候开始带团队,怕他带的人跟着他会不成长,一直非常焦虑.他总结了3个不成长的方式:

  1. 老黄牛:三年都在重复前三个月的经验
  2. 布道师:会很多技术,但都不是自己的
  3. 好人:永远出现在感谢列表上的人

老黄牛

三年都在重复前三个月的经验,有时候你认为你努力了,但其实你只是重复做之前做过的事,做几年还不如一个新来的

布道师

比起老黄牛更具有麻痹性,很多人都没意识到,追着国外的技术学,追着最新的技术用,什么都会,技术用的都很高端,做什么看看文档就行,但是没有一个技术是自己的,让他自己做个东西出来,文档有就照文档做,文档没有就做不出来

好人

永远出现在感谢列表的人.产品出来后,产品经理说:感谢前端的大力支持…
好人-产品经理说什么就干什么,把产品经理提的要求都做了,也没自己的想法,一直默默无闻.成就了他人,自己却很困惑.

与其想方设法让自己成长,不如避免自己的不成长

面向切面编程

Aspect Oriented Programming(AOP),面向切面编程,是一个比较热门的话题。AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。比如我们最常见的就是日志记录了,举个例子,我们现在提供一个查询学生信息的服务,但是我们希望记录有谁进行了这个查询。如果按照传统的OOP的实现的话,那我们实现了一个查询学生信息的服务接口(StudentInfoService)和其实现 类 (StudentInfoServiceImpl.java),同时为了要进行记录的话,那我们在实现类(StudentInfoServiceImpl.java)中要添加其实现记录的过程。这样的话,假如我们要实现的服务有多个呢?那就要在每个实现的类都添加这些记录过程。这样做的话就会有点繁琐,而且每个实现类都与记录服务日志的行为紧耦合,违反了面向对象的规则。那么怎样才能把记录服务的行为与业务处理过程中分离出来呢?看起来好像就是查询学生的服务自己在进行,但却是背后日志记录对这些行为进行记录,并且查询学生的服务不知道存在这些记录过程,这就是我们要讨论AOP的目的所在。AOP的编程,好像就是把我们在某个方面的功能提出来与一批对象进行隔离,这样与一批对象之间降低了耦合性,可以就某个功能进行编程。

阅读全文 »

组合模式

组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。掌握组合模式的重点是要理解清楚 “部分/整体” 还有 ”单个对象“ 与 “组合对象” 的含义。
组合模式可以让客户端像修改配置文件一样简单的完成本来需要流程控制语句来完成的功能。
经典案例:系统目录结构,网站导航结构等。

模式作用:

  1. 你想表示对象的部分-整体层次结构时
  2. 你希望用户忽略组合对象和单个对象的不同,用户将统一地使用组合结构中的所有对象(方法)

注意事项:

  1. 该模式经常和装饰者模式一起使用,它们通常有一个公共的父类(也就是原型),因此装饰必须支持具有add,remove,getChild操作的component接口
阅读全文 »

职责链模式

责任链模式是一种设计模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。

模式作用:

  1. dom的冒泡有些类似职责链
  2. nodejs当controller有很多负责操作逻辑的时候拆分中间件
  3. 解耦发送者和接收者

注意事项:

  1. JavaScript中的每一次【.】是有代价的,在有必要的时候应用
    阅读全文 »

模版方法模式

模版方法是一种只需使用继承就可以实现的非常简单的模式
模版方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类.通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序.子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法

模式作用:

  1. 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现
  2. 各子类中公共的行为应被提取出来并集中到一个公共父类中,避免代码重复,不同之处分离为新的操作,最后用一个钩子的模版方法来替换这些不同的代码
  3. 控制子类扩展,模版方法只在特定点调用”hook”操作,这样就允许在这些点进行扩展

注意事项

  1. 和策略模式不同,模版方法使用继承来改变算法的一部分,而策略模式使用委托来改变整个算法
阅读全文 »