乐趣区

关于linux:KoaMongodb-搭建商家店铺服务端项目总结

背景

自从实现了客户端和治理后盾我的项目后,一个残缺的 web 利用前端方面的我的项目算是搭建实现了。最初还须要有一个提供 API 服务的后端我的项目服务前端利用运行,通过一个月的开发,当初基本上曾经全副实现,通过部署后,实现了最初的上线运行。

我的项目应用技术栈

本我的项目是基于 nodejs 次要应用 koa+mongodb 为外围开发的轻量级服务端利用。接口是依照 RESTful 格调进行设计

应用次要中间件有
koa-compress,
koa-parameter,koa-connect-history-api-fallback,koa-static,koa-mount。具体应用形式请在koa 官网仓库查看

数据库操作:mongoose

接口权限验证:jsonwebtoken

用户明码加密:bcryptjs

上传资源存储:koa-multer

路由散发:koa-router

接口参数解析: koa-bodyparser

接口应用文档: https://konglingwen94.github….

开发过程

数据库设计

本项目选择应用 mongodb 作为数据存储的数据库,因为其对于前端开发者有着人造的有好性,应用容易上手。
因为自己涉猎服务端畛域尚处于初级阶段,在数据库设计方面教训无限,在此分享进去仅供参考应用。

mongodb应用 bson 类型作为数据存储格局。因为跟前端 js 的 json 类型能够相互转换应用。所以这就减小了入门者设计数据库表字段的难度,咱们能够参照前端页面须要展现的数据进行设计。
而后通过应用 mongoose 这个库作为疾速操作数据库的模型,咱们能够用代码的模式设计 mongodb 表字段的模型,通过 mongoose 的编译能够存储到实在的数据库表中。

拿本我的项目中的 商品 表来说,这是一个申明好的 mongooseSchema

 {
    name: String,
    price: Number,
    oldPrice: Number,
    description: String,
    sellCount: Number,
    rating: Number,
    info: String,
    menuID: ObjectId,
    image: String,
    online: {type: Boolean, default: true},
  },

通过 mongoose 模型的编译办法后存储到数据库中的字段是这个样子

数据库存储字段

Field Type Description
menuID ObjectId 商品分类 ID
name String 商品题目
info String 商品信息
description String 商品简介
image String 商品封面
online Boolean 是否公布
oldPrice Number 商品原价
price Number 商品售价
sellCount Number 售卖个数

查看残缺的模型文件点这里

接口搭建

一个残缺的 API 接口从接管申请到响应数据实现,两头这个过程就是服务端解决各种代码逻辑的。这其中次要包含 裸露接口地址 , 接口权限验证 申请参数验证 查询数据库 返回响应信息 这几个阶段。为了合乎服务端业务逻辑分层设计的模式,每一个解决阶段都能够抽离到一个独自的模块,最初再把各种相关联的模块组装起来打包成一个残缺的我的项目,这样的模块化设计能够很大的加强我的项目的 维护性 可读性。用目录构造的形式展示就是这个样子的

├── model  // 数据库模型
│   ├── administrator.js
│   ├── seller.js
│   ├── rating.js
│   ├── category.js
│   └── food.js
├── helper
│   ├── validatorRules.json  // 参数验证规定
│   ├── mongoose.js  // mongoose 连贯脚本
│   ├── middleware.js // 我的项目中间件
│   └── util.js  // 工具函数
├── controller  // 控制器
│   ├── administrator.js
│   ├── seller.js
│   ├── rating.js
│   ├── category.js
│   └── food.js
├── config
│   └── config.default.json  // 我的项目配置文件
├── router
│   └── index.js  // 路由配置

