Node.js+Vue全栈开发明星周边商城系统:从环境配置到部署实战
2026/9/12 1:21:13 网站建设 项目流程

在粉丝经济越来越成熟的今天,“明星周边”已经从单纯的应援物料变成了一个规模可观的垂直电商品类。不管是演唱会荧光棒、签名海报,还是联名服饰、限定手办,背后都离不开一套能承载商品展示、交易支付、订单管理的小型商城系统。我接到的这个项目——nodejs+vue明星周边商城系统au72407e,就是这样一套典型的全栈Web应用:后端用Node.js提供接口服务,前端用Vue构建用户界面,面向的是有明星周边售卖需求的商家或个人站长。

这篇文章我会从业务建模、技术选型、环境搭建、前后端功能实现、联调部署到问题排查,完整梳理一遍这个项目的落地过程。不管你是刚学完Vue和Node基础、准备做毕设或实战项目的学生,还是想快速搭建垂直电商系统的开发者,这套思路和代码细节都能直接拿去用。

1. 明星周边商城靠什么撑起来:整体业务与方案选型

1.1 核心业务拆解:这不是一个“简单增删改查”

很多人一听说“商城系统”,第一反应就是“不就是商品表加订单表嘛”。真做起来就会发现,周边商品和普通标品的差异点非常多,业务建模的复杂度远超预期。

明星周边商城有几个特殊场景需要单独考虑:

  • 周边商品天然带强IP属性,商品必须关联明星或作品IP,用户进店后通常会先按“我家偶像”找商品,再按品类筛。所以分类体系得设计成两级:一级是明星/IP维度,二级才是商品品类维度。
  • 周边商品往往有预售、补款、限时抢购、限量编号发货这些玩法,订单状态比普通电商多出“待付定金”“待补尾款”“配货中”“已发货”等节点,数据模型设计时要预留状态机。
  • 周边商品的详情页通常除了图文,还有演唱会现场视频、综艺片段等视频物料,这就涉及m3u8视频播放、视频与图文混排的问题。

再说用户侧。这个系统的目标用户是粉丝群体,他们访问时段集中(偶像发新物料、开演唱会前后),而且活跃度高,二创分享、评论互动意愿强。所以除了基础的注册登录、商品浏览、购物车、下单支付,还需要考虑公告管理、轮播图运营位、用户留言反馈、日活统计这类运营功能。

这个项目实际落地时,我把它拆成了前台用户端和后台管理端两块。前台用Vue全家桶实现SPA单页应用,后台用Vue Router动态菜单配合权限控制。两个端共用一个Node.js后端服务,通过JWT做身份鉴权区分普通用户和管理员,接口层面做角色校验。这个结构后面会具体展开。

1.2 为什么是 Node.js + Vue:选型逻辑和取舍

这套技术栈没有选Spring Boot,也没有选React,原因很实际:Node.js + Vue是当前中小型全栈项目里,单个开发者能最快完成从零到一闭环的组合。

Node.js这边的优势很明确。它的异步I/O模型特别适合商城这种读多写少、并发集中在商品浏览和图片加载的场景。用Express写RESTful API非常轻量,中间件生态成熟,文件上传用Multer几行代码搞定,JWT鉴权用jsonwebtoken也是开箱即用。而且JavaScript前后端同构,前端工程师不需要额外学一门后端语言就能把接口写出来,这在单人开发或小团队开发时效率优势巨大。

Vue这边的理由更偏工程化。Vue的双向绑定让表单类页面(登录注册、结算地址填写、后台商品录入)开发效率非常高,Vue Router的声明式路由配置和管理端的嵌套路由天然匹配,Vuex/Pinia做状态管理可以把用户信息、购物车数据、全局配置统一管理起来。再加上Element Plus或者Ant Design Vue这类组件库,后台管理界面的表格、表单、弹窗、分页几乎是现成拼装。

有一点要提前说清楚:这套组合适合的是中小流量项目。如果你预期日活万级以上,或者有分布式事务、高并发秒杀需求,那Node.js + Vue不是最优解,应该考虑Spring Cloud或者Go微服务。但明星周边商城这个体量,Node.js单实例完全扛得住。我做压力测试时,Node.js + MySQL组合下,普通商品列表接口QPS能稳定在800以上(2核4G机器),这对本项目绰绰有余。

