一个小程序也能获得 50W 天使投资!图书社交小程序的核心步骤及源码分享

·  阅读 6422
一个小程序也能获得 50W 天使投资!图书社交小程序的核心步骤及源码分享

前言

2017年8月,我曾开发过一个小程序,叫懂你觅书(谐音梗扣钱)。用户可以通过这个小程序管理自己家的书架/书房,然后通过图书的品位去匹配和你有相同喜好的人,并且在地图上显示出来。

image.png

当时开发完后在某乎疯狂自我推荐,机缘巧合之下还认识了愿意给这个产品做天使投资的投资人

这是当时写的BP的一部分

image.png

image.png

image.png

不过,由于没想清楚商业闭环,盈利模式,后来决定不继续做,我把投资款还给投资人了。

2017年共享经济很火,所以很多人在共享这事儿上动脑筋,也包括我。现在事实证明当时大部分的共享经济概念都死了。咱们国内图书价格非常便宜,共享借书连物流成本都负担不起,商业计划中的社交体系短期内也不可能搭建起来。

当时整个产品设计,策划,前端开发,后端PHP都是我一人搞完的...最近在深入学习uniCloud,所以把这个创意重新用uni-app+uniCloud复活一下,反正云开发也不需要什么服务器成本。

不想看啰嗦长文的可直接跳到文末下载源码

uniCloud云开发

事先声明,本人没有恰uniCloud的饭,但uniCloud确实好用,阿里云给的免费额度也高。我已经多次使用uniCloud零成本搭了好几个应用了。甚至后端用uniCloud,前端用Flutter的事儿我也干过。

云开发的好处已经不用多讲了吧?FAAS = Functions as a Service

image.png

有了云开发,我们开发者只需要专注于业务逻辑代码的编写,以上那些和业务无关的知识都不需要掌握了。原先开发一个产品的业务流程也发生了很大的变化

传统流程 image.png

基于云开发的流程 image.png

uniCloud提供以下这些服务,但这些基础服务其实是由阿里云腾讯云提供的,使用前你要选择用哪个服务商。

image.png

在这里直接推荐大家使用uniCloud时选择阿里云,因为腾讯真抠啊~那点免费额度还是算了吧,不过选腾讯云的唯一好处是腾讯系生态可以免鉴权

获取微信用户信息

获取openid

uni.login({
    provider:"weixin",
    success:(res)=>{
        uniCloud.callFunction({
            name:"login",
            data:{
                code:res.code
            }
        })
    }
})
复制代码

通过小程序后台获取到的AppIdAppSecret,在云函数里调用微信服务端APIcode2session

const { code } = event;

const res = await uniCloud.httpclient.request(
        "https://api.weixin.qq.com/sns/jscode2session?appid="+appId+"&secret="+appSecret+"&js_code="+code+"&grant_type=authorization_code",
        {
                dataType:"json"
        }
)

const openid = res.data.openid;
复制代码

这样我们就拿到了微信用户的openid,已经可以用来建立识别用户,建立用户系统了。后续所有用户产品的数据,都会和这个openid关联起来。

JWT实现用户凭证

在云开发里没有Session这回事,所以就不能使用Session那套机制来实现用户凭证

image.png

不过,有更先进的机制为我们所用。

  • 服务端将用户的id或需要携带的信息加密成Token,加密使用的密钥仅在服务端保存。
  • 通过登录接口返回给前端,前端自己缓存起来。
  • 后续所有请求都携带这个Token作为参数,云函数里获取Token解密,并检查有效性

image.png

安装jsonwebtoken依赖

npm install jsonwebtoken
复制代码

使用jwt签名,我这里就是为了不直接暴露openid,密钥自己随便写一个字符串就行,这里图省事儿,直接用了小程序的appSecret

const jwt = require('jsonwebtoken'); 

//生成token
//jwt.sign(携带数据,密钥,过期时间等选项);
jwt.sign({openid:openid},appSecret,{expiresIn:60*60*24});
复制代码

前端封装了网络请求的api,只要发现有缓存的token,后续请求都会自动携带。需要对用户进行校验的接口也通通会校验token的有效性,然后解密取出openid用于用户的数据操作。

jwt.verify(token,appSecret);
//校验成功的话会解密出 {openid:openid}
复制代码

获取昵称头像

小程序SDK也一直在迭代,今年很多小程序里的用户昵称都变成了微信用户对不对,就是因为这个接口改掉了。

image.png

