1. 项目概述
这个社区蔬菜商城项目采用Node.js+Vue+ElementUI技术栈构建,是一个典型的B2C电商平台。我在实际开发中发现,生鲜电商系统与传统电商相比有几个显著特点:商品保质期短、库存变动频繁、配送时效要求高。因此系统在设计时需要特别关注实时库存管理、订单时效控制和用户地理位置处理。
2. 技术架构设计
2.1 前后端分离架构
项目采用经典的前后端分离架构:
- 前端:Vue 2.x + ElementUI + Axios
- 后端:Node.js + Express框架
- 数据库:MySQL 5.7+
选择这个架构主要考虑:
- Vue的响应式特性非常适合电商类应用频繁的数据更新
- ElementUI提供了现成的电商组件(如商品卡片、购物车)
- Express轻量灵活,适合快速迭代的社区电商项目
2.2 数据库设计要点
针对蔬菜商城的特殊需求,数据库设计有几个关键点:
sql复制CREATE TABLE products (
id INT AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL COMMENT '实时库存',
shelf_life TINYINT COMMENT '保质期(天)',
category_id INT,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注意:生鲜商品必须设置shelf_life字段,这对后续的库存预警和促销策略很重要
3. 核心功能实现
3.1 商品实时库存管理
蔬菜库存的特殊性在于:
- 每日凌晨进货后更新库存
- 用户下单后需要立即锁定库存
- 超时未支付订单需要释放库存
实现代码示例:
javascript复制// 库存锁定中间件
app.post('/api/order', async (req, res) => {
const connection = await mysql.getConnection();
try {
await connection.beginTransaction();
// 检查并锁定库存
for (const item of req.body.items) {
const [rows] = await connection.execute(
`UPDATE products SET stock = stock - ?
WHERE id = ? AND stock >= ?`,
[item.quantity, item.productId, item.quantity]
);
if (rows.affectedRows === 0) {
throw new Error('库存不足');
}
}
// 创建订单
await connection.execute(
`INSERT INTO orders (...) VALUES (...)`
);
await connection.commit();
res.json({ success: true });
} catch (err) {
await connection.rollback();
res.status(400).json({ error: err.message });
} finally {
connection.release();
}
});
3.2 地理位置服务集成
社区电商需要处理用户位置信息:
- 使用腾讯地图API实现地址选择
- 根据用户位置筛选附近商家
- 计算配送范围和时效
Vue中集成地图组件:
javascript复制// 在vue.config.js中配置
module.exports = {
configureWebpack: {
externals: {
'TMap': 'TMap'
}
}
}
// 组件中使用
export default {
mounted() {
const script = document.createElement('script');
script.src = `https://map.qq.com/api/gljs?v=1.exp&key=YOUR_KEY`;
script.onload = this.initMap;
document.head.appendChild(script);
},
methods: {
initMap() {
new TMap.Map('map-container', {
center: new TMap.LatLng(39.984120, 116.307484),
zoom: 12
});
}
}
}
4. 关键问题解决方案
4.1 高并发库存竞争
在秒杀活动时可能出现超卖问题,解决方案:
-
数据库层面:
- 使用SELECT...FOR UPDATE悲观锁
- 设置库存字段为无符号整数
-
应用层面:
- 使用Redis分布式锁
- 实现队列削峰
javascript复制// Redis锁实现示例
const acquireLock = async (key, ttl = 3000) => {
const lock = await redis.set(key, 'LOCK', 'PX', ttl, 'NX');
return lock === 'OK';
};
const releaseLock = async (key) => {
await redis.del(key);
};
4.2 订单状态管理
蔬菜订单有特殊状态流转:
code复制待支付 → 已支付 → 配送中 → 已完成
↘ 已取消(超时未支付)
↘ 已退款(质量问题)
使用状态机模式管理:
javascript复制class OrderState {
constructor(state) {
this.state = state;
this.states = {
pending: ['paid', 'cancelled'],
paid: ['shipping', 'refunded'],
shipping: ['completed'],
cancelled: [],
completed: [],
refunded: []
};
}
transitionTo(newState) {
if (this.states[this.state].includes(newState)) {
this.state = newState;
return true;
}
return false;
}
}
5. 性能优化实践
5.1 图片处理方案
蔬菜商品需要大量图片展示,优化方案:
- 使用WebP格式替代JPEG
- 实现懒加载
- CDN加速
ElementUI图片懒加载配置:
html复制<el-image
:src="product.image"
lazy
:preview-src-list="[product.image]">
</el-image>
5.2 数据库查询优化
针对商品列表页的慢查询问题:
- 添加复合索引:
sql复制ALTER TABLE products ADD INDEX idx_category_status (category_id, status); - 使用分页缓存:
javascript复制// 使用redis缓存分页结果 const getProducts = async (page, size) => { const cacheKey = `products:${page}:${size}`; const cached = await redis.get(cacheKey); if (cached) return JSON.parse(cached); const products = await Product.find() .skip((page - 1) * size) .limit(size); await redis.setex(cacheKey, 3600, JSON.stringify(products)); return products; };
6. 部署与监控
6.1 PM2生产环境部署
Node.js服务使用PM2守护进程:
bash复制# 安装PM2
npm install pm2 -g
# 启动应用
pm2 start ecosystem.config.js
# 配置文件示例
module.exports = {
apps: [{
name: 'vegetable-shop',
script: 'app.js',
instances: 'max',
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'production'
}
}]
};
6.2 异常监控
使用Sentry捕获前端错误:
javascript复制// main.js中配置
import * as Sentry from '@sentry/vue';
import { Integrations } from '@sentry/tracing';
Sentry.init({
Vue,
dsn: 'YOUR_DSN',
integrations: [new Integrations.BrowserTracing()],
tracesSampleRate: 0.2
});
7. 开发经验总结
在实际开发中,有几个特别需要注意的点:
-
时间处理:MySQL默认使用UTC时间,建议统一使用UTC+8存储
javascript复制// 配置sequelize时设置时区 const sequelize = new Sequelize(..., { dialectOptions: { useUTC: false }, timezone: '+08:00' }); -
ElementUI表单验证:对于复杂的蔬菜规格选择,需要自定义验证规则
javascript复制rules: { weight: [ { required: true, message: '请选择重量', trigger: 'change' }, { validator: (_, v, cb) => v > 0 ? cb() : cb(new Error('重量必须大于0')), trigger: 'blur' } ] } -
跨域问题:开发环境配置proxyTable
javascript复制// vue.config.js devServer: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } } }
这个项目让我深刻体会到,社区电商系统不仅需要处理常规的电商逻辑,还要特别关注生鲜商品的特殊性。比如我们实现了每日凌晨自动下架临期商品的功能,这在普通电商中是不需要的。