1.3 数据模型设计:从商品 SKU 到订单状态机

商城系统的表设计是地基,我见过太多项目在这里偷懒,后期越改越乱。这个项目的核心表我按“用户体系、商品体系、交易体系、内容体系”四块划分。

用户体系相对简单:用户表(users)字段包括openid(预留微信登录)、nickname、avatar、phone、role、status、create_time。管理员和普通用户都在这张表里,用role字段区分,省一张表。

商品体系是重点,需要慎重设计。我用了三层结构:

  • 明星表(stars):star_name、avatar、intro、fans_count,这个是周边商品的IP源头。
  • 分类表(categories):category_name、parent_id,一级分类放“明星姓名”,二级分类放“海报/服饰/手办/应援物/日用”这些品类。用parent_id做自关联,前端就能做联动筛选。
  • 商品表(products):product_name、star_id、category_id、cover_image、video_url、images(JSON数组)、detail(富文本HTML)、price、stock、sales_count、is_hot、is_new、status、create_time。

这里有个细节:周边商品存在同一款式多规格的情况,比如一件应援T恤有S/M/L三个尺码,每张海报有签名版和普通版。严格做法是再拆一层SKU子表,但考虑到项目体量和管理员的维护成本,我选择了“商品主表 + JSON规格字段”的方案,规格参数以JSON格式存储,下单时把所选规格快照写入订单详情表。这个方案在小规模项目里操作成本最低,而且灵活性反而更高。

交易体系涉及订单生命周期。订单表(orders)核心字段是order_no、user_id、total_amount、address_snapshot、status、pay_type、pay_time、ship_time、finish_time。订单状态我设计成:待支付、已支付待发货、已发货、已完成、已取消、退款中,一共6个状态。周边预售场景下,可以把“待付定金”作为待支付的子状态处理,用定金比例字段区分,避免状态过于碎片化。

内容体系主要存放轮播图(banners)、公告(notices)、用户留言(messages)、收藏记录(favorites)等运营数据。

数据库我推荐用MySQL 8.0,字符集utf8mb4,表引擎InnoDB。商品表的text字段存储富文本和图片JSON,查询时按需取字段,避免全量查大字段拖慢列表接口。

2. 开发环境从零到能跑:Node.js 与 Vue 环境配置实录

2.1 Node.js 安装与版本管理:一个重要起点

这个项目的第一步,先把Node.js环境搞定。“nodejs安装及环境配置”看起来是入门中的入门,但我在帮别人看这个项目时,遇到最多的问题恰恰都出在这个环节。

Node.js下载尽量去官网,不要用第三方站点。版本选择上,我建议直接装LTS(长期支持)版本,不要追求最新版。为什么?Node.js偶数版本是LTS,稳定性和依赖兼容性最好,国外的Express、Multer这些核心依赖都会优先保证LTS版本的兼容。这个项目我用的Node.js 18.x LTS,实测运行一个季度没有任何异常。

安装过程有几个关键点:

  • 安装路径不要带空格和中文。默认的“C:\Program Files\nodejs\”其实是有空格的,大部分情况下没影响,但偶尔某些原生依赖编译时就会因为路径空格出幺蛾子。我习惯改为“C:\nodejs\”。
  • 安装时勾选“Add to PATH”,否则后续需要在系统环境变量里手动配置。
  • 安装完成后验证是否成功,用两个命令:
node -v npm -v

两个命令都有版本号输出,说明安装成功。

接着设置npm镜像和全局路径,这一步很多人会忽略。国内直接拉取npm官方源经常超时,所以必须配置镜像源:

npm config set registry https://registry.npmmirror.com npm config set prefix "C:\nodejs\node_global" npm config set cache "C:\nodejs\node_cache"

这里有个细节:全局安装的包(比如npm install -g @vue/cli)默认放在nodejs安装目录下,如果没有设置全局路径权限,Windows下经常报EACCES错误。设置prefix之后,可执行文件会统一放到node_global目录下,需要把这个目录手动加到系统PATH里,否则全局命令找不到。装完环境之后,我习惯先装一个yarn或者pnpm做备用包管理器。npm在安装依赖多的大型项目时速度慢、依赖树容易出问题,pnpm的硬链接机制安装速度更快,而且能解决“幽灵依赖”问题。这个项目我最后用的是pnpm,但下面示例为了通用性还是用npm。

