欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

从头到尾实现个人项目的前后端分离:结合小程序、Koa Node.js、MongoDB和Vue.js实战指南

最编程 2024-07-28 15:41:24
...

前言

掘金潜藏良久,游历各种技术文章,悄悄点赞都藏,偶尔逛逛沸点,留言互动。心中一直拥想自己写点什么的想法,但是写点什么呢???这是一个值得思考的问题,想了几年了也没想出来,尤其是看过别人的文章之后,因为总觉得自己太菜写不出什么东西,所以迟迟未敢动笔。

不过最近想明白了,不追求一定要输出多么高深的东西,就只是写篇文章记录下自己的学习,有输入就得有输出,有想法就得有行动,只要开始,一切都会慢慢变好。

全文的很多内容,官网上都可以找到(请叫我一把梭)

梭.jpg

写着写着发现内容太多,所以拆出来两篇。这样文章就不用一指头划不到底了。哈哈哈哈

最后有代码地址。

项目说明:

用户端:

技术栈:微信纯原生小程序
说明:主要是商品展示功能,大概页面为首页+列表页+详情页

后台:

技术栈:NodeJS+Koa+mongodb(MongoDB Node Driver)
说明:接受后管平台上传的数据并将其保存在数据库

后台管理端:

技术栈:Vue+Element UI+VueRouter
说明:主要功能有banner上传、商品种类添加、商品列表、商品详情

效果图

  • 后管和小程序放一起的截图。

11.png

22.png

33.png

业务流程图

未命名文件.png

关于选型的纠结

数据库和Node框架名词听过很多,但真的要选择用哪一个的时候,真的是一脸懵逼。。。 网上关于MySQL和MangoDB的介绍有很多,在这里就不多赘述了(koa和express也一样),下面只说下我简单粗暴的选型理由。

数据库选型

MySQL?还是MangoDB?

选择:MangoDB

MySQL是关系型数据库MangoDB是文档型,因为要把图片存在数据库中,所以选择MangoDB作为数据库类型。(不要问我为啥要把图片放数据库里)

Nodejs中框架的选择

koa?还是express?

koa是express开发的,语法较新,小巧精悍,由于项目不大,koa足够。

NodeJs连接数据库工具选择mongodb VS mongoose?

mongoose相当于mongodb的一层封装,好比Koa至于express,因为想要学习基本操作,所以选了官方提供的mongodb,即MongoDB Node Driver(哎~,后面付出了的代价有些大)

MongoDB Node Driver官方文档说明

好了,该选的都选好了,现在开始操作。

操作。。。。什么????

???.webp

NodeJs? 数据库? NodeJs操作数据库?还是NodeJs监听前端请求?

在东一下西一下后,感觉做了很多事情,但是没一件有头绪,所以梳理了下操作步骤。

流程图.png

mongodb的概念解析

SQL 术语/概念 MongoDB 术语/概念 解释/说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
table joins 表连接,MongoDB 不支持
primary key primary key 主键,MongoDB 自动将_id 字段设置为主键

mongodb的下载和安装(window版)

详情请见这篇文章(拆出来的第一篇文章) 一个前端小白的数据库之路:MongoDB和可视化工具的下载和安装

NodeJs操作数据库

拆出来的第二篇文章

从0开始:NodeJs操作MongoDB(官网搬运版)

NodeJs监听前端请求并返回内容

没有单独新建Node项目,直接在NodeJs操作数据库的那个项目直接操作的。

  • 安装所需依赖包
npm i koa koa-bodyparser koa-router
  • 新建一个damo.js文件
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
app.use(bodyParser()); // 解析request的body

const router = require('koa-router')();
router.post('/management-system/user/addUser', async (ctx) => {
  console.log(ctx);
  console.log(ctx.request.body, '请求参数---用户信息');
  ctx.status = 200;
  ctx.body = {
    code: 200,
    data: {
      msg: '请求成功啦!',
    },
  };
});
app.use(router.routes());
app.listen(3031); // 该端口号为后管项目启动的端口号
console.log('正在监听 3031...');
  • 前端请求效果如下图

3.png

2.png

第一阶段基本完成,一切都很顺利。

第二阶段只是用户信息的增、删、改、查,没有其他多余步骤,也还算顺利。

之所以说还算顺利,是因为基本流程逻辑都可以实现,但是没能在mongodb中实现限制传入数据库类型的操作。像如下这种类型限制。

let StudentSchema = mongoose.Schema({
    name: String,
    age: Number
})

顺利的进行完了,现在说说特别不顺利的:图片上传。

