一、前期框架的搭建

后台采用的框架为Koa2框架,编程软件为vs code,node环境

  • 步骤一:创建一个空文件
  • 步骤二:在vs code打开该文件,并按ctrl+`打开终端
  • 步骤三:输入命令npm init(初始化package.json),也可以用npm init -y(直接默认初始化package.json)
    在这里插入图片描述
  • 步骤四:输入命令npm install koa(下载koa)
    在这里插入图片描述

二、获取AcessToken(接口调用凭证)

      获取小程序全局唯一后台接口调用凭据(access_token)。调用绝大多数后台接口时都需使用 access_token,开发者需要进行妥善保存。
      由于,我是调用云函数,所以必须需要接口调用凭证。

注意:
在这里插入图片描述
中控服务器是自己搭建的后台

请求地址及参数
GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

在这里插入图片描述
因为要不断使用接口调用凭证,所以将其封装成一个方法,再向外抛出接口。

代码实现:

  • 在实现前,因为需要向第三方请求数据(请求接口调用凭证),故需要下载第三方库,我使用的是request-promise(依赖于request)
  • 在终端输入先输入命令npm install request 下载成功后再输入命令npm install request-promise
  • 也可以使用axios第三方库进行请求数据
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const rp = require('request-promise')
const fs = require('fs')//自带的
const path = require('path')//自带的
const fileName = path.resolve(__dirname, './access_token.json')//创建文件并获取绝对路径
const APPID = '您的小程序ID'
const APPSECRET = '您的小程序密钥'//不能泄露
const URL = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`

//更新AccessToken
const updateAccessToken = async () => {
//发送请求获取AccessToken
const resStr = await rp(URL)
const res = JSON.parse(resStr)

// 将AccessToken写入文件
if (res.access_token) {
//JSON.stringify()将对象转化成字符串
fs.writeFileSync(fileName, JSON.stringify(
{
access_token: res.access_token,
createTime: new Date()
}
))
} else {//如果AccessToken不存在,再次更新AccessToken
await updateAccessToken()
}
}

//获取AccessToken
const getAccessToken = async () => {
//读取文件获取AccessToken
try {
//读取文件,编码为utf-8
const readRes = fs.readFileSync(fileName, 'utf8')
const readobj = JSON.parse(readRes)
const createTime=new Date(readobj.createTime).getTime()
const nowTime=new Date().getTime()
if((nowTime-createTime)/1000/60/60>=2){
//判断是否已经超过俩个小时,超过俩个小时再次调用updateAccessToken方法
await updateAccessToken()
}
return readobj.access_token
} catch (error) {//文件不存在,说明AccessToken没有被更新
await updateAccessToken()
getAccessToken()
}
}

// 定时器(定时获取AccessToken 提前5分钟)
setInterval(async () =>{
await updateAccessToken()
},6900*1000)

//导出接口(给调用方使用)
module.exports=getAccessToken

运行测试

  • 在终端进入到该文件夹中(cd ..返回到上级目录或者cd 目标目录)
  • 输入命令编译文件 node 文件名
    在这里插入图片描述

三、搭建后台,并返回数据到前端(重点)

前期准备
(1)由于我前端和后台都是在本地运行,但端口号不同,此时会产生跨域问题。

  • 解决方法:
    我采用的方法时用cors来解决,首先在项目中打开终端,输入命令npm install koa2-cors
    在这里插入图片描述
    然后在项目的入口文件(app.js)中,导入该第三方库,解决跨域问题
1
2
3
4
5
6
7
const cors=require('koa2-cors')
//解决跨域问题
app.use(cors({
//配置允许访问后台的URL地址(前端地址)
origin:['http://localhost:9528'],
credentials:true
}))

在实现前后端分离,都会出现跨域问题,有以下几种形式

(2)配置后端路由,我使用的是koa-router,打开项目终端,输入命令npm install koa-router
在这里插入图片描述

(3)下载第三请求库request-promise,由于前面部分已经下载了,所以直接导入即可。

代码实现

知识点一:调用云函数
我这里是通过调用云函数实现从云数据库中获取歌单信息,下面是我部署的云函数(云函数名为music,路由名(地址)为playlist)
1
2
3
4
5
6
7
8
9
app.router('playlist', async (ctx, next) => {
ctx.body = await cloud.database().collection('playlist')
.skip(event.start)
.limit(event.count)
.orderBy('createTime', 'desc').get()
.then((res) => {
return res
})
})

后台调用云函数

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
const Router=require('koa-router')
const router=new Router()

const getAccessToken=require('../utils/getAccessToken.js')
const rp=require('request-promise')

