最近因为需要帮女朋友实现一个微信小程序,所以去了解了一下小程序的开发。对于微信小程序的开发,之前完全没接触过(但有一些前端开发经验),作为完全的新手,入手资料肯定是微信小程序开发文档,根据这些天的开发过程的经验来看,最好先详细的过一遍官网的文档,大部分的坑是因为没有详细阅读文档,而另一部分坑是因为文档没有写。。。
注:项目架构是:C/S架构,前后端完全分离,前端:微信小程序,后端:springboot
开发过程遇到的坑主要以下几点:
@PostMapping("/wxLogin")
public Result wxLogin(LoginVo loginVo) {
log.info("wxLogin request:{}", loginVo);
String url = "https://api.weixin.qq.com/sns/jscode2session";
Map<String, String> param = new HashMap<>();
param.put("appid", Constants.WEIXIN_APPID);
param.put("secret", Constants.WEIXIN_SECRET);
param.put("js_code", loginVo.getCode());
param.put("grant_type", "authorization_code");
String wxResult = HttpClientUtil.doGet(url, param);
log.info("weixin response:{}", wxResult);
WXSessionVo wxSessionVo = FastJsonUtil.fromJson(wxResult, WXSessionVo.class);
if (wxSessionVo == null){
return Result.fail(LOGIN_FAIL.getDesc(), LOGIN_FAIL.getCode());
}
UserInfo oldUserInfo = userInfoService.queryByOpenId(wxSessionVo.getOpenid());
String token = "";
if (oldUserInfo == null) {
//写入数据库
UserInfo newUserInfo = new UserInfo();
newUserInfo.setHeaderUrl(loginVo.getHeaderUrl());
newUserInfo.setUserName(loginVo.getUserName());
newUserInfo.setOpenId(wxSessionVo.getOpenid());
newUserInfo.setGmtDate(System.currentTimeMillis());
userInfoService.save(newUserInfo);
log.info("after userInfoService save, userInfo:{}", newUserInfo);
token = TokenUtil.sign(newUserInfo.getId());
} else {
token = TokenUtil.sign(oldUserInfo.getId());
}
return Result.success(token);
}
@Data
public class LoginVo implements Serializable {
private String userName;
private String headerUrl;
private String code;
private static final long serialVersionUID = 1L;
}
前端代码实现:
wx.login({
success: function(res) {
var code = res.code; //登录凭证
if (code) {
//2、调用获取用户信息接口
wx.getUserInfo({
success: function(res) {
app.globalData.userInfo = res.userInfo;
//3.请求自己的服务器,
var argu = {
code: code,
userName: res.userInfo.nickName,
headerUrl: res.userInfo.avatarUrl,
}
//这个地方换成wx.request方法发起http请求,我为了方便,所以封装了wx.request方法
httpClient.login(argu).then((data) => {
console.log("login http", data);
if (data.status == 200) {
//然后把token设置在每次的http请求中的header上,后端设置拦截器拿到token实现登陆逻辑的校验
app.globalData.token = data.data;
//如果后端校验成功,然后直接跳转到首页
wx.switchTab({
url: '../index/index'
})
} else {
wx.switchTab({
url: '../auth/auth'
})
}
}).catch(e => console.log(e));
},
fail: function() {
console.log('获取用户信息失败')
}
})
} else {
console.log('获取用户登录态失败!' + r.errMsg)
}
},
fail: function() {
console.log('登陆失败')
}
});
这里需要强调的是,这个登陆授权页是单独的页面,不是首页,那怎么实现小程序加载时先启动这个页面登陆成功后才跳转首页呢?在全局配置中,有一句加粗的话,数组的第一项代表小程序的初始页面(首页),所有只要把这个授权页面放到pages中的第一项就能最先执行;登陆成功后在跳转到真正的首页。
//app.json
{
"pages": ["pages/auth/auth", "pages/index/index"]
}
注:我当时,因为没仔细阅读官方文档,从github下载的demo看登陆授权的页面并没有被首页调用,但就是比首页先被执行了,深感疑惑。排查很久,才怀疑是page配置的顺序问题,然后重新阅读文档才发现答案原来是这样。
注:这里有个大坑是,习惯java命名参数的方式-驼峰命名法,在 data-parameter 绑定参数的时候,最后不要使用这种命名方式,因为在event对象中,会自动把parameterName全转换成小写,这么反人类的设计是真的坑,,,比如说,在wxml中data-tranctionId=“1”,然后js中e.currentTarget.dataset.tranctionId,你这样是永远获取不到tranctionId的值的,但如果e.currentTarget.dataset.tranctionid是可以获取到tranctionId的值;所以在小程序中,不管你是data-tranctionId=“1”,还是data-tranctionid=“1”,js都只能用小写命名的方式获取e.currentTarget.dataset.tranctionid参数值。
<view class="content" data-tranctionid="{{item.id}}" data-type="{{item.type}}" bindtap="toDiary">
toDiary: function(e) {
console.log("toTopicOrDiary click", e)
if (e.currentTarget.dataset.type == app.globalData.publishTypeEnum.topic) {
wx.navigateTo({
url: '../topic/topic?topicId='+e.currentTarget.dataset.tranctionid
})
}else{
wx.navigateTo({
url: '../diary/diary?diaryId='+e.currentTarget.dataset.tranctionid
})
}
},
// url: '../topic/topic?topicId='+e.currentTarget.dataset.tranctionid
onLoad: function(options) {
console.log('options', options);
var diaryId = options.topicId;
}
这里,对于url的参数命名是可以使用驼峰命名法的,你在options中的parameterName是不会被转换成小写的,,,
<view class="operation-btn flex-item">
<text>{{commentNum}}</text>
</view>
//js
data: {
commentNum: 0
},
//修改data中属性的值有两种:
1. this.data.commentNum = this.data.commentNum + 1;
2. this.setData({
commentNum: this.data.commentNum + 1
})
//两者的区别是,第一种,只会改变js中commentNum的值,但不会对页面中commentNum的值重新渲染刷新,
//而第二种是可以实现的页面中绑定的commentNum的值也被刷新。官方文档中竟然没有提示,,,
uplaodFile(files) {
console.log('upload files', files)
var _this = this;
//文件上传的函数,返回一个promise
return new Promise((resolve, reject) => {
for (var i = 0; i < files.tempFilePaths.length; i++) {
wx.uploadFile({ //必须使用wx.uploadFile,而不是wx.request,我在这个地方试了半天,一直不成功,在github上找到合适的demo才发现问题
url: URL.UPLOAD_DIARY_FILES, // 上传自己服务器的接口地址
filePath: files.tempFilePaths[i],
name: 'file', // 参数名称,后端接收时的参数名称
formData: {},
header: {
"Content-Type": "multipart/form-data", //传文件必须是form-data类型
'Authorization': app.globalData.token
},
success: (res) => {
// 图片上传成功后,后端会返回存到文件存储路径的id
//注意这个地方,跟你正常wx.request返回的结果不一样,后端return返回的数据data在res.data中,且这个data是jsonString(字符串格式),而不是json格式,
//我就说data.data一直报错,所以如果想通过对象引用字段的方式拿到某字段值,必须先进行json解析JSON.parse,这么重要的一点,官方文档竟然也没有说明,,,,
var data = JSON.parse(res.data);
_this.data.picUrlIds.push(data.data);
},
fail: (err) => {
console.log("error", err)
}
})
}
})
},
注:由于小程序有特殊作用,就不把小程序的全部源码分享出来了,敬请谅解!如果小伙伴有啥问题,可以评论共同探讨学习,毕竟我也只是一个刚上手的小白。