卡住的地方太多了,每次卡住都会问自己为啥要选mongodb,选个mongoose他不香吗? 操作简单,常见问题一搜一大把。何必卡在这里。。。。

虽然卡住多次,每次也在纠结要不要换成mongoose,几次纠结后,还是决定不换,遇到问题解决问题就好了,不能逃避。

遇到的主要问题:

  1. Node如何获取到文件流
  2. 如何将获取到的文件流转换为数据库能接受的形式并上传至服务器
  3. 如何将图片从数据库中取出来并转变成前端可以接受的形式
  4. 异步问题

1.Node如何获取到文件流

前面不是说 ctx.request.body中可以拿到请求数据吗,但是文件数据流却不在ctx.request.body中,而是在 ctx.request.files中。

router.post('/management-system/user/addUser', async (ctx) => {
  console.log(ctx); // 请求实例
  console.log(ctx.request.body, '请求参数---用户信息');
  console.log(ctx.request.files,'------文件数据在这里')
});

第一个问题解决了,下面看看第二个问题。

2. 如何将获取到的文件流转换为数据库能接受的形式并上传至服务器

网上或者官网找到的图片上传都是下面例子这个样子。node读取本地图片存储数据库,从数据库中读取图片在存于本地。

GridFS是官方推荐的上传数据的形式,可以将大数据进行自动切割存储。

mongodb驱动 GridFS API 文档

thecodebarbarian.com/mongodb-gri…

依据文档可以实现上传和下载功能

const { MongoClient, GridFSBucket } = require('mongodb');
var fs = require('fs');
const assert = require('assert');
const URL = 'mongodb://127.0.0.1:27017';
const MYDATA = 'MANAGEMENT_BUCKET';
const MYCOLLECTION = 'Bucket';
const client = new MongoClient(URL, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  retryWrites: true,
});
async function run() {
  try {
    await client.connect();
    const database = client.db(MYDATA);
    database.collection(MYCOLLECTION);
    const bucket = new GridFSBucket(database);
    // console.log('db===============', bucket);
    // 图片上传 读取本地111.png上传至数据库,该图片在数据库的名字为111.png
    // fs.createReadStream('./111.png')
    //   .pipe(bucket.openUploadStream('111.png'))
    //   .on('error', function (error) {
    //     assert.ifError(error);
    //   })
    //   .on('finish', function () {
    //     console.log('done!');
    //     process.exit(0);
    //   });
    // 图片下载 从数据库中读取111.png,将其改为output.png输出
    bucket
      .openDownloadStreamByName('111.png')
      .pipe(fs.createWriteStream('./output.png'))
      .on('error', function (error) {
        assert.ifError(error);
      })
      .on('finish', function (e) {
        console.log('done!', e);
        process.exit(0);
      });
  } finally {
    // Ensures that the client will close when you finish/error
    // await client.close();
  }
}
run().catch(console.dir);

但这并不是我想要的,node拿到的是文件流,不是可被如上方法读取的图片,作为一个菜鸟也没找到可以转换的方法。尝试了很久最终找到如下解决办法。就一行代码

const { path, name } = ctx.request.files.files;

  • 画一下图片上传的大概流程(图中忽略了具体网络请求过程)

未命名文件 (1).png

  • 文件上传
// 文件上传至数据库
router.post('/file/uploadFiles', async (ctx) => {
  await client.connect();
  const database = client.db(MYDATA);
  database.collection(MYCOLLECTION);
  const bucket = new GridFSBucket(database);
  // 解决问题的代码
  const { path, name } = ctx.request.files.files;
  fs.createReadStream(path)
    .pipe(bucket.openUploadStream(name))
    .on('error', function (error) {
      assert.ifError(error);
    })
    .on('finish', function (e) {
      console.log('done!------', e);
    });
});

图片上传成功后,数据库会生成两个集合,一个存放文件信息,另一个存放文件切片。内部会根据id,自行拆分组合。

image.png

这个问题搞定。

3. 如何将图片从数据库中取出来并转变成前端可以接受的形式

解决问题的也是一行代码。

const base64 = data.toString('base64');

哎~ 知道的简单的要死,不知道的快要被难死了。。。。。

image.png

router.post('/file/downloadFiles', async (ctx) => {
  await client.connect();
  const database = client.db(MYDATA);
  database.collection(MYCOLLECTION);
  const bucket = new GridFSBucket(database);
  // 下载.jpg 是已经被上传到数据库的文件名,可以根据ID查询图片
  const result = await bucket
    .openDownloadStreamByName('下载.jpg')
    .on('data', (data) => {
       // data 为从数据库中拿到的bucket形式文件流
      const base64 = data.toString('base64');
      // base64可以获得
      let img = 'data:image/png;base64,' + base64;
      console.log(img,'我拿到了bese64  啦啦啦啦啦啦啦!')
    });

});