// /list是后台路由地址
router.get('/list',async(ctx,next)=>{
// 获取调用接口凭证
const access_token= await getAccessToken()
//云数据库运行环境
const ENV='您的云开发环境ID'
//调用云函数地址(参数name是您的云函数名字)
const URL=`https://api.weixin.qq.com/tcb/invokecloudfunction?access_token=${access_token}&env=${ENV}&name=music`
//获取前端的发送过来的请求数据
const query=ctx.request.query
//调用云函数的请求参数(封装成对象)
const option={
method:'POST',//必须是POST请求
url:URL,
body:{
$url:'playlist',//云函数的路由名(地址)
start:parseInt(query.start),
count:parseInt(query.count)
},
json:true//返回格式为JSON格式
};
//发送请求
const data=await rp(option).then((res)=>{
//console.log(res)
return JSON.parse(res.resp_data).data
}).catch((err)=>{
console.log(err)
})
//返回数据给前端
ctx.body={data,code:20000}
})
module.exports=router//将路由抛出

在返回数据中,一个数据code:20000 目的是:因为我前端采用的是vue-admin-template搭建的前端管理系统,必须返回code:20000。

优化代码
由于后台大部分功能是调用云函数,所以说调用云函数的代码内容基本是一样的。可以将调用云函数的方法进行封装,下面是封装的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const getAccessToken = require('./getAccessToken.js')
const rp = require('request-promise')

const callCloudFn = async (ctx, fnName, params) => {
const ACCESS_TOKEN = await getAccessToken()
const options = {
method: 'POST',
uri: `https://api.weixin.qq.com/tcb/invokecloudfunction?access_token=${ACCESS_TOKEN}&env=${ctx.state.env}&name=${fnName}`,
//ctx.state.env通过全局中间件获取云开发环境,在入口文件(app.js)有配置
body: {
...params
},
json: true
}

return await rp(options)
.then((res) => {
return res//注:返回的格式是字符串
})
.catch(function (err) {
})
}

module.exports = callCloudFn

此时需要修改路由list中的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Router=require('koa-router')
const router=new Router()
const rp=require('request-promise')
const callCloudFun=require('../utils/callCloudFn')

router.get('/list',async(ctx,next)=>{
//获取前端请求的数据
const query=ctx.request.query
//发送请求
const res=await callCloudFun(ctx,'music',{
$url:'playlist',
start:parseInt(query.start),
count:parseInt(query.count)
})
let data=[]
if(res.resp_data){
data=JSON.parse(res.resp_data).data
}
ctx.body={
data,
code:20000,
}
})
module.exports=router

这样的好处就是减少代码的重复率,增加代码的利用率。

知识点二:API调用云数据库

前期准备
(1)下载koa-body,获取post请求数据
在这里插入图片描述
在app.js配置以下代码

1
2
3
4
//接收post参数解析
app.use(koaBody({
multipart: true,
}))

(2)创建功能模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const rp=require('request-promise')
const getAccessToken = require('./getAccessToken.js')
const callCloudDB=async(ctx,fnName,query={})=>{
//fnName数据操作的类型 query数据操作语句
const access_token=await getAccessToken()
const options={
method:'POST',
uri:`https://api.weixin.qq.com/tcb/${fnName}?access_token=${access_token}`,
body:{
query,
env:ctx.state.env,
},
json:true
}
return await rp(options).then((res)=>{
return res
}).catch((err)=>{
console.log(err)
})
}
module.exports=callCloudDB

(3)更新数据
因为前端提交更新数据的形式是post,所以后端代码应该为post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
router.post('/updatePlaylist',async(ctx,next)=>{
//获取请求体
const params=ctx.request.body
//更新语句
const query=`db.collection('playlist')
.doc('${params._id}').update({
data:{
name:'${params.name}',
copywriter:'${params.copywriter}'
}
})
`
const res= await callCloudDB(ctx,'databaseupdate',query)
ctx.body={
code:20000,
data:res
}
})
module.exports=router

(4)删除数据

1
2
3
4
5
6
7
8
router.get('/deletePlaylist',async(ctx,next)=>{
const query=`db.collection('playlist').doc('${ctx.request.query.id}').remove()`
const res=await callCloudDB(ctx,'databasedelete',query)
ctx.body={
code:20000,
data:res
}
})

(5)增加数据

1
2
3
4
5
6
const query=`db.collection('swiper').add({
data:{
fileId:'${fileid}'
}
})`
const res=await callCloudDB(ctx,'databaseadd',query)

(6)查询数据

1
2
3
4
5
6
7
8
9
router.get('/getById',async(ctx,next)=>{
const query=`db.collection('playlist').doc('${ctx.request.query.id}').get()`
const res=await callCloudDB(ctx,'databasequery',query)
//console.log(res)
ctx.body={
code:20000,
data:JSON.parse(res.data)
}
})
知识点三:文件的上传、下载和删除

创建功能模块

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
const getAccessToken = require('./getAccessToken.js')
const callCloudDB = require('./callCloudDB.js')

const rp =require('request-promise')
const fs=require('fs')