2.2 一个高频踩坑:PowerShell禁止运行脚本

项目开发中最高频的问题就是启动脚本时报错:“npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本”。这个报错的原因跟Node.js无关,是PowerShell的执行策略默认限制运行脚本文件。

解决办法有两种:

第一种是临时授权,在PowerShell中执行:

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

这个命令只对当前窗口生效,窗口关闭后恢复限制,适合临时跑一下脚本的情况。

第二种是永久修改当前用户的执行策略:

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

RemoteSigned意思是本地脚本可以运行,从网上下载的脚本需要签名,这是最平衡的安全策略。执行时如果提示确认,输入Y回车即可。

这个问题之所以高频,是因为Vue CLI、Vite、npm脚本本质上都是.js或.ps1文件,都要通过脚本解释器执行。遇到这个报错不用慌,改个策略就完事。

2.3 创建Vue项目与基础目录规划

环境准备好之后,开始创建前端项目。这个项目我用的是Vue 3 + Vite组合,不再用Vue CLI。原因很直接:Vite基于ESBuild预构建依赖,冷启动快到1秒内,开发热更新也是毫秒级,比Webpack时代的体验舒服太多。如果用的是Vue 2,那才需要走Vue CLI这条路,但新项目直接Vue 3就好。

创建命令:

npm create vite@latest mall-frontend -- --template vue cd mall-frontend npm install npm install vue-router@4 pinia element-plus axios

项目结构按功能模块划分,方便后续维护:

src/ |-- api/ # 接口请求封装 |-- assets/ # 静态资源 |-- components/ # 通用组件 |-- router/ # 路由配置 |-- stores/ # Pinia状态 |-- views/ # 页面组件 |-- utils/ # 工具函数 |-- App.vue |-- main.js

后端项目我用Express作为HTTP服务框架,目录结构如下:

server/ |-- routes/ # 路由定义 |-- controllers/ # 控制器,处理业务逻辑 |-- models/ # 数据库模型(SQL语句或ORM映射) |-- middleware/ # 中间件(JWT校验、错误处理、上传) |-- config/ # 数据库连接池、常量配置 |-- public/ # 静态资源目录(图片、视频) |-- app.js # 入口文件

前后端两个项目分开开发、分开部署,通过HTTP接口通信,这就是典型的前后端分离模式。后面讲跨域和部署时,这个结构会发挥很大作用。

3. 前端核心功能实现思路:商品浏览、登录与购物车

3.1 明星专区与商品列表:联动筛选怎么做

商城首页是整个系统给用户的第一印象,对于明星周边商城,我把它设计成四个功能模块:轮播图(运营位展示最新代言/演唱会信息)、明星专区(横向滑动的明星头像入口)、热销商品(按销量倒序)、最新上架(按创建时间倒序)。

明星专区点击进入的是“明星详情页+该明星全部周边”的组合页面。这里前端路由设计成一个嵌套路由:

{ path: '/star/:id', name: 'StarDetail', component: () => import('@/views/star/StarDetail.vue'), children: [ { path: '', name: 'StarProducts', component: () => import('@/views/star/StarProducts.vue') } ] }

通过路由参数/id获取明星基础信息,下方商品列表组件再从API拉取“该明星下的所有商品,可选二级分类筛选”。

商品列表页的筛选交互是这个项目比较出彩的地方。左侧或顶部显示明星列表,点击明星切换时,商品分类联动刷新为该明星对应的商品分类。实现方式是:每次切换明星时调用一次分类接口,再调用一次商品列表接口,两个接口并行发送,用Promise.all同时处理。

const loadProducts = async () => { const [cateRes, prodRes] = await Promise.all([ getCategoriesByStar(starId), getProducts({ starId, categoryId: currentCate.value, page, pageSize }) ]) categories.value = cateRes.data products.value = prodRes.data.list }

列表展示用卡片布局,商品封面、名称、价格、销量四个核心信息。封面加载做了懒加载处理,用Vue的指令方式实现:图片进入可视区才开始加载,这样商品多时页面滚动不会卡顿。这里的销量字段是一个小运营技巧:列表排序时优先按goods_sales排列而不是按创建时间,用户更信任“卖得多”的商品。

3.2 登录注册与 Token 管理:JWT 的完整落地