到这一步,大的问题基本都解决了,开开心心写代码。

开心不过1秒钟。。。。。。。。。

前端拿不到bese64的图片,马上来到下一个问题。

4. 异步问题

  • 文件下载(存在异步问题,执行顺序不如预期)
  • 问题现象描述:图片被处理成base64之前,请求主体ctx已经被返还给前端,致使前端拿不到想要的内容。
router.post('/file/downloadFiles', async (ctx) => {
  await client.connect();
  const database = client.db(MYDATA);
  database.collection(MYCOLLECTION);
  const bucket = new GridFSBucket(database);
  const result = await bucket
    .openDownloadStreamByName('下载.jpg')
    .on('data', (data) => {
      const base64 = data.toString('base64');
      // base64可以获得
      let img = 'data:image/png;base64,' + base64;
      ctx.status = 200;
      ctx.body = {
        code: 200,
        data: img,
      };
      console.log('后执行', ctx.status);

      return Promise.resolve('====pppp');
    });

  console.log('先执行', ctx.status); // ctx.status  404
  return result;
});

代码执行打印处的结果顺序为:

先执行 404

后执行 200

跟想要的结果正好相反。

  • 解决办法
// 将数据库取出GridFSBucketReadStream流转为base64
async function GridFSBucketReadStreamToBase64(database) {
  return new Promise(async function (resolve, reject) {
    const bucket = new GridFSBucket(database);
    await bucket.openDownloadStreamByName('下载.jpg').on('data', (data) => {
      // base64可以获得
      let base64Img = 'data:image/png;base64,' + data.toString('base64');
      resolve(base64Img);
    });
  });
}

router.post('/file/downloadFiles', async (ctx) => {
  await client.connect();
  const database = client.db(MYDATA);
  database.collection(MYCOLLECTION);
  let base64 = await GridFSBucketReadStreamToBase64(database);
  console.log(ctx.status, 'ctx.stauts=先执行', base64);
  ctx.status = 200;
  ctx.body = {
    code: 200,
    data: base64,
  };
});

一个图片的异步问题解决了。还有多张图片处理问题。如:一次请求3张banner图片。

  • banner图片存储思路
  1. 将图片以文件流的形式存统一储于数据库MANAGEMENT_BUCKET,另将文件名称按类型存于Banner集合中。

  2. 先在Banner集合中查出所有图片名称,然后根据图片名称循环拿到转换的图片。

获取图片流时,遇到异步循环问题。

和下面的参考文章遇到的问题以及解决思路简直一模一样。

异步循环参考文章: zhuanlan.zhihu.com/p/70785259

banner_controller.js文件 解决后代码

    let bannerList = [];
    promiseArr = bannerIdList.map(async (item) => {
      const base64 = await GridFSBucketReadStreamToBase64(bucket, item['_id']);
      console.log(base64.length);
      bannerList.push({ img: base64, id: item['_id'] });
    });
    await Promise.all(promiseArr);
    console.log(bannerList);

到此,真的就可以开开心心写代码了。后面的过程很快,大概2、3天就写完了全部代码。

项目地址

代码未重构,有代码洁癖的不喜勿喷。

github.com/MangoSeven/…

结束语

这篇文章拖拖拉拉写了近一个礼拜,终于完成了。全文没有什么高深的技术,大多都是基础的内容,更多的可能是整个项目的新路历程吧。

看了下最初的小程序文件夹,2019年7月,原以为只是想写个简单的小程序,做一个产品展示,从原型到初稿完成大概用了一礼拜时间。

后面发现单纯的小程序,无法解决大量图片问题,在云开发和学Nodejs之间纠结很一段时间,选了Nodejs,但是后来什么都没有做,被搁置了很长时间,直到今年年初,和一个朋友说了下自己的想法,我们一拍即合,我来写前端,他来写后台。

一切都看似顺利的进行,画需求图、需求讲解、各自工作计划,小程序和后管都开发完了,胜利就在眼前。哪知道这仅仅是个开始,异地联调环境搭建用了半个多月,才请求到对方服务器。又过了一个月左右我们调试通了第一个登陆接口。

嗯。。。。。也是最后一个接口。

恍恍惚惚又过去了半年。 123.webp

从最初开始那天,它像一根刺卡在那里,咽不下去也吐不出来,很难受。直到今年我决定拔掉这根卡着的刺,然后也就有了这篇文章。

既然开始了,那就做完吧。 很感谢你能耐心的读完,这篇流水账般的文章。

888.webp