个人案例
- #小程序云开发挑战赛#-魅力单词-未央队
1. 引言 背单词十分枯燥乏味,为了给广大英语学习者提供一个比较好的背英语模式和应用,魅力单词应运而生。 基于微信小程序,天然低门槛、易上手。 2. 应用场景 课间、地铁上、旅途中,任何碎片化时间。 3. 作品简介 魅力单词是一款面向学生的背单词小程序,提供了丰富的单词词库以及好玩的背单词模式。 4. 目标用户 初高中生、大学生,英语爱好者。 5. 实现思路 本小程序基于云开发,用到了云数据库存储数据,使用云函数获取词库列表,随机获取单词词条,图片列表。 按批次拉取数据缓存到内存及localStorage中,实时对比输入内容与当前单词,基于图片奖励,继续循环下个单词。 图片数据:来源于微博。 单词词库:来源于百词斩。 6.流程图 [图片] 7. 效果图 [图片] [图片] [图片] [图片] 8. 功能代码展示 云函数 [图片] 数据库 [图片] 页面源码 [图片] [图片] 9. 作品二维码 [图片] 作品代码仓库地址:请私信 10. 团队简介 团队名称:未央队 团队成员:曹禄丰
2022-03-14 - #小程序云开发挑战赛#-情侣券-想做就做
应用场景 灵感源于 [图片] 目标用户 情侣,夫妻 原型图 [图片] 架构图 [图片] 效果截图 主流程 [图片] 模块 模版 [图片] 我的 [图片] 项目演示视频 地址:https://v.qq.com/x/page/v3153g8zs5p.html 功能代码展示 云函数代码(卡券云函数) [代码]// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() if (event.action && ticketHelper[event.action]) { const result = await ticketHelper[event.action](wxContext, event) return result } return { message: 'This action was not found', error: -1, } } const db = cloud.database(); const ticketHelper = { // 添加卡券 async addTicket(context, params) { params.ticket._openid = context.OPENID let res = await db.collection('tickets').add({ data: params.ticket }) return res }, // 查看我的卡券 async queryMyTicket(context, params) { const { OPENID } = context let res = await db.collection('tickets').where({ _openid: OPENID }).orderBy('createdAt', 'desc').get() return res }, // 查看卡券详情 async queryCurrentTicket(context, params) { let res = await db.collection('tickets').doc(params.ticketId).get() return res }, // 删除卡券 async removeTicket(context, params) { let res = await db.collection('tickets').doc(params.ticketId).remove() return res }, } [代码] 页面代码(首页) [代码]<!--index.wxml--> <view class="container"> <view class="top-img"> <image class="top-img" src="../../images/top_img.png"></image> </view> <view class="ticket-list"> <view bindtap="toAdd" class="create"> + 添加自定义模版 </view> <view wx:for="{{myTemplates}}" class="custom" style="background-image: url({{item.backgroundurl}});" bindtap="toInfo" data-item="{{item}}"> <view class="custom-title" style="color:{{item.color}}">{{item.title}}</view> <view class="custom-info" style="color:{{item.color}}">{{item.info}}</view> </view> </view> <view class="ticket-list"> <view wx:for="{{templates}}" bindtap="toInfo" data-item="{{item}}" class="ticket-item {{index%2==0?'ticket-pink-bg':'ticket-blue-bg'}}"> <text class="ticket-title">{{item.title}}</text> <view class="ticket-info {{index%2==0?'ticket-pink-info':'ticket-blue-info'}} ">{{item.info}}</view> </view> </view> </view> [代码] 逻辑代码(首页) [代码] // 首页 const app = getApp() import { queryPrivateTemplate, queryPublicTemplate } from '../../api/template' Page({ data: { }, onLoad: function (opt) { // 领取路径跳转 if (opt.type == 1) { wx.navigateTo({ url: '/pages/giveList/giveList?ticket=' + opt.ticket, }) } // 查询公用的卡券模版 queryPublicTemplate().then(res => { this.setData({ templates: res.result.data, }) }); }, onShow() { // 查询私有的卡券模版 queryPrivateTemplate().then(res => { this.setData({ myTemplates: res.result.data, }) }); }, // 去添加模版 toAdd() { if (app.authorized !== true) { wx.navigateTo({ url: '/pages/authorize/authorize' }); return; } wx.navigateTo({ url: '/pages/selectBackground/selectBackground', }) }, // 查看详情 toInfo(res) { // 没有授权就先去授权 if (app.authorized !== true) { wx.navigateTo({ url: '/pages/authorize/authorize' }); return; } let item = res.currentTarget.dataset.item var templateInfo = JSON.stringify(item); wx.navigateTo({ url: '/pages/add/add?action=update&templateInfo=' + templateInfo }) } }) [代码] 样式代码(app.js) [代码].container{ width: 100vw; /* height: 100vh; */ background-color: #FFFFFF; display: flex; flex-direction: column; align-items: center; } page{ background: #f6f6f6; --color-p: #F58B98; } button { padding-left: 0rpx; padding-right: 0rpx; border-radius: 0rpx; } button::after { border: none; } /* 底部按钮 */ .next-btn { position: absolute; bottom: 0rpx; left: 0rpx; width: 750rpx; height: 120rpx; background: var(--color-p); text-align: center; line-height: 120rpx; font-size: 30rpx; font-family: PingFang SC; font-weight: 600; color: #FFFFFF; } /* 已使用样式 */ .image-gray { -webkit-filter: grayscale(100%); -moz-filter: grayscale(100%); -ms-filter: grayscale(100%); -o-filter: grayscale(100%); filter: grayscale(100%); filter: gray; opacity:0.9; } /* 所有输入框,未输入文字的样式 */ .placeholder { font-size: 26rpx; font-family: PingFang SC; font-weight: 400; color: #CCCCCC; } /* 卡券样式:主页、添加页、我收到的、我赠送的、详情页 */ .custom { width: 670rpx; height: 240rpx; margin-top: 25rpx; background: #FFF7F7; border-radius: 20rpx; background-size: 100% 100%; position: relative; } .custom-title { margin-left: 40rpx; padding-top: 40rpx; font-size: 40rpx; font-weight: 600; color: #FFFFFF; } .custom-info { margin-left: 40rpx; padding-top: 25rpx; font-size: 26rpx; font-weight: 600; color: #FFFFFF; } /* 头部样式:选择背景、添加页、详情页 */ .top { width: 690rpx; display: flex; flex-direction: row; margin: 20rpx 30rpx; align-items: center; justify-content: space-between } .top-title { font-size: 34rpx; font-family: PingFang SC; font-weight: 600; color: #2A2A2A; } .top-right-text { font-size: 24rpx; font-family: PingFang SC; font-weight: 400; color: var(--color-p); } [代码] 作品体验二维码 [图片] 团队简介 夫妻档 陈宇明:负责产品与开发 王丝雨:负责设计与交互 觉得不错那就【点赞】支持一下
2020-09-20 - 2019-02-21
- 一次安全可靠的通信——HTTPS原理
我们知道小程序的wx.request网络接口只支持HTTPS协议(文档-小程序网络说明),为什么HTTPS协议就比HTTP安全呢?一次安全可靠的通信应该包含什么东西呢,这篇文章我会尝试讲清楚这些细节。 Alice与Bob的通信 我们以Alice与Bob一次通信来贯穿全文,一开始他们都是用明文的形式在网络传输通信内容。 [图片] 嗅探以及篡改 如果在他们的通信链路出现了一个Hacker,由于通信内容都是明文可见,所以Hacker可以嗅探看到这些内容,也可以篡改这些内容。 [图片] 公众号的文章之前就遇到很多被挟持篡改了内容,插入广告。 [图片] 加密解密 既然明文有问题,那就需要对明文进行加密处理,让中间人看不懂内容,于是乎要对原来的内容变成一段看不懂的内容,称为加密,反之则是解密。而本质其实就是一种数学运算的逆运算,类似加法减法,例如发送方可以将 abcd…xyz 每个字母+1映射成 bcd…yza,使得原文的字母变成看不懂的序列,而接收方只需要将每个字母-1就可以恢复成原来的序列,当然这种做法规律太容易被破解了,后边会有个案例示意图。 [图片] 对称加密 如果对2个二进制数A和B进行异或运算得到结果C, 那C和B再异或一次就会回到A,所以异或也可以作为加密解密的运算。 [图片] 把操作数A作为明文,操作数B作为密钥,结果C作为密文。可以看到加密解密运用同一个密钥B,把这种加解密都用同一个密钥的方式叫做对称加密。 [图片] 可以看到简单的异或加密/解密操作,需要密钥跟明文位数相同。为了克服这个缺点,需要改进一下,把明文进行分组,每组长度跟密钥一致,分别做异或操作就可以得到密文分片,再合并到一起就得到密文了。 [图片] 但是这种简单分组的模式也是很容易发现规律,可以从下图看到,中间采用对原图进行DES的ECB模式加密(就是上边提到简单分组的模式) [图片] 很明显,原图一些特征在加密后还是暴露无遗,因此需要再改进一把。一般的思路就是将上次分组运算的结果/中间结果参与到下次分组的运算中去,使得更随机混乱,更难破解。以下图片来自维基百科: [图片] 经过改良后,Alice与Bob如果能提前拿到一个对称加密的密钥,他们就可以通过加密明文来保证他们说话内容不会被Hacker看到了。 [图片] 非对称加密 刚刚还引发另一个问题,这个对称加密用到的密钥怎么互相告知呢?如果在传输真正的数据之前,先把密钥传过去,那Hacker还是能嗅探到,那之后就了无秘密了。于是乎出现另外一种手段: [图片] 这就是非对称加密,任何人都可以通过拿到Bob公开的公钥对内容进行加密,然后只有Bob自己私有的钥匙才能解密还原出原来内容。 [图片] RSA就是这样一个算法,具体数学证明利用了大质数乘法难以分解、费马小定理等数学理论支撑它难以破解。相对于前边的对称加密来说,其需要做乘法模除等操作,性能效率比对称加密差很多。 [图片] 由于非对称加密的性能低,因此我们用它来先协商对称加密的密钥即可,后续真正通信的内容还是用对称加密的手段,提高整体的性能。 [图片] 认证 上边虽然解决了密钥配送的问题,但是中间人还是可以欺骗双方,只要在Alice像Bob要公钥的时候,Hacker把自己公钥给了Alice,而Alice是不知道这个事情的,以为一直都是Bob跟她在通信。 [图片] 要怎么证明现在传过来的公钥就是Bob给的呢?在危险的网络环境下,还是没有解决这个问题。 [图片] 一般我们现实生活是怎么证明Bob就是Bob呢?一般都是政府给我们每个人发一个身份证(假设身份证没法伪造),我只要看到Bob身份证,就证明Bob就是Bob。 网络也可以这么做,如果有个大家都信任的组织CA给每个人出证明,那Alice只要拿到这个证明,检查一下是不是CA制作的Bob证书就可以证明Bob是Bob。所以这个证书里边需要有两个重要的东西:Bob的公钥+CA做的数字签名。 [图片] 前边说到用公钥进行加密,只有拥有私钥的人才能解密。数字证书有点反过来:用私钥进行加密,用公钥进行解密。CA用自己的私钥对Bob的信息(包含Bob公钥)进行加密,由于Alice无条件信任CA,所以已经提前知道CA的公钥,当她收到Bob证书的时候,只要用CA的公钥对Bob证书内容进行解密,发现能否成功解开(还需要校验完整性),此时说明Bob就是Bob,那之后用证书里边的Bob公钥来走之前的流程,就解决了中间人欺骗这个问题了。 这种方式也是一种防抵赖的方式,让对方把消息做一个数字签名,只要我收到消息,用对方的公钥成功解开校验这个签名,说明这个消息必然是对方发给我的,对方不可以抵赖这个行为,因为只有他才拥有做数字签名的私钥。 [图片] CA其实是有多级关系,顶层有个根CA,只要他信任B,B信任C,C信任D,那我们基本就可以认为D是可信的。 [图片] 完整性 上边基本上已经解决了保密性和认证,还有一个完整性没有保障。虽然Hacker还是看不懂内容,但是Hacker可以随便篡改通信内容的几个bit位,此时Bob解密看到的可能是很乱的内容,但是他也不知道这个究竟是Alice真实发的内容,还是被别人偷偷改了的内容。 [图片] 单向Hash函数可以把输入变成一个定长的输出串,其特点就是无法从这个输出还原回输入内容,并且不同的输入几乎不可能产生相同的输出,即便你要特意去找也非常难找到这样的输入(抗碰撞性),因此Alice只要将明文内容做一个Hash运算得到一个Hash值,并一起加密传递过去给Bob。Hacker即便篡改了内容,Bob解密之后发现拿到的内容以及对应计算出来的Hash值与传递过来的不一致,说明这个包的完整性被破坏了。 [图片] 一次安全可靠的通信 总结一下,安全可靠的保障: 对称加密以及非对称加密来解决:保密性 数字签名:认证、不可抵赖 单向Hash算法:完整性 来一张完整的图: [图片]
2019-02-20