手把手教你从零上线小程序+Node.js服务端 (上)
发表时间:2021-1-5
发布人:葵宇科技
浏览次数:94
一、引言与准备
17年微信小程序正式发布后,凭借其无需安装、触手可及、用完即走的优势爆火。至今依旧处于火热状态,不仅如此,小程序生态圈也在不断扩大,除了微信,各大主流app都安排上了自己的小程序,比如百度、支付宝、淘宝等。
据统计,至20年上半年,微信小程序数量已超320万,是所有平台中占比最高的;全网小程序累积已超500万个,并依旧呈上升趋势。
在这样的背景下,谁不想跟一波风,写几个小程序的bug呢。
本教程分为上下两章。第一章主要介绍本地开发与联调,你将了解到:
-
基本的全栈开发过程
-
- 数据库设计
- 小程序开发
- 服务端开发
- 本地联调
第二章主要介绍上线,你将了解到:
- 小程序与服务端的上线
- 域名与https配置
整个过程中最重要的是:
- 扩宽知识面(知道有这些工具可以用来做这些事情)
- 解决问题能力
- 阅读第一手资料的能力
- 学习总结能力
这是小程序+服务端的代码地址。
准备
小程序代码跟前端类似,所以有前端基础的朋友会更容易上手和理解。
开始阅读之前,确保你本地电脑已有Node.js + Git 环境。
请使用以下方式检测,如果没有显示版本号,建议阅读Windows-Node.js安装与配置 、Git安装与配置先进行安装与配置。
其次,编译器准备:
- 小程序:微信开发者工具 (下载完就先放着,第三章会用到)
- Node.js:VS Code / WebStorm ...
最后,先让大家有个了解,本教程将会使用的技术栈如下:
- 微信小程序原生语法 + weui-wess
- 后端 Node.js (express)
- 数据库 MySQL
- 线上数据库 phpMyadmin
如果你纠结先写后端还是先实现页面,那就按照本文步骤来吧。再简单的全栈开发,也是需要踩坑的~
二、数据库设计-MySQL
为什么第一步是设计数据库呢?
因为数据库设计要求我们了解自己的产品,让我们知道在做什么。
比如页面的数据从哪儿来,到哪儿去;哪些数据需要抽象成事物存储到数据库,每个事物使用哪些信息去描述,这些都是在编码之前要设计好的东西,否则很有可能带来频繁修改代码的后果。
环境准备
我使用的本地数据库是MySQL,它有个可视化工作区叫MySQLWorkbench,长这样,使用起来很方便。
如果你没使用过MySQL,那就继续阅读,接下来将会说明如何使用它创建数据库。如果你会使用并且已经设计创建好自己的数据库了,那就开始阅读下一章吧。
如果你还没有这些环境,就进入上面的链接先去下载吧,有不清楚的自行搜索教程。
创建数据库
当你下载完MySQL并且出现以上界面的时候,就可以继续跟着我的步骤走了。
- \1. 新建数据库 在SCHEMAS区域右键,选择
Create Schema
- \2. 为你的schema起一个名字
- \3. Apply 执行对应的SQL语句
创建表格
普通表格
这时可以看到左侧的SCHEMAS中多了我们新建的数据库
- \4. 双击选择到该数据库,这一步不要漏掉,否则后面执行SQL语句会报错
- \5. Tables区域右键,选择
Create Table
此时弹出new_table的表格,你需要
- \6. 为你的表起一个名字
- \7. 为你的表添加描述信息
这里我添加了一张用户表user,用来存储用户信息,包括了id(PK表示主键primary key,NN表示不允许为空not null,AI表示自增auto increasement,其余自行搜索)、openid(openid是微信向开发人员提供的用户标识,这里知道即可)、createTime 创建时间。因为仅做demo,所以只存储2个字段了,你可以根据自己的想法进行设计。
- \8. Apply 执行创建user表
带有外键的表格
- \9. 创建一张带有外键的表格
与普通表一样,先创建完所有字段。这里我创建了advice的表格,包含id、content和userId三个字段,其中userId为外键,关联user表中的id字段。
- 9.1 选择
Foreign Keys
- 9.2 为你的外键起一个名字
- 9.3 选择需要外键的关联表
- 9.4 选择需要本表中的外键字段
- 9.5 选择关联表中的被关联的字段
- 9.6 当被关联字段更新或删除的时候,关联表中字段如何处理。CASCADE表示级联,SET NULL表示设置为空。这里的意思就是当user表中的某个id变化的时候,关联它的外键也一起变化,id被删除的时候,关联它的外键就会变成null。
- 9.1 选择
创建完毕
此时,我己经创建完了我的数据库mini_demo,有两张表格,advice和user,其中advice的userId为外键,关联user表中的id。
如果你还没完成,就先创建完吧;如果你已经完成,开始阅读下一章吧。
三、小程序注册与编码
注册账号
小程序与公众号类似,需要使用账号管理。如果没有账号,先去平台申请一个,然后登录。
- 需要使用邮箱登录,一个邮箱只能绑定一个小程序。
- 登录到邮箱,点击链接以激活
- 填写信息,需登记本人真实信息。一个身份证号目前可以绑定五个小程序。
- 然后,很自然得跳转到这个页面。按照页面提示的流程,填写小程序信息,填写完后将显示如下界面。
- 在菜单开发-开发设置里可以看到我们的小程序id,跟身份证一样,AppID是小程序的唯一标识。之后很多地方都会用到。
新建项目
这时候打开前一章下载完的微信开发者工具,微信扫码登录后,就会出现此页面,让我们新建一个小程序项目吧。你需要
- 起一个项目名字
- 选择存放目录
- 输入刚刚的AppID
- 点击不使用云服务,我们使用自己搭建的后端服务。
如果仅是为了完成小程序功能,可以选择更快捷的方式。这里是为了学习如何上线后端服务及云存储。
- 新建
稍等片刻,我们的第一个小程序便诞生了!如下图所示。
熟悉项目
先介绍一下常用的界面区域和按钮,见图。(不理解也没关系,这些都是熟能生巧的,现在做到心中有数就行。)
相信这时你已经在模拟器上都点过一遍了,这里再给大家梳理一下。
- 初始页面,有一个按钮和欢迎语。
- 点击获取头像昵称后,跳出一个授权弹窗。
- 允许之后,初始页面的按钮变为了用户信息。
- 点击头像跳转到另一个页面,可以查看启动日志。
接下来,思考这两个问题。
既然有两个页面,为什么初始页面是第一个呢,在哪控制?
每个类型文件是干嘛用的,数据如何展示?
如果你存在疑惑,请先阅读官文-小程序代码构成,开始编码前,我们务必先了解这些东西。
初始化页面分析
这时你对小程序代码构成有了初步了解,甚至已经动手改过代码了,很棒。
接下里分析一下两个页面的逻辑及实现。
回到刚才的问题,为什么初始页面是第一个呢。
其实是由app.json
的pages
决定的,第一行就是要初始化展示的界面。
小程序初始化后,会首先触发app.js
的onLaunch
方法,会在小程序启动时触发
这里面做了三步操作:
- 记录本次启动时间,存入本地
storage
的logs
变量,该数据是logs页面的数据来源 - 登录,这里需要自己补充
- 获取用户信息。如果用户已授权过用户信息,那么这些信息可以直接通过
wx.getUserInfo
获取到,获取成功后保存到全局变量userInfo
中
然后,启动初始化页面,也就是index
。 index.js
中有个onLoad
方法 (该方法会在页面加载时触发,详见页面生命周期) ,主要就是判断是否已获取到用户信息,有的话就赋值给当前页面的data,没有就设置回调,等有了之后再赋值。最后一个else是没有“获取用户按钮”的兼容处理。
另外两个函数bindViewTap
和getUserInfo
是事件绑定函数,分别是点击头像和点击按钮的回调。
bindViewTap` 就做了一件事,让页面跳转到`logs
复制代码
getUserInfo
将获取到的用户信息存储到全局变量和当前页面data中。e是通过参数透传实现的,由小程序API返回。
logs
页面很简单,就是把启动信息从本地storage中取出来展示。
编码
看到这儿,你或许对小程序代码有大概了解了,那么接下来就照葫芦画瓢,去写自己的页面吧(登录会在下一章讲,不着急实现)
编码过程中,可以预先定义好接口,变量结构和名称就根据前一章设计好的数据库来写,避免重复修改。
页面中涉及到的很多小程序API,不清楚的就去微信小程序API文档查,大家要有看第一手资料的意识和能力。
这是我的小程序代码地址(下面有页面截图),仅供参考。
四、服务端搭建-Koa
接口准备
看这节之前,你应该实现了自己的静态页面了。这是我当前的两个页面,很简单。
总共涉及两个接口,登录和提交建议。
在写服务端之前,先约定好接口,如路径、请求方法、参数。
- 登录 get /login?js_code
- 提交建议 post /addAdvice body={openid, advice}
hello world
明确接口之后,开始编写服务端,我使用的是Node.js框架Koa。
首先,创建一个空文件夹,从终端进入目录,执行下面两个命令
npm init
初始化npm信息,生成package.json
npm i koa
下载koa框架
这时我们的目录下有三个文件:node_modules、package.json、package-lock.json
根据Koa文档,我们先写一个最简单的demo
// index.js
const Koa = require('koa')
const app = new Koa()
app.use(async ctx => {
ctx.body = 'hello zhaji'
})
app.listen(3000)
复制代码
然后命令行执行这个文件node index
仿佛什么都没有,但实际上项目已经启动起来了,http://localhost:3000/ 就能访问到。
所以我们可以在相应的地方加上提示。
// index.js
// ...
app.listen(3000)
console.log(`项目已启动,地址为 http://127.0.0.1:3000`);
复制代码
到此为止,我们已经能够启动Koa项目了,接下来就是写接口以及对接数据库。
提供接口
也就是上面定义的两个接口,登录和提交建议
接口需要路径,我们使用koa-router
先写一个koa-router的hello world,甚至直接从文档拷下来就行。
const Koa = require("koa");
const app = new Koa();
const Router = require("koa-router");
const router = new Router();
// app.use(async ctx => {
// ctx.body = 'hello zhaji'
// })
router.get("/", async (ctx) => {
ctx.body = "hello zhaji";
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);
console.log(`项目已启动,地址为 http://127.0.0.1:3000`);
复制代码
get请求
接下来,实现自己的接口。先讲一下登录,也就是根据jscode获取用户唯一标识。
官网是这样描述的:
登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。更多使用方法详见 小程序登录。
请求地址 GET
api.weixin.qq.com/sns/jscode2…
我们要做的其实很简单,在后台向这个地址发送请求,携带三个参数,然后保存返回信息即可。
三个参数分别是appid 之前新建小程序项目也用到过的那个、secret 需要下载的密钥下载链接、js_code 小程序登录接口wx.login返回信息之一
通过这个方法得到三个值后,我们可以先测试一下。复制已经修改为你自己的信息的链接到浏览器,能看到返回的信息,说明接口没问题。
node-fetch可以帮助我们在node.js下发起请求。为了符合关注点分离,我们将接口和数据库操作分离。
在index.js
下新增/login
接口
// index.js
// 登录
router.get('/login', async ctx => {
const {js_code} = ctx.query
const res = await api.login(js_code)
ctx.body = res
})
复制代码
在api/index.js
做具体实现。说明一下第6行const { APPID, SECRET } = process.env
这样写的目的是可以从命令行中获取项目的私密信息,避免把信息暴露在项目中。启动格式export APPID="xxx" && export SECRET="xxx" && node index.js
之后的数据库名和密码也可以用这样的方式启动。
// api/index.js
const fetch = require("node-fetch");
function getOpenId(JSCODE ) {
const { APPID, SECRET } = process.env;
console.log(APPID, SECRET);
const api = `https://api.weixin.qq.com/sns/jscode2session?appid=${APPID}&secret=${SECRET}&js_code=${JSCODE}&grant_type=authorization_code`;
return fetch(api)
.then((res) => res.json())
.then((json) => json);
}
module.exports = {
async login(JSCODE) {
// 1. 获取openid
const openid = await getOpenId(JSCODE)
return openids
// 2. 根据openid读取数据库,返回用户信息
}
};
复制代码
重启项目后,用浏览器测试一下接口好用不。
虽然返回的是错误码说明js_code无效,但也已经说明我们的login接口可以使用了。
post请求
post请求的数据通常放在body里,但如果直接ctx.request.body
读取其实为空,所以需要借助koa-bodyparser
库,像这样使用。
const bodyParser = require("koa-bodyparser");
app.use(bodyParser());
router.post('/addAdvice', async ctx => {
const data = http://www.wxapp-union.com/ctx.request.body
console.log(data)
...
})
复制代码
连接数据库
使用sequelize进行数据库连接。我们连接本地数据库进行调试,先确保本地数据库已启动。
然后进行数据库配置,新建config.js
文件,配置数据库名称、用户名和密码。其余配置大同小异,基本上也不需要修改,代码仓库里详细说明,感兴趣同学可以看看。
// config.js
module.exports = {
// 数据库名称
database: process.env.DATABASE,
// 用户名
username: process.env.USER,
// 密码
password: process.env.PWD,
// 地址
host: '127.0.0.1',
// 使用什么数据库
dialect: 'mysql',
...
}
复制代码
然后新建mysql/index.js
主要作用就是声明表格、表格属性以及表格之前的关系。数据库有几张表格,就应该有多少个类。(应该支持导入导出操作避免手工劳动力,有兴趣的朋友的研究下。)
// mysql/index.js
const Sequelize = require("sequelize");
const mysqlConfig = require("../config");
const sequelize = new Sequelize(mysqlConfig);
const Model = Sequelize.Model;
class User extends Model {}
class Advice extends Model {}
User.init(
{
// 属性
id: {
type: Sequelize.INTEGER, // 要与数据库声明的类型匹配
autoIncrementIdentity: true, // 自增
primaryKey: true, // 主键
},
openid: {
type: Sequelize.STRING,
allowNull: true,
},
createTime: {
type: Sequelize.DATE,
allowNull: true,
}
},
{
sequelize,
modelName: "user",
// 参数
}
);
Advice.init(
{
id: {
type: Sequelize.INTEGER,
autoIncrementIdentity: true,
primaryKey: true,
},
content:{
type: Sequelize.TEXT,
allowNull: true
},
userId: {
type: Sequelize.INTEGER,
references: { // 外键声明
model: "user",
key: "id",
}
}
},
{
sequelize,
modelName: "advice",
}
)
module.exports = {
User,
Advice,
};
复制代码
那么,如何操作放佛已经被对象化的表格呢?这就是sequelize
特色之处,它让开发者能够像写中文一样去操作数据库。比如,新增和查找,分别是create
和findOne
// api/index.js
const { User, Advice } = require("./mysql/index.js");
// 新增用户
async function addUser(openid) {
await User.create({ openid, createTime: new Date().toLocaleString() });
}
// 查找用户
async function getUser(openid) {
const user = await User.findOne({ where: { openid } });
// 用户不存在,新增用户
// ...
return user;
}
复制代码
一些报错
在执行过程中,项目总会报错的。报错不要紧,要紧的是我们解决它的能力。刚开始学的时候,项目一报错我就复制到度娘下,甚至嫌麻烦都懒得看一眼。渐渐的发现,其实很多问题自己就能看明白,不要太依赖搜索等,尽量先尝试自己解决,只有这样才能熟能生巧。没错,写代码就是一个熟能生巧的过程。
比如这里,写完增删改查后,准备启动项目,却发现报了个这样错。
很明显引入的路径有问题,应该是../mysql/index.js
再启动,又报错。
让我们下载mysql2依赖包,那就下呗。下完之后再启动,ok了。其实这种自己慢慢把一个个bug干掉的过程,很爽的。
调试接口
到这里我们写好了接口并成功启动项目,接下来就是调试阶段,看看接口是否可用。
调试方法有很多种,最简单的可以直接用浏览器调试,get请求直接浏览器打开,post请求可以写一个xhr脚本;
也使用postman工具,专门用来测接口的。
我们选用postman测试,如果没有该工具,可以点击上面的超链接进行下载并登录。
同样,从最简单的开始。
没问题后,开始测登录接口,是否与预料中一致,若存在问题,看报错解决。(前面说过,js_code在小程序项目里登录wx.login时输出res.code一下就可以得到)
没问题后,打开数据库确认。
确保每个接口都没问题后,就可以跟小程序联调了!
这是我的服务端代码地址
五、本地联调
小程序接口请求
如果之前没在小程序里写接口,那就在这一步写吧。
根据文档,我们使用wx.request
发送请求,下面是一个调用login
接口的示例。
wx.request({
url: `${this.globalData.baseUrl}/login`, // 在全局变量里定义baseUrl,方便之后修改
method: 'GET',
data: {
js_code: res.code
},
success: res => {
console.log(res);
}
})
复制代码
如果出现了这样的报错,只需要点击右上角详情,勾选不校验...
即可。但如果小程序发布,其规定为了安全还是需要使用https,现在取消校验是为了本地联调。
联调
再次点击编译,接口正常运行并输出,同时可以再去数据库查看数据是否符合预期。
挨个把所有的接口都调试一遍。
最后
过了好一会儿,终于调试完了所有接口。恭喜你,离小程序上线又近了一大步!
为了保证服务端接口能够被非本地访问(如使用其他电脑访问),下一步我们需要把它放到云上
作者:猪是倒着读的
链接:https://juejin.im/post/6875594800503259143
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。