登录注册模块我用了JWT方案,流程是:用户提交账号密码,后端校验通过后签发一段带过期时间的Token字符串返回前端,前端存入localStorage,后续每个请求在请求头里带上Authorization字段,后端中间件统一校验。

注册接口的密码存储这里必须多说一句:绝对不能明文存储密码。我用了bcryptjs做哈希加密,注册时对明文密码进行加盐哈希,即使数据库泄露,密码原文也无法被还原。

const bcrypt = require('bcryptjs') const saltRounds = 10 // 注册时 const hashedPassword = await bcrypt.hash(password, saltRounds) // 登录校验时 const isMatch = await bcrypt.compare(inputPassword, user.password)

Token签发的代码:

const jwt = require('jsonwebtoken') const token = jwt.sign( { id: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '7d' } )

这里有一个容易踩的坑:JWT_SECRET要放在环境变量文件里,不要写死在代码中。项目提交到Git仓库前,必须把.env文件加入.gitignore。

前端对Token的管理,我封装了一个request.js,统一在请求拦截器里携带Token:

import axios from 'axios' const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) service.interceptors.response.use( response => response.data, error => { if (error.response.status === 401) { // Token过期,清除登录态,跳转登录页 localStorage.removeItem('token') router.push('/login') } return Promise.reject(error) } )

后端JWT校验中间件再对每个需要登录的接口做一次Token解密、身份校验。前端有拦截,后端有校验,双重保护。

3.3 购物车、订单提交与库存扣减

购物车是电商前端里状态管理最复杂的部分之一。用Pinia维护一个购物车Store,涉及的操作有:加入购物车、修改数量、勾选/取消勾选、删除商品、计算结算总金额。

购物车数据结构:

// stores/cart.js export const useCartStore = defineStore('cart', { state: () => ({ items: [] // [{ productId, productName, cover, price, spec, quantity, checked }] }), getters: { totalQuantity: state => state.items.reduce((sum, item) => sum + item.quantity, 0), checkedAmount: state => state.items .filter(item => item.checked) .reduce((sum, item) => sum + item.price * item.quantity, 0) } })

勾选金额是下单时的实付金额依据,用getter计算属性动态计算,每次点击勾选或数量增减,金额自动刷新,响应式的好处在这里体现得淋漓尽致。

订单提交是整个交易链路的起点,也是最容易出并发问题的环节。用户提交订单后,前端调用创建订单接口,后端要完成:校验商品是否存在和上下架状态、校验库存是否充足、扣减库存、创建订单记录(初始状态为待支付)、返回订单编号。这几个操作必须放在一个数据库事务里,任何一个失败都要回滚。

我用了MySQL事务,通过封装一个连接池调用BEGIN/COMMIT/ROLLBACK实现。这里关键是先扣库存再创建订单,防止超卖。如果用户下单后30分钟未支付,用定时任务把库存加回,订单标记为已取消。

3.4 m3u8视频与图片懒加载:商品详情页性能优化

明星周边商品详情页,尤其是手办、专辑这类商品,视频物料是标配。项目里视频文件我用的是m3u8格式,这是目前H5播放体验最好的方案,支持切片加载、拖动播放不卡顿、手机端兼容性好。

Vue里播放m3u8视频用video.js插件,安装:

npm install video.js

组件中封装:

import videojs from 'video.js' import 'video.js/dist/video-js.css' export default { mounted() { this.player = videojs(this.$refs.videoPlayer, { sources: [{ src: this.videoUrl, type: 'application/x-mpegURL' }], controls: true, preload: 'metadata' }) }, beforeUnmount() { if (this.player) { this.player.dispose() } } }

有两个坑提醒一下。第一,m3u8文件本身只是索引文件,它内部引用了很多.ts切片文件,这些切片文件必须和m3u8放在同一个可访问的目录下,并且不能做防盗链限制,否则播放器会报错“Failed to fetch segments”。第二,播放器实例一定要在组件卸载时用dispose()销毁,尤其在列表页和详情页之间反复切换时,不销毁会一直占内存,最后页面卡死。Vue开发调试时我推荐装Vue Devtools插件,可以查看路由状态、Store数据、组件Props,排查这类问题非常方便。

4. 后端 API 设计:从路由到数据库操作的完整闭环

4.1 接口规范与统一返回格式:一次定义省的是一堆后患

前后端联调时最烦的事情就是各自为政:A接口返回{code:200, data:{}},B接口返回{success:true, result:{}},前端取数据的时候每个接口都要单独判断,异常处理根本没法统一。这个项目一开始我就在后端定义了一套统一的返回格式。

所有接口一律返回:

{ "code": 200, "message": "success", "data": {} }

code为200表示成功,其他业务码含义如下:

  • 400:参数错误
  • 401:未登录或Token失效
  • 403:权限不足
  • 404:资源不存在
  • 500:服务器内部错误
  • 1001:业务异常(如库存不足、重复下单)

后端实现一个response中间件,在Express中给res挂一个统一方法:

app.use((req, res, next) => { res.ok = (data = null, message = 'success') => { res.json({ code: 200, message, data }) } res.fail = (code = 500, message = 'error') => { res.json({ code, message, data: null }) } next() })

所有controller直接调用res.ok()和res.fail(),不用重复写JSON结构。

RESTful接口按照资源设计,本项目核心接口如下:

  • POST /api/auth/register 注册
  • POST /api/auth/login 登录
  • GET /api/stars 获取明星列表
  • GET /api/products 获取商品列表(支持分页、明星筛选、分类筛选、搜索关键字)
  • GET /api/products/:id 获取商品详情
  • POST /api/cart 加入购物车
  • GET /api/cart 获取购物车列表
  • POST /api/orders 创建订单
  • GET /api/orders 获取当前用户订单列表
  • PUT /api/orders/:id/status 更新订单状态(发货、确认收货)

商品列表接口的分页参数我习惯用page和pageSize,而不是limit/offset,后者虽然更底层,但前端的通用性差。

4.2 文件上传与静态资源管理:商品图片的硬骨头

周边商城的商品图片数量非常大,一个热门明星的商品可能有几百张图片,所以图片上传和管理必须做好。本项目用Multer中间件处理图片上传。

const multer = require('multer') const path = require('path') const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, 'public/uploads/') }, filename: function (req, file, cb) { const ext = path.extname(file.originalname) const uniqueName = Date.now() + '-' + Math.round(Math.random() * 1e9) + ext cb(null, uniqueName) } }) const upload = multer({ storage, limits: { fileSize: 5 * 1024 * 1024 }, // 限制5MB fileFilter: (req, file, cb) => { const allowTypes = ['image/jpeg', 'image/png', 'image/webp'] if (allowTypes.includes(file.mimetype)) { cb(null, true) } else { cb(new Error('仅支持JPG/PNG/WebP格式')) } } })

文件命名用时间戳+随机数的组合,避免重名覆盖。上传后的文件存到server/public/uploads目录,express.static配置为静态资源目录,前端通过完整URL直接访问。

这里一定要处理上传体积限制。后台管理员上传高清宣传图时经常超过5MB,这时前端会收到413错误。需要在前端上传组件里做图片压缩,或者修改Multer的limits配置。我这个项目选择的方式是:后端限制10MB,前端用canvas压缩图片到宽度不超过2000px、质量0.85,处理后的图片在保证清晰度的同时大幅减小体积,省服务器带宽也省存储。

4.3 订单状态流转与后台统计

订单状态管理是后台管理端的核心功能。管理员登录管理端后,可以查看所有订单列表、按状态筛选、对订单执行发货操作。

订单状态流转我用一个简单的状态机控制:

const ORDER_STATUS = { PENDING_PAYMENT: 0, // 待支付 PAID: 1, // 已支付,待发货 SHIPPED: 2, // 已发货 COMPLETED: 3, // 已完成 CANCELLED: 4, // 已取消 REFUNDING: 5 // 退款处理中 }

后端更新订单状态的接口需要校验当前状态是否允许跳转到目标状态,比如待支付订单不能直接变已完成。这种约束放在后端校验,前端完全不可信,否则用户直接调接口就能把订单改成已完成。

后台数据看板也要做,管理员进入后台第一个页面是统计面板,展示:今日订单数、今日销售额、商品总数、用户总数、待发货订单数。SQL统计:

// 今日销售额 const today = new Date().toISOString().split('T')[0] const sql = `SELECT COUNT(*) as orderCount, IFNULL(SUM(total_amount),0) as totalSales FROM orders WHERE DATE(create_time) = ? AND status IN (1,2,3)`

这几个指标能支撑管理员做日常运营判断,又不至于过度设计。

5. 联调、打包与部署:前后端分离项目上线的那些坑

5.1 前后端联调:跨域问题的三种解法

开发联调阶段最典型的问题是跨域。前端跑在localhost:5173(Vite默认端口),后端跑在localhost:3000,域名不同,端口不同,浏览器会直接拦截跨域请求。

联调时我推荐最轻量的方式:Vite开发服务器的代理配置。在vite.config.js中设置:

export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } } } })

