ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Node.js和Vue构建全栈小零食商城系统实战记录

基于Node.js和Vue构建全栈小零食商城系统实战记录 做这套小零食超市购物商城销售系统起因其实特别务实。朋友在社区开了家零食店每天的订货、盘点、算账全靠Excel客户想买点东西还得专门跑一趟他找我帮忙做个小商城既能在线逛店、加购、下单也能在后台上架商品、处理订单。我合计了一下技术栈决定用Node.js写后端接口前端用Vue来搭页面。这两个词“nodejs”和“Vue”在现在的全栈项目里出现频率非常高也确实能打Node.js的异步特性适合处理商城这种大量高并发请求Vue的组件化和响应式能让我们快速把页面拆起来而且前后端都是JavaScript一个团队可以无缝切换少了很多上下文切换的损耗。这篇内容不是我凭空编的教程而是我从零把项目搭起来、挨个模块写出来、最终部署上线的完整记录里面包含环境配置、核心代码逻辑、踩过的坑和排查思路适合有基础前端知识、想尝试全栈开发的朋友也适合正在做类似商城系统的开发者当参考。我不展开长篇大论的理论只讲实际操作里真正有效、能落地的做法以及为什么这么做。1. 项目整体设计与技术选型思路1.1 为什么选择Node.js Vue这套组合在做技术选型时我实际上认真对比过Java Spring Boot、PHP Laravel和Node.js。最后选了Node.js核心原因是项目规模不大团队又都是写JavaScript为主的前端后端共用一套语言类型模型和数据格式都可以复用。Node.js基于V8引擎事件驱动、非阻塞I/O处理商品查询、订单提交这种高并发读写很合适。商城系统不是计算密集型应用瓶颈往往在数据库I/O和网络请求Node这门语言恰好能扛住。前端选Vue而不选React是因为Vue的模板语法直观组件化开发加上响应式数据绑定编写界面时心智负担小。我的朋友不是专业开发后续维护可能要交给懂点前端的人Vue对新手更友好。而且Vue Router和Vuex在生态里很成熟做商城这种多页面、状态管理复杂的项目非常对口。这套组合确认后项目就从“想想看”变成了“马上做”。1.2 商城系统的功能模块拆解小零食超市购物商城和标准电商相比有共性也有特殊性。共性模块包括用户注册登录、商品浏览、商品详情、购物车、订单结算、订单查询、后台管理。特殊性在小零食这个品类上零食品牌多、口味分类复杂、计量单位多样一包薯片和一盒巧克力重量不同、价格区间大而且容易出现“组合套餐”和“满减促销”的需求。另外零食客单价低、购买频次高购物车和结算页的交互流畅度非常重要页面切来切去很影响转化率。我把系统拆成前端客户端、后端管理端、服务端接口三层。前端客户端给普通用户用包含商品列表、分类筛选、购物车、结算、订单中心后台管理端给店主用包含商品管理、库存管理、订单审核、统计报表服务端接口负责处理所有业务逻辑统一通过RESTful API对外提供能力。这样的划分确保了职责清晰后续要加小程序端或者移动H5端也能直接复用现有的后端接口。1.3 项目目录结构与数据流设计项目的目录结构我维持了前后端分离的经典模式根目录下分server和client两个文件夹。server放置Node.js后端代码包括routes、models、middleware、utils等client放置Vue前端项目包括src下的views、components、store、router、api等。开发时前端启动在8080端口后端启动在3000端口通过proxy配置转发API请求生产时前端构建成静态文件由Nginx托管再把/api路径反向代理到Node服务。数据流的基本链条是用户在Vue页面操作触发组件方法调用axios封装的API函数请求发到Express定义的路由路由经过中间件比如JWT鉴权、参数校验进入Controller层处理业务逻辑再通过模型层访问MySQL数据库拿到结果之后一层层返回前端前端再通过响应式数据更新视图。理解这个链路非常重要因为后面所有排查问题的思路都会围绕这个链路展开。snacks-shop/ ├── server/ │ ├── app.js │ ├── routes/ │ ├── controllers/ │ ├── models/ │ ├── middleware/ │ └── config/ └── client/ ├── src/ │ ├── api/ │ ├── router/ │ ├── store/ │ ├── views/ │ ├── components/ │ └── App.vue └── package.json2. 环境准备从零安装配置Node.js和Vue2.1 Node.js下载、安装与环境变量配置很多新手卡在第一步就是环境装不明白。我这里说的都是自己的操作经验。先去Node.js官网下载LTS版本的安装包别下载Current尝鲜版Current更新的太快开发环境要稳定。安装时注意两件事第一安装路径不要带中文和空格我见过有人装在“D:\程序 files”下面后面跑npm命令全报错第二安装到选择组件那一步时务必勾选“Add to PATH”这个选项能自动把Node和npm写进系统环境变量。如果安装时没勾选或者装完发现命令行输node -v没反应就需要手动配置环境变量。打开系统属性里的“高级系统设置”点“环境变量”在系统变量里新建NODE_HOME填Node的安装目录比如C:\Program Files\nodejs然后在Path变量里新增两条记录分别是%NODE_HOME%和%NODE_HOME%\node_global。配置完之后一定要重新打开命令行窗口让它生效。使用cmd或PowerShell输入node -v和npm -v能输出版本号就说明装好了。另外建议把npm的全局包目录单独设置避免后续全局安装工具时权限问题执行npm config set prefix D:\nodejs\node_global这样的操作按照自己的目录调整即可。npm默认从官方源下载包国内网络环境下速度很不稳定。我习惯直接切换镜像源执行一行命令npm config set registry https://registry.npmmirror.com配置完可以执行npm config get registry验证能看到镜像源说明设置成功。切换之后安装依赖的速度提升非常明显在项目目录里跑npm install基本几分钟就完成了。2.2 解决“无法加载文件npm.ps1禁止运行脚本”的经典报错如果是在Windows下用PowerShell执行npm命令大概率会遇到下面这条报错npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本。有关详细信息请参阅 https:/go.microsoft.com/fwlink/?LinkID135170 中的 about_Execution_Policies。所在位置 行:1 字符: 1我第一次遇到时也懵了一下后来搞明白了原因Windows PowerShell默认执行策略是Restricted也就是禁止运行任何脚本文件npm通过npm.ps1这个PowerShell脚本暴露命令自然就被拦下来了。解决办法其实很温和不需要关闭PowerShell只需要调整执行策略用管理员身份打开PowerShell执行Set-ExecutionPolicy RemoteSigned然后按Y确认。RemoteSigned策略的意思是本地创建的脚本可以运行但从网上下载的脚本必须有可信数字签名才能运行。相比把策略改成UnrestrictedRemoteSigned在方便和安全之间取了平衡。如果你实在不想动PowerShell策略还可以直接用cmd命令行窗口操作cmd不会受到这个限制。不过我还是建议把PowerShell策略配置好毕竟现代前端工具链里很多命令都依赖脚本运行比如后面要用的Vue CLI、Vite等都可能在PowerShell里执行。2.3 Vue项目创建与开发工具链配置环境和npm搞定之后Vue项目的创建就顺理成章了。我创建项目用的是Vue CLI脚手架先全局安装一下npm install -g vue/cli然后在工作目录执行vue create snacks-shop选择手动配置功能勾上Router、Vuex、ESLint、CSS预处理器等脚手架把依赖装上。如果你喜欢更轻量的构建体验也可以用Vite的Vue模板npm create vitelatest snacks-shop -- --template vue创建完成进入项目目录执行npm install安装依赖再执行npm run serve启动开发服务器。浏览器访问localhost:8080就能看到Vue默认页面。调试工具方面强烈建议安装Vue Devtools浏览器插件。这个插件可以在Chrome或Edge的扩展商店直接搜索Vue Devtools安装装好之后打开Vue开发页面开发者工具里会多出一个Vue面板能直观查看组件的props、data、computed还能在时间旅行式调试里看到状态变更。商城项目状态层级深没有Devtools排查问题真的很痛苦。热词里提到的“vue devtools插件下载”其实没那么复杂记住只在官方扩展商店装不要装来路不明的文件。3. 核心功能实现从后端API到前端页面3.1 后端接口设计与数据库表结构商城系统的核心是数据模型我先把表结构设计清楚再写接口避免后面推倒重来。基础表有用户表users、商品表products、分类表categories、购物车表cart、订单表orders、订单明细表order_items。小零食的规格比通用商品更杂我在商品表里增加了一个spec字段用JSON格式存储口味、包装规格、单位重量等信息。简化版建表语句大概是这样CREATE TABLE products ( id BIGINT PRIMARY KEY AUTO_INCREMENT, category_id BIGINT NOT NULL, name VARCHAR(255) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, image VARCHAR(255), spec JSON NOT NULL, status TINYINT DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, order_no VARCHAR(64) NOT NULL UNIQUE, total_amount DECIMAL(10,2) NOT NULL, discount_amount DECIMAL(10,2) DEFAULT 0, pay_amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0, address VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );API设计遵循RESTful风格关键接口有POST /api/auth/register、POST /api/auth/login、GET /api/products、GET /api/products/:id、POST /api/cart、GET /api/cart、PUT /api/cart/:id、DELETE /api/cart/:id、POST /api/orders、GET /api/orders。每个接口都放在独立路由文件里便于维护。在写订单相关接口时有一点必须反复强调库存扣减和订单生成必须放在同一个数据库事务里。如果分开操作用户下单扣了库存但插入订单失败就会出现“货没了但订单没生成”的严重问题。所以我在Controller层使用sequelize或mysql2的transaction方法包住整个写入流程确保操作原子性。3.2 Vue前端页面与路由守卫设计前端页面我按用户角色分成两块。普通用户端页面有首页、商品分类页、商品详情页、购物车页、订单结算页、用户中心页。管理端页面有商品管理、订单管理、销售统计。使用Vue Router配置路由时动态路由参数是很常见的场景比如商品详情页需要根据商品ID渲染不同内容路由定义写成const routes [ { path: /, name: Home, component: Home }, { path: /products/:id, name: ProductDetail, component: ProductDetail }, { path: /cart, name: Cart, component: Cart, meta: { requiresAuth: true } }, { path: /login, name: Login, component: Login } ];路由传参的方式我在开发时踩过小坑。如果直接用/products/${id}拼接路径刷新页面后参数不会丢失如果用query传参刷新后依然能拿到但在管理后台列表跳详情时不利于分享。所以我对需要收藏、分享的页面一律优先使用动态路由参数而不是query字符串。路由守卫是商城系统的门槛。我需要实现两个功能一是未登录用户不能访问购物车和结算页二是普通用户不能访问管理后台。在Vue Router 4里用beforeEach钩子实现router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next({ name: Login, query: { redirect: to.fullPath } }); } else { next(); } });这样做的好处很明显把认证逻辑集中在一处不用在每个页面里自己判断登录态。注意token存在localStorage里只是中等安全的做法生产环境如果有更强的安全要求可以考虑用HttpOnly的Cookie存储防止XSS脚本窃取。3.3 购物车与订单结算的联动逻辑购物车模块是商城交互里最核心的部分。前端购物车状态我放在了Vuex里状态对象包含一个items数组每个元素是商品ID、名称、单价、数量、规格、小计。用户操作购物车时前端先更新本地状态再同步调后端API持久化这样用户刷新页面后数据不会丢。关于数量加减这里有个容易忽略的细节用户在购物车疯狂点“”的时候前端应该先做本地校验不能超过库存上限但真正的库存校验必须以后端为准。因为前端校验可以被绕过直接调用接口提交订单。所以我在后端createOrder接口里会重新查询每个商品的当前库存再和订单数量比较如果库存不足就直接返回“库存不足”的错误信息让前端回滚本地状态并提示用户。订单结算页的逻辑更繁琐涉及优惠计算。小零食客单价低往往用“满50减10”“第二件半价”这种促销。我把促销规则定义成一个JSON配置存到数据库后端下单时根据订单金额和商品组合计算优惠金额前端结算页则根据当前购物车临时模拟计算结果展示给用户。这里需要注意前后端计算逻辑的一致性避免前端展示的优惠和后端实际扣减不一致。我做法是结算页上的金额只做展示所有最终金额以后端接口返回为准前端拿到结果后直接渲染不自行“纠正”后端金额。防重复提交我用了两个简单手段前端点击“提交订单”按钮后立即把按钮禁用并在文案上加“处理中”后端在生成订单号时用户ID加时间戳再拼接随机串并在数据库里给order_no字段加唯一索引。这样即使请求意外重发第二次插入也会因为唯一键冲突而失败订单不会出现重复记录。3.4 小零食场景的营销功能实现除了常规商品发布我还加了几个贴合零食店铺的功能。第一组合套餐。零食买散客容易“选择困难”平台把热门小吃做成“办公室零食包”“追剧零食包”。实现上并不复杂在数据库增加套餐表package关联多个商品ID和数量下单时用户选择一个套餐后端展开成多个商品明细加入购物车。这个功能有效提升了客单价实施成本却不比普通商品高。第二满减活动。在活动表中配置满多少金额减多少元后端在创建订单时遍历当前所有活动的条件按最优优惠自动应用。如果多个活动互斥要明确规则我采用的是“只取优惠金额最大的一条”避免叠加带来的财务管理混乱。第三积分抵扣。用户下单完成后赚取积分下次购物时可以用积分抵扣一部分现金。我用一个简单的积分比例比如1积分抵0.01元。这个功能提升复购率的效果很明显而且实现成本不高。营销功能的代码核心是一个计算函数输入购物车商品列表和用户优惠券信息输出各优惠项和最终应付金额。这个函数不写在某个页面组件里而是抽成utils/calculator.js独立文件让前后端都可以复用同一套逻辑减少维护不同版本的折磨。4. 打包部署与上线避坑4.1 前端build与Node.js服务部署项目开发完剩下就是部署上线。前端部署相对简单在client目录执行npm run build生成dist静态目录。但如果用Vue Router的history模式直接把这个目录扔进Nginx还不够SPA刷新会404。因为前端路由是虚拟的刷新后客户端会向服务器请求真正的路径而服务器上并不存在这个物理文件。解决办法是在Nginx配置里加一个try_filesserver { listen 80; server_name shop.example.com; root /data/www/snacks-shop/dist; location / { try_files $uri $uri/ /index.html; } location /api { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }后端部署我用的是pm2。Node服务如果没有守护进程管理一崩就没人能管。pm2可以监控进程状态、自动重启、看日志对小项目足够用了。启动命令pm2 start src/app.js --name snacks-shop pm2 save如果用了环境变量建议在pm2的配置文件ecosystem.config.js里统一维护比如数据库地址、JWT密钥、端口等不要把敏感信息写在代码仓库里。部署完成后用curl或者浏览器打开域名确认API能正常返回JSON页面能正常渲染。4.2 商城上线的安全与性能措施安全问题在商城系统上尤其重要我不能只保证功能用户资金和隐私都要守住。我做的第一件事是改密码存储方式不能用明文。用户的注册密码我用bcrypt哈希后入库登录时用bcrypt.compare比对。第二件事是加JWT鉴权登录成功后签发token有效期内请求API时在Authorization头带上token后端中间件校验。第三件事是参数校验和数据清洗避免SQL注入和XSS攻击。在Express里我用的MySQL查询全部使用预编译占位符比如WHERE id ?而不是拼接字符串前端提交的商品名或地址文本在后端会做HTML标签过滤防止存储型XSS。性能方面商城首页的商品列表最容易成为瓶颈。我把热点商品和分类数据在Redis里做了缓存缓存过期时间设置成5分钟。同时前端商品图片全部压缩成webp格式列表页采用懒加载。Vue路由默认是懒加载的也就是首次打开不会把所有页面JS都拉下来而是访问到对应路由才加载组件这对首屏速度帮助很大。配置方式不需要多花哨在路由定义里把component写成import函数就行。5. 常见问题排查与实操心得5.1 开发阶段的高频报错npm安装慢、ESLint报错、端口被占用先说说npm安装慢。完美解决方案就是切换镜像源上文已经写过了。但如果你已经换了镜像还是慢有可能是依赖包缓存损坏执行npm cache verify清理缓存。还有种情况是PowerShell脚本权限问题导致的安装中断处理方法同上面的Set-ExecutionPolicy。ESLint报错是每个Vue项目开发者的噩梦。刚创建的项目默认开启严格规则比如单引号、末尾不要分号、template缩进等等。我和团队的习惯跟默认规则不一样结果每次commit都过不了。解决办法是项目根目录的.eslintrc.js文件调整规则把不必要的检查关闭比如module.exports { rules: { semi: [error, always], quotes: [error, double] } };但更建议不关而是使用Prettier统一风格配合eslint-plugin-prettier插件让格式化工具和Lint规则协同工作。这个小改动能让团队协作省下大量拌嘴时间。端口被占用也是高频问题。启动开发服务器提示listen EADDRINUSE说明8080被其他程序占了。解决方式是先用命令行查看端口PIDnetstat -ano | findstr :8080找到最后一列的PID再用taskkill /PID 该PID /F强制结束进程。不过每次都去命令行操作太麻烦我直接在package.json的启动命令里改端口npm run serve -- --port 8081或者配置vue.config.js里的devServer.port属性。后端同理在代码里读取环境变量PORT比如3000被占就临时设置PORT3001。5.2 部署后的棘手问题刷新404、跨域、中文乱码刷新404是history模式最典型的坑解决方案已经写在Nginx配置里了。如果配了try_files还是404记得检查Nginx配置是否生效执行nginx -t测试语法再reload。跨域问题发生在开发环境更多。前端在8080端口后端在3000端口直接fetch会报CORS。开发环境最简单的方式是在vue.config.js里配置devServer.proxy把所有/api请求转发到http://localhost:3000。生产环境因为有Nginx反向代理只要配置好location /api的proxy_pass就不会出现跨域。中文乱码主要分布在两个场景页面显示乱码和数据库存储乱码。页面乱码一般是HTML的charset没有设置成utf-8或者接口返回的Content-Type里没有charsetutf-8数据库存储乱码是表本身的编码不是utf8mb4或者连接字符串里没指定charset。我的做法是建库建表时都指定CREATE DATABASE snacks DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;连接MySQL时连接参数也加上charsetutf8mb4这样中文字符、特殊符号、emoji表情都不会出问题。5.3 个人经验这套系统后续还能怎么扩展做完这套系统我心里其实还有不少规划。后台目前是Web版好友希望我后续出一个简单的老板端小程序能够扫码看当天营业额、低库存预警就行。我调研了一下现有Vue代码虽然不能直接用于小程序但API可以完全复用只需要做适配层。第二个想优化的地方是商品推荐。现在只是按销量排序没有个性化以后可以在用户浏览、下单记录基础上做一个简单的协同过滤推荐不过这个对数据量有一定要求先把数据仓库做好再说。另外一个我觉得很有价值的改造方向是进销存一体化。小超市不光卖零食还要管进货、供应商、效期零食保质期短临期商品处理特别重要。商城现在只管理了在线库存和线下库存没打通。后续我打算增加批次管理字段在商品表里记录生产日期和保质期后台新增临期库存提醒这样能大大减少浪费。这些规划看起来都是一个一个功能点但对小商家来说每个细节都是省下来的钱。最后再分享一点给正在写同类项目的朋友别一开始就想着把系统做得大而全先跑通最小闭环商品能上架、用户能下单、店主能发货就足够支撑运营了。后面所有的新功能都是从这个闭环上长出来的枝叶。开发前先把数据库表结构设计严密一些比写多少代码都管用很多“返工”都起因于表结构没想清楚。希望我的这些记录能帮你绕开我已经踩过的坑。
返回列表