model文件夹用来放数据库表模型,数据库存储了哪些字段在这个文件目录查看高深莫测。helper目录寄存了一些辅助的我的项目文件和一些脚本,其中 middleware.js 这个文件寄存了整个我的项目的所有中间件,依照模式分层的准则,我把服务端接口的一些解决逻辑都抽离到了中间件里,其中包含 接口权限验证 申请参数验证 这两个次要的代码解决逻辑。controller目录则是寄存接口业务逻辑的中央,咱们也把他叫做 控制器 查询数据库 返回响应信息 也是在这个模块外面实现的。最初就是对立散发路由接口,router目录是我的项目所有接口散发的中央,在这里能够把不同的控制器散发到一个或多个路由接口地址上,这样能够实现控制器文件的复用,不须要写反复的业务代码。

权限验证和登录(蕴含注册性能)

面向多用户服务的后端我的项目,权限验证是不可或缺的。本我的项目应用了 authorization 申请头验证的形式判断每一个申请的权限。为了不便解决,我把这一块的代码逻辑抽离到了一个中间件里。这样对每一个接口是否验证权限也容易治理和浏览。本我的项目的权限验证应用 jsonwebtoken 这个第三方插件作为生成秘钥 token 的工具,用户在登录的时候服务端会生成一个 token 响应到前端,前端依据运行环境把它存储下来,之后的每一个申请依据业务须要携带这个 token 传递到服务端,服务端依据设置好的验证规定返回不同的验证后果,这就是本我的项目 接口权限验证 整体运行过程。

通过 [控制器中的用户登录接口]() 剖析其中的业务逻辑时怎么解决的

// 这里仅展现业务逻辑代码
  async login(ctx) {const { username, password} = ctx.request.body;

    let result = await AdministratorModel.findOne({username});
    // 如果没有后果则 创立新用户
    if (!result) {
      // 加密明码
      const hashPass = await bcrypt.hash(password, 10);

      const newUser = await AdministratorModel.create({password: hashPass, username});

      const token = jwt.sign({username, role: newUser.role, level: newUser.level}, secretKey, {expiresIn,});

      return (ctx.body = { admin: omit(newUser.toObject(), ["password"]), token });
    }

    if (!bcrypt.compareSync(password, result.password)) {
      ctx.status = 400;

      return (ctx.body = { message: "明码谬误"});
    }
    const user = result.toObject();
    const token = jwt.sign(user, secretKey, { expiresIn});
     
    ctx.body = {admin: omit(user, ["password"]), token };
  },

为了反对治理先天 首次登陆即注册 的性能,本登录代码接口也蕴含了用户注册的业务逻辑。通过参数解析和校验的过程后(代码局部以中间件解决的形式在其余模块),通过解构即取到了前端传递的无效参数。依据数据库查问的后果解决不同的业务逻辑,在取到创立后的用户信息后需通过 jsonwebtoken 的签名生成一个 token,此token 也是其余接口在验证用户登录状态时惟一的验证信息。

通过登录接口生成 token 后咱们就能够对其余须要增加拜访权限的接口进行鉴权验证了。上面是通过验证 token 是否无效判断用户登录状态的逻辑代码中间件

// 对立抽离到一个中间件中,这里省略了引入其余模块的过程
module.exports={adminRequired() {return async (ctx, next) => {let token = ctx.headers["authorization"];

      if (!token) {
        ctx.status = 400;
        return (ctx.body = { message: "没有传递 token"});
      }
      token = token.split(" ")[1];

      try {var decodeToken = jwt.verify(token, secretKey, { expiresIn});
      } catch (error) {
        ctx.status = 403;
        if (error.name === "TokenExpiredError") {return (ctx.body = { message: "过期的 token"});
        }
        return (ctx.body = { message: "有效的 token"});
      }

      ctx.state.adminInfo = decodeToken;
      await next();};
  },
 }

