这个项目标题拿到手里,第一反应是"又一个商城系统",但真把它拆开看,"电子外设"这四个字才是关键。我前后做过几个商城类的项目,从图书到生鲜都碰过,电子外设这个品类在商品建模、订单流程、库存管理上都有它独特的坑。趁这次重新梳理vue加nodejs这套技术组合下的实现方案,我把从架构设计到部署上线的完整思路都沉淀出来,希望能给正在做类似项目的读者一些参考。
1. 项目怎么定位:为什么是vue加nodejs的组合
先说说这套技术选型的思路。前端用vue,后端用nodejs,这在国内的中小型电商项目里是非常常见的一套组合,原因很实际。
1.1 前后端分离给这个项目带来了什么
电子外设商城这个项目,业务边界其实很清楚:用户端要浏览商品、加购物车、下单支付,管理端要维护商品、处理订单、管理库存。这两块界面差异很大,用户端追求浏览体验的流畅,管理端追求信息密度和操作效率。如果混在一个项目里做,代码会越来越臃肿,开发的时候两边互相干扰,上线之后改一处动不动就要整体回归。
前后端分离之后,前端团队可以完全专注于界面交互,后端只提供API接口。我自己实践下来最直观的感受是,调试效率提高了很多。以前用服务端模板渲染,改个样式可能还要重启服务,现在前端跑在webpack的开发服务器上,后端跑在node的调试进程里,两边完全独立,前端mock数据能先行开发,后端用接口测试工具也能并行推进。
另外一点很实际,电子外设这个品类的商品详情页有不少交互细节,像参数对比、规格切换、图片预览,这些用vue的响应式机制来实现非常顺手。组件化的开发方式也让商品卡片、购物车列表这些复用度很高的模块可以到处使用。
1.2 nodejs在后端承担了什么角色
nodejs在这个项目里主要负责业务API层,具体来说是一套基于express的RESTful接口服务。选node而不是java或者php,一个原因是团队的技术栈统一,前端同学也能看懂后端代码,出问题的时候前后端可以互相排查;另一个原因是node在处理高并发的I/O密集型任务上表现不错,商城的核心操作比如商品查询、订单读写,大部分都是数据库I/O操作,node的事件驱动模型很适合这种场景。
当然node的劣势也很明显,CPU密集型任务不行,所以项目里如果有复杂的图片处理、报表生成这些操作,我一般会拆出来单独做成服务,而不是让node主进程硬扛。还有一点需要注意,node进程崩溃之后整个服务就不可用了,所以生产环境必须配合pm2做进程守护。
1.3 项目整体的目录结构设计
project-root/ ├── admin-web/ # 管理后台前端(vue) ├── client-web/ # 用户端前端(vue) ├── server/ # node后端服务 │ ├── controllers/ # 控制器层,处理请求逻辑 │ ├── models/ # 数据模型层 │ ├── routes/ # 路由定义 │ ├── services/ # 业务逻辑层 │ ├── middlewares/ # 中间件(鉴权、日志、错误处理) │ ├── utils/ # 工具函数 │ └── app.js # 服务入口 └── database/ # SQL脚本和初始化数据用户端和管理后台拆成两个独立前端项目,后端统一提供API。这是我反复调整之后比较满意的结构,管理端和用户端的路由、组件、样式都可以独立维护,不会出现互相污染的问题。
2. 电子外设的商品建模:这不仅是"放一个商品表"的事
商城系统的核心是商品,但电子外设的商品建模比普通商品要复杂不少。这里说的复杂不是技术上的复杂,而是业务上的各种特殊需求。我做这个项目的时候,在数据模型设计上花的时间最多。
2.1 电子外设品类的字段特点
如果只是做一个卖衣服的商城,商品表里放个标题、价格、库存、图片就差不多了,最多加个尺码颜色。但电子外设不一样,它的参数维度特别丰富。就拿鼠标来说,有传感器类型、DPI范围、按键数量、连接方式、续航时间、重量、尺寸这么多属性。
这些参数如果都放在商品主表里,字段会爆炸,而且不是所有商品都有同一套参数,键盘和鼠标的参数就完全不同。所以我采用了两层结构:公共字段放在商品主表,规格参数通过一个通用的属性表来存储。
商品主表存的是所有商品都会有的字段,比如名称、价格、分类、品牌、上下架状态、销量、库存总量。
规格参数表设计如下:
CREATE TABLE product_attributes ( id INT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, attr_name VARCHAR(50) NOT NULL, attr_value VARCHAR(255) NOT NULL, sort_order INT DEFAULT 0, KEY idx_product (product_id) );这样设计的好处是灵活,以后商城要加游戏耳机、智能手表这些新品类,不需要改表结构,直接在后台配置新的属性名就行。查询的时候,商品详情页需要展示参数列表,一条SQL就能取回来。
2.2 SKU和规格组合的处理
电子外设商品经常有多规格的需求,比如说一款机械键盘有红轴、茶轴、青轴三种轴体,每种轴体对应不同的SKU和价格,库存也是独立的。这个用电商的SKU模型来处理,建模思路是"规格项 + 规格值 + SKU"三层结构。
规格模板表定义这个商品有哪些规格维度,商品规格值表存放具体规格值,SKU表则是规格值的笛卡尔积组合结果,每个SKU拥有自己的价格、库存和编号。我实际操作的时候一般会在SPU层面记录规格模板,然后通过后端接口把笛卡尔积组合好,生成SKU列表,管理员只需要填写每个SKU的价格和库存即可。
这里面有个容易出错的点,有些商品虽然只有一个规格,但可能因为颜色不同分成多个SKU。所以建立统一的规格处理逻辑之后,前端的规格选择器、后端的购物车、订单详情用的都是同一套数据格式,避免各搞一套出现数据不一致。
2.3 品牌和分类的层级设计
电子外设的品牌效应很明显,用户在买鼠标键盘的时候往往会指定品牌,搜索的时候也习惯用品牌来筛选。所以分类和品牌是两个独立的维度,商品挂在分类下,同时关联品牌。
分类设计成两级就够用了,一级是品类,二级是具体类型。品牌表单独维护,包括品牌名称、logo、描述和排序权重。分类页和首页的品牌专区都需要从这里取数据,另外品牌名在搜索权重里应该占有比较高的比例,用户的搜索词经常是"罗技鼠标"这种"品牌词加品类词"的组合。
3. 用户端核心功能的设计思路
用户端是商城面向用户的脸面,核心流程是浏览商品、搜索、加购、下单、支付、查看订单。这个项目的用户端功能模块我按业务重要性排了个优先级,先做商品展示和购物车下单这条主链路,再做搜索、个人中心和售后相关功能。
3.1 首页和分类页的构建逻辑
首页不用做得很复杂,重心放在商品推荐和活动位上。我用一个接口返回首页整体的数据块配置,包括轮播图、新品推荐、热销榜单和分类快捷入口。前端按数据块类型去渲染对应组件,这种配置化的方式让运营在后台修改首页内容不需要发版。
分类页和商品列表页用同一个组件来承载,通过路由参数区分当前是哪个分类。商品列表支持排序,有综合、销量、价格升序、价格降序、新品几个选项,排序逻辑放在后端实现,前端只需要传排序参数。筛选功能支持品牌、价格区间,按分类的不同还可以动态加载对应的属性筛选项。
3.2 商品详情页的规格交互和参数展示
商品详情页是这个项目交互最复杂的地方之一。进入页面后要展示商品主图、标题、价格区间、SKU选择器、参数规格、商品描述和评论。
SKU选择器需要根据后端返回的SKU列表计算出哪些规格组合是可选的,哪些是不可选的。比如一款键盘有三个轴体和两种布局,总共六个SKU,但其中某个轴体配某种布局的组合没有上线,那么用户在选轴体之后,布局那边不可用的选项就应该置灰。这个逻辑我封装成了一个通用组件,通过计算当前已选规格和所有SKU的匹配关系,动态更新可选状态。
规格参数展示在详情页的下半部分,通过属性接口取回的键值对按照分组展示。商品描述使用的是富文本图片内容,后端存储的是html片段,前端直接渲染即可。
3.3 购物车和下单流程的联动设计
购物车最大的问题是登录状态和未登录状态的打通。我的方案是购物车数据全部存在服务端,用户没有登录时可以往本地购物车添加商品,登录时把本地购物车合并到服务端。这个方案在用户换设备或者清本地缓存时不会丢失数据,稳定性更好。
下单流程按"确认订单、提交订单、支付、支付结果回调"分四步。确认订单页可以调整商品数量、填写收货地址、选择支付方式、查看商品总价和运费。提交订单时后端要做库存校验,防止超卖。
支付模块我接了通用的第三方支付接口,整个流程是前端请求创建订单接口,后端生成订单记录并返回支付参数,前端唤醒支付客户端,支付完成后第三方异步通知后端,后端验证签名后更新订单状态,再通过轮询或者websocket告知前端支付结果。测试环境可以直接跳转到模拟支付页面,方便跑通全流程。
4. 管理后台的功能拆解与权限控制
管理后台是给运营人员和管理员用的,功能上比用户端更复杂,但在界面设计上不用追求炫酷,重点是信息清晰、操作高效。我按"商品、订单、用户、营销、统计"五大模块来组织管理后台。
4.1 商品管理和库存维护
商品管理模块要支持商品的创建、编辑、上下架、库存调整。创建商品的表单是动态的,管理员选择分类之后,系统自动加载该分类的规格模板和属性模板,不需要手动输入属性名。
库存维护单独设计了页面,按SKU粒度展示库存数量,支持批量导入导出。库存变更记录全部存日志,方便追踪操作历史。库存低于安全阈值时会在列表上标红提示,这个功能虽然实现简单,但对运营来说非常实用。
4.2 订单处理流程的设计
订单管理是管理后台里最需要严谨对待的模块。订单状态机我定义成待付款、待发货、已发货、已完成、已取消、售后中这么几个状态,每个状态能执行的操作是受限的,比如待发货状态只能发货或取消,发货之后只能修改物流信息,不能直接取消订单。
订单列表支持多维筛选,按状态、时间范围、订单号、收货人手机号都可以查。订单详情页展示订单的完整链路信息,包括商品快照、收货信息、支付信息、操作日志。订单操作日志是必须记录的模块,什么时候改了什么状态、是谁操作的,这些信息在售后排查纠纷时能派上大用场。
4.3 基于JWT的权限控制实现
管理后台的账号分为超级管理员和运营人员两类,运营人员可以分配角色权限。我这里用了很轻量的方案,后端定义权限点,比如"商品创建"、"订单发货"、"用户管理",角色关联权限点,管理员关联角色。
权限控制的实现流程是登录时后端返回用户信息和该用户持有的权限点列表,前端拿到后存储在vuex里,路由守卫根据当前路由需要的权限点判断是否放行。后端每个接口的中间件也会校验对应的权限点,仅靠前端控制是不够的,接口层的权限校验才是安全屏障。
登录状态用JWT来维护,token过期时间设置为12个小时,管理员操作时如果token过期会弹出重新登录提示。生产环境里JWT的密钥必须单独配置,不要硬编码在代码里,签名的payload里不要放敏感信息,因为JWT的payload只是base64编码,不是加密的。
5. 后端接口设计和关键业务流程的落地
这一部分是整个项目技术含量最高的地方。接口设计要规范,业务流程要经得起推敲,尤其是涉及库存、金额、订单状态的改动,每一步都得想清楚。
5.1 接口规范:统一返回格式和错误码
前后端接口联调最容易出现问题的地方就是返回格式不统一。我的做法是后端所有接口统一返回标准响应结构,成功和失败都是同一个格式。
{ "code": 0, "message": "success", "data": {} }code为0表示成功,非0表示业务错误码,比如10001表示参数错误,10002表示未登录,20001表示库存不足。前端封装的axios实例统一拦截响应,非0的code直接弹出对应的错误提示,不需要每个页面各自处理错误逻辑。
分页接口的参数和返回也统一约定。请求参数固定为page(页码)和pageSize(每页条数),返回结构固定为total(总数)和list(当前页数据)。这样一套规范在前后端都形成习惯之后,联调效率明显提升,很多问题在开发阶段就规避了。
5.2 商品列表接口的缓存策略
商品列表是用户端访问频率最高的接口,必须做好缓存。我这里分了两个层面的缓存:一个是在数据库查询上做优化,列表接口只查需要的字段,不查商品描述这种大字段;另一个是热点数据用内存缓存,因为node单进程内可以直接共享内存数据。
具体实现上,用一个简单的内存缓存模块,key是接口路径加参数的哈希值,value是查询结果,同时设置过期时间。首页热销商品和分类页前几页的商品数据缓存时间设置成5分钟,后台编辑商品或者上下架时主动删除对应缓存。这个策略实测下来接口响应时间从平均120ms下降到了30ms以内。
但注意不要对订单相关接口做缓存,这类数据必须实时查询。
5.3 订单提交的幂等性和库存扣减逻辑
订单提交最容易出现的问题是用户重复点击提交按钮,导致生成多个订单。我用的方案是前端提交订单时生成一个唯一的幂等键,后端在创建订单前检查该键是否已存在,存在则直接返回已有的订单信息。
库存扣减这里有个取舍,是下单时扣减还是支付时扣减。我实际采用的是下单锁库存、超时释放的方案。用户提交订单后锁定对应商品库存,订单在30分钟内未支付则自动取消,同时释放锁定的库存。这个方案能保证下单成功基本就能支付成功,体验较好。
超时关单的实现用定时任务扫描即可,每5分钟执行一次,把创建时间超过30分钟且状态为待付款的订单置为已取消,并回滚库存。对于秒杀这种极高并发场景可能这套方案不够用,但普通的商城流量完全能支持。
并发扣减库存的防超卖我用的是条件更新语句。具体来说就是执行UPDATE语句时加上库存大于等于扣减数量的条件,影响行数为0则说明库存不足,直接返回提示。
5.4 文件上传和图片处理
商城的商品图片上传涉及两个节点,一是管理后台上传商品图,二是用户上传评价图。统一走同一个上传接口,后端用multer处理multipart格式,文件存储到服务器的磁盘目录,数据库只保存文件访问路径。
图片处理上,上传的原图不能直接用于列表页展示,因为文件大加载慢。我的做法是使用sharp库生成多尺寸缩略图,列表页用200px的小图,详情页用800px的中图,需要放大的时候才加载原图。生成缩略图这个过程是CPU密集型的,如果图片量大,生产环境最好改用一个独立服务来处理,避免阻塞node主进程。
文件访问路径要做一层鉴权,尤其是订单相关的用户上传凭证图片这类隐私数据,不能直接暴露静态路径。公共的商品图片因为是公开资源,可以放在public目录下直接访问。
6. 数据库设计的关键表结构
数据库是商城项目的地基,表结构设计得好不好,直接影响业务逻辑的复杂度和查询性能。这个项目涉及的表比较多,我挑几个核心的讲讲设计思路。
6.1 商品、分类、品牌相关的表结构
商品相关表是我最用心设计的部分,核心是SPU和SKU的拆分。SPU是商品抽象层,SKU是具体售卖单元。
商品表主要字段包括id、分类id、品牌id、商品名称、副标题、详情内容、上下架状态、销量、浏览量。品类表是二级结构,一级分类和子分类用parent_id关联。品牌表包含品牌名称、品牌logo、品牌描述。
SKU表的字段包括id、商品id、SKU名称、价格、原价、库存、SKU编码、规格值组合的JSON字符串、图片。价格字段我强调的是使用整数类型,单位为分,这个后面会专门展开说。
6.2 购物车、订单、订单项的设计
购物车表设计得比较简单,用户id、商品id、SKU id、数量、选中状态,加一个唯一索引约束同一用户下同一SKU不能重复加车。
订单表是重量级的,核心字段有订单号、用户id、订单总金额、运费、实付金额、收货人姓名、手机号、地址、订单状态、支付方式、支付时间、发货时间、完成时间。订单号我单独生成,要能体现一定的规律,方便线下沟通报订单号时快速识别。
订单项表用于记录每个订单里的每个SKU的购买信息,注意这里必须存商品快照。用户下单之后,商品名称、价格、图片这些都可能被商家修改,但订单里的信息必须保持下单时的样子,否则后续产生售后纠纷时没有依据。快照字段用JSON格式存储商品基本信息,查询时直接取出使用,不关联商品表。
6.3 用户和地址相关表
用户的表结构包含账号信息、密码、昵称、头像、手机号、邮箱、状态。密码必须用加盐哈希存储,我用的是bcrypt库,每次生成的盐都不同,这样即使数据库泄露,密文被逆向破解的成本也非常高。
收货地址表需要设置一个默认地址标记。用户每次下单选择地址时,如果当前不存在默认地址则需要引导设置。地址表的字段包含收货人、手机号、省市区、详细地址、邮政编码、是否默认。
考虑到用户可能会用同一个账号在多个设备上登录,token信息存redis并设置过期时间,可以做到一个用户只允许同一时间唯一登录态。对于项目起步阶段,这个方案比JWT全无状态管理更容易控制登录态,比如强制下线某个设备时直接删除对应的token。
7. 权限之外的安全防护事项
商城涉及真实资金交易和数据隐私,如果把安全只停留在"加了JWT就安全"的认知上,上线后很可能吃大亏。我从这几个方向做了加固。
7.1 SQL注入的防护
Node后端最大的安全风险其实是SQL注入。我在项目里用的ORM框架内置了参数化查询机制,禁止任何地方拼接SQL字符串。但只要存在原生SQL查询的需求,就必须使用参数占位符。
Node后端最大的安全风险其实是SQL注入。我是强制约定:禁止任何地方拼接SQL字符串,查询一律走参数化查询。
7.2 XSS攻击和数据校验
用户生成的内容,比如评论、昵称、收货地址,都必须做XSS过滤。前端在渲染这些内容时用vue的插值表达式,但后端同样要在存储前过滤一遍,双重保险。富文本内容字段是重灾区,需要用白名单机制过滤标签和属性。
另外所有接口的入参校验我统一放在一个校验模块里,用JSON schema的方式做校验,不满足规则直接返回错误,不进入业务逻辑。这样能挡住很大一部分无意识的错误请求和有意的恶意请求。
7.3 支付回调的验签
支付回调接口是对外暴露的,如果验签不严可能被伪造请求。支付的验签逻辑是回调请求带上签名参数,我们用商户密钥对回调参数重新计算签名,和请求带过来的签名做比对,一致才认为是合法的支付结果通知。
验签时还要校验订单金额和支付金额是否一致,防止篡改订单金额的支付攻击。支付回调的业务处理要保证幂等性,同一个支付通知可能发送多次,重复处理会导致订单状态错乱。
7.4 管理员操作安全
管理后台的账号禁止使用弱密码,登录接口做失败次数限制,防止暴力破解。管理员的登录日志和关键操作日志都要持久化存储,一旦出现安全事件可以回溯当时的情况。
生产环境的数据库账号密码、支付密钥、JWT密钥这些敏感配置必须放在环境变量或单独的配置文件中,并且配置文件要加到gitignore中,禁止提交到代码仓库。
8. 性能优化的具体实践
商城系统的性能优化可以从两个角度切入,一是首屏加载速度,二是接口响应速度。两个方向各有各的手段,我把项目里的实践经验按模块整理出来。
8.1 前端构建层面的优化
vue项目的打包优化主要围绕代码体积展开。路由懒加载是必须做的,首屏只加载首页需要的路由组件,其他模块在路由切换时才动态加载。
第三方库的按需引入也很关键,比如UI组件库如果全量引入,打包体积会达到几百KB。我这里是手动按需注册用到的组件模块,配合图片资源压缩、字体文件精简,打包后首屏js大概在240KB左右,配合gzip压缩传输后实际加载量很小。配置项比较多,推荐从项目初期就做好规范,否则后期统一处理要翻很多文件。
电子外设商品详情页的图片特别多,图片懒加载组件是必须的。页面滚动到图片区域时才加载真实图片,统计下来首屏图片加载数量可以减少60%以上。
8.2 后端接口的响应优化
接口响应时间是商城用户体验的核心指标,这个项目的优化手段集中在几个方面:
第一是数据库查询优化,给高频查询字段建立合适的索引,慢查询日志定期审查。商品列表接口的排序字段、筛选字段都要有对应的索引,否则数据量上来之后性能下降明显。
第二是连接池的合理配置。使用mysql连接池之后,要设置合理的min和max连接数。太小会阻塞查询,太大会给数据库服务器造成压力。我根据压测情况将连接池max设置为20,单个请求占用连接时间太长的SQL要重点排查。
第三是列表接口尽量单表查询或最少表关联,关联超过两张表就容易出现性能瓶颈。商品列表需要展示品牌名和分类名,我通过在商品表直接冗余这两个字段来避免join查询。
8.3 数据库层的分页优化
传统的limit和offset分页在数据量大时会有性能问题,offset越深查询越慢。商城前几页的分页压力最大,我用两种方式优化:第一种是基于主键的分页,分页参数传到搜索条件里;第二种是限制最大查询深度,一般用户最多翻到十几页就到极限了,没必要支持无限翻页。
应用层做兜底,深度分页显著变慢时直接限制offset最大不超过10000条,再往后的数据通过搜索引擎来补位。
9. 从开发到上线的完整流程
项目开发完成只是第一步,从开发环境到生产环境这段路才是最考验经验的。有几个环节我专门记录一下,因为踩过的坑不具备普遍性,写出来希望同行不用再跳一次。
9.1 环境配置的管理
开发环境、测试环境、生产环境的多环境配置是必不可少的一环。我在项目中使用了dotenv方式,不同环境的变量文件方式管理,环境切换时指定加载对应的变量。
环境变量覆盖数据库主机、数据库名、数据库账号密码、JWT密钥、支付参数、上传文件路径、前端域名这些配置。配置文件一定要简单清晰,用统一的前缀命名,例如全部使用APP_前缀。
9.2 使用pm2守护node进程
生产环境的node服务必须使用进程管理工具。我用pm2启动服务,并且配置了几个关键参数:实例数根据服务器CPU核数设置,启动时设置max_memory_restart避免内存泄漏引发服务不可用,同时配置了日志输出路径。
pm2最重要的是自动重启能力,服务异常崩溃后会自动拉起新进程,不会出现服务静默停止的现象。更新代码后一条命令滚动重启,零停机切换用户无感。
9.3 使用nginx做反向代理
生产环境node服务一般监听本机端口,直接对公网开放是不安全的,所以nginx反向代理是标配。nginx层负责HTTP、HTTPS、证书配置、请求转发。nginx代理node服务时要注意正确配置超时时间,因为有些接口的处理时间可能超过nginx的默认超时时间,导致请求被切断。
项目的静态资源也交给nginx托管,前端构建后的文件放到nginx指定的目录下,配合gzip配置。
9.4 HTTPS的配置
商城系统涉及用户注册、登录、支付,必须启用HTTPS。证书我使用的是免费的证书方案,nginx配置好证书之后将HTTP请求301重定向到HTTPS,同时注意后端接口的请求地址全部改用HTTPS协议,避免出现混合内容被浏览器拦截。
根据我自己的实践,从HTTP切到HTTPS之后不会有明显的性能损耗,但用户在浏览器上的信任感提升很大,而且搜索还是更倾向于收录HTTPS站点的。所以就算是一个小商城,我也建议上线第一天就启用HTTPS。
10. 上线后遇到的实际问题和排查过程
这部分是我最想分享的,因为项目中很多问题不是网上搜索就能直接解决的,必须根据实际情况一步步排查。挑几个典型的排查例子,希望能给大家提供排查思路上的参考。
10.1 内存泄漏问题排查
项目上线运行一周后,发现node进程的内存占用持续增长,大约两天后pm2日志报出内存超限,服务自动重启。刚开始我猜测是图片处理库导致,后来逐一排查定位到内存缓存模块,业务逻辑中缓存的值更新比较频繁,删除旧缓存时没有正确删除引用,导致旧缓存对象一直占用内存无法释放。
排查过程用了一个笨方法,我在缓存模块里加上缓存条目计数器,然后对比日志里的缓存数量和实际内存占用,发现内存增长和缓存数量增长不成比例,确认存在释放不彻底的问题。最终修复方式是缓存写入时设置过期检查,定期清理过期条目。
10.2 订单超时未关闭的问题
定时任务上线后,某一天发现有几笔订单超时没有被自动取消。排查后发现问题出在定时任务的执行频率和订单创建时间的边界,当订单恰好卡在定时任务扫描边界时会被跳过。
修复方式是每次定时扫描时主动查询所有待付款订单且时间已超时的全部数据,不以单个时间点为判断依据,扫描更彻底。我反复修改后也写了一些自动化断言逻辑,定时任务的逻辑每次改动都套用一套模拟数据断言。
10.3 并发下单时库存扣减不一致
商品做活动时,因为并发量上来,数据库出现库存扣减不一致的情况。这里说的"不一致"不是超卖,而是个别请求明明库存够却扣减失败。排查后发现是我在事务里先查询库存再判断再执行的流程,在高并发场景下会出现判断正确的请求更新时被其他事务阻塞。
修复方式是改成条件更新的原子操作,一条SQL完成库存检查和扣减,彻底避免了判断和更新之间的竞态条件。这里强烈建议各位做电商后端的朋友,库存相关的任何操作直接使用原子条件更新,而不是查询再更新。
10.4 搜索引擎收录异常
上线后站长报告站点收录数量少,我自己检查发现页面跳转逻辑不对,商品详情页在未登录状态下会被强制跳转到首页。这个设计的本意是防止未管理用户访问,但搜索引擎的爬虫也会被拦截。
修复方法是针对爬虫放行详情页内容渲染,同时在页面meta标签里补充商品描述信息。修复后两周内收录数量明显回升,各类长尾关键分词也能抓到商品的规格属性词了。
尾声
从项目立项到稳定上线,vue加nodejs这套组合在电子外设商城这个项目上经受住了完整的流量考验。回顾整个实施过程,我最深的体会是:技术选型只要能稳稳地托住业务,就是好的选型,vue的组件化和node的高效迭代,正好匹配了商城项目界面多变、接口独立的特性,唯一要守住的是基本功——数据库设计和安全防护这两块千万不能偷懒。
这个项目后续的扩展空间也很大。根据我个人做项目的习惯,下一步可以先给管理后台加入更丰富的营销工具,比如优惠券引擎和秒杀模块,然后逐步把搜索模块升级成独立的搜索服务,让商品搜索的响应速度和相关性都能进一步提升。如果你也在做类似的商城项目,希望这些设计和排查经验能帮你少走一段弯路。