1. 项目计划
本项目为基于微信手机运用平台的一款活动互动型小顺序,完成了用户立即活动步数群内PK与个人动态的宣布,小顺序前端采纳原生框架,后端采纳基于Node的koa2框架,数据库采纳MYSQL,对象存储采纳七牛云,服务器采纳阿里ECS,域名采纳CA认证。
运转结果以下
http://pgwn32i53.bkt.clouddn….
2. 支持手艺点剖析
2.1 七牛云存储
在这个项目中,有个功用为动态宣布,许可用户上传图片,动态宣布后,一切人可在动态广场看到该动态,存储图片有许多体式格局,比方经由过程表单将文件转为为二级制发送给后端,然后存数据库中,然则,如许我就要多写许多代码,一切我决定将图片存储到图床,我数据库中保留图片链接即可,图床有许多,不逐一形貌了,我此次用的是七牛云,个人认证胜利后将取得肯定空间的免费存储空间。
竖立存储空间(ydonline),选定存储地区(华北)。
注册胜利后,将取得两组秘钥,这东西很主要,上传文件时,须要晓得uptoken,uptoken是依据AK与SC天生的,七牛云开辟文档中发起后端天生uptoken值,但我嫌贫苦,直接在线天生了uptoken,也就是说该uptoken是写死的。
uptoken天生地点:http://pchou.qiniudn.com/qini… deadline的时候设置长一些
引入官方开辟文件:qiniuUploader.js
小顺序端存储图片症结代码:
releaseNotice.js
const qiniuUploader = require("qiniuUploader.js");
function didPressChooesImage(that) {
initQiniu();
// 微信 API 选文件
wx.chooseImage({
count: 1,
success: function (res) {
var filePath = res.tempFilePaths[0];
// 交给七牛上传
qiniuUploader.upload(filePath, (res) => {
let imageurl = that.data.imageurl;
imageurl.push(getApp().data.qiniu_domain + res.key);
that.setData({
'imageurl': imageurl
});
}, (error) => {
console.error('error: ' + JSON.stringify(error));
});
}
}
)
}
function initQiniu() {
//设置详解 https://github.com/gpake/qiniu-wxapp-sdk/blob/master/README.md
var options = {
region: 'NCN', // 华东区 依据存储地区填写
//uptokenURL: 'UpTokenURL.com/uptoken',//// 从指定 url 经由过程 HTTP GET 猎取 uptoken,返回的花样必需是 json 且包括 uptoken 字段,比方: {"uptoken": "0MLvWPnyy..."}
uptoken: getApp().data.qiniu_uptoken,
domain: getApp().data.qiniu_domain
};
qiniuUploader.init(options);
}
---------------------------------------------------------------------------------------
app.js
App({
data: {
qiniu_domain: 'http://pgwn32i53.bkt.clouddn.com/',//七牛图片外链域名
qiniu_uptoken: 'uxQXOgxXDtF-1uM_V15KQSIky5Xkdww0GhoAksLF:LWUt0HMVbICEDaSOMnMF3YLoUH4=:eyJzY29wZSI6Inlkb25saW5lIiwiZGVhZGxpbmUiOjE4NTU2NzA0MDF9'
},
})
上传文件到指定的存储空间,七牛会返回文件的key值,加上七牛给你的外链域名,如许,你就能够显现文件了。
2.2 服务器与域名
我买过两次服务器,第一次是阿里的,别的一次也是阿里的。但此次,我买的是windows版云ECS ,起首,明白需求。
- 长途衔接布置项目(装置环境,运转背景,域名解析,外网接见)
- 举行域名CA认证, 小顺序的要求必需得是HTTPS。
在windows 服务器上布置项目 简朴, 长途链接时,挑选同享当地某个硬盘的材料,衔接胜利后,把当地的环境软件悉数装置上去,本次须要在服务器上装置 node.js、git、mysql这三个软件。
购置SSL证书
https://yundunnext.console.al…
阿里有对单域名免费的证书,时候为1年,因而我为该项目标域名购置了https。
点击下载 =>挑选 其他
解压后,内里有两个文件,一个是crt,一个是key,将这两个文件发在背景文件夹下/ssl包下 (可随意定名)
背景加载这两个文件。
app.js 症结代码
var app = require('koa'),
https = require('https');
app=new app();
var options = {
key: fs.readFileSync('./ssl/key.key'), //ssl文件途径
cert: fs.readFileSync('./ssl/crt.crt') //ssl文件途径
};
// start the server
https.createServer(options, app.callback()).listen(443);
如许,背景就跑在htpps下了。
在服务器上运转背景:
然后你用本身的电脑翻开该域名,你会发明基础衔接不上。
<!–more–>
那末,这是什么鬼?
本来 阿里的windows 服务器 防火墙做了限定,且服务器安全组也做了限定。
翻开防火墙=>高等设置=>入站划定规矩=>新建划定规矩。
挑选端口 =>填写端口
本次须要填写三个端口号, 背景的80(http) 443(https) 3306(mysql)
- 在阿里云控制器那边设置安全组,以下图所示。
填写 那三个端口号, 受权对象那边 填写 0.0.0.0/0
做完这一步,功德圆满了,你的域名能够被接见了。
3. 数据库设想
数据表有两张,分为动态表与活动数据表,以下图所示。
4. 前端设想与开辟
4.1 首页
首页主要由动态广场与底部的tabbar构成,动态广场显现状况一般(state==1)的动态,私家动态与制止动态不能被别人所瞥见,在数据表设想中,uid实在就是openid。 用户上传了图片后,数据库中保留的是其接见地点,多个地点之间用逗号分开,构成字符串, 前端拿到图片地点后,将其转成数组。
症结代码以下:
onShow:async function(){
var that = this;
let pageno = 1;
let pagesize = this.data.pagesize;
this.setData({ pageno:1});
noticesource.findalllnotice(pageno, pagesize).then(function (res) {
console.log(res);
for (let i in res) {
let image = res[i].photo.split(',');
res[i].imageurl = image;
}
that.setData({ allnotice: res });
})
},
动态页面运用分页加载,每次下拉加载10条,内容展现区运用 scroll-view组件,设置bindscrolltolower=”upper”,下拉时触发upper要领。
症结代码以下:
upper: function () {
var allnotice = this.data.allnotice;
var pageno = ++this.data.pageno;
var pagesize = this.data.pagesize;
console.log(pageno);
var that = this;
this.setData({ pageno: this.data.pageno++ });
noticesource.findalllnotice(pageno, pagesize).then(function (res) {
console.log(res);
for (let i in res) {
let image = res[i].photo.split(',');
res[i].imageurl = image;
}
if (res.length > 0) {
allnotice=allnotice.concat(res);
}
console.log(allnotice);
that.setData({ allnotice: Array.from(new Set(allnotice)) });
})
}
4.2 动态宣布页
动态宣布页主要有文本输入框,图片上传区、宣布按钮构成。
该页面为主要页面,在宣布前,我们须要猎取宣布者的头像、昵称、openid,假如这三个必要条件缺一的话,我们就会阻挠用户宣布动态而且提醒用户受权登录。
猎取openid症结代码以下:
app.js
wx.login({
success(res) {
console.log('code: ' + res.code);
if (res.code) {
loginsource.login(res.code).then(function (data) {
console.log(data);
wx.setStorage({
key: 'openid',
data: data.openid,
})
wx.setStorage({
key: 'session_key',
data: data.session_key,
})
})
} else {
console.log('登录失利!' + res.errMsg)
}
}
});
用户在进入小顺序时,我就会让一次背景要求,依据wx.login返回的code解密天生 openid与 session_key,我将其存放在storage中,实在,我不引荐将session_key放在storage中,按一般开辟体式格局,应该是背景暂时存储session_key,并返回一个sessionid给用户,不应该把session_key返回给用户。然则,我在用koa-session时,一向没有胜利,不晓得什么鬼!没办法,我只好把session_key返回给用户了。
上传图片症结代码:
didPressChooesImage: function () {
if (this.data.imageurl.length>=4){
wx.showToast({
title:"不能超过4张",
})
return
}
var that = this;
didPressChooesImage(that); //没有写错,只是同名,见2.1节
},
//删除指定图片
deleteImage: function (e) {
let index = e.currentTarget.dataset.index;
var that = this;
var imagetemp = that.data.imageurl;
imagetemp.splice(index, 1);
that.setData({ imageurl: imagetemp });
},
4.3 我的页面
我的页面包括了用户的数据统计信息与小顺序的推行信息,现在有待完美,从我的页面进去的动态页面只包括用户本身宣布的。
在未登陆时,页面显现默许头像,点击头像,受权个人信息,显现微信头像与昵称。
4.4 群间活动PK
接下来,重头戏来了,活动数据pk为该小顺序的中心功用,
功用点:
1)猎取用户此时的活动步数并展现出来
2)分享本身的活动步数到微信群 并在页面上构成 pk排名区
3)其他用户经由过程分享进入小顺序,体系猎取其群id与活动步数 与统一微信群的用户举行pk
4)每次分享或点击分享链接,体系将自动更新该用户的活动步数
5) pk排名区只展现当日的排名状况,晚上12点后自动清空pk区
手艺点:
1)猎取用户活动步数
2)猎取群id
3) 各群之间间活动数据断绝
结果以下图所示
受权后显现步数。
点击挑选一个聊天后,将宣布分享到微信群里,分享胜利后,前端猎取到ShareTicket,群内其别人经由过程该链接进来,前端也会猎取到ShareTicket,挪用 wx.getShareInfo()将加密数据发送给后端解密,可取得 openGid ,将用户的步数与openGid等信息存储起来,构成了groupsport表。
保留当日已分享的群id,猎取ShareTicket 症结代码:
onLoad: function (opt) {
//在storage中建立用户的当日分享状况 也就是分享到了哪些群,将这些群id存在一个与日期挂钩的对象中,到了第二天,清空群id.
var that =this;
var nowDate = new Date();
var year = nowDate.getFullYear();
var month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1)
: nowDate.getMonth() + 1;
var day = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate
.getDate();
var dateStr = year + "-" + month + "-" + day;
var value = wx.getStorageSync('openGidlist')
if (value!=undefined||value!=null) {
console.log(value);
if (value.date != dateStr || value.Gidlist == undefined || value.Gidlist==null){
wx.setStorageSync('openGidlist', { date: dateStr, Gidlist: [] });
}
}
//设置开启ShareTicket
wx.showShareMenu({
withShareTicket: true
})
}
猎取个人活动数据
onShow: function () {
wx.getWeRunData({
success: function (res) {
console.log(res);
let session_key = wx.getStorageSync("session_key");
console.log(session_key);
sportsource.getsportdata(res, session_key).then(function (data) {
console.log(data);
that.setData({ todaysportcount: data.data.stepInfoList[30].step });
})
}
})
}
分享时猎取群id
onShareAppMessage: function () {
var nowDate = new Date();
var year = nowDate.getFullYear();
var month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1)
: nowDate.getMonth() + 1;
var day = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate
.getDate();
var dateStr = year + "-" + month + "-" + day;
var that=this;
return {
title: '我已活动了' + this.data.todaysportcount+'步,你敢来PK吗?',
path: 'pages/sportpk/sportpk',
success: function (res) {
var shareTickets = res.shareTickets;
if (shareTickets.length == 0) {
return false;
}
console.log('shareTickets'+shareTickets[0]);
wx.getShareInfo({
shareTicket: shareTickets[0],
success: function (res) {
var encryptedData = res.encryptedData;
var iv = res.iv;
console.log(res);
let session_key = wx.getStorageSync("session_key");
sportsource.getsportdata(res, session_key).then(function (data) {
console.log(data);
let openGid = data.data.openGId;
that.setData({ openGid: openGid});
let openid = that.data.openid;
let todaysportcount = that.data.todaysportcount;
let avatarUrl = that.data.userInfo.avatarUrl;
let nickname = that.data.userInfo.nickName;
if (todaysportcount == undefined || todaysportcount==null){
wx.showToast({
title: '请从新翻开活动权限',
duration: 2000
})
return
}
if (avatarUrl == undefined || avatarUrl == null || nickname == undefined || nickname==null) {
wx.showToast({
title: '请先点击头像登录',
duration: 2000
})
return
}
// let openGidlist = that.data.openGidlist;
let openGidlist = wx.getStorageSync('openGidlist');
openGidlist.Gidlist.push(openGid);
openGidlist.Gidlist = [...new Set(openGidlist.Gidlist)]
console.log("---------------------------------------");
console.log('openGidlist.Gidlist' + openGidlist.Gidlist);
console.log("---------------------------------------");
wx.setStorageSync('openGidlist', { date: dateStr, Gidlist: openGidlist.Gidlist});
sportsource.creategroupsport({
openGid,
openid,
todaysportcount,
avatarUrl,
nickname
}).then(function(){
that.setData({ sharegroupdata: [] });
that.getfirstgroupsport();
})
})
}
})
},
fail: function (res) {
// 转发失利
}
}
}
革新群内pk
getfirstgroupsport: async function () {
var that = this;
let openGidlist = wx.getStorageSync('openGidlist');
if (openGidlist.Gidlist != undefined || openGidlist.Gidlist != null) {
if (openGidlist.Gidlist.length > 0) {
for (let i in openGidlist.Gidlist) {
let data = await sportsource.getgroupsport(openGidlist.Gidlist[i]);
console.log(data);
let sharegroupdata = that.data.sharegroupdata;
sharegroupdata.push(data.data.rows);
that.setData({ sharegroupdata: sharegroupdata })
}
}
}
},
点击分享链接猎取shareTicket,经由过程链接进入小顺序的场景值是1044
app.js
onShow (opt){
console.log("opt.scene" + opt.scene);
if (opt.scene==1044){
this.globalData.shareTicket = opt.shareTicket;
}
},
5. 后端开辟
后端采纳的是koa2,经由过程sequelize.js完成与mysql的衔接。
5.1 猎取用户openid与session_key
function getOpenId(code) {
console.log(code);
return new Promise((resolve, reject) => {
const id = ''; // AppID(小顺序ID)
const secret = '';// AppSecret(小顺序密钥)
let url = `https://api.weixin.qq.com/sns/jscode2session?appid=${id}&secret=${secret}&js_code=${code}&grant_type=authorization_code`;
axios.get(url).then(function (response) {
console.log("response.data:"+response.data);
resolve(response.data);
})
.catch(function (error) {
console.log(error);
reject(error);
});
})
}
async function login(ctx) {
const {code} = ctx.query;
const data = await getOpenId(code);
ctx.body = data;
}
5.2 活动数据与群id数据猎取
这两个数据属于隐私数据,须要用算法解密,详细的流程,微信开辟手册上有,我就不多说了,须要用到上一步的session_key来解密。
//WXBizDataCryptconst:微信供应的解密要领
WXBizDataCryptconst WXBizDataCrypt = require('./WXBizDataCrypt')
var appId = '';
async function getsportdata(ctx) {
var encryptedData=ctx.query.encryptedData;
var iv=ctx.query.iv;
var session_key=ctx.query.session_key;
console.log("session_key"+session_key);
var pc = new WXBizDataCrypt(appId,session_key);
var data = pc.decryptData(encryptedData,iv);
console.log('解密后 data: ', data);
ctx.body={
success:true,
data:data
}
}
5.3 建立与读取活动数据
当用户分享本身的活动数据到微信群内时或许微信群内其他用户经由过程该链接进入小顺序时,后端将获建立或许更新该用户的群内活动数据。
const creategroupsport = async function(data){ // 给某个用户建立一条群活动纪录
let nowDate = new Date();
let year = nowDate.getFullYear();
let month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1)
: nowDate.getMonth() + 1;
let day = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate
.getDate();
let dateStr = year + "-" + month + "-" + day;
var countdata = await Todolist.findAndCount({
where:{openGid:data.openGid,createdate:dateStr,openid: data.openid}
})
var count=0;
if(countdata!=undefined||countdata!=null) {
count = countdata.count;
}
if(count==0) {
await Todolist.create({
openGid: data.openGid,
openid: data.openid,
todaysportcount: data.todaysportcount,
createdate: dateStr,
avatarUrl: data.avatarUrl,
nickname: data.nickname
})
}
else {
await Todolist.update({
todaysportcount: data.todaysportcount,
avatarUrl: data.avatarUrl,
nickname: data.nickname,
openid: data.openid,
},{
where:{
id:countdata.rows[0].id
}
})
}
return true
}
//读取群内活动数据
const getgroupsport = async function(openGid){
console.log('openGid'+openGid);
let nowDate = new Date();
let year = nowDate.getFullYear();
let month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1)
: nowDate.getMonth() + 1;
let day = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate
.getDate();
let dateStr = year + "-" + month + "-" + day;
const data= await Todolist.findAndCount({
where:{openGid:openGid,createdate:dateStr},
order: [
['todaysportcount', 'DESC']
]
})
console.log(data);
return data;
}
5.4 动态的猎取
对用户宣布的动态,背景现在主要有 增,改,查三类要领,我说一下分页查询。
const findallnotice = async function(ctx){ // 查询一切
let pageno=ctx.pageno;
let pagesize=ctx.pagesize;
console.log(pageno,pagesize);
const data= await Todolist.findAndCount({
where: {state:1},
offset:(pageno - 1) * pagesize,//最先的数据索引,比方当page=2 时offset=10 ,而pagesize我们定义为10,则现在为索引为10,也就是从第11条最先返回数据条目
limit:pagesize*1//每页限定返回的数据条数
})
console.log(data);
return data;
}
const findmynotice = async function(ctx){ // 查询本身的
let pageno=ctx.pageno;
let pagesize=ctx.pagesize;
let uid=ctx.openid;
console.log(pageno,pagesize,uid);
const data= await Todolist.findAndCount({
where:{uid:uid},
offset:(pageno - 1) * pagesize,//最先的数据索引,比方当page=2 时offset=10 ,而pagesize我们定义为10,则现在为索引为10,也就是从第11条最先返回数据条目
limit:pagesize*1//每页限定返回的数据条数
})
console.log(data);
return data;
}
6. 总结
我洋洋洒洒写了几千字,看上去举重若轻,然则在现实开辟中经常会遇见林林总总的题目,该项目从原型设想与开辟到布置都是我单独完成的,中心也踩了一些坑,这个项目终究没能上线,是因为,个人主体账号不能宣布关于GUC的小顺序。
本文首发于我的个人博客: https://www.catac.cn,转载时请说明泉源,
该项目源码地点:https://github.com/ouminglian…
也迎接列位与我交换,个人QQ:2541511219