最近在工作上需要在微信上开发小程序。作为一个熟练于电脑客户端开发,网页前后台全栈开发,驱动开发,系统底层开发等多年经验的老鸟而言,刚开始接触小程序任务时居然一时有点懵逼,这是任何人面对全新领域时的正常状态,经过一段时间摸索后我很快掌握了小程序开发的基本要领,有关小程序开发的资料很多,但在我看来能够让一个毫无小程序开发经验的人能在短时间内快速上手掌握教程还是不多,因此我想把自己做过的项目展现出来,只要你跟着完成一遍,小程序的开发技巧基本就掌握了,本文能够起到省时省力的效果。
如果你有过前端开发经验,那么你会体会到小程序其实就是把前端开发时的浏览器转换为微信APP,如果你了解reactjs的开发模式,你也会体会到小程序的开发框架与思路其实与reactjs如出一辙,我一度怀疑腾讯将reactjs做了点变换,然后搬过来成为小程序的开发模式。
腾讯为了支持小程序生态,特意开发了一套专门用于小程序开发的IDE名为“微信开发者工具”,其易用性超出了我的想象,因此君欲成其事必先利其器,开发的第一步就是下载该工具。运行起来后,通过选择“项目”->“新建项目”就可以快速构建一个可以运行的小程序了,程序代码的整体布局如下所示:
上图中几个模块需要注意,小程序使用的开发语言是javascript,因此项目中带有.js后缀的文件都是写代码的地方。小程序的开发也完全遵守MVC结构,因此开发需要完成三部分工作,一部分是定义或设计小程序的界面UI,一部分是负责处理业务逻辑,一部分是将业务逻辑生成的数据与界面UI绑定以便于用户进行交互。
小程序开发的特定规范是,每个”模块“都对应四个文件,后缀分别为.json,.js,.wxml,.wxss,.json用于配置一些全局数据,它有点类似于windows系统里面的注册表,通过设定一些特定变量的值就能够产生一定范围内的影响,例如打开index目录,打开里面的index.json文件,然后添加如下内容:
{ "navigationBarTitleText": "请拍摄身份证正面", "usingComponents": {} }
然后运行程序,你会看到界面最上面的标题变成了“请拍摄身份证正面”,由此可见在.json里面配置一些特定变量的内容就会对模块的运行产生特定影响。小程序本质上是一种页面应用,就像开发网页应用需要使用html标签语言来设计界面UI,小程序也需要在.wxml为后缀的文件里,通过腾讯定义的标记语言来设计界面,小程序的标记语言其实与HTML差不多但略有修改。我们先不管index里面的内容,先在app.json里面添加内容:
"pages":[ "pages/prepare/prepare", "pages/index/index", "pages/logs/logs" ],
在该文件中有很多内容,这里我们改动的只是加了第一行,修改完保持后程序立马发生了变化,首先在pages目录下多了一个prepare文件夹,打开可以看到里面全是以prepare开始但后缀分别为.js, .json,.wxml,.wxss的文件,在网页前端开发时,我们需要使用css来设定各种控件的属性,在小程序里.wxss这个文件就是用来写css的地方,而.wxml就是用来写类似HTML代码的地方,我们打开wxml文件去定义模块的界面:
<!--pages/prepare/prepare.wxml--> <view> <view class="title"> <text >强州学员数据采集小程序</text> </view> <button bindtap="open_camera_model">点击启动</button> </view>
然后打开.wxss文件在里面添加:
/* pages/prepare/prepare.wxss */ .title { text-align: center; line-height: 200rpx; }
这里需要注意的是在微信小程序里,一个像素对应的单位是rpx,在网页前端开发时对应的是px,打开.json文件,在里面添加:
{ "navigationBarTitleText": "强州培训学校数据采集", "usingComponents": {} }
上面代码完成后所得界面如下所示:
接下来我们要做得是响应按钮的点击事件,注意到在小程序里事件对应的是bindtap,在网页上对应的就是onclick,我们进入.js文件,在里面实现bindtap对应的响应函数open_camera_model,
‘’‘
open_camera_model: function(e) {
console.log(e)
wx.navigateTo({
url: ‘/pages/index/index?from=prepare’,
success: res=> {
console.log(“from prepare jump to index”)
},
fail: res=> {
console.log("navigae fail: ", res)
}
})
},
’‘’
在.js文件里面有很多模块的生命周期函数,例如onlaunch是模块加载时被调用的函数,如果你了解reactjs,那么该函数其实对应oncomponentdidmout,这些周期函数我们暂时不需要关注。在按钮的响应函数里,我们需要实现从当前模块跳转到另一个模块,因此我们需要调用微信提供的接口navigateTo,在小程序里,关键字wx其实对应于页面前端开发中的window,它本质上可以看做一个操作系统内核,很多小程序需要的功能,例如文件读取,数据发送等等都需要通过它来实现。
在上面代码中要跳转的对象是index模块,该模块也是新建项目完成后对应的默认模块,因此上面代码执行后界面会切换到项目生成时的默认界面,到这里有一定开发经验的程序员基本上就能上手小程序开发了,接下来我们将介绍小程序另一个重要功能,那就是如何与后台服务器进行交互。
首先我们先完成拍照模块,以下的功能并非我原创,而是从网上搜来的,在这里我借花献佛,通过“拿来主义”加快我自己项目开发速度的同时,也能帮助读者朋友进一步了解小程序的开发技巧,首先进入index目录下,将.wxml里面的内容情况,然后添加如下代码:
<view class="camera_box"> <camera class="camera" wx:if="{{!show}}" device-position="back" flash="off" binderror="error"> <cover-view class="id_m"> <!-- 这是拍照轮廓图(如果是人像拍照则替换图片即可) --> <cover-image class="img" src="https://img-blog.csdnimg.cn/2020081419131165.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDE5ODk2NQ==,size_16,color_FFFFFF,t_70#pic_center"></cover-image> <!-- END --> <!-- 这是文字提示(不需要删除即可) --> <cover-view class="tips_txt">请将头像面放到框内,并调整好光线</cover-view> <!-- END --> </cover-view> </camera> <image class="camera_img" src="{{src}}" wx:if="{{show}}"></image> <view class="action"> <!-- 这是三个按钮:拍照 / 确认 / 重拍(取消) --> <button class="takeBtn" type="primary" bindtap="takePhoto" wx:if="{{!show}}"></button> <button class="saveImg" type="primary" bindtap="saveImg" wx:if="{{show}}"></button> <button class="cancelBtn" wx:if="{{show}}" type="primary" bindtap="cancelBtn"></button> <!-- END --> </view> </view>
上面的代码使用了摄像头控件,也就是camera,运行后它展现的模式就跟我们打开手机的摄像头程序一样,这里有值得注意的地方是UI与程序逻辑的联动,注意在camera组件里有一句wx:if="{{!show}}",其中wx:if是一条判断指令,它会告诉小程序判断变量show的值,该变量定义在.js文件里,如果该变量的值为true,那么就运行camera组件,如果为false那么camera组件就不运行。
这种联动机制非常重要,他们能够让我们通过代码逻辑来控制UI的设计,例如控制某些组件在给定条件下才出现等等,或者是让界面显示的数据与程序运行过程联动起来,当后台数据变化后前端UI显示的数据也跟着进行相应变化,了解reactjs开发的朋友一定很容易理解这种机制。打开wxss文件,添加如下内容:
.camera_box { height: 100vh; width: 100vw; position: relative; } .camera { height: 85vh; width: 100vw; z-index: 1; } .id_m { height: 85vh; width: 100vw; z-index: 999; background: rgba(0, 0, 0, 0.1); display: flex; position: absolute; } .id_m .img { width: 550rpx; height: 900rpx; display: block; position: absolute; left: 0; right: 0; margin: auto auto; top: 0; bottom: 0; } .id_m .tips_txt { transform:rotate(90deg); } .camera_box .action { height: 15vh; position: relative; display: flex; justify-content: space-around; align-items: center; } .camera_box .takeBtn { height: 120rpx; width: 120rpx; border-radius: 50%; font-size: 24rpx; background: url('https://cdn.ctoku.com/1123123123123e3241.png') no-repeat center; background-size: contain; border: none; } .camera_box .cancelBtn { font-size: 24rpx; height: 120rpx; width: 120rpx; border-radius: 50%; background: url('https://cdn.ctoku.com/12311123342312231.png') no-repeat center; background-size: contain; border: none; } .camera_box .saveImg { background: url('https://cdn.ctoku.com/1232123434231231231.png') no-repeat center; font-size: 24rpx; height: 120rpx; width: 120rpx; border-radius: 50%; background-size: contain; border: none; } .camera_box .takeBtn::after { border: none; } .camera_img { height: 85vh; width: 100%; } .id_m .tips_txt { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) rotate(90deg); width: 542rpx; height: 35rpx; font-size: 34rpx; color: #EA952F; font-weight: 500; }
上面代码完成后所得界面如下:
界面实现上的技术细节读者朋友可以直接谷歌,接下来我们看看如何点击按钮后采集图像数据,首先我们进入到对应的.js文件,首先需要添加的是与界面UI联动的变量定义:
data: { src: '',//拍照后图像路径(临时路径) show: false//相机视图显示隐藏标识 },
里面的show变量对应的正是前面camera组件里的show变量,如果我们使用代码将该变量的值设置为false,那么camera组件就不会运行,需要注意的是,show变量的改变不能直接进行赋值,它的变更方法后面我们可以看到,接下来我们先完成点击圆形按钮后的响应代码:
// 点击拍照按钮 takePhoto() { // 创建camera上下文CameraContext对象 // 具体查看微信api文档 const ctx = wx.createCameraContext() // 获取camera实时帧数据 const listener = ctx.onCameraFrame((frame) => { //如果不需要则注释掉 }) // 实拍照片配置 ctx.takePhoto({ quality: 'high',//成像质量 success: (res) => {//成功回调 this.setData({ src: res.tempImagePath,//tempImagePath为api返回的照片路径 show: true }) }, fail: (error) => {//失败回调 //友好提示... } }) },
代码首先通过 wx.createCameraContext获得摄像头对象,然后调用takePhoto来实现图像获取功能,一旦成功拍照后,success对应函数会被调用,res.tempImagePath对应拍照图像存储的路径,在这里代码更改了show变量,一定要注意,它使用接口setData来更改,只有通过setData来更改data对象里面的数据时,界面UI才能与程序逻辑联动起来,如果采用this.data.show=true这种方式,那么界面就不会产生任何变化,这一点与reactjs里面的setState完全一模一样。
回看wxml里面的代码,当show变量为true时,摄像头控件会失效,接下来image控件以及两个个按钮控件就会显示出来,于是我们在js文件里面继续实现这三个按钮对应的功能:
saveImg() { wx.showLoading({ title: '身份证识别中...', mask: true, }) wx.getFileSystemManager().readFile({ filePath: this.data.src, encoding: 'base64', success: result=> { let img_encoded = `data:image/png;base64,${result.data}`; console.log('image base64 encode: ', img_encoded) } }) }, // 取消/重新拍照按钮 cancelBtn() { this.setData({//更新数据 show: false }) },
取消按钮的逻辑很简单,它只要重新启动摄像头控件即可,如果是确认按钮,那么我们就调用wx.showLoading来展示一个正在处理的动画特效,然后通过wx.getFileSystemMananager().readFile,将前面通过拍照得到的图像文件读取到内存,然后进行base64编码,这是在图像通过网络传输前必做的准备。
接着是小程序开发的要点,那就是与后台服务器进行数据交互,我们需要将刚才拍摄到的图像传递给后,让后台识别图像里面的内容,并将识别结果返回给小程序。腾讯对小程序后台服务器有比较严格的要求,首先要求服务器必须具备已经通过ICP备案的域名,第二是域名必须要有ssl证书,同时通讯必须走https或wss协议。
我个人绝对最方便的是在腾讯云上买一台服务器,申请一个域名,然后使用腾讯云提供的免费ssl证书功能,这些要点都可以通过谷歌获得,我在对服务器进行免费认证后从腾讯云下载了认证证书,它是一个压缩包,解压后里面有好几个名字分别为apach, IIS, Nginx, Tomcat的文件夹,每个文件夹里面都是RSA的公钥证书,我选择了Apache里面的证书,然后使用Flask作为后台服务器。
假设我们的服务器域名为xxxx.com,同时服务器程序监听的断开为5007,那么我们打开往下公众平台,通过手机微信扫码后进入小程序测试号,在服务器域名处设置"request合法域名“为https://xxxx.com:5007,然后保存即可。如果你没有自己的域名,那么也可以通过小程序开发工具中的右上角点击详情,点击”本地设置“,勾选”不校验合法域名,。。。“那个选项:
勾选了之后,在开发时可以使用任何服务器,同时可以走http协议,但是小程序要发布的话还是必须像前面所说的那样配置,我们先看后台服务器的基本代码框架:
import json from flask import Flask from flask import request from flask import jsonify from flask_cors import CORS from gevent import pywsgi from geventwebsocket.handler import WebSocketHandler import queue app = Flask(__name__) def return_jsonify(json_data): response = jsonify(json_data) response.headers.add('Access-Control-Allow-Origin', '*') return response @app.route('/weixin', methods=['POST']) def weixin(): print('receive post from weixin ') form_data = request.form.get('weixin_data') print("get form data: ", form_data) data = json.loads(request.values.get('weixin_data')) print('receive weixin data: ', data) weixin_res = {} weixin_res['result'] = True return return_jsonify(weixin_res) if __name__ == "__main__": app.config['SECRET_KEY'] = '!QAZ2wsx' http_server = pywsgi.WSGIServer(('', 5007), app, keyfile='qiangzhou.key',certfile='qiangzhou.crt', handler_class = WebSocketHandler) http_server.serve_forever()
它的逻辑很简单,就是启动后监听5007端口,等待小程序连接并发送数据,它将发送来的数据打印出来然后返回一个简单的json数据给小程序端即可,接下来我们看小程序如何与服务器进行交互:
post_to_server: data=> { wx.request({ method: 'POST', url: 'https://xxxxx.com:5007/weixin', data: { weixin_data: JSON.stringify(data) }, header: { 'content-type': 'application/x-www-form-urlencoded', 'chartset': 'utf-8' }, success: res=> { console.log('post return: ', res) } }) },
小程序与后台服务器交互也是走RESTFUL接口模式,通过POST,GET将数据提交给服务器,然后等待服务器处理结果,这里我们使用wx.request接口来发送网络数据,该接口相当与网页前端开发中对应的fetch,这里我们使用了post方法,将数据以form的方式提交给服务器,接下来我们在takePhoto里面调用该函数:
this.post_to_server("hello world from weixin")
再次执行takePhoto函数后,数据会发生给服务器端,我们可以看到如下结果:
在小程序端我们可以收到服务器返回的数据,他们显示在console里面:
至此小程序开发的基本流程就已经完成了,有一定开发经验的工程师到这一步就可以知道如何开发小程序,剩下的就是谷歌的问题而已,下面我们看看如何实现身份证的智能识别。如今在网上有很多已经训练好的OCR神经网络能用于识别图片中的字符,这里我使用的是https://github.com/Raymondhhh90/idcardocr.git,但该项目存在一个问题就是它只能运行在GPU上,因此需要读者自己科学上网,然后使用谷歌的GPU来运行接下来的代码:
!apt-get -qq install -y tesseract-ocr tesseract-ocr-chi-sim tzdata libsm6 libxext6 #安装必要依赖 !pip install jsonpickle !git clone https://github.com/Raymondhhh90/idcardocr.git #下载身份证图片文字识别的神经网络 !pip uninstall opecv-python !pip install opencv-contrib-python==3.4.2.17 #必须要将opencv版本进行更换要不然代码无法运行 !pip install pytesseract #上面项目所使用的神经网络来自tesseract
上面的代码用于安装运行环境,接下来我们实现flask 服务器代码:
%cd /content/idcardocr/ from flask import Flask, request, Response import jsonpickle import numpy as np import cv2 from flask import request import time import idcard_recognize import cv2 sift = cv2.xfeatures2d.SIFT_create() app = Flask(__name__) @app.route('/read_id_card', methods=['POST']) def read_idcard(): r = request # convert string of image data to uint8 nparr = np.fromstring(r.data, np.uint8) # decode image img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) cv2.imwrite('/content/1.jpg', img) #解析手机拍照的身份证相片 result = idcard_recognize.process('/content/1.jpg') print("ocr result: ", result) response = {'message': result} # encode response using jsonpickle response_pickled = jsonpickle.encode(response) return Response(response=response_pickled, status=200, mimetype="application/json") import threading threading.Thread(target=app.run, kwargs={'host':'0.0.0.0','port':801}).start()
在谷歌colab上运行flask时,要想被外部客户端访问,我们还需使用一些数据包分发服务器的帮忙,因此执行下面代码:
!npm install -g localtunnel !lt --port 801
运行后它会导出一个url,我们要使用该url与运行在colab上的flask服务器进行数据交互。接下来我们修改一下小程序的代码,我们需要把拍摄的照片先上传给配置好的后台服务器,然后它再将接收到的照片分发给colab上的识别服务器,然后后台服务器再将识别结果返回给小程序,首先我们修改小程序的代码,让它把拍摄的照片上传给后台服务器:
saveImg() { wx.showLoading({ title: '身份证识别中...', mask: true, }) this.post_to_server(this.data.src) }, post_to_server: photo_path=> { wx.uploadFile({ filePath: photo_path, name: 'file', url: 'https://hkmlqiangzhou.com:5007/weixin', success: res=> { var res_content = JSON.parse(res.data) console.log('post data success: ', res_content) #后面我们会修改这里代码 } }) },
接着我们要修改后台服务器代码,让它接收小程序发送过来的照片,然后再转发给运行在colab上的服务器:
@app.route('/weixin', methods=['POST']) def weixin(): print('receive post from weixin : ', request.files) weixin_res = {} weixin_res['result'] = False save_img_name = '' if 'file' not in request.files: print('no file in upload...') return return_jsonify(weixin_res) weixin_file = request.files['file'] if weixin_file.filename == '': print('no file name') return return_jsonify(weixin_res) if weixin_file and allowed_file(weixin_file.filename): filename = secure_filename(weixin_file.filename) save_img_name = os.path.join('/home/qiangzhou/chenyi/python_server_code/python_mysql/', filename) weixin_file.save(save_img_name) print('save img name: ', save_img_name) ocr_url = 'https://pretty-fireant-38.loca.lt/read_id_card' content_type = 'image/jpg' headers = {'content-type': content_type} img = cv2.imread(save_img_name) _, img_encoded = cv2.imencode('.jpg', img) try: response = requests.post(ocr_url, data=img_encoded.tostring(), headers = headers, timeout=20) print("response txt:", response.text) ocr_result = json.loads(response.text)['message'] print("ocr_result:", ocr_result) weixin_res['result'] = True weixin_res['id_info'] = ocr_result except (ReadTimeout, ConnectTimeout, HTTPError, Timeout, ConnectionError, JSONDecodeError) as e: print("error:", e) return return_jsonify(weixin_res)
注意到代码中“https://pretty-fireant-38.loca.lt/read_id_card”,这个是笔者本人在colab上导出的url,读者记得在上面代码中将其换成你自己导出的url,完成这些代码后,将小程序在真机上调试,将当前开发的代码运行起来后,对准身份证拍照,很快你就能在控制台的输出中看到身份证被识别的内容了。
原文链接:https://blog.csdn.net/tyler_download/article/details/114370494?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165918471416782391879297%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165918471416782391879297&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~times_rank-4-114370494-null-null.nonecase&utm_term=%E5%85%8D%E5%A4%87%E6%A1%88cdn
原创文章,作者:优速盾-小U,如若转载,请注明出处:https://www.cdnb.net/bbs/archives/1720