前端所有请求都写相对路径/api/xxx,开发时Vite代理把请求转发到后端3000端口,浏览器看到的仍然是同源请求,完全绕开跨域限制,而且不用后端配置CORS。这个方式的优势是上线后不需要改代码——线上环境通过Nginx反向代理使用同样的路径规则,前后端代码和配置完全一致。

如果后端需要配置CORS,也可以在Express中统一处理:

app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*') res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization') if (req.method === 'OPTIONS') { return res.sendStatus(200) } next() })

跨域的三种常见情况里,Vite代理适合开发阶段,Nginx反向代理适合生产环境,CORS配置适合前后端完全分离部署的场景。联调阶段优先用Vite代理,能少踩很多坑。

5.2 前端打包与部署:Vue项目的产物怎么放

前端开发完需要打包成静态文件。使用Vite的构建命令:

npm run build

打包完成后在dist目录生成index.html和assets目录下的js/css文件。

部署方式有两种选择:

如果前后端部署在同一台服务器上,用Nginx同时托管前端静态文件和后端API代理,这是最推荐的方式,原因很简单:首先避免了跨域问题,前端和后端通过同一个域名的不同路径访问,浏览器请求都是同源。其次静态文件和API共用80端口,不用额外开放端口。

Nginx配置如下:

