网上购物系统源代码
一套能跑起来的购物系统,核心就三块:用户能登、购物商品能看、系统订单能下。源代我见过太多人一上来就想搞微服务、网上搞分布式,购物结果连购物车合并逻辑都写不利索。系统直接看代码——下面这个 Cart 类,源代处理了商品添加、网上数量修改、购物删除和总价计算,系统前后端都靠它吃饭。源代
class Cart {
constructor() {
this.items = new Map(); // key: productId,购物 value: { product, qty}
}
addItem(product, qty = 1) {
const existing = this.items.get(product.id);
if (existing) {
existing.qty += qty;
} else {
this.items.set(product.id, { product, qty });
}
}
getTotal() {
let sum = 0;
for (let item of this.items.values()) {
sum += item.product.price * item.qty;
}
return sum;
}
}
注意那个 Map 的选型——不是随便挑的。用普通对象存商品 ID 当键,系统数字会被转成字符串,后面做深比较容易翻车。Map 保留键的类型,而且迭代顺序稳定。这算个细节,但线上环境里省掉不少 bug。
后端用 Node.js + Express 搭起来最快。商品列表接口二十行搞定,但订单接口就得小心。下面这段是下单时的库存扣减逻辑,用了 MySQL 事务,不然超卖能把你搞崩溃。
async createOrder(req, res) {
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
for (let item of req.body.items) {
const [rows] = await conn.query(
'SELECT stock FROM products WHERE id = ? FOR UPDATE',
[item.productId]
);
if (rows[0].stock < item.qty) throw new Error('库存不足');
await conn.query(
'UPDATE products SET stock = stock - ? WHERE id = ?',
[item.qty, item.productId]
);
}
await conn.commit();
res.json({ code: 0, msg: '下单成功' });
} catch (err) {
await conn.rollback();
res.status(400).json({ code: 1, msg: err.message });
} finally {
conn.release();
}
}
看那个 FOR UPDATE,行锁。不加的话,两个人同时买最后一件商品,两个事务都读到库存 1,都去扣减,最后库存变成 -1。这种事故我亲眼见过,凌晨两点被叫起来修数据。所以别嫌麻烦,事务加锁是底线。
前端商品列表用 React 写,逻辑不复杂。但图片懒加载和分页得做,不然商品一多,首屏加载慢得用户直接关页面。下面这个 ProductList 组件,用了 IntersectionObserver 做图片懒加载,代码不多,效果立竿见影。
function ProductList() {
const [products, setProducts] = useState([]);
const [page, setPage] = useState(1);
const loaderRef = useRef();
useEffect(() => {
fetch(`/api/products?page=${ page}&limit=20`)
.then(res => res.json())
.then(data => setProducts(prev => [...prev, ...data]));
}, [page]);
useEffect(() => {
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) setPage(p => p + 1);
}, { threshold: 0.1 });
if (loaderRef.current) observer.observe(loaderRef.current);
return () => observer.disconnect();
}, []);
return (
<div className="grid">
{ products.map(p => <ProductCard key={ p.id} data={ p} />)}
<div ref={ loaderRef}>加载中...</div>
</div>
);
}
数据库建表也有讲究。商品表 products 里,price 用 DECIMAL(10,2),别用 FLOAT。浮点数算钱,0.1 + 0.2 不等于 0.3,财务对账能把你逼疯。订单表 orders 和订单项表 order_items 分开,别把商品信息冗余进订单表——商品改价改名字,历史订单不能跟着变。
最后说部署。前端打包扔 Nginx,后端 Node 用 PM2 守护,数据库单独一台机器。别把所有东西塞进一台 1 核 2G 的服务器,并发一上来直接 OOM。我见过有人用 pm2 start app.js -i max 在单核机器上开满进程,结果上下文切换把 CPU 吃光。根据核数来,一般 -i 2 就够了。
```