微信小程序开发填坑记录

最近因为需要帮女朋友实现一个微信小程序,所以去了解了一下小程序的开发。对于微信小程序的开发,之前完全没接触过(但有一些前端开发经验),作为完全的新手,入手资料肯定是微信小程序开发文档,根据这些天的开发过程的经验来看,最好先详细的过一遍官网的文档,大部分的坑是因为没有详细阅读文档,而另一部分坑是因为文档没有写。。。

注:项目架构是:C/S架构,前后端完全分离,前端:微信小程序,后端:springboot

开发过程遇到的坑主要以下几点:

  1. 微信登录实现模块
    开发文档中,登陆逻辑描述的很清楚,首先小程序中调用 wx.login() 获取 微信提供的临时登录凭证code ,然后把code传到开发者服务器(我们的后端服务器),后端再使用申请的专属appid和appsecret、code(刚才前端传过来的code)作为三个入参,调用微信授权url(https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code(APPID即appid,SECRET即appsecret,JSCODE即code,其他的不变)),微信授权中心会返回两个字段:用户唯一标识 OpenID 和 会话密钥 session_key;为了安全,一般不会把session_key作为sessionId,而是自己生成一个sessionId,我是使用jwt技术实现的sessionId的生成和校验。
    java实现代码:
@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配置的顺序问题,然后重新阅读文档才发现答案原来是这样。

  1. 非button按钮,通过bindtap=“clickFunctionName”,实现点击触发相应的方法后,需要传递特定的参数。
    • 场景:对于用for循环展示文章列表的item数据,然后点击某一条数据,触发相应的方法,但需要知道这条数据的唯一标示id
    • 实现方式:在wxml中使用 data-parameter 的方式,在点击实现的方法toDiary: function(event)的时候可以获取到parameter的value,即在js中使用 event.currentTarget.dataset.patameter 可以获取到绑定的值,如果有需要传入多个参数,设置多个data-parameter 就好了。

注:这里有个大坑是,习惯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
    })
    }
  },
  1. 前端页面url跳转,在新的页面获取url链接上的参数
    • 场景:从文章列表中,点击谋篇文章跳转到文章详情
    • 实现方式:这个不同于正常的web js获取url上的参数方式,微信小程序中,是将url中的参数放到新的页面onload(options)方法的options对象中,获取参数value的方式是,options.parameterName。
// url: '../topic/topic?topicId='+e.currentTarget.dataset.tranctionid
onLoad: function(options) {
    console.log('options', options);
    var diaryId = options.topicId;
 }

这里,对于url的参数命名是可以使用驼峰命名法的,你在options中的parameterName是不会被转换成小写的,,,

  1. 动态数据绑定后,怎么在js中改变变量的值,页面中自动渲染出变量新的值
  • 场景:文章的评论数量显示,刚点击查看文章详情时,先要去后端拿到目前的评论数量,然后我在底下加了一条评论,所以评论数量是要加1的。
  • 实现方式
<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的值也被刷新。官方文档中竟然没有提示,,,
  1. uploader组件上传图片
    • 场景:为了上传图片,直接使用了微信官方提供weUI框架的uploader组件,说实话,可能是因为是小程序开发的全新小白,感觉组件demo写的也太简单了,以至于怎么实现上传到自己的服务器,以及多图上传的问题。。。
    • 实现方式:
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)
          }
        })
      }
    })
  },

注:由于小程序有特殊作用,就不把小程序的全部源码分享出来了,敬请谅解!如果小伙伴有啥问题,可以评论共同探讨学习,毕竟我也只是一个刚上手的小白。

你可能感兴趣的:(微信小程序)