跳到主要内容

题小侠

愧怍

很早就了解与学习过微信小程序开发相关的技术栈与框架,小程序的账号也都已经申请过。但写过的 demo 项目也迟迟没有发布到小程序上。这主要的原因还是觉得不值得发布,加上各种审核相关的。而这次准备写一个搜题相关的小程序,也是时候实战发布一下,顺带记录下整个开发与发布过程。

在线体验:扫下图小程序二维码

itopic

小程序的源码地址:https://github.com/kuizuo/question-man

技术栈

小程序所采用的是 Taro + Vue3 + NutUI,之所以选这套技术栈,主要是想上 Vue3,而 uniapp 对 Vue3 的支持并不友好,在我的上篇文章中也有说明到,同时支持 uniapp 的 vue3 屈指可数。所以便选用了这套技术栈来进行开发。

页面设计

image-20220405213930313

项目配置

项目搭建

安装及使用 | Taro 文档 (jd.com)

taro init myApp

配置如下

image-20220405214126617

安装完依赖,使用npm run dev:weapp,在打开微信开发者工具,导入项目即可。

axios 封装

web 端 http 请求使用最多的库就是 axios 了,但是在小程序中使用 axios 会提示 adapter 未定义,原因是小程序不能解析 package.json 中的 browser module 等字段。

要使用 axios 的话可以安装 axios-miniprogram 或者 taro-axios 库(我选择后者,但前者稍小 5kb),也就是会适配小程序的 axios 的 adapter,引入和使用与 axios 并不特别大的差异。以下是我封装后的代码

“utils/http.ts”
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 域名,所以上面的方案在小程序端失效,只能转到后端上。小程序官方有张实践图

img

整个步骤

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)

获取手机号 | 微信开放文档 (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,能很轻松的实现上面的效果,并且有个很客观的响应速度。(具体实现自行了解,后端代码暂不考虑开源)

上传发布

当本地开发完毕时,点击右上角的上传,填写版本号相关以及项目备注,然后上传成功如下图

image-20220403125339679

在版本管理中的开发版本可以看到刚刚上传的代码

image-20220403133323838

点击提交审核后,会提示确保项目不是测试版与 Demo(否则将受到相应处罚)如下图

image-20220403133707429

测试版

或者选择体验版(只有管理员与体验者的账号才可以访问),扫描体验版的二维码,即可使用微信访问项目。

审核版本

如果是要发布正式版的话,还需要填写如下表格,接着静等 1-7 天即可(可选择加急,一年一次)

image-20220406003544712

等待审核完毕,实测一天内,审核版本如下

image-20220406123345533

线上版本

最终提交发布,线上版本如下

image-20220406125311142

至此,用户即可打开微信,通过小程序访问到该应用。

关于改名

可能有些人(这个人就是我)会担心自己上线小程序后,期间能否改名,有何影响。

首先是可以改名的,小程序的标识是 APPID,而不是一个名称。但改名会直接影响到用户对小程序的认知,从而影响小程序用户流失。同时改名要求很苛刻,会检索是否有相似同名小程序,以及是否有商标证明等等,并且一年只能修改两次名字,所以起名与改名都要慎重。

一些开发时的注意事项(坑)

request:fail url not in domain list(跨域)

浏览器最烦躁之一跨域,在小程序上也不一意外,但小程序更加苛刻,即便后端允许跨域,小程序请求也会提示标题所示错误。这时候一般就如下几种做法。

网络 | 微信开放文档 (qq.com)

首先要确保是 https,同时开发时为了方便调试,一般会在本地设置中勾选,不效验合法域名选项,如下

image-20220403012958069

这时候开发环境下就能正常发送请求相关了(我这里后端是直接允许跨域了,不然需要在 config/index.js 中设置 devServer.proxy 的反向代理)。

但在生成环境下就需要在小程序中的开发管理中配置服务器域名了

image-20220403013733314

点击开始配置会提示身份认证相关,扫完码后将会到如下配置

image-20220403013828824

这里就是填写生产环境下要请求资源的域名了,并且是需要开启 https 的。

设置完毕,重启微信开发者工具或刷新项目配置,这里需使用小程序账号的 AppID 进行登录,测试号无效,然后项目配置就会设置好 request 合法域名,再次请求便能正常响应。

image-20220403014345645

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。