【AI番外】今天说微信小程序开发,不能上线的AI,都是纸老虎
言有三
毕业于中国科学院,计算机视觉方向从业者,有三工作室等创始人
作者 | 言有三
编辑 | 言有三
训练好一个深度学习模型之后,必须要将其部署到生产环境当中,才能产生真正的价值,为更多的用户所体验。
部署到线上现在最轻便且最方便传播的当属微信小程序了,微信小程序依托于微信,不需要下载安装即可使用,用户扫一扫或搜一下即可打开应用。
腾讯官方已经宣布微信小程序的数量超过了app,对于我们个人来说,只要准备好https服务,就可以比较方便的开发。
下面我们就讲讲微信小程序前端开发和服务端开发。
“这年头,什么都的会”
01
前端开发
微信小程序前端开发与网页的开发有非常多共通的技术,但是也有它独特的特点,比如微信小程序的前端工具等。这一小节从微信小程序的技术特点,工具的使用,小程序的通用目录配置等各个方面来详细讲述前端的开发流程。
1.1 小程序简介
小程序一般都指微信小程序,是嵌入到微信APP里面的一种伪原生的方式,”无需安装就可运行”,当前小程序包的上限已经扩展到了8M。
如图是微信小程序《言有三工作室》和其AI摄影菜单的主界面。
相对于微信APP,小程序有几个重要的优势,分别可以从用户和开发者的角度来看。
从用户角度来看:
(1) 使用便捷,简单方便,不需要安装额外APP,节省内存。
(2) 安全,小程序经过微信严格筛选,相比于APP,不容易存在病毒、信息泄露、诈骗等情况的出现。
从开发者角度:
(1) 降低了开发门槛,缩短了开发周期。一款成熟的APP需要适配IOS,Android两大平台,各自都有非常庞大的生态体系,而小程序只需要基于微信的生态环境开发就可以实现共用。
(2) 微信庞大的用户基数给小程序带来了流量优势,相比于APP的推广更加容易。传播方便,扫二维码即可体验。
1.2 小程序开发工具
以Mac上的小程序开发工具为例,下载链接在这里,https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
打开咱们自己的小程序,体验一下页面。
上面工具有一些重要的选项。
(1) 编译,顾名思义就是用于编译小程序代码。
(2) 扫码,用于生成二维码在手机上体验。
(3) 提交版本,用于将代码提交到后台,这样拥有体验权限的用户就可以在小程序没有正式发布之前就能体验。另外,也用于正式版本的提交与发布。
以上就是小程序常用的选项,另外在开发的过程中经常需要与后台进行交互,这时就需要在小程序开发工具和后台同时进行debug,使用起来也非常的方便。
1.3 小程序前端目录
熟悉了工具后,我们来仔细审视一下如何开发小程序。我们先看看微信小程序的项目路径。
1.工程目录
包含目录为images,pages,另包含文件app.json,app.js,app.wxss,project.config.json。
(1) image,存储小程序本地使用的一些图。一般是页面的缩略图,log日志等。这里不适合放较多的图和较大的图,因为小程序本身有大小限制,最新的标准是整个小程序所有分包大小不超过8M,单个分包/主包大小不能超过 2M。
(2) pages,包含小程序所有功能页面的配置。
(3) app.js,包含程序的主流程代码,我们可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量。它可以是空的,但是必须存在,它通常会包含以下函数。
onLaunch函数,用于监听小程序的初始化,全局只触发一次,因此,可以在这里加载一些默认变量。
onShow函数,用于监听小程序显示,小程序的启动,从后台重新回到前台也会触发它。顾名思义,我们常常在这里对一些图片文字进行显示。
OnHide函数,是小程序从前台到后台的触发函数。
除了这3个函数之外,我们可以在js脚本里面任意添加函数,将其注册到Object参数中,就可以用this进行访问了。通常与服务端的数据交互与手机本地的文件访问,都是另外定义函数。
小程序启动之后,在app.js定义的App实例的onLaunch回调会被执行:
App({
onLaunch: function () {
// 小程序启动之后 触发
}
})
整个小程序只有一个App实例,是全部页面共享的,然后就可以进入pages目录了。
(4) app.json 是对当前小程序的全局配置,包括了小程序的所有页面路径、界面表现、网络超时时间、底部tab等,一个示例配置如下。
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle":"black"
}
}
pages用于描述当前小程序所有页面路径,这是为了让微信客户端知道当前你的小程序页面定义在哪个目录。Window定义里小程序所有页面的顶部背景颜色,文字颜色等。
(5) 工具配置 project.config.json
通常大家在使用一个工具的时候,都会针对各自喜好做一些个性化配置,例如界面颜色、编译配置等,当你换了另外一台电脑重新安装工具的时候,你还要重新配置。考虑到这点,小程序开发者工具在每个项目的根目录都会生成一个project.config.json,你在工具上做的任何配置都会写入到这个文件,当你重新安装工具或者换电脑工作时,你只要载入同一个项目的代码包,开发者工具就自动会帮你恢复到当时你开发项目时的个性化配置,其中会包括编辑器的颜色、代码上传时自动压缩等一系列选项。
2. Pages目录
pages,是我们小程序开发的主要工作目录,它包含以下文件。
js,即JavaScrip文件。
json,即项目配置文件,负责窗口颜色等。
wxml,类似HTML文件。
wxss,类似CSS文件。
这4个文件一起组成一个页面。
(1) wxss文件是css样式文件。最底层的样式,比如整个页面的背景颜色等,可以在这里配置,wxss文件可以没有。wxss具有css大部分的特性,小程序在wxss也做了一些扩充和修改。比如新增了尺寸单位,在写css样式时,开发者需要考虑到手机设备的屏幕会有不同的宽度和设备像素比,采用一些技巧来换算一些像素单位。wxss在底层支持新的尺寸单位rpx,开发者可以免去换算的烦恼,只要交给小程序底层来换算即可,由于换算采用浮点数运算,所以运算结果会和预期结果有一点点偏差。
(2) wxml文件是当前页面的配置文件,我们在这里定义整个页面中各个元素的排版,给一些控件绑定事件等,这个文件也可以没有。它和HTML非常相似,有标签、属性等构成,但是也有很多不一样的地方,比如标签名字有点不一样。写HTML的时候,经常会用到的标签是div,p,span,而小程序的wxml用的标签是view,button,text等,另外还多了一些wx:if这样的属性以及{{ }}这样的表达式,它可以把一个变量绑定到界面上,称之为数据绑定,非常有用。
(3) json文件是项目配置文件,配置一些窗口颜色等等,笔者一般在app.json中配置,page中的文件留空。
(4) js文件就是javascript页面逻辑文件。按钮的响应以及与后端的交互,都在这里处理。
1.4 小程序前端开发
下面我们开始正式开发,小程序的前端开发需要实现以下几个功能。(1)选择图片并展示。(2)提交图片到后台以及获取处理结果并展示。(3)保存相册结果与分享。
我们以有三工作室的小程序为例。
1 定义好样式文件
我们这里实现3个页面的功能,第一个是ais页面,它在小程序主菜单中添加美学评分模块。
在ais.wxml中添加如下代码
<view>
<button bindtap='aestheticscore'>
<image src='../../image/building_score.jpg'></image>
<image src='../../image/i1.png'></image>
<text>美学评分</text>
</button>
</view>
可知,在这里添加了缩略图,一个按钮,以及文本等属性,并绑定了函数aestheticscore,函数的定义如下,实现到aestheticscore页面的跳转。
aestheticscore: function (e) {
wx.navigateTo({
url: '../aestheticscore/aestheticscore',
})
},
最终的结果如上图。
接下来第二步就是实现aestheticscore页面,我们先看一下整个页面的样式,如下图。
包含了几个按钮和一个图像显示框,完整的wxml文件如下
<!--pages/aestheticscore/aestheticscore.wxml-->
<view>
<image src='../../image/background.jpg'> </image>
<image src='../../image/logo.jpg'></image>
<view style='text-align:center;margin:20rpx;font-weight:600; font-family: "Microsoft Yahei";'>{{title}}</view>
<view>
<image wx:if="{{bgPic}}" src="{{bgPic}}" mode='widthFix'></image>
<view wx:else></view>
</view>
<view>
<button data-way="camera" bind:tap="chooseImage">使用相机</button>
<button data-way="album" bind:tap="chooseImage">相册选择</button>
<view>
<button bindtap='back'>返回</button>
<button bindtap='nextPage'>下一步</button>
</view>
</view>
</view>
在页面的顶端包含了一个背景image,一个logo image,一个text控件,是为了显示该页面的背景,小程序的logo,以及该页面的功能名字,并且功能名字{{title}}是从js文件中获取的。
然后页面使用了一个container控件来保存所选择的图片,当有选择的图片的时候显示{{bgPic}},当没有的时候显示“emptyBg”。
最后是若干个按钮,包括“使用相机”,“相册选择”两个选择不同图片来源的按钮,返回上一级菜单的“返回”按钮以及进入下一步的“下一步”按钮。
第三个页面就是showaesthetic页面,它返回结果并显示,包含一个保存至当前目录和分享的按钮,按钮和显示图片的控件与第二个页面中的按钮的样式相同,这里多了一个显示结果,即下图美学分数“88.0495548248”的text控件,属性如下。
<view style='text-align:center;margin:20rpx;font-weight:600; font-family: "Microsoft Yahei";'>{{text}}</view>
以上就是界面的定义,分别实现进入菜单,选择图片并上传,获取结果并展示,接下来具体实现其中的功能。
2 实现选择图片并显示的功能
直接使用微信的官方API就可以实现选择图片并显示的功能,需要使用到chooseImage接口,代码如下:
chooseImage(from) {
wx.chooseImage({
count: 1,
sizeType: ["original", "compressed"], // 可以指定是原图还是压缩图,默认二者都有
sourceType: [from.target.dataset.way],
// sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: (res) => {
var tempFilePaths = res.tempFilePaths;
this.setData({
bgPic: res.tempFilePaths[0]
});
this.assignPicChoosed();
},
fail: (res) => {
this.assignPicChoosed();
},
complete: (res) => {
this.assignPicChoosed();
},
})
},
3 实现提交图片到后台并获取返回结果的功能
同样,直接使用微信的官方API即可,用到的函数是wx.uploadFile。
//上传图片并获取结果
nextPage: function (e) {
var that = this;
//将选择的图片作为全局数据
app.globalData.bgPic = that.data.bgPic;
wx.showToast({
title: '正在处理', icon: 'loading', duration: 100000
});
wx.uploadFile({
url: 'https://yanyousan.com/aestheticscore',
filePath: that.data.bgPic,
name: 'file',
header: {
'content-type': 'multipart/form-data'
},
success: function (res) {
// console.log(res.data);
wx.hideToast();
if (res.statusCode == 200) {
var jj = JSON.parse(res.data);//将json字符串转为json对象
console.log('200');
app.globalData.aestheticscore = jj["score"];
console.log(app.globalData.aestheticscore);
wx.navigateTo({
url: '../showaestheticscore/showaestheticscore',
})
} else {
wx.showModal({
title: '提示',
content: '服务器错误,请稍后重试!',
});
}
},
fail: function (res) {
console.log(res);
}
})
},
4 显示结果与分享
上面已经定义好了显示的样式,并且将结果绑定到了app.globalData.aestheticscore中,所以在showaesthetic只需要获取到结果,通过onload函数实现,其中调用了getImageInfo函数。
onLoad: function (options) {
wx.getImageInfo({
src: app.globalData.bgPic,
text: app.globalData.aestheticscore,
success: res => {
this.bgPic = res.path
console.log(res.width)
console.log(res.height)
console.log(app.globalData.aestheticscore)
}
}),
this.setData({
bgPic: app.globalData.bgPic,
text: app.globalData.aestheticscore
})
},
另外,这里还包括保存结果以及分享按钮,前者需要使用到saveImageToPhotosAlbum函数,后者即实现onShareAppMessage函数。
savePic() {
wx.getImageInfo({
src: app.globalData.bgPic,
success: function (res) {
var path = res.path;
wx.saveImageToPhotosAlbum({
filePath: path,
success(result) {
console.log(result)
wx.showToast({
title: "保存成功",
icon: 'success',
duration: 1000
})
}
})
}, fail(e) {
console.log("err:" + e);
}
}),
wx.navigateTo({
url: '../ais/ais',
})
},
onShareAppMessage: function () {
return {
title: "言有三工作室",
path: "/pages/ais/ais",
success: function (res) {
// 转发成功
console.log("转发成功:" + JSON.stringify(res));
wx.showToast({
title: "转发成功",
icon: 'success',
duration: 2000
})
},
fail: function (res) {
// 转发失败
console.log("转发失败:" + JSON.stringify(res));
}
}
}
})
前端开发的核心内容如上,主要就是定义好样式和相关的跳转逻辑。
02
服务端开发
上一节,我们说了微信小程序的前端开发,这一节我们就来完成服务端的开发。主要包括:(1)域名注册与证书申请。(2)Flask服务端框架介绍。(3)算法搭建与实现。
2.1 域名注册与管理
所谓域名就是我们熟知的网址了,它相比IP地址更加方便记忆,如www.yanyousan.com。域名注册有很多的渠道,国内可以到腾讯云等拥有域名服务的厂商处注册,只要选择的域名没有被注册就行。
选择域名注册的时候有一些类型,需要简单了解。
.com域名,是国际最广泛流行的通用域名格式,全球注册量超过1.1亿。.com域名一般用于商业性的机构或公司,任何人都可以注册。如果可以的话,我们尽量注册这个域名。
.cn,中国使用的顶级通用域名,这是全球唯一由中国管理的国际顶级域名。com.cn属于.cn里面的分类,其他还有net.cn,org.cn,gov.cn等。
.net是类别顶级域名,适用于各类网络提供商。
.org是非营利性机构。
.gov.cn是政府机构。
我们尽量使用.com或者.cn。
由于是微信小程序的开发,直接选择腾讯云很方便。https://cloud.tencent.com/
注册得到域名之后,下一步就是进行域名备案,大陆的域名必须通过备案才能提供服务。至于备案的具体流程,根据官网的操作进行就可以了。
备案完成之后,就可以去申请https服务了。实际上没有https,用http服务也是可以正常搭建网络访问的,但是微信小程序必须使用https协议。所以,我们需要去申请https证书。
腾讯云提供了一年的免费证书,大家可以使用。
2.2 服务端框架简介
1.Web应用程序流程
对于Web应用来说,当客户端想要获取动态资源时,就会发起一个HTTP请求,比如用浏览器访问一个URL,Web应用程序会在后台进行相应的业务处理,从数据库或者进行一些计算操作取出用户需要的数据,生成相应的HTTP响应。当然,如果访问静态资源,则直接返回资源即可,不需要进行业务处理。服务器要想对每个URL请求返回对应的结果,需要建立URL和函数之间的一一对应关系,这就是Web开发中所谓的路由分发。
客户端接到响应后,必须显示给用户看。这个时候就需要一个视图函数,它可以返回简单字符串,也可以返回复杂的表单,然后在前端渲染。
2.Flask服务端框架基础
由于对python比较熟悉,我决定使用python作为服务端的web框架语言。python的web框架,有Django,Flask,Pyramid,Tornado,Bottle,Diesel,Pecan,Falcon等等。其中Flask、Pyramid和Django,是同时适合开发小项目和大型商业项目的框架。
相对来说,Flask更适合面向需求简单的小应用,所以我们选择了Flask。
Flask是一个微型的服务端框架,它旨在保持核心的简单,但同时又易于扩展。默认情况下,Flask不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask支持用扩展来给应用添加这些功能,众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask的这些特性,使得它在Web开发方面变得非常流行。
既然如此简单,那我们就先看一个程序员必写的HelloWorld,7行Python代码就够了。
# from http://flask.pocoo.org/ tutorial
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "欢迎来到言有三工作室"
if __name__ == "__main__":
app.run()
一个没什么Python web开发经验的人就可以很快的上手,我们看看上面都包含什么。
from flask import Flask
app = Flask(__name__)
这就是用于创建一个程序实例,__name__变量就是程序主模块的名字。
@app.route("/")
def hello():
return "欢迎来到言有三工作室"
Flask底层使用werkzeug来做路由分发,上面的代码就是用于创建路由映射,@app.route就是程序实例提供的修饰器,它将hello函数注册为路由,而且是根地址的处理程序。所以,一旦我们打开https://yanyousan.com,就会显示该函数的结果,大家可以试试。
if __name__ == "__main__":
app.run()
这就是主函数了,只有执行这个脚本后,才会启动开发web服务器。服务器启动后,进入轮询,等待并处理请求,直到程序停止。
更多Flask的使用,我们就不一一说明,大家可以去参照官网http://flask.pocoo.org/学习。
2.3 服务端开发
下面开始服务端的开发,主要是实现表单,路由映射功能。
1.目录划分
部署服务时,我们应该先划分一下目录结构。根目录下包含,app,models,manager.py。
manager.py是主文件入口,其中的内容如下:
from app import app
if __name__=='__main__':
app.run(debug=True)
app中存储代码文件和其他的一些资源文件如图片,models是我们专门用来保存模型的目录,因为这些文件一般比较大,不适合放在git中托管。app中包括static,utils,以及form.py,view.py等脚本。static存储的是一些静态文件,utils下面放置我们的算法模块,所有算法相关的代码就定义在这里。
2 表单文件form.py
表单即WebForm,它负责封装用于用户端显示的数据,充当了在视图及程序之间传输、处理数据的媒介,可以通过form.get("键名")的方式来读取这些数据,也可以通过form.set("属性名",值)来改变视图中传过来的数据值。
每个Web表单都由一个继承自Form的类表示。这个类定义表单中的一组字段,每个字段都用对象表示。字段对象可附属一个或多个验证函数,它用来验证用户提交的输入值是否符合要求。
因为我们是处理图片,所以需要定义图片的表单类,看下面代码。
#coding=utf8
from flask_wtf import FlaskForm
from wtforms import FileField, SubmitField
from wtforms.validators import DataRequired
class PictureForm(FlaskForm):
picture = FileField(
label=u'图片',
validators=[
DataRequired(u'上传图片不能为空')
],
render_kw={'accept': 'image/*', 'style': 'display:none', 'align': 'margin-top:10px'}
)
submit = SubmitField(
u'提交',
render_kw={'style': 'text-align: center; font-size:30px; margin-top:10px; margin-left:5px', 'align': 'center'}
)
它包含一个picture字段和一个submit按钮。Picture字段需要验证传输的图片不能为空,即表单验证。只要存在表单元素,基本就少不了表单验证。
3. 路由映射view.py
这个脚本主要用于与前端的交互,我们看一下例子,这就是上一节我们演示的美学评分的那个例子。
#美学评分
@app.route('/aestheticscore', methods=['GET', 'POST'])
def get_aestheticscore():
file_data = request.files['file']
if file_data and allowed_file(file_data.filename):
filename = secure_filename(file_data.filename)
file_uuid = str(uuid.uuid4().hex)
time_now = datetime.now()
filename = time_now.strftime("%Y%m%d%H%M%S")+"_"+file_uuid+"_"+filename
file_data.save(os.path.join(app.config['AESTHETICSCORE'], filename))
src_path = os.path.join(app.config['AESTHETICSCORE'], filename)
score = aestheticscore(src_path)
if float(score) < 0:
data = {
"code": 0,
"score": "请使用建筑图片"
}
else:
data = {
"code": 0,
"score": str(score)
}
return jsonify(data)
return jsonify({"code": 1, "msg": u"文件格式不允许"})
看上面的代码,get_aestheticscore()是创建的路由函数,它接收前端上传的图,并调用aestheticscore函数进行处理,然后返回json格式的结果。aestheticscore就是我们实现算法的主函数了,这里不在本书的讲述内容,所以不做介绍。
我们再回到前端中怎么和服务端进行交互,在前端的上传图片那里有以下代码。
wx.uploadFile({
url: 'https://yanyousan.com/aestheticscore',
filePath: that.data.bgPic,
name: 'file',
header: {
'content-type': 'multipart/form-data'
},
success: function (res) {
// console.log(res.data);
wx.hideToast();
if (res.statusCode == 200) {
var jj = JSON.parse(res.data);//将json字符串转为json对象
console.log('200');
app.globalData.aestheticscore = jj["score"];
console.log(app.globalData.aestheticscore);
wx.navigateTo({
url: '../showaestheticscore/showaestheticscore',
})
} else {
wx.showModal({
title: '提示',
content: '服务器错误,请稍后重试!',
});
}
},
其中url: 'https://yanyousan.com/aestheticscore'就是我们上传图片的网址接口,var jj = JSON.parse(res.data)就是获取的json格式的返回结果,jj["score"]就是得到的分数。
至此,我们就完成了上传图片,后台处理并返回结果。前后端的基本技术内容到此基本结束,感谢“有三AI学院实习生杨皓博”在1.1部分的分享。