内容介绍
微信小程序毕设——抽奖,展示,商城,个人信息
去年毕设时候与美院同学一起合作实现的一个微信小程序,课题包含金主爸爸某咖啡品牌的一些元素。具体分为抽卡,卡片的展示,商城,以及个人信息四个界面。使用的微信原生开发框架,SpringBoot进行开发。详细功能的介绍已经在下文说明,有需要的可以参考下。
代码上传至gitee:https://gitee.com/zhu-16541/wechat-mini-program
实现介绍
前端模块的实现
在绘制前端界面的过程中,本项目避免了对WXSS直接编写代码,改为使用Less来方便代码的编写。通过给不同组件设定不同的类选择器,来进行样式的赋值。在前端的设计过程中,主要使用view组件来进行不同样式的界面模块设计,主要通过调用view组件中的bindtap事件来进行点击事件的编写。
为了实现适应不同手机的响应式布局,本项目在宽度上一律采用了rpx做为单位进行设计,相比与常用的单位px,rpx可以根据手机的宽度进行计算,对响应式开发有着巨大的帮助,同时在高度上使用百分比来实现更加准确的布局,二者共同来保证前端界面随着手机的改变而调整到合适宽度。在设计过程中,本项目大量使用了flex布局来实现不同view的布局,同时使用position中fixed,absolute以及relative来更加有效的实现不同情况的布局。
由于微信小程序开发过程中对程序大小的限制,本项目选择将所有动画与图片文件存入云存储之中,只保留少部分的图标文件,通过在utils文件中保存需要加载资源的相对路径,进行拼接后从云存储中进行加载。当小程序首次进入时,本项目设计了加载界面,用来实现对所需要显示的资源的提前加载,保证了小程序中图片显示的效果。
界面1
此界面的前端模块主要分成加载与抽奖时的动画,顶部文字轮播图,以及规则和历史的查看。
1. 加载与抽奖动画效果
为了加载过程中的流畅,本项目首先对本界面的所有动画效果进行了预加载,使用wx.cloud.getTempFileURL()获取需要预加载图片的链接,查看其tempFileURL并通过wx.downloadFile()将图片进行下载获取图片的本地链接,由于JS单线程的特性,为了保证下载效果,本项目创建多个Promise来进行图片异步下载,在下载完成后。使用微信API中的animation接口,实现对图片的平移,旋转,放大,以及透明度的改变,具体动画时间线如图所示。
当用户点击取消后,动画会进行还原,用户可以进行第二次卡片的抽取。为了解决动画所使用的gif文件不能重复加载的问题,本项目使用了wx.getImageInfo()重新获取图片信息并进行再次渲染。
2. 文字轮播图
本项目使用swiper组件来实现抽卡界面上方的文字轮播图,通过设定duration和interval均为4000毫秒,easing-function为easeInOutCubic,autoplay为true来实现文字的循环流畅播放。为了实现更好的显示效果,采用抽奖信息随机生成的方法,而非实时监测数据库的变化。最初使用了Unicode码来实现随机文字的生成,由于会导致大量生僻字的出现,本项目替换成在常见一些姓氏和名字中随机选择的办法,来更好的实现抽奖信息随机生成的效果。为了避免了第一张轮播图由于没有进入动画而长时间停留,使显示更加流畅,本项目设置了第一张轮播图为空。
界面2
此界面的前端模块主要分成卡片与勋章板块的界面设计,详细卡片的查看与分享,收藏,时间选择器的设计,屏幕指定位置的跳转。
1. 卡片与勋章板块的界面设计
在卡片的界面设计中,根据每一类别的卡片数量来进行不同的效果显示。为了实现更好的显示效果,本项目使用多个view进行不同旋转角度的叠加,增强用户的使用体验。在勋章收集的界面中,使用progress组件来进行进度条的绘制,计算用户收集卡片的数量,判断勋章的显示效果。
2. 卡片的查看与切换动画
在详细卡片查看界面中,通过view的点击函数来开启遮罩层,显示卡片的详细内容。为了实现点击卡片的左右两边来实现卡片的翻转,切换为其他卡片,供用户查看。本项目设计将两张卡片叠加在一起,其中一张卡片翻转180度作为卡片的反面,当用户点击旋转事件以后,计算两张卡片应该显示的卡片内容,同时将二者顺时针(逆时针)旋转180度来实现卡片的左旋(右旋),最后更改相关的文字内容显示。
3.数字藏品的分享
由于微信的封装的分享方法wx.onShareAppMessage()仅能在微信page中使用,本项目不得不在组件中重新实现了分享功能,通过给点击按钮绑定卡片的id信息,便于在处理函数中获取的卡片的图片路径,使用wx.downloadFile()进行图片的下载,当下载成功后,调用wx.showShareImageMenu()弹出分享界面来进行卡片的分享。
4.卡片的收藏
为了实现用户长按进行卡片的收藏功能,本项目使用view的longpress事件,进行用户长按动作的监听。通过在view上绑定卡片的id信息,在监测到用户长按动作后,判断卡片的收藏状态并进行取反处理,使用absolute布局来进行收藏图标的设计,当卡片为收藏状态时,进行图标的显示,当卡片为非收藏状态时,进行图标的隐藏。
本项目同时可以点击顶部的爱心按钮,来实现对所有收藏卡片的查看,为了避免使用justify-content为space-between时,最后一排内容无法有效显示的问题,本项目设定了justify-content为flex-start,让所有图片以最左侧为主轴进行排列,同时给每张图添加右边距,并搭配使用nth-child()选择器,设置最右边卡片的右边距为0,来实现所要的排列效果。
5.时间选择器的设计
为了让用户有更好的体验,本项目放弃了对微信选择器组件picker 的使用,使用picker-item对picker进行重新设计。分别调用了两个picker-view-column来作为年月选择器,同时定义了年月数组。同时为了保证用户选择不到未到达的月份,本项目首先获取当前日期,对2023年(当前年份)的月份数组进行处理。在年选择器的设计上,当用户选择2023年时,刷新月份选择器的内容。当设置时间选择器bottom为0的时候,其会出现在底部导航栏的上方,因此需要调用 wx.hideTabBar()方法对tabBar进行隐藏,同时对添加遮罩层,并设置其catchtouchmove()事件返回为空。
6.屏幕指定位置的跳转
为了实现用户使用时间选择器跳转到指定的位置,本项目给列表中的每个不同月份的view进行id的定义。在JS文件中,首先通过时间选择器计算合适的,滚动到达的位置,通过创建选择器分别查找到顶部导航栏和指定位置的view,获取其相关位置信息,并计算屏幕应该下滑的距离。最后使用wx.pageScrollTo()来进行界面的滚动。
界面3
此界面的前端模块主要分为首页轮播图的设计,定制产品的展示,结算。
1.首页轮播图的设计
轮播图分为三个定制产品的展示,分别为杯子,保温杯和杯垫。在轮播图的每一项中,本项目使用了图片动态切换来实现定制产品中不同类型的循环播放。为了实现图片间隔1000ms进行切换,本项目定义了index变量,并将它作为图片数组显示的序号,最初使用setinterval()方法来实现对index的改变,然而当运行时间较长时,setinterval()会出现内存泄露的问题。在查阅了资料后,本项目改用setTimeout()方法进行递归调用,同时定义全局变量timer来保证计时器实现单例模式运行,在切出界面时,调用clearTimeout()方法来清除计时器,来实现首页图片轮播的优化。
为了实现所需要的效果,本项目通过height与width相等,border-radius为50%的样式来设计了一个实心圆点,用户在点击不同的产品文字时,圆点查询会计算文字上方的坐标,计算出滑动的距离,使用transform来实现圆点在不同产品文字上的滑动。本项目也给文字设计了一个透明度的过渡动画,使的用户点击效果更加流畅。
2.定制产品的展示
为了保证用户可以流畅得选择不同的定制样式,当用户进入定制页面时,本项目首先对该页面的图片数据进行预加载。同时添加了选择图案按钮,让用户跳转到角色图案选择界面。当选择完毕后,页面跳转至上一级,并返回选择角色的id,通过id查询其角色的图片信息,从云存储中加载相应的图片。
由于同一类型定制产品仅仅在角色图和颜色进行区分,因此本项目在设计的过程中,采用获取角色图并置于定制产品的上方的方法,当用户改变角色图时,仅改变其上面的角色图案,并不改变下方的产品图案,来更好的实现不同角色下定制产品显示的切换。当用户切换所选择的角色图案时,本项目会根据角色id来计算角色所属类型,以及卡片的位置,通过scroll-view的scroll-into-view方法滑动到指定类型,同时根据卡片的位置计算屏幕滚动的距离,来实现重新定位到上一次选择的卡片位置。
在颜色选择的过程中,为了区分用户选择的颜色按钮,本项目内置了一个具有白色外边框的圆形view,通过absolute布局调整其位置在正中心,来实现所点击按钮的区分显示。
界面4
此界面的前端模块主要分为用户信息的展示与修改,订单与优惠券的查询。
1.用户信息的展示与修改
当用户进入关于我界面时,首先通过用户openId去获取用户的相关信息,并显示在屏幕上。当用户性别为男时,显示男性角色图片,当用户性别为女时,显示女性角色图片。
在设置中点击不同的信息会有不同的修改方式。在头像修改界面中,本项目根据用户所收集的卡片来判断用户可以选择的头像集合,用户点击头像进行头像的切换。这里为了区分用户所选择的头像,当用户选中某个头像时,本项目使用box-sizing: border-box;为其添加一个黑色内边距,来方便区分。在昵称修改界面中,本项目通过使用bindinput事件来监听输入的内容,为了禁止输入非法字符,本项目采用将正则表达式(^a-zA-Z0-9\u4e00-\u9fa5)替换为空的方式,来保证用户只能输入中文、英文和数字。同时,本项目使用bindblur与bindconfirm对用户修改完毕进行监听,当用户输入长度超过6个字符或者为空时,自动回退到输入前的内容,同时弹出提示框提示用户输入相对应的输入规范,来实现输入内容的长度小于等于6。在性别和生日选择器中,本项目分别调用了picker的普通选择器和日期选择器,来实现用户对内容的选择。在送货地址的填写中,使用radio组件来实现用户性别的选择,使用input组件的type="number"设置用户手机号输入仅能输入数字。在地址选择的过程中,调用picker的地址选择器,来实现省市区的选择。当用户填写完全部信息后,将全部信息转换为字符串进行拼接,并用@隔开,方便对收货地址信息的存储与读取。
2.订单与优惠券的查询
在订单查询界面中,本项目通过对用户订单数据的处理,获取用户订单的定制产品类型,颜色,角色类型等信息。通过角色信息和定制产品以及其颜色信息,本项目在订单界面绘制了订购产品的图片。为了简单地区分订单的状态,本项目使用简单的判断,若订单日期为当日,则订单正在运输中,否则订单已经完成。
在优惠券查询界面中,本项目通过调节优惠券view的height,来实现使用规则的展开与收缩,同时设置transition: all 1s ease;来使动画更加流畅运行。为了实现倒计时的功能,本项目对传入的时间与当前时间做差并除1000,若差值大于86400,则处理并输出日期,若差值大于3600,则做除法并赋值给变量h,并输出仅剩h小时,同理进一步输出分和秒。
其他
1.顶部导航栏的设计
由于微信原生导航栏组件无法进行自定义的设计,因此为了方便后续设计,本项目选择对导航栏组件进行重新设计,通过wx.getSystemInfoSync()与wx.getMenuButtonBoundingClientRect()获取手机的相关信息,并根据这些数据进行导航栏的设计。本项目设计了有返回键和没有返回键两种类型的导航栏,用户通过调用时传入不同的值,来实现两种风格导航栏的显示。
2.request请求的封装
由于前端与后端交互的过程中存在大量的request操作,因此对其进行了封装,同时使用了Promise来实现请求的异步操作,在调用的过程中,使用then对返回结果进一步处理。
3.数据的缓存
为了避免多次请求数据库,提高小程序效率,本项目使用了wx.setStorageSync()对数据进行缓存,同时存入数据获取的时间信息。当数据需要使用前,通过wx.getStorageSync()获取缓存的数据,若为空,或者数据所存储的时间距离当前时间高于10秒,则重新执行request请求进行数据的获取,否则便使用缓存中的数据。
4.数据的加密与解密
为了保证数据传输过程中的安全性[15],本项目选择使用AES加密算法对数据进行加密。AES作为一款免费的、标准化的、高效的和高度安全的加密算法[16],常用于小程序开发的加密中。CryptoJS是一个JavaScript的加解密的工具包。通过下载并调用其JS文件,设置参数mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7,同时设置64位密钥以及16位偏移量,调用其encrypt与decrypt方法对数据进行在接收时进行解密,发送时进行加密。
后端模块的实现
在后端的开发中,本项目使用SpringBoot框架,为了更好的实现代码的编写,添加了MyBatis-Plus,lombok,hutool等工具。运用MyBatis-Plus访问My SQL数据库[17]并编写Entity实体类,Mapper接口,以及Controller类实现对数据库的一些了操作。
在数据库实体层中,本项目定义了用户,卡片,用户与卡片关系映射,用户与订单优惠券映射等一系列实体类。通过使用MyBatis-Plus中的@TableId注解,可以给每一个实体的主键设置自增的特性,方便表现层对实体的处理。同时使用了lombok中的@Data注解,可以在实体中自动生成set与get方法,方便了本项目的书写。
在数据访问层中,为了更方便的使用MyBatis-Plus,本项目定义多个Mapper接口继承BaseMapper类,可以在表示层中调用其对数据操作的方法。对于无法直接调用的方法,本项目重新定义方法,并用SQL语句进行数据库的操作。
表示层主要负责具体的业务模块流程的控制[18]。在表示层中,本项目直接使用Mapper继承的方法,来更有效的实现对数据的增删改查。同时,表示层也是与前端进行数据交互的层,因此本项目也在表示层中,对数据进行处理,以及数据的加密解密等其他操作。
1.获取openId
为了识别用户,每一个使用小程序的用户都会产生一个保证安全的openId。本项目可以通过使用openId来进行用户独一性的验证。为了获取openId,本项目首先在前端调用wx.login()获取一个短暂有效的授权码,并将授权码发送至后端进行处理。当授权码到达后端时,使用code、AppId以及AppSecret三个参数对微信官方提供的地址进行访问,并解析返回值,以此来获取每一个用户的openId。
当后端获得了openId以后,前端可以通过GET请求去获取加密后的openId,以便之后使用。
2.卡片数据
为了减少冗余,本项目建立了用户与卡片关系映射表,通过userId和CardId来描述每一个用户与其所有卡片的关系。在Controller层,定义了getUserCard()、updateUserCardCollected()、getOneCard()三个方法,分别用于查找卡片,更新卡片,抽取一个卡片。在定义实体类时,本项目使用@TableField(exist = false)注解来表示关系映射表中不存在的信息。
2.1卡片的查找
由于用户与卡片映射表未保存卡片的详细信息,因此本项目使用Select语句进行多表联查,获取某一用户所有的卡片的信息。同时将不同类型的卡片分组,按日期进行排序,本项目使用了流(Steam)中的Comparator、Collectors以及groupingBy来进行数据的处理,返回分组有序的Map。为了更有效的对数据进行加密,本项目还使用了ObjectMapper来实现Map向String类型的转换,以实现以JSON形式进行保存。
2.2卡片的抽取
当前端执行抽卡动作时,会向后端发送抽取卡片的请求。为了实现卡片不重复的抽取,本项目首先查找用户与卡片映射表,通过List保存用户所有的卡片id,然后查找卡片表,通过List保存所有卡片的id。同样使用Steam对两个List进行做差,定义结果为idReduce,最后使用Random在0和idReduce.size()中进行随机数的生成,返回卡片的信息,同时向用户与卡片的表中插入数据,并将用户表中用户的抽奖次数减1,来实现完整的用户获取卡片过程。
3.订单与优惠券查询
鉴于订单与优惠券数据量较小,为了方便存储,本项目将订单与优惠券放入一个数据表中进行存储。通过设立type表项,当type为1时表示订单,当type为2时表示优惠券。同时将二者的信息全部存入message表项。
当插入订单到数据库时,本项目在前端将订单中定制产品的类型,时间等描述信息用字符@隔开,并使用字符串进行拼接并发送给后端,在后端获取数据后存入到数据库中。当查询订单时,通过构造QueryWrapper并使用eq去查询指定openId及type等于1的数据,直接返回拼接好的字符串给前端进行处理。
对于优惠券的查询同上所述。
4.用户数据
为了对用户数据进行操作,本项目在Controller层定义了getUserData(),updateUserData(),insertUserData()分别对用户表进行查询,更新,插入操作。对于查询和更新操作,均为前端request请求访问,由于插入操作需要在后端获取openId后进行判断,若不存在此openId则进行插入操作,因此本项目在获取openId的方法中对插入操作进行重定向并执行。
5.定制产品数据
由于定制产品数据较多,本项目选择将其放入数据库中进行保存。当用户选择定制产品进行定制时,通过传入类型type来进行不同定制产品的选择。后端接受到传入的type时,通过selectList进行查询。
6.加密与解密
为了实现与前端相同的加密方式,本项目添加了bcprov-jdk15on的依赖,新定义了加密解密类,通过配置加密方式为“AES/CBC/PKCS5Padding”,并配置与前端相同的密钥和偏移量,成功实现了对数据的加密解密,如图5-5所示。同时由于加密的参数为String类型,本项目添加hutool依赖,利用其中的toStr()方法来有效地实现各种类型向String类型的转换。
数据库的设计
本文数据库设计的主要表项有用户信息表(users_data),卡片信息表(card-group),用户与卡片关系映射表(users_card_link),用户与订单优惠券关系表(user_info_link),定制产品样式表(diy_form)等。
表4-1 用户信息表结构
用户信息表 users_data | |||
---|---|---|---|
字段名称 | 数据类型 | 约束 | 说明 |
id | int | not null | 主键,唯一性标识 |
open_id | varchar(255) | not null | 用户的openId |
nick_name | varchar(255) | not null | 用户昵称 |
image | varchar(255) | not null | 用户头像 |
date | date | 用户生日 | |
sex | int | not null | 用户性别0:未知1:男2:女 |
address | varchar(1000) | 用户地址 | |
time | int | not null | 用户允许抽卡的次数 |
表4-2 数字藏品信息表结构
数字藏品信息表 card_group | |||
---|---|---|---|
字段名称 | 数据类型 | 约束 | 说明 |
id | int | not null | 主键,唯一性标识 |
category | varchar(255) | not null | 数字藏品类别 |
card_id | int | not null | 藏品序号 |
image | varchar(255) | not null | 藏品图片信息 |
表4-3 用户与数字藏品关系表结构
用户与数字藏品关系表 users_card_link | |||
---|---|---|---|
字段名称 | 数据类型 | 约束 | 说明 |
id | int | not null | 主键,唯一性标识 |
user_id | varchar(255) | not null | 用户的openId |
card_id | int | not null | 数字藏品的id |
date | date | not null | 用户获取数字藏品日期 |
collect | int | not null | 是否收藏0:否1:是 |
表4-4 用户与订单优惠券关系表结构
用户与订单优惠券关系表 users_info_link | |||
---|---|---|---|
字段名称 | 数据类型 | 约束 | 说明 |
id | int | not null | 主键,唯一性标识 |
open_id | varchar(255) | not null | 用户的openId |
type | int | not null | 类型1:订单2:优惠券 |
date | datetime | not null | 订单日期或优惠券到期时间 |
message | varchar(1000) | not null | 订单或优惠券描述信息 |
表4-5 定制产品样式关系表结构
定制产品样式表 diy_form | |||
---|---|---|---|
字段名称 | 数据类型 | 约束 | 说明 |
id | int | not null | 主键,唯一性标识 |
type | int | not null | 产品类型0:水杯1:保温杯2:杯垫 |
name | varchar(255) | not null | 样式名称 |
category | varchar(255) | not null | 样式类别 |
img_src | varchar(255) | not null | 样式图片地址 |
color | varchar(255) | not null | 样式颜色 |