本文最后更新于:2 个月前
前言 记录小程序开发过程中的问题
正文 PicMemories
2025 年 1 月 11 日
后端刚启动就报错,这么刺激:
1 2 3 4 5 6 Connected to the target VM, address: '127.0.0.1:10270' , transport: 'socket' 16:26:56.138 [main] DEBUG reactor.util.Loggers - Using Slf4j logging framework 16:26:56.142 [main] DEBUG reactor.core.publisher.Hooks - Enabling stacktrace debugging via onOperatorDebug 16:26:57.051 [main] ERROR org.springframework.boot.SpringApplication - Application run failed org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 1 at org.yaml.snakeyaml.reader.StreamReader.update(StreamReader.java:218)
竟然又是配置文件带了中文,这么古老。
1 2 3 4 5 6 7 8 9 10 11 12 knife4j: enable: true openapi: title: PicMemories 接口文档 description: PicMemories 壁纸分享小程序 concat: 3348407547 @qq.com url: https://deng-2022.gitee.io/blog/ version: 1.0 license: Apache 2.0 group: test1: group-name: 壁纸分享
启动成功。
启动第一步,先把图床资源翻新下,前年用七牛云的临时域名,图片链接早都过期了。
这个小程序初步计划仅实现最核心的功能,即服务端上传图片,小程序端下载图片。
想起来当时是因为暑假那会儿有同学找我组队,说是需要一个会做小程序的,我从零开始边学边做,两周左右的时间就完善了基本页面。
后来这个同学就没有消息了,小程序也暂且搁置。
当时把页面分为四个板块:精选,分类,频道,我的,其实是想实现一个集交友,壁纸下载,聊天等的小程序。
不过没什么使用价值,连壁纸究竟该提供哪些分类和数量都没有想法,大部分编码时间里都在纠结图片间距和预览效果,其核心功能开发倒没多少进展。
直到昨天。
昨天下午我想起来那会儿确实也想要做一个个人网站,尝试学习不同领域的知识,小程序,Web,桌面端,保持学习的心态去实现心愿。
后来确实做到了,我成功运营起来自己的个人博客网站,相当成功。
昨天又想起来自己之前一直想搭建文档站点,后来还尝试开发过壁纸分享小程序,这两项工作也是时候该完成了。
文档站点,可以选择提炼出个人博客网站中的编程学习经验栏目;而壁纸分享小程序,则专门用来收录个人博客站点的精选壁纸图片。
初步打造属于自己的全生态软件集合。
时间还有很多,先执行起来吧。
小程序开发
2023 年 7 月 30 日
开发准备 HBuilderX+uni-app+uinCloud
微信开发者工具连接失败
微信开发者工具
最近在开发 PicMemories 壁纸分享小程序,浅浅记录遇到的一些问题
请求域名不合法
全局封装 request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 App ({ request (options ) { const { url, method, data, success, fail } = options; wx.request ({ url, method, data, success (res ) { success && success (res.data ); }, fail (error ) { fail && fail (error); }, }); }, onLaunch : function ( ) { console .log ("App launched" ); }, onShow : function ( ) { console .log ("App showed" ); }, onHide : function ( ) { console .log ("App hided" ); }, globalData : { userInfo : null , themeColor : "#F44336" , }, });
1 2 3 4 5 6 7 8 9 10 app.request ({ url : "http://localhost:8084/api/user/current" , method : "GET" , success : function (data ) { console .log (data); }, fail : function (error ) { console .log (error); }, });
1 Invalid character found in method name [0x160x030x010x020x000x010x000x010xfc0x030x031E0xe3]0x8eQ0xd60x0e0xf7mJ0x950x920x810xe00x0c0x820xb5)0xc70xdf0xefY0xe30xb8|0x9d0xd430xe50x8f5 ]. HTTP method names must be tokens
经排查,是因为把 http 写成 https 了:
数据双向绑定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <van-cell-group > <van-field required model:value ="{{username}}" label ="昵称" placeholder ="请输入昵称" /> <van-field required model:value ="{{phone}}" label ="密码" placeholder ="请设置密码" /> <van-field required model:value ="{{password}}" label ="手机号" placeholder ="请输入手机号" /> </van-cell-group >
1 2 3 4 5 data : { username : '' , password : '' , phone : '' },
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 goToRegister ( ) { app.request ({ url : 'http://localhost:8084/api/user/register' , method : 'POST' , data : { username : this .data .username , password : this .data .password , phone : this .data .phone }, success : function (data ) { console .log (data); }, fail : function (error ) { console .log (error); } }) },
即可实现简单的双向数据绑定(2023/08/04 早)
生命周期函数 1 2 3 4 5 6 onLoad (options ) { },
1 2 3 4 5 6 onShow ( ) { Toast ("分类~" ) },
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 onPullDownRefresh ( ) { },onReachBottom ( ) { },onShareAppMessage ( ) { }
路由跳转
1 wx.switchTab 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
1 wx.reLaunch 关闭所有页面,打开到应用内的某个页面
1 wx.redirectTo 关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面
1 wx.navigateTo 保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
1 wx.navigateBack 关闭当前页面,返回上一页面或多级页面
我们可以分析出在不同的业务场景下,使用怎样的路由跳转更合适:
wx.switchTab:支持跳转至 tabbar 页面
跳转至 tabbar 页,关闭其他所有非 tabBar 页面
wx.redirectTo、wx.navigateTo:支持跳转至非 tabbar 页面
前者关闭当前页面,后者保留当前页面
wx.navigateBack:与 wx.navigateTo 配合,返回上一级页面
wx.reLaunch:支持跳转至任何页面
那么从登录页跳转至个人页,就应该用 wx.switchTab / wx.reLaunch(2023/08/05 早)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 goToLogin ( ) { app.request ({ url : 'http://localhost:8084/api/user/login' , method : 'POST' , data : { username : this .data .username , password : this .data .password , }, success : function (res ) { console .log (res) const data = res.data ; if (data.code == 0 ) { wx.setStorageSync ('sessionID' , res.header ["Set-Cookie" ]); Notify ({ type : 'success' , message : data.description }); wx.reLaunch ({ url : '/pages/user/index' }) } else { Notify ({ type : 'warning' , message : data.description }); } }, fail : function (error ) { console .log (error); } }) },
data 已初始化,未触发数据绑定 1 2 3 4 5 Page ({ data : { imgs : [], urlList : [] },
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 onShow ( ) { Toast ("惊喜多多~" ) var _this = this app.request ({ url : 'http://localhost:8084/api/wallpaper/listPage' , method : 'GET' , success : function (res ) { const data = res.data ; _this.setData ({ 'imgs' : data.data .records }); _this.data .imgs .forEach ((item ) => { _this.data .urlList .push (item.wallpaperUrl ); }); }, fail : function (error ) { console .log (error); } }) },
1 2 3 <block class ="image-list" wx:for ="{{urlList}}" wx:key ="index" > <image class ="img" src ="{{item}}" alt ="刚刚" mode ="widthFix" /> </block >
这里的 image 标签,仍旧无法正常绑定到 urlList
当 show()生命周期函数未执行完毕,即 urlList 仍为空数组时,data 在页面展示时已经初始化
如此改造 show()生命周期函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 onShow ( ) { Toast ("惊喜多多~" ) this .getData (); },getData ( ) { var _this = this ; app.request ({ url : 'http://localhost:8084/api/wallpaper/listPage' , method : 'GET' , success : function (res ) { const data = res.data ; _this.setData ({ 'imgs' : data.data .records }); _this.setUrlList (); } }); },setUrlList ( ) { var urlList = []; this .data .imgs .forEach ((item ) => { urlList.push (item.wallpaperUrl ); }); this .setData ({ 'urlList' : urlList }); },
成功解决 urlList 数据双向绑定问题(2023/08/05 早)
无法获取 Session
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 goToLogin ( ) { app.request ({ url : 'http://localhost:8084/api/user/login' , method : 'POST' , data : { username : this .data .username , password : this .data .password , }, success : function (res ) { console .log (res) const data = res.data ; if (data.code == 0 ) { wx.setStorageSync ('sessionID' , res.header ["Set-Cookie" ]); Notify ({ type : 'success' , message : data.description }); wx.reLaunch ({ url : '/pages/user/index' }) } else { Notify ({ type : 'warning' , message : data.description }); } }, fail : function (error ) { console .log (error); } }) },
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 app.request ({ url : 'http://localhost:8084/api/user/current' , method : 'GET' , header : { 'Content-Type' : 'application/json' , 'sessionID' : wx.getStorageSync ('sessionID' ) }, success : function (data ) { console .log (data); }, fail : function (error ) { console .log (error); } }) },
小程序登录 - 用户身份标识
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 goToLogin ( ) { var _this = this ; wx.login ({ success (res ) { if (res.code ) { console .log ("code = " + res.code ) wx.request ({ url : 'http://localhost:8084/api/user/login' , method : 'POST' , data : { code : res.code , username : _this.data .username , password : _this.data .password , }, success : function (res ) { console .log (res) const data = res.data ; if (data.code == 0 ) { Notify ({ type : 'success' , message : data.description }); wx.reLaunch ({ url : '/pages/user/index' }) } else { Notify ({ type : 'warning' , message : data.description }); } }, fail : function (error ) { console .log (error); } }) } else { console .log ('登录失败!' + res.errMsg ) } } }) },
1 2 private final String appId = "wxd12f7c79bd639a9b" ;private final String secret = "8a99ccc1802486c1387c1cda45288dd5" ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public String getCode2Session (String appId, String secret, String code) { String url = String.format("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code" , appId, secret, code); HttpRequest request = HttpRequest.get(url); log.info("result = " + request.execute()); return "request" ; } String url = "https://api.weixin.qq.com/sns/jscode2session" ; HashMap<String, Object> paramMap = new HashMap <>(); paramMap.put("appid" , appId); paramMap.put("secret" , secret); paramMap.put("js_code" , code); paramMap.put("grant_type" , "authorization_code" ); String result = HttpUtil.get(url, paramMap); log.info("result = " + result); return result;
1 2 Response Body: {"session_key" :"oZ0ju27QocpPhHF2Z1MrtQ==" ,"openid" :"otcWC6_yPHlB2Q6e0vIrB_J2iYHs" }
1 {"errcode" :40029,"errmsg" :"invalid code, rid: 64cf07f9-36a4c687-09c960f0" }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 onShow () { Toast ('个人中心~' ); console .log ("seeesion_key = " + wx.getStorageSync ('session_key' )) app.request ({ url : 'http://localhost:8084/api/user/current' , method : 'GET' , header : { 'Authorization' : 'Bearer ' + wx.getStorageSync ('session_key' ) }, success : function (data ) { console .log (data); }, fail : function (error ) { console .log (error); } }) },
路由跳转、携带参数
1 2 3 4 5 6 7 8 9 10 11 onClickShow (e ) { const imageUrl = e.currentTarget .dataset .url ; const parameter = { url : imageUrl }; const url = '/pages/imageInfo/index?parameter=' + encodeURIComponent (JSON .stringify (parameter)); wx.navigateTo ({ url : url }); },
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 onLoad (options ) { const {url} = JSON .parse (decodeURIComponent (options.parameter )); console .log (url) this .getData (url) },getData (url ){ this .setData ({ 'url' : url }) console .log (this .data .url ) },
要注意这里的 options 拿到的数据是封装好的对象,要使用如上方式接收参数:(2023/08/09 晚)
1 const { url } = JSON .parse (decodeURIComponent (options.parameter ));
点击 image 标签携带 URL
要给每个 <image>
标签添加 click 函数,以便点击不同的图片跳转到不同的页面,可以通过以下方法实现:
1 2 3 4 5 6 7 8 <image class ="img" src ="{{item}}" alt ="刚刚" mode ="widthFix" bindtap ="handleImageTap" data-url ="{{item}}" />
在当前页面的 JS 文件中定义 handleImageTap
方法来处理点击事件
可以使用 wx.navigateTo
方法跳转到不同的页面并传递参数
1 2 3 4 5 6 7 8 9 Page ({ handleImageTap (e ) { const imageUrl = e.currentTarget .dataset .url ; wx.navigateTo ({ url : `/pages/detail?imageUrl=\${imageUrl}` , }); }, });
在 handleImageTap
方法中,使用 e.currentTarget.dataset.url
获取到点击的图片链接,然后根据需要的逻辑进行不同的跳转。
请注意,上述代码是使用小程序原生的 wx.navigateTo
进行页面跳转,如果你使用的是框架或组件库,可能有不同的方法来实现页面跳转。(2023/08/09 晚)
实现点赞图片 1 2 3 4 5 6 7 8 <view class ="like" > <van-icon name ="{{isLiked ? 'like' : 'like-o'}}" color ="red" bindtap ="handleIconClick" /> </view >
1 2 3 4 5 6 7 8 9 10 11 handleIconClick ( ) { this .setData ({ isLiked : !this .data .isLiked , }); if (this .data .isLiked ){ Toast ("已收藏~" ) }else { Toast ('取消收藏~' ) } },
效果呈现
2024 年 4 月 22 日
半年前尝试开发过第一个微信小程序,花费了二十天时间认真做功能实现.。当时为了开发这个小程序,还借鉴了不少相关小程序的页面排版,多方参照才最终定型了这版页面。那么最终的效果如下 :
踩坑记录
首先,能够正确获取到小程序的 appID、appSecret:
开发者工具的 appID 必须设置正确,否则会不可避免地出现 invalid code 错误:(2023/08/06 早)
1 { "errcode" : 40029 , "errmsg" : "invalid code, rid: 64cf07f9-36a4c687-09c960f0" }
公众号开通
2024 年 4 月 22 日
今下午,我突然想要开通一个微信公众号,借此来激励我每天读书写作。那是因为最近开始慢慢回忆起小时候的乡村生活 ,写的东西渐渐多了起来。但总归是没时间没精力,写东西断断续续的,我对这样的写作现状很不满意。
于是下午看了很多相关公众号文章,计划近段时间就开启公众号文章写作计划:
我快速回顾了前段时间里了解到的有关微信公众平台的信息,包括小程序开发、智能机器人对话(动手做个 AI 机器人,帮我回消息!_牛客网 (nowcoder.com) )这些,把这些快遗忘的知识捡起来。
微信公众平台访问入口:微信公众平台 (qq.com)
注册一个微信公众号,选择订阅号 即可。
但接下来的一步就让我止步于此了,我必须要提供一个未被微信公众平台注册过 的邮箱
详细要求可以看这里:公众号注册提示“邮箱已被占用” (qq.com)
除了现在正在使用的 qq 邮箱外,去年写小程序时还用手机号申请了网易邮箱。但现在由于这个网易邮箱绑定了一个小程序,要想顺利开通微信公众号,目前只有两种解决办法:修改小程序登录邮箱 和注销小程序
虽然这个小程序是废稿,但毕竟是我开发的第一个微信小程序,未来可能还要继续优化呢。就这么注销了的话,本地的开发可能受到影响,怪可惜的。那最终的解决办法就是:今天晚上回去找我兄弟,让他注册一个网易邮箱,完事了我把小程序邮箱账号更换成他的,我开通的微信公众号就绑定我的网易邮箱吧。
那么现在就开始研究研究今年年初搞过的微信智能 AI 机器人了,不知道当时怎么想的,竟然没有记录下代码是如何起来的。相关的开发文档也没有多少记录,只有运行效果,只好是我再继续摸索摸索。
2024 年 4 月 23 日
很好,昨天下午总结了一番微信智能 AI 机器人的开发流程,晚上又让我的好兄弟注册了新的网易邮箱。
这下我就成功开通了微信公众号了。
总结