申请进入到这里后,通过认证申请头先提取到 token 变量,当 token 取到具体值后,再应用 jsonwebtoken 外部提供的验证函数校验,依据不同的验证后果响应不同的状态码和错误信息。具体的验证后果谬误类型自行到插件仓库查看,这里不做具体介绍。当验证通过后会解析出 token 的签名内容,如果鉴权接口其余中央的业务逻辑须要用到此信息的话,咱们能够把它挂载到 koa 提供的特定命名空间字段上,这样不便部分的逻辑代码获取。

备注:token应用的认证类型须要依据前后端开发人员的约定应用,本我的项目应用 Bearer ${token} 的格局作为令牌拜访头

为了合乎 koa 中间件导出格局的设计准则,这个文件的中间件是以闭包的模式导出的,理论利用到接口上的是这个闭包函数,这样设计的益处是咱们在调用中间件函数的时能够传递参数进去,外部理论失效的中间件能够依据内部传递的参数做逻辑上的解决。在路由配置表外面对立应用这个中间件的形式是这个样子的

// 局部代码省略
const Router = require("koa-router");

const router = new Router({prefix: "/api"});
const middleware = require("../helper/middleware");

router.post("/admin/foods", middleware.adminRequired(),FoodController.createOne);

数据库分页查问性能

对于大多数前端我的项目,分页显示数据在一个十分常见的性能,对应到服务端的代码逻辑就是数据库的过滤查问。应用 mongoose 提供的过滤查问操作 API 能够很容易实现这个需要,当咱们用到的中央比拟多的时候,问题就呈现了。对于前端申请的接口门路个别是这个样子的 /api/foods?page=1&size=20,咱们须要对传递的querystirng 做进一步的判断和解析能力利用到数据库参数的查问上。问题是很多个接口都须要这个性能,应用起来比拟繁琐,那不如咱们把这个解析查问参数的过程抽离成一个模块,这样更不便咱们应用和保护。当初让咱们看一下封装好的全副代码吧!

module.exports = {resolvePagination(pagination = {}) {const defaults = { page: 1, size: 10};

    pagination.page = parseInt(pagination.page, 10);
    pagination.size = parseInt(pagination.size, 10);

    if (Number.isNaN(pagination.page) || pagination.page <= 0) {pagination.page = defaults.page;}
    if (Number.isNaN(pagination.size) || pagination.size <= 0) {pagination.size = defaults.size;}

    const {page, size} = pagination;
    return {
      page,
      size,
    };
  },
  resolveFilterOptions(filter = {}) {
    let sort = {createdAt: -1,};
    sort = defaults({}, filter.sort, sort);

    const {page, pageSize} = resolvePagination({
      page: filter.page,
      size: filter.size,
    });
    return {
      limit: size,
      skip: (page - 1) * size,
      sort,
    };
  },
};

首先通过 resolvePagination 这个函数咱们能够解析出无效的 query 参数,在通过 resolveFilterOptions 这个函数解析进去合乎 mongoose 数据筛选操作的查问选项。通过模块化引入的操作形式,利用到理论的数据库查问过程中如下

// 代码片段来自我的项目 `controller` 目录
const {resolveFilterOptions, resolvePagination} = require("../helper/utils");

module.exports={async queryListByOpts(ctx) {const { page, size} = resolvePagination({page: ctx.query.page, size: ctx.query.size});

    const {skip, limit, sort} = resolveFilterOptions({page, size});

    const total = await FoodModel.countDocuments();

    var results = await FoodModel.find().populate("category").sort(sort).skip(skip).limit(limit);

    ctx.body = {
      data: results,
      total,
      pagination: {
        page,
        size,
      },
    };
  },
}

从代码中能够看到以获取到前端传递的 query 类型参数为解析值,resolvePagination函数负责解析无效的数据分页查问选项,resolveFilterOptions函数解析进去了 mongoose 特定查问语句格局的参数,咱们通过拆散业务代码和逻辑代码的形式无效加强了代码的模块化构造,也减少了代码的复用性,进步了我的项目的开发效率。

利用的部署和运行

