很早就了解与学习过微信小程序开发相关的技术栈与框架,小程序的账号也都已经申请过。但写过的 demo 项目也迟迟没有发布到小程序上。这主要的原因还是觉得不值得发布,加上各种审核相关的。而这次准备写一个搜题相关的小程序,也是时候实战发布一下,顺带记录下整个开发与发布过程。
在线体验:扫下图小程序二维码
小程序的源码地址:https://github.com/kuizuo/question-man
技术栈
小程序所采用的是 Taro + Vue3 + NutUI,之所以选这套技术栈,主要是想上 Vue3,而 uniapp 对 Vue3 的支持并不友好,在我的上篇文章中也有说明到,同时支持 uniapp 的 vue3 屈指可数。所以便选用了这套技术栈来进行开发。
页面设计
项目配置
项目搭建
taro init myApp
配置如下
安装完依赖,使用npm run dev:weapp
,在打开微信开发者工具,导入项目即可。
axios 封装
web 端 http 请求使用最多的库就是 axios 了,但是在小 程序中使用 axios 会提示 adapter 未定义,原因是小程序不能解析 package.json 中的 browser module 等字段。
要使用 axios 的话可以安装 axios-miniprogram 或者 taro-axios 库(我选择后者,但前者稍小 5kb),也就是会适配小程序的 axios 的 adapter,引入和使用与 axios 并不特别大的差异。以下是我封装后的代码
import axios from 'taro-axios'
import Taro from '@tarojs/taro'
import { useAuthStore } from '@/stores/modules/auth'
const showErrorToast = msg => {
Taro.showToast({
title: msg,
icon: 'none',
})
}
const instance = axios.create({
baseURL: process.env.BASE_URL,
})
instance.interceptors.request.use(
config => {
Taro.showLoading({
title: '加载中',
mask: true,
})
let token = Taro.getStorageSync('token')
if (typeof token == 'undefined') {
token = ''
}
config.headers = {
'Content-Type': 'application/json;charset=utf-8',
Authorization: token,
}
return config
},
error => {
console.log(error)
return Promise.reject(error)
},
)
// respone拦截器
instance.interceptors.response.use(
(response: any) => {
Taro.hideLoading()
if (response.data.isError) {
showErrorToast(response.data.error.message)
} else {
return response
}
},
error => {
if (error.response) {
Taro.hideLoading()
console.log('err', error)
let res = error.response.data
switch (res.code) {
case 400:
showErrorToast(res.message || '非法请求')
break
case 401:
const authStore = useAuthStore()
authStore.login()
// showErrorToast(res.message || '当前登录已过期,请重新登录')
// Taro.navigateTo({
// url: '/pages/login/index'
// })
break
case 403:
showErrorToast(res.message || '非法请求')
break
case 404:
showErrorToast(res.message || '请求资源不存在')
break
case 500:
case 502:
showErrorToast(res.message || '服务器开小差啦')
break
default:
showErrorToast(res.message || res.statusText)
}
} else {
console.log(error)
showErrorToast('请检查网络连接状态')
}
return Promise.reject(error)
},
)
export default instance
没什么好说的,和网页端基本一致。主要是加了个 wx 的 Loading 与 Toast。然后在 token 失效的时候,应该是要跳转到登录页面,但我并没有编写登录页面,而是重新调用一遍 login,达到静默登录的效果。
获取用户唯一标识(openid)
借助微信小程序能十分方便的获取到微信用户。在微信中,为了识别用户,每个用户针对每个公众号或小程序等应用会产生一个安全的 openid,开发者可以通过这个标识识别出用户。
要获取 openid 有以下几种方法(这里以 Taro 为例子,而为 wx 官方文档),具体代码可在官方文档中查看到。
Taro.login(option) | Taro 文档 (jd.com)
首先调用Taro.login()
获取 5 分钟时长的 code,然向 api.weixin.qq.com 获取 openid 代码如下
Taro.login({
success(res) {
let code = res.code
let appId = '小程序->开发管理->开发设置->开发者ID获取'
let appSecret = '小程序->开发管理->开发设置->开发者ID获取'
Taro.request({
url: 'https://api.weixin.qq.com/sns/jscode2session',
data: {
appid: appId,
secret: appSecret,
js_code: res.code,
grant_type: 'authorization_code',
},
method: 'GET',
success(res) {
console.log('openid', res.data.openid) // 得到openid
console.log('session_key', res.data.session_key) // 得到 session_key
},
})
},
})
但现在小程序是无法添加 api.weixin.qq.com 域名,所以上面的方案在小程序端失效,只能转到后端上。小程序官方有张实践图
整个步骤
1、调用wx.login 获取 code,此时也可调用 wx.getUserInfo 来获取用户数据(昵称,头像)
2、由于小程序后台授权域名无法授权微信的域名,所以需要将 code 与用户数据传入到自己的服务器上。
3、服务器后台接收到 code,后台将 appid+appsecret+code 向微信 api 服务获取用户的登录态信息(openid 与 session_key),服务器对这些登录态信息进行封装,如 session 或者 jwt 的 token 都可以(这里以 token 为例),返回给小程序端
4、小程序接收到 token 时,将其保存到本地存储上(wx.setStorage),每次携带该 token 请求向服务器发送请求。
不过如果使用云开发可以免去很多鉴权相关的,但由于数据库存储相关的,所以我是采用自建后台服务器,而后端暂不考虑开源,故具体逻辑代码就不演示了(考虑安全为主),自行编写后端 login 接口。
获取手机号
注意:只有企业小程序才可以获取用户手机号,个人小 程序没有办法获取的。
在这篇文章中有说明到如何获取 5 行代码获取小程序用户手机号 | 微信开放社区 (qq.com)
所以就没有然后了(因为我肯定是个人账号)。
获取用户信息
要获取用户信息的需要调用 getUserProfile,演示代码如下
Taro.getUserProfile({
desc: '获取用户个人信息',
success: function(res) {
const userInfo = res.userInfo
console.log(res.userInfo)
}
}
要注意的是 getUserProfile 必须通过按钮来触发,而不能通过生命周期的形式,弹出授权窗口,用户可接受与拒绝。说白了就是开发者无法在用户不知情的情况下获取到用户信息(考虑到用户隐私相关的)
同时有一个小坑,getUserInfo 获取到微信用户与灰色头像
由于小程序官方调整了接口,导致 getUserInfo 无法获取正确的用户信息。详情可看 小程序登录、用户信息相关接口调整说明 | 微信开放社区 (qq.com)
一个正常的登录流程:
按理来说一般是要提供一个专门的登录页面,哪怕登录页面只有一个按钮,按钮名为一键登录。然后用户点击触发 getUserProfile 获取用户信息,如果用户拒绝则不允许使用软件,允许则进入主页,然后将 用户信息保存置服务器上,以便下次用户访问时无需再次调用 getUserProfile 接口,直接从服务器上取。
而我的做法相对比较粗略,我是直接允许用户进入首页,但当他使用搜索功能时,则判断服务器是否存有该用户的信息,如果没有,调用 getUserProfile,弹出授权框给用户选择。同时在首页的时候通过 checkSession 来判断 session 是否过期,过期则调用 wx.login 静默登录,更新登录态。(不过如果后端不判断该用户信息是否完善的话,那么是有办法直接绕过这种方式来进行调用接口了)。
数据库搭建
实际上这个小程序最主要的依赖就是数据库了,而这个数据库与传统的关系型(Mysql)和文档型(MongoDB)不同,要做到搜索引擎式的搜索。举个例子,搜索“李白”,能得到 【李白的诗风是】【李白的称呼】有关李白的字样,并且要求返回的速度快。像上面举到的两种数据库实现起来不方便,如果涉及到千万级别的数据库,将是按秒,甚至数十秒的响应速度。
而我所选择的数据库是Elasticsearch,能很轻松的实现上面的效果,并且有个很客观的响应速度。(具体实现自行了解,后端代码暂不考虑开源)
上传发布
当本地开发完毕时,点击右上角的上传,填写版本号相关以及项目备注,然后上传成功如下图
在版本管理中的开发版本可以看到刚刚上传的代码
点击提交审核后,会提示确保项目不是测试版与 Demo(否则将受到相应处罚)如下图
测试版
或者选择体验版(只有管理员与体验者的账号才可以访问),扫描体验版的二维码,即可使用微信访问项目。
审核版本
如果是要发布正式版的话,还需要填写如下表格,接着静等 1-7 天即可(可选择加急,一年一次)
等待审核完毕,实测一天内,审核版本如下
线上版本
最终提交发布,线上版本如下
至此,用户即可打开微信,通过小程序访问到该应用。
关于改名
可能有些人(这个人就是我)会担心自己上线小程序后,期间能否改名,有何影响。
首先是可以改名的,小程序的标识是 APPID,而不是一个名称。但改名会直接影响到用户对小程序的认知,从而影响小程序用户流失。同时改名要求很苛刻,会检索是否有相似同名小程序,以及是否有商标证明等等,并且一年只能修改两次名字,所以起名与改名都要慎重。
一些开发时的注意事项(坑)
request:fail url not in domain list(跨域)
浏览器最烦躁之一跨域,在小程序上也不一意外,但小程序更加苛刻,即便后端允许跨域,小程序请求也会提示标题所示错误。这时候一般就如下几种做法。
首先要确保是 https,同时开发时为了方便调试,一般会在本地设置中勾选,不效验合法域名选项,如下
这时候开发环境下就能正常发送请求相关了(我这里后端是直接允许跨域了,不然需要在 config/index.js 中设置 devServer.proxy 的反向代理)。
但在生成环境下就需要在小程序中的开发管理中配置服务器域名了
点击开始配置会提示身份认证相关,扫完码后将会到如下配置
这里就是填写生产环境下要请求资源的域名了,并且是需要开启 https 的。
设置完毕,重启微信开发者工具或刷新项目配置,这里需使用小程序账号的 AppID 进行登录,测试号无效,然后项目配置就会设置好 request 合法域名,再次请求便能正常响应。
pinia 持久化
pinia 有个插件pinia-plugin-persist
,可以对 store 状态进行持久化操作,在小程序中引入时则会提示 sessionStorage is not defined,原因是小程序中并无 sessionStorage 与 localStorag。所以还是得使用原生的数据缓存方法(getStorage,setStorage)来解决,或者将数据存至后端。
第三方组件修改样式
在 vue3 中要修改第三方组件库中的组件样式的话,需要使用 :deep(css选择器)
,同时一般会在 style 加上 scoped, 但如果在小程序中使用会发现子组件并不生效,而编译成 h5 却生效。我在这个 issues taro 3.0 + Vue 中 scoped 在 h5 下生效,在微信小程序中无效 下找到了解决问题
在 h5 模式下 scoped 会生成**[data-v-xxx]** 的属性,但是在小程序下则不会有,所以使用 scopd 在小程序中是无用的。就可以使用 cssModules,前提是需要在 config/index.js 中添加 cssModules 的支持,在上面 issues 中提到过,具体也就这样。
然后在 style 中添加 module 即可生效,都不用使用:deep
。
invalid code, rid: 6249d588-48af462b-xxxxxxx
服务器将 wx.login 获取到的 code 向小程序 API 获取 openid 的时候,如果提示该错误,那么大概率是 APPID 有问题,使用了测试号或者填写了错误 APPID。
总结
由于小程序(h5 手机端)应用写的少,所以在页面布局方便写的相对简陋,但本质与前端开发无特别大致区别,无非就是自定义了些相关的标签与 api,遇到时在查阅即可。
但相比传统 Web 开发而言,网站是无需审核只需要有个公网服务器就能访问,而小程序必须要经过审核才可发布,同时对小程序名称有严格的审核。主要方便在于微信用户的获取,同时提供完备的开发以及部署环境(开发者工具,云开发),加上用户数据分析等等。
在我发布完以及写完本文章后,我的建议是:
如非必要,不建议上传小程序供他人访问,尤其对于个人开发者而言,网页版也许是个更好的选择。
在回到开发者的角度,Taro 对 Vue3 的体验远比 Uniapp 来的好(至少目前是这样的,个人感受),Uniapp 太依赖于自家的 Hbuilder,但目前 Taro 的 Vue3 无法编译成安卓(React Native),所以如果考虑安卓端与小程序的话,还是 Uniapp 略胜一筹,H5 两者区别不大。但如果只是写小程序,我还是会毫不犹豫使用的使用 Taro。