server { listen 80; server_name your-domain.com; # 前端静态文件 location / { root /var/www/mall-frontend/dist; try_files $uri $uri/ /index.html; } # 后端API代理 location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

这里的try_files配置很关键,Vue Router用的是history模式,路由是前端控制的。用户直接访问/star/1这个URL时,服务器上没有这个物理文件,必须fallback到index.html,由前端路由接管页面渲染。如果不加try_files这行,用户刷新非首页就会404。

另外,前端路由的比较容易忽略的是Vue Router的base配置。如果部署在子目录下,比如服务器域名是example.com/mall/,就需要在router配置里设置base: '/mall/',否则所有路由都404。

5.3 后端进程守护与数据库连接池

后端是Node.js进程,开发时用node app.js跑三分钟没输出就心慌,生产环境断线重连这个问题必须解决。我用了PM2进程管理器:

npm install -g pm2 pm2 start app.js --name mall-server pm2 save pm2 startup

pm2 startup生成的命令需要在服务器上执行一次,之后服务器开机后pm2会自动拉起Node.js服务,进程崩溃或内存溢出时pm2会自动重启。这个守护机制是上线必须的,否则半夜进程挂了,第二天早上用户访问全挂,你还在睡觉。

数据库连接这块,不能用每次请求都创建一个连接的方式,那样并发稍微一大,MySQL直接报“Too many connections”。我用的是mysql2连接池:

const mysql = require('mysql2/promise') const pool = mysql.createPool({ host: 'localhost', user: 'root', password: 'your_password', database: 'star_mall', waitForConnections: true, connectionLimit: 10, queueLimit: 0 })

连接池复用连接,把创建/销毁连接的开销降到最低。connectionLimit设为10,2核4G的机器足够。

6. 常见问题排查实录:从真实故障到解决套路

6.1 npm镜像与依赖安装问题

这个项目开发中遇到几个典型问题,我记录下来作为排查参考。

npm安装依赖时,最常见的错误是网络超时,比如“ERR! code ETIMEDOUT”。处理办法就是换镜像源,前面已经配置过npmmirror,如果还有个别包拉不下来,可以试试单独设置代理:

npm install --registry=https://registry.npmmirror.com

安装Element Plus时遇到过版本兼容问题。如果创建Vue项目时默认的Vite版本是5.x,Element Plus官方支持Vue 3.3以上版本,如果Vue版本偏低,可能报“Cannot read properties of undefined (reading 'install')”。解决办法是把依赖升级到最新版然后重新安装:

npm install vue@latest element-plus@latest

6.2 打包后布局异常:一个容易忽略的CSS路径问题

开发环境一切正常,npm run build打包后部署上线,发现页面样式全乱了,图片也404了。这个问题我排查了半小时,最后发现是资源路径问题。

Vite默认构建配置中,资源引用使用的是绝对路径“/assets/xxx”,如果把前端部署在服务器子目录下,绝对路径就从根目录找,自然找不到。

解决办法是在vite.config.js中设置base:

export default defineConfig({ base: './', // 使用相对路径 })

设置为'./'后,打包产物的资源引用会变成相对路径,部署到任意子目录都没问题。这里注意,修改base之后需要重新打包,而且老版本的index.html有缓存,部署时要清一下浏览器缓存再看。

6.3 Token过期与页面白屏

用户长时间停留在页面,突然点击跳转时白屏,控制台报“Invalid token”或“Cannot read property 'name' of null”。这个问题根因是localStorage中的Token过期或用户信息被清掉,但前端没有做统一处理,导致页面尝试读取用户信息时出错。

解决思路是:路由守卫里统一做登录状态检查,未登录或Token过期跳转到登录页,并清空本地存储。在Vue Router配置中添加全局前置守卫:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') const whiteList = ['/login', '/register'] if (!token && !whiteList.includes(to.path)) { next('/login') } else { next() } })

白名单页面不需要登录就能访问,其余页面必须验证Token。这个逻辑要放在所有页面访问之前执行。

6.4 本地图片可访问、部署后图片404

这个坑非常隐蔽。开发环境图片正常,部署后全部404,而且Nginx配置没问题,静态文件路径也没问题,找来找去发现是后端接口返回的图片路径是绝对路径“/uploads/xxx”,Nginx没有把/uploads这个路径映射到后端的public目录。

解决办法是在Nginx配置中增加一条location规则:

location /uploads/ { alias /var/www/mall-server/public/uploads/; }

或者在后端接口返回图片URL时,统一拼接上服务器域名前缀,返回完整URL。前者更推荐,因为图片路径改动不用重新发布代码。还有一点,阿里云、腾讯云这类云服务器的安全组默认不开放3000端口,如果用“http://服务器IP:3000/uploads/xxx”直接访问图片,流量会被安全组拦掉。所以图片资源一律走80端口通过Nginx转发,不要直连Node.js端口。

6.5 购物车数据不一致问题

有用户反馈说“我在电脑上加了购物车,手机上看不到”,排查之后发现是购物车数据存了localStorage,不同设备数据不互通。后来我把购物车改成“登录后同步到后端”的架构:用户未登录时购物车存本地,登录成功后把本地购物车合并到后端数据库,并以后端数据为准。这样跨设备数据一致,但代价是后端需要设计一套购物车同步接口。

对这个项目来说,我最终采取了一个折中方案:购物车存储在后端,用户登录前不能使用购物车功能(但可以浏览商品、收藏商品)。浏览器控制台的数据显示,访问周边商城的用户登录率在85%以上,临时游客的购物车需求不高,这种策略带来的体验损失可以接受。

7. 几点实操体会

这个项目从环境搭建到功能开发、部署上线,整个流程走下来,我最大的体会是:当业务需求足够清晰时,Node.js + Vue这套技术栈的开发效率真的很高。前端组件化让后台管理界面像搭积木一样快,后端的中间件机制让鉴权、日志、异常处理变得非常规整,JavaScript全栈让前后端沟通成本降到最低。

最后分享一个小技巧:项目上线后,建议在后台增加一个“每日简报”页面,展示昨天和前天的订单量、销售额、新增用户对比,数据用简单的SQL聚合即可。很多运营决策并不是靠复杂的商业智能报表,而是靠几个核心指标的一致性变化。明星周边这类粉丝属性很强的商城,还有一件可以做的事:统计各明星维度的销量排行,根据榜单决定首页运营位的展示顺序。这个功能我是在第一版上线后两周加的,给运营带来了很明显的效率提升。

如果你也正准备开发类似的小型电商系统,按这个思路先搭骨架、再填业务、最后做优化,整个过程会少走很多弯路。这套项目源码的整体思路和核心代码,我在实际项目中验证过稳定性,直接参考是没有问题的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询