背景
自从实现了客户端和治理后盾我的项目后,一个残缺的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 fileson: [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....