本我的项目应用 github-actions 的继续集成性能主动部署到云服务器,有了继续集成的服务,就省去了我的项目手动构建,测试,公布这一系列流程,而且升高了手动操作程序出错的危险,具体的配置文件如下

name: Deploy files
on: [push]
jobs:

  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: copy file via ssh key
      uses: appleboy/scp-action@master
      with:
        host: ${{secrets.SERVER_HOST}}
        username: ${{secrets.SERVER_USERNAME}}
        key: ${{secrets.SERVER_SSH_KEY}}
        port: ${{secrets.SERVER_PORT}}
        source: "*"
        target: "/var/www/elm-seller-server"

    - name: executing remote ssh commands using ssh key
      uses: appleboy/ssh-action@master
      with:
        host: ${{secrets.SERVER_HOST}}
        username: ${{secrets.SERVER_USERNAME}}
        key: ${{secrets.SERVER_SSH_KEY}}
        port: "22"
        script: |
          cd /var/www/elm-seller-server
          npm install
          
          npm start
          

从配置文件中看出,服务器公布的环境变量都采纳了加密的形式传递,比方 ${{secrets.SERVER_HOST}} 这个环境变量,实在的存储值须要咱们在 github 仓库的设置面板里的 secret 选项配置的,当本地应用 git 治理的仓库推送到近程仓库的时候就会触发 github-actions 的主动部署操作,同时咱们还能够在 workflows 文件夹上面配置多个以 .yml 结尾的配置文件,一个配置文件对应一个 actions 部署工作,本我的项目我就应用了两个继续集成的工作,因为我的项目对应的阐明文档也须要及时的更新公布。至于部署文件模板怎么抉择须要依据集体的需要本人抉择设置,github-actions官网市场提供了罕用的集成工作模板供咱们抉择

公布到云服务器的利用我抉择应用 PM2 治理利用,利用启动的配置文件点这里。pm2 是一个面对 node 利用的管理工具,咱们能够不便的查看,重启,删除,进行,启动利用

API 文档编写

文档的撰写是一个后端我的项目不可或缺的一部分内容,学会写文档能够回顾我的项目从设计到开发的过程,发现有问题的中央能够第一工夫发现,及时的修复 bug。我的项目文档是应用markdown 语法编写的 REAEME 文件,所有文件均在我的项目的 docs 目录内。文档应用 vuepress 作为构建工具预览和公布。具体应用形式自行查看官网文档,不做具体介绍

文档公布地址:https://konglingwen94.github….

工具和环境

vscode mac node mongodb git github postman ssh

总结心得

从我的项目的需要布局,到数据库表设计,api 接口逻辑关注点的拆散,最初胜利的部署运行以及文档的撰写实现,本人初步把握了服务端我的项目残缺的开发流程,并积攒了一些开发教训能够在这分享。

作为编程开发人员,在我的项目开发过程中遇到困难是很失常的,尤其是在调试代码的时候各种各样的错误信息看的 ” 目迷五色 ”, 尤其是服务端 node 的环境没有浏览器客户端调试不便。遇到代码出错不要怕,咱们须要一步步排查出错的起因,如果错误信息看起来不直观咱们能够借助第三方工具调试,本我的项目我应用的是 nodemon 这个工具,他能够热加载利用,也能够开启 debug 的命令关上一个相似浏览器开发者工具的调试面板,咱们能够在控制台面板查看程序抛出的错误信息,在 source 面板查看出错的代码堆栈,借助这些工具的剖析,只有有急躁,一点点思考呈现谬误的问题,最终咱们肯定能够解决它。

反对

感激所有点赞和关注的小伙伴们,对本我的项目有趣味的同学能够一块和我交换,欢送在上面留言!

如果您对本我的项目由好的倡议或者发现 bug 能够到我的项目仓库提issues, 也欢迎您的珍藏和关注,谢谢!

仓库地址:https://github.com/konglingwe…

文档地址:https://konglingwen94.github….

退出移动版