当前位置: 首页 > Linux

Koa+Mongodb搭建商城服务器项目总结

时间:2023-04-06 19:14:48 Linux

背景完成客户端和管理后台项目后,搭建了一个完整的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模型编译完成后,存储在数据库中的字段是这样的:数据库存储字段FieldTypeDescriptionmenuIDObjectId商品类别IDnameString商品标题infoString商品信息descriptionString商品简介imageString商品封面onlineBoolean是否发布oldPriceNumber商品原价priceNumber商品售价sellCountNumber查看完整模型文件点此搭建完整的API接口从接收请求到响应数据完成,中间过程是服务端处理各种代码逻辑。主要包括暴露接口地址、接口权限验证、请求参数验证、查询数据库、返回响应信息等阶段。为了符合服务端业务逻辑的分层设计模式,可以将每个处理阶段分离成一个单独的模块,最终将各个相关模块组装打包成一个完整的项目。这样的模块化设计可以大大增强项目的可维护性和可读性。这是目录结构的样子├──model//databasemodel│├──administrator.js│├──seller.js│├──rating.js│├──category.js│└──food.js├──helper│├──validatorRules.json//参数校验规则│├──mongoose.js//mongoose连接脚本│├──middleware.js//项目中间件│└──util.js//工具函数├──controller//Controller│├──administrator.js│├──seller.js│├──rating.js│├──category.js│└──food.js├──config│└──config.default.json//项目配置文件├──router│└──index.js//路由配置模型文件夹,用于放置数据库表模型。数据库中存储了哪些字段,在这个文件目录下可以一目了然。helper目录存放一些辅助工程文件和一些脚本。其中middleware.js文件存放了整个项目的所有中间件。根据模式分层的原则,我将服务端接口的一些处理逻辑提取到中间件中,它包括接口权限验证和请求参数验证两大代码处理逻辑。controller目录是存放接口业务逻辑的地方。我们也称它为控制器,查询数据库和返回响应信息也是在这个模块中完成的。最后统一分配路由接口。router目录是项目所有接口分布的地方。这里可以将不同的controller分配到一个或多个路由接口地址,这样controller文件就可以重复使用,不会重复。业务代码。权限验证和登录(包括注册功能)是一个多用户服务的后端项目,权限验证是必不可少的。本项目采用授权请求头校验方式来判断每个请求的权限。为了处理方便,我把这段代码的逻辑抽取出来放到了一个中间件中。这样每个接口是否验证权限也方便管理和阅读。本项目权限验证使用第三方插件jsonwebtoken作为生成秘钥token的工具。当用户登录时,服务端会生成token响应给前端,前端会根据运行环境进行存储,后续的每次请求都会根据业务需要携带此token给服务端,服务器根据设置的验证规则返回不同的验证结果。以上就是本项目接口权限验证的整体操作流程。通过[controller中的用户登录接口]分析业务逻辑时如何处理()//这里只展示业务逻辑代码asynclogin(ctx){const{username,password}=ctx.request.body;让result=awaitAdministratorModel.findOne({username});//如果没有结果创建新用户if(!result){//加密密码consthashPass=awaitbcrypt.hash(password,10);constnewUser=awaitAdministratorModel.create({password:hashPass,username});consttoken=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:"密码错误"});}constuser=result.toObject();consttoken=jwt.sign(user,secretKey,{expiresIn});ctx.body={admin:omit(user,["password"]),token};},为了支持首次登录后管理注册的功能,本次登录代码接口还包含了用户注册的业务逻辑。经过参数解析和校验的过程(代码部分由其他模块中的中间件处理),通过解构得到前端传递过来的有效参数。根据数据库查询的结果处理不同的业务逻辑。获取创建的用户信息后,需要通过jsonwebtoken的签名生成一个token。这个token也是其他接口在验证用户登录状态时唯一的验证信息。通过登录接口生成token后,我们就可以对其他需要添加访问权限的接口进行认证了。下面是通过验证token是否有效来判断用户登录状态的逻辑代码中间件//统一抽取出一个中间件,这里省略引入其他模块的过程module.exports={adminRequired(){returnasync(ctx,next)=>{lettoken=ctx.headers["authorization"];如果(!token){ctx.status=400;return(ctx.body={message:"Notokenpassed"});}令牌=令牌。分裂(“”)[1];try{vardecodeToken=jwt.verify(token,secretKey,{expiresIn});}赶上(错误){ctx.status=403;if(error.name==="TokenExpiredError"){return(ctx.body={message:"Expiredtoken"});}return(ctx.body={message:"Invalidtoken"});}ctx.state.adminInfo=decodeToken;等待下一个();};},}请求进入这里后,首先通过认证请求头提取token变量。当token得到一个特定值时,使用jsonwebtoken提供的校验函数进行校验,根据不同的校验结果码和错误信息响应不同的状态。具体的校验结果错误类型可以自行去插件仓库查看,这里不再详细介绍。验证通过后,会解析出token的签名内容。如果认证接口其他部分的业务逻辑需要用到这些信息,我们可以挂载到koa提供的特定命名空间字段,方便本地逻辑代码。获得。备注:token使用的认证类型需要根据前后端开发者的约定来使用。本项目使用Bearer${token}的格式作为token访问头。为了符合koa中间件导出格式的设计原则,这个文件的中间件是以闭包的形式导出的,而真正应用到接口上的正是这个闭包函数。这样设计的好处是我们可以在调用中间件函数的时候传入参数,内部真正生效的中间件可以根据外部传入的参数进行配置。逻辑处理。在路由配置表中统一使用这个中间件的方式是这样//部分代码省略constRouter=require("koa-router");constrouter=newRouter({prefix:"/api"});constmiddleware=require("../helper/middleware");router.post("/admin/foods",middleware.adminRequired(),FoodController.createOne);数据库分页查询功能对于大多数前端项目来说,分页展示数据是一个很常见的功能,对应服务器的代码逻辑就是数据库的过滤查询。使用mongoose提供的过滤查询操作API可以轻松实现这一需求。当我们在很多地方使用它时,就会出现问题。前端请求的接口路径一般是这样的/api/foods?page=1&size=20,我们需要对传入的querystring做进一步的判断分析,才能应用于数据库参数的查询。问题是很多接口都需要这个功能,使用起来比较麻烦。最好把解析查询参数的过程单独放到一个模块中,这样更方便我们使用和维护。现在让我们看看所有打包后的代码吧!module.exports={resolvePagination(pagination={}){constdefaults={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}=分页;返回{页面,大小,};},resolveFilterOptions(filter={}){letsort={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函数解析出有效的查询参数,然后通过resolveFilterOptions函数解析出满足mongoose数据过滤操作的查询选项。将模块化引入的运行方式应用到实际的数据库查询过程中,如下//代码片段来自项目`controller`目录const{resolveFilterOptions,resolvePagination}=require("../helper/utils");module.exports={asyncqueryListByOpts(ctx){const{page,size}=resolvePagination({page:ctx.query.page,size:ctx.query.size});const{skip,limit,sort}=resolveFilterOptions({page,size});consttotal=awaitFoodModel.countDocuments();varresults=awaitFoodModel.find().populate("category").sort(sort).skip(skip).limit(limit);ctx.body={data:results,total,pagination:{page,size,},};},}从代码中可以看出,传递给前端的查询类型参数作为解析值,resolvePagination函数负责解析有效的数据分页查询选项。resolveFilterOptions函数解析出mongoose特定查询语句格式的参数。通过业务代码和逻辑代码的分离,我们有效增强了代码的模块化结构,增加了代码的复用性,提高了项目的开发效率。应用的部署和运行本项目使用github-actions的持续集成功能自动部署到云服务器。通过持续集成服务,消除项目的人工构建、测试、发布,减少人工操作流程。有出错的风险,具体配置文件如下ssh密钥使用:appleboy/scp-action@masterwith:主机:${{secrets.SERVER_HOST}}用户名:${{secrets.SERVER_USERNAME}}密钥:${{secrets.SERVER_SSH_KEY}}端口:${{secrets.SERVER_PORT}}source:"*"target:"/var/www/elm-seller-server"-name:executingremotesshcommandsusingsshkeyuses:appleboy/ssh-action@masterwith:host:${{秘密。SERVER_HOST}}用户名:${{secrets.SERVER_USERNAME}}密钥:${{secrets.SERVER_SSH_KEY}}端口:“22”脚本:|cd/var/www/elm-seller-servernpminstallnpmstart从配置文件中,服务器发布的环境变量以加密方式传递,如${{secrets.SERVER_HOST}}这个环境变量,真正的存储值需要在github仓库设置面板的secret选项中配置。当git管理的本地仓库推送到远程仓库时,会触发github-actions的自动部署操作。同时我们也可以在workflows文件夹下配置多个.yml结尾的配置文件。一个配置文件对应一个动作部署任务。在这个项目中,我使用了两个持续集成任务,因为项目相应的文档也需要及时更新发布,至于如何选择部署文件模板,需要根据自己的需要选择设置。github-actions官方市场提供了常用的集成任务模板,供我们选择应用发布到云服务器。我选择使用PM2来管理应用程序并启动应用程序。配置文件点击这里。pm2是node应用的管理工具。我们可以很方便地查看、重启、删除、停止、启动应用程序API文档编写文档编写是后端项目不可或缺的一部分。学习写文档可以回顾项目从设计到开发的过程,第一时间发现问题,及时修复bug。项目文档是用markdown语法编写的REAEME文件,所有文件都在项目的docs目录下。使用vuepress作为构建工具来预览和发布文档。具体使用方法查看官方文档,没有详细介绍,api接口逻辑关注点分离,最终部署运行成功,文档编写完成,初步掌握了服务器项目的完整开发流程,积累了一些开发经验,在此分享。作为一名编程开发人员,在项目开发过程中遇到困难是很正常的,尤其是在调试代码的时候,各种错误信息让人“眼花缭乱”,尤其是在服务端节点环境没有浏览器客户端的情况下,更容易调试。遇到代码错误不要害怕。我们需要逐步排查错误原因。如果报错信息看起来不直观,我们可以借助第三方工具进行调试。对于这个项目,我使用nodemon,它可以热加载应用程序或打开它。debug命令会打开一个类似于浏览器开发人员工具的调试面板。我们可以在控制台面板查看程序抛出的错误信息,在源代码面板查看错误代码栈。借助这些工具的分析,只要你有耐心,你可以去思考问题出在哪里,最终我们一定能够修复它。支持感谢所有喜欢和关注的朋友。对这个项目感兴趣的同学可以和我一起交流。欢迎在下方留言!如果您对本项目有好的建议或者发现bug,可以向项目仓库提交issue。也欢迎您的收藏和关注,谢谢!仓库地址:https://github.com/konglingwe...文档地址:https://konglingwen94.github....