过去使用的getUserInfo方法,改为使用getUserProfile,我们就能获得微信用户的昵称头像等数据了。只是这个api每次都会有弹窗提示用户

uni.getUserProfile({
    desc: '用于完善会员资料',//必填参数
    success: (res) => {
        res.userInfo;
    }
})
复制代码

image.png

我个人的做法是先通过login换取openid。然后用户会看到自己没有昵称和头像,通过按钮提示用户同步微信用户信息。点击后调用getUserProfile获取信息显示并更新到用户数据库中。

2021-08-26 10_37_49.gif

//云函数取参
const { userInfo,token } = event;
//获取数据库对象
const db = uniCloud.database();
//校验并解密token
const payload = verifyToken(token);

//更新数据库中指定openid的用户信息
const dbRes = await db.collection("users").where({
        openid:payload.openid
}).update({
        nickName:userInfo.nickName,
        avatarUrl:userInfo.avatarUrl,
        gender:userInfo.gender,
        country:userInfo.country,
        province:userInfo.province,
        city:userInfo.city
});
复制代码

云数据库操作

有了上面基于jsonwebtoken的用户凭证后,接下来的业务基本就是对其他数据集合的CRUD了,创建书架,上传图书,获取书架列表,获取图书列表,删除书架,删除图书...

书房/书架数据库

const payload = event.token?verifyToken(event.token):null;
const action = event.action;

let dbRes;
if(action=="create"){
        //将书房的名字提交至内容安全审核
        const res = await msgSecCheck(payload.openid,event.name);
        if(res.result.suggest!="pass"){
                return {err:1,msg:"内容不安全"};
        }

        //安全内容则写入数据库,建立书架数据
        dbRes = await db.collection("bookshelfs").add({
                owner:payload.openid,
                name:event.name,
                address:event.address,
                geopoint:new db.Geo.Point(event.longitude, event.latitude),
                totalbook:0
        })

        return dbRes;
}
复制代码

创建书房时,由于书房名称由用户自定义,并且其他用户可见。这就叫UGC的内容,必须通过内容安全审核...

uniCloud里实现内容安全审查

const res = await uniCloud.httpclient.request("https://api.weixin.qq.com/wxa/msg_sec_check?access_token="+access_token,{
        method:"POST",
        dataType:"json",
        headers:{
                "Content-Type":"application/json"
        },
        data:{
                version:"2",
                openid:"内容产出者的openid",
                scene:2,
                content:"要审查的文本内容"
        }
});
复制代码

我们的书架需要在地图上显示,所以必须让用户选择并提交一个地理位置。而云数据库还可以非常方便的添加基于地理位置的索引,让我们可以通过地理位置相关的查询方法来罗列书架数据

new db.Geo.Point(经度, 纬度)
复制代码

image.png

获取书房列表

和身份有关的数据都会通过token的校验获取openid来进行

else if(action=="listmy"){
    dbRes = await db.collection('bookshelfs').where({
            owner:dbCmd.eq(payload.openid)
    })
    .orderBy("_id","desc")
    .limit(10)
    .get()
}
复制代码

寻找距离我最近的书房

else if(action=="listbygeo"){
        dbRes = await db.collection('bookshelfs').field({owner:false}).where({
                geopoint:dbCmd.geoNear({
                        geometry: new db.Geo.Point(event.longitude, event.latitude),
                        maxDistance: 3000,//单位米
                        minDistance: 0
                })
        })
        .limit(100)//做个数量限制
        .get()
}
复制代码

修改书房

else if(action=="update"){
    const res = await msgSecCheck(payload.openid,event.name);

    if(res.result.suggest!="pass"){
            return {err:1,msg:"内容不安全"};
    }

    dbRes = await db.collection("bookshelfs").where({
            "_id":dbCmd.eq(event._id),
            "owner":dbCmd.eq(payload.openid)
    }).limit(1).update({
            name:event.name,
            address:event.address,
            geopoint:new db.Geo.Point(event.longitude, event.latitude)
    })

    return dbRes;
}
复制代码

删除书房

else if(action=="delete"){
        dbRes = await db.collection("bookshelfs").where({
                "_id":dbCmd.eq(event._id),
                "owner":dbCmd.eq(payload.openid)
        }).remove();
}
复制代码

图书数据库

通过ISBN爬取豆瓣图书信息

首先通过scanCodeapi来扫描图书背后的条码,获取到条码信息后通过云函数向某瓣爬取图书标题和封面