const cloudStore={

//下载图片
async download(ctx,fileList){
const ACCESSTOKEN=await getAccessToken()
const options={
method:'POST',
uri:`https://api.weixin.qq.com/tcb/batchdownloadfile?access_token=${ACCESSTOKEN}`,
body:{
env:ctx.state.env,
file_list:fileList
},
json:true
}
return await rp(options).then((res)=>{
return res
}).catch((err)=>{
console.log(err)
})
},

//上传图片到云存储
async uploadfile(ctx){
//步骤一:发送上传文件的请求,获取相应信息
const ACCESSTOKEN=await getAccessToken()
const file=ctx.request.files.file//上传文件信息
//文件路径(随机)
const path=`swiper/${Date.now()}-${Math.random}-${file.name}`
const options={
method:'POST',
uri:`https://api.weixin.qq.com/tcb/uploadfile?access_token=${ACCESSTOKEN}`,
body:{
env:ctx.state.env,
path:path
},
json:true
}
const info= await rp(options).then((res)=>{
return res
}).catch((err)=>{
console.log(err)
})
//console.log(info)

//步骤二:上传图片
const params={
method:"POST",
headers:{
"content-type":'multipart/form-data',
},
uri:info.url,
formData:{
key:path,
Signature:info.authorization,
'x-cos-security-token':info.token,
'x-cos-meta-fileid':info.cos_file_id,
file:fs.createReadStream(file.path)//二进制文件
},
json:true
}
await rp(params)
return info.file_id//返回文件id到调用方,将其存储在云数据库
}

//删除文件
async delFile(ctx,fileid_list){
const ACCESSTOKEN=await getAccessToken()
const options={
method:'POST',
uri:` https://api.weixin.qq.com/tcb/batchdeletefile?access_token=${ACCESSTOKEN}`,
body:{
fileid_list,
env:ctx.state.env,

},
json:true
}
return await rp(options).then((res)=>{
return res
}).catch((err)=>{
console.log(err)
})
}
}

module.exports= cloudStore

(2)调用功能模板

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
//读取图片(获取歌曲链接)
router.get('/list',async(ctx,next)=>{
//读取数据库文件信息
const query=`db.collection('swiper').get()`
const res=await callCloudDB(ctx,'databasequery',query)

//获取图片链接(http形式)
let fileList=[]
const data=res.data
//遍历文件信息,封装请求对象
for(let i=0,len=data.length;i<len;i++){
fileList.push({
fileid:JSON.parse(data[i]).fileId,
max_age:7200
})
}
//获取下载链接(http形式)
const dlRes=await cloudStore.download(ctx,fileList)

//封装对象,将所需要的信息返回到前端
let returnData=[]
for(let i=0,len=dlRes.file_list.length;i<len;i++){
returnData.push({
download_url:dlRes.file_list[i].download_url,//文件下载链接
fileId : dlRes.file_list[i].fileid,
_id:JSON.parse(data[i])._id
})
}
ctx.body={
code:20000,
data:returnData
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//上传图片
router.post('/upload',async(ctx,next)=>{
//上传图片 返回文件id
const fileid=await cloudStore.uploadfile(ctx)
//console.log(fileid)
const query=`db.collection('swiper').add({
data:{
fileId:'${fileid}'
}
})`
//写入数据库
const res=await callCloudDB(ctx,'databaseadd',query)
ctx.body={
code:20000,
id_list:res.id_list
}
})
1
2
3
4
5
6
7
8
//删除文件
export function del(params){
return request({
params,
url:`${baseURL}/swiper/del`,
method:'get'
})
}

四、配置入口文件(app.js)

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
41
42
43
44
45
46
const Koa=require('koa')
const Router=require('koa-router')
const cors=require('koa2-cors')
const koaBody=require('koa-body')
const ENV='haocloud-xagb6'
const app=new Koa()
const router=new Router()

//解决跨域问题
app.use(cors({
//配置允许访问后台的URL地址
origin:['http://localhost:9528'],
credentials:true
}))

//接收post参数解析
app.use(koaBody({
multipart: true,
}))

//全局中间键
app.use(async(ctx,next)=>{
ctx.state.env=ENV
await next()
})

// http://localhost:3000/playlist/list(调用获取歌单的URL)
const playlist=require('./controller/playlist.js')
router.use('/playlist',playlist.routes())

const swiperList=require('./controller/swiper.js')
router.use('/swiper',swiperList.routes())

const blogList=require('./controller/blog.js')
router.use('/blog',blogList.routes())

//声明router
app.use(router.routes())
//允许router下的所有方法
app.use(router.allowedMethods())



app.listen(8080,()=>{
console.log('服务开启在端口为8080')
})

此时,如果前端要获取歌单信息,则需要向后端地址为http://localhost:8080/playlist/list发送请求

  • playlist指的是 routers.use(‘/playlist’,playlist.routes())中的playlist
  • list指的是router.get(‘/list’,async(ctx,next)=>{}中的list(就是一个叫playlist的router的路由下的名为list的路由 个人理解)

最后启动后端服务,在终端输入命令 node app.js(编译入口文件)

结束编译:ctrl+C即可