let { isbn } = event;
let res = await uniCloud.httpclient.request("https://search.douban.com/book/subject_search?search_text="+isbn+"&cat=1001");

let reg = /window\.__DATA__ = "(.*)"/;
if(reg.test(res.data)){
      let bookdata=RegExp.$1;//这是数据是被加密的,需要解密
      
}
复制代码

爬取到的信息需要解密,本文就不讲述解密过程了,小伙伴们可以自行看源码。

这个爬虫违法吗?我认为不违法,因为封面,标题信息属于公开信息,不从某瓣爬,我们也可以从别的地方获取,比如图书电商,比如付费API等。另外我们并没有收集某瓣对于图书的评分,评论等这些某瓣独有的数据。

不过有其他意见的小伙伴可以在评论区帮我普普法

建立自己的ISBN库

  • 封面图上传至云存储
let coverImage = await uniCloud.httpclient.request(data.cover_url);
	  
let uploadResult = await uniCloud.uploadFile({
    cloudPath:isbn+".jpg",
            fileContent:coverImage.data
})
复制代码
  • 自己的ISBN数据库
let resData = {
        isbn:isbn,
        title:data.title,//图书标题
        cover_url:uploadResult.fileID,//封面在云存储的url地址
        abstract:data.abstract//图书附加信息
}

dbResult = await db.collection("isbnlib").add(resData);
复制代码

那么有了自己的ISBN数据库,下次再有用户扫描相同的图书时,信息就不用再去爬取了,直接从自己的ISBN数据库返回,这也是一种利用众包的机制来建立数据库的方法

let dbResult = await db.collection("isbnlib").limit(1).where({
              isbn:isbn
}).get();

//数据库里已有相同ISBN的话,则直接返回数据库里的信息
if(dbResult.affectedDocs>0){
      return dbResult.data[0];
}
复制代码

书架图书数据库

放到书架上的图书,我们新建一个books的表来管理,并绑定书架idisbn

  • 从ISBN库获取图书信息
const bookInfo = await db.collection("isbnlib").doc(event.isbnid).get();
复制代码
  • 建立图书数据

绑定书架id,isbnid,openid等,确定图书是谁的,在哪个书架里。

dbRes = await db.collection("books").add({
        owner:payload.openid,
        shelfid:event.shelfid,
        title:bookInfo.data[0].title,
        cover_url:bookInfo.data[0].cover_url,
        isbn:bookInfo.data[0].isbn,
        isbnid:event.isbnid,
        createtime:now,
        updatetime:now,
});
复制代码
  • 书架里图书总数字段更新
await db.collection("bookshelfs").where({
        owner:payload.openid,
        _id:event.shelfid,
}).update({
        totalbook:dbCmd.inc(1)//inc就是在当前字段的数值基础上增加
})

return dbRes;
复制代码
  • 图书的搜索
dbRes = await db.collection('isbnlib')
                .where({
                    title=new RegExp(event.keyword, 'i')
                })
		.orderBy("_id","desc")
		.orderBy("updatetime","desc")
		.limit(12)
		.get();
复制代码

生成书架海报

IMG_9065.jpg

利用canvas生成海报,canvas里的绘制细节大家看项目源码吧。这里我主要使用小程序码接口来获取带有参数的小程序码。

image.png

生成小程序码的API一共有三种,其中接口A和接口C是数量有限的,最多只能生成10w个。万一这个小程序用户量大了,书房书架数量超过10w个,那就不能用接口A了。所以我们要使用接口B,数量无限。

  • 云函数调用wxacode接口
const access_token = await getAccessToken();
const res = await uniCloud.httpclient.request("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token="+access_token,{
        method:"POST",
        headers:{
                "Content-Type":"application/json"
        },
        data:{
                scene:"微信扫码后小程序里可以获取到的信息",
                page:"扫码后进入的小程序页面"
        }
});
复制代码

但这个接口B的局限是scene字段最长32个字符,参数名只能叫scene。并且该接口只能用于已发布的小程序。

结语

这是我2017年共享图书项目的核心技术部分,用uniCloud重新实现,本文像记流水账一般把核心过程给分享了出来,阅读体验可能不佳,不过分享不易,还请大家多多点赞支持。

如果你喜欢大帅的教程,请收藏,点赞,关注吧

  • 哔哩哔哩:大帅老猿
  • 微信公众号:大帅老猿
分类:
前端
分类:
前端
收藏成功!
已添加到「」, 点击更改