当前位置: 首页 > news >正文

Spring Boot + Vue 实现一个在线商城(商品展示、购物车、订单)!从零到一完整项目

上周,实习生小周问我:

“马哥,我想做一个完整的电商项目练手,但网上教程要么太简单(只有后端),要么太复杂(直接上微服务)。有没有一个前后端分离、功能完整的商城 demo,从商品展示到购物车到订单都讲一遍?”

我笑了:“电商项目,核心就三点:商品、购物车、订单。今天我就带你用 Spring Boot + Vue,一步步搭建一个能跑的商城!”

✅ 后端:Spring Boot + MyBatis + MySQL + Redis
✅ 前端:Vue 3 + Element Plus + Axios
✅ 功能:商品列表 → 加入购物车 → 生成订单

🛠️ 一、准备工作(只需 3 分钟)

你需要:

  • JDK 17
  • Node.js 18+(用于 Vue)
  • MySQL 8.0
  • IDEA + VS Code(或 WebStorm)
  • Redis(用于购物车缓存)

💻 二、后端项目搭建(Spring Boot 部分)

步骤 1:创建 Spring Boot 项目(pom.xml)

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency>
</dependencies>

步骤 2:配置文件(application.yml)

spring:datasource:url: jdbc:mysql://localhost:3306/mall?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverredis:host: localhostport: 6379database: 1timeout: 2000mslettuce:pool:max-active: 8max-idle: 8min-idle: 0mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.example.mall.entityconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 跨域配置(开发用)
server:port: 8080

步骤 3:创建数据库表(SQL)

-- 创建数据库
CREATE DATABASE mall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;USE mall;-- 商品表
CREATE TABLE product (id BIGINT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(100) NOT NULL,price DECIMAL(10,2) NOT NULL,description TEXT,image_url VARCHAR(255),stock INT NOT NULL DEFAULT 0
);-- 订单表
CREATE TABLE `order` (id BIGINT AUTO_INCREMENT PRIMARY KEY,user_id BIGINT NOT NULL,total_amount DECIMAL(10,2) NOT NULL,status VARCHAR(20) NOT NULL DEFAULT 'PENDING',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);-- 订单项表
CREATE TABLE order_item (id BIGINT AUTO_INCREMENT PRIMARY KEY,order_id BIGINT NOT NULL,product_id BIGINT NOT NULL,quantity INT NOT NULL,price DECIMAL(10,2) NOT NULL
);-- 插入测试数据
INSERT INTO product (name, price, description, image_url, stock) VALUES
('iPhone 15', 5999.00, '最新款苹果手机', 'https://example.com/iphone15.jpg', 100),
('MacBook Pro', 12999.00, '专业笔记本电脑', 'https://example.com/macbook.jpg', 50),
('AirPods Pro', 1999.00, '无线降噪耳机', 'https://example.com/airpods.jpg', 200);

步骤 4:创建实体类(entity/)

// Product.java
package com.example.mall.entity;public class Product {private Long id;private String name;private Double price;private String description;private String imageUrl;private Integer stock;// getter/setter...public Long getId() { return id; }public void setId(Long id) { this.id = id; }public String getName() { return name; }public void setName(String name) { this.name = name; }public Double getPrice() { return price; }public void setPrice(Double price) { this.price = price; }public String getDescription() { return description; }public void setDescription(String description) { this.description = description; }public String getImageUrl() { return imageUrl; }public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }public Integer getStock() { return stock; }public void setStock(Integer stock) { this.stock = stock; }
}// Order.java
package com.example.mall.entity;import java.util.Date;
import java.util.List;public class Order {private Long id;private Long userId;private Double totalAmount;private String status;private Date createdAt;private List<OrderItem> items; // 关联订单项// getter/setter...public Long getId() { return id; }public void setId(Long id) { this.id = id; }public Long getUserId() { return userId; }public void setUserId(Long userId) { this.userId = userId; }public Double getTotalAmount() { return totalAmount; }public void setTotalAmount(Double totalAmount) { this.totalAmount = totalAmount; }public String getStatus() { return status; }public void setStatus(String status) { this.status = status; }public Date getCreatedAt() { return createdAt; }public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; }public List<OrderItem> getItems() { return items; }public void setItems(List<OrderItem> items) { this.items = items; }
}// OrderItem.java
package com.example.mall.entity;public class OrderItem {private Long id;private Long orderId;private Long productId;private Integer quantity;private Double price;// getter/setter...public Long getId() { return id; }public void setId(Long id) { this.id = id; }public Long getOrderId() { return orderId; }public void setOrderId(Long orderId) { this.orderId = orderId; }public Long getProductId() { return productId; }public void setProductId(Long productId) { this.productId = productId; }public Integer getQuantity() { return quantity; }public void setQuantity(Integer quantity) { this.quantity = quantity; }public Double getPrice() { return price; }public void setPrice(Double price) { this.price = price; }
}

步骤 5:创建 Mapper 接口和 XML

// ProductMapper.java
package com.example.mall.mapper;import com.example.mall.entity.Product;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.List;@Mapper
public interface ProductMapper {List<Product> findAll();Product findById(@Param("id") Long id);void updateStock(@Param("id") Long id, @Param("quantity") Integer quantity);
}
<!-- resources/mapper/ProductMapper.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mall.mapper.ProductMapper"><resultMap id="ProductResultMap" type="Product"><id property="id" column="id"/><result property="name" column="name"/><result property="price" column="price"/><result property="description" column="description"/><result property="imageUrl" column="image_url"/><result property="stock" column="stock"/></resultMap><select id="findAll" resultMap="ProductResultMap">SELECT id, name, price, description, image_url, stock FROM product</select><select id="findById" parameterType="java.lang.Long" resultMap="ProductResultMap">SELECT id, name, price, description, image_url, stock FROM product WHERE id = #{id}</select><update id="updateStock" parameterType="map">UPDATE product SET stock = stock - #{quantity} WHERE id = #{id} AND stock >= #{quantity}</update>
</mapper>

步骤 6:创建购物车服务(Redis 缓存)

// CartService.java
package com.example.mall.service;import com.example.mall.entity.Product;
import com.example.mall.mapper.ProductMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;@Service
public class CartService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate ProductMapper productMapper;@Autowiredprivate ObjectMapper objectMapper;/*** 添加商品到购物车*/public void addToCart(Long userId, Long productId, Integer quantity) {String key = "cart:" + userId;// 获取当前购物车ValueOperations<String, Object> ops = redisTemplate.opsForValue();Object cartObj = ops.get(key);Map<Long, Integer> cart = new HashMap<>();if (cartObj != null) {cart = (Map<Long, Integer>) cartObj;}// 添加或更新商品数量cart.put(productId, cart.getOrDefault(productId, 0) + quantity);// 保存到 Redis,30分钟过期ops.set(key, cart, 30, TimeUnit.MINUTES);}/*** 获取购物车商品列表*/public List<Map<String, Object>> getCartItems(Long userId) {String key = "cart:" + userId;ValueOperations<String, Object> ops = redisTemplate.opsForValue();Object cartObj = ops.get(key);if (cartObj == null) {return new ArrayList<>();}@SuppressWarnings("unchecked")Map<Long, Integer> cart = (Map<Long, Integer>) cartObj;List<Map<String, Object>> items = new ArrayList<>();for (Map.Entry<Long, Integer> entry : cart.entrySet()) {Long productId = entry.getKey();Integer quantity = entry.getValue();Product product = productMapper.findById(productId);if (product != null) {Map<String, Object> item = new HashMap<>();item.put("product", product);item.put("quantity", quantity);item.put("totalPrice", product.getPrice() * quantity);items.add(item);}}return items;}/*** 清空购物车*/public void clearCart(Long userId) {String key = "cart:" + userId;redisTemplate.delete(key);}
}

步骤 7:创建订单服务

// OrderService.java
package com.example.mall.service;import com.example.mall.entity.*;
import com.example.mall.mapper.ProductMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.Date;
import java.util.List;
import java.util.Map;@Service
public class OrderService {@Autowiredprivate CartService cartService;@Autowiredprivate ProductMapper productMapper;@Autowiredprivate OrderMapper orderMapper;@Transactionalpublic Long createOrder(Long userId) {// 获取购物车商品List<Map<String, Object>> cartItems = cartService.getCartItems(userId);if (cartItems.isEmpty()) {throw new RuntimeException("购物车为空");}// 计算总金额Double totalAmount = 0.0;for (Map<String, Object> item : cartItems) {totalAmount += (Double) item.get("totalPrice");}// 创建订单Order order = new Order();order.setUserId(userId);order.setTotalAmount(totalAmount);order.setStatus("PENDING");order.setCreatedAt(new Date());orderMapper.insert(order);// 创建订单项并扣减库存for (Map<String, Object> item : cartItems) {Product product = (Product) item.get("product");Integer quantity = (Integer) item.get("quantity");OrderItem orderItem = new OrderItem();orderItem.setOrderId(order.getId());orderItem.setProductId(product.getId());orderItem.setQuantity(quantity);orderItem.setPrice(product.getPrice());orderMapper.insertOrderItem(orderItem);// 扣减库存productMapper.updateStock(product.getId(), quantity);}// 清空购物车cartService.clearCart(userId);return order.getId();}
}

步骤 8:创建控制器

// ProductController.java
package com.example.mall.controller;import com.example.mall.entity.Product;
import com.example.mall.mapper.ProductMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/api/products")
@CrossOrigin(origins = "*") // 开发用
public class ProductController {@Autowiredprivate ProductMapper productMapper;@GetMappingpublic List<Product> getProducts() {return productMapper.findAll();}@GetMapping("/{id}")public Product getProduct(@PathVariable Long id) {return productMapper.findById(id);}
}// CartController.java
package com.example.mall.controller;import com.example.mall.service.CartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;
import java.util.Map;@RestController
@RequestMapping("/api/cart")
@CrossOrigin(origins = "*")
public class CartController {@Autowiredprivate CartService cartService;@PostMapping("/add")public String addToCart(@RequestParam Long userId, @RequestParam Long productId, @RequestParam Integer quantity) {cartService.addToCart(userId, productId, quantity);return "OK";}@GetMapping("/{userId}")public List<Map<String, Object>> getCart(@PathVariable Long userId) {return cartService.getCartItems(userId);}@DeleteMapping("/clear/{userId}")public String clearCart(@PathVariable Long userId) {cartService.clearCart(userId);return "OK";}
}// OrderController.java
package com.example.mall.controller;import com.example.mall.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/api/orders")
@CrossOrigin(origins = "*")
public class OrderController {@Autowiredprivate OrderService orderService;@PostMapping("/create")public String createOrder(@RequestParam Long userId) {Long orderId = orderService.createOrder(userId);return "订单创建成功,ID: " + orderId;}
}

🌐 三、前端项目搭建(Vue 3 部分)

步骤 1:创建 Vue 项目

npm create vue@latest mall-frontend
cd mall-frontend
npm install
npm install axios element-plus

步骤 2:创建前端页面(App.vue)

<template><div id="app"><el-container><el-header><h1>在线商城</h1><div class="cart-info"><el-button type="primary" @click="showCart = true">购物车 ({{ cartItems.length }})</el-button></div></el-header><el-main><!-- 商品列表 --><el-row :gutter="20"><el-col :span="6" v-for="product in products" :key="product.id"><el-card class="product-card"><img :src="product.imageUrl" class="product-image" /><div class="product-info"><h3>{{ product.name }}</h3><p class="price">¥{{ product.price }}</p><p class="stock">库存: {{ product.stock }}</p><el-button type="primary" @click="addToCart(product.id, 1)":disabled="product.stock <= 0">加入购物车</el-button></div></el-card></el-col></el-row></el-main></el-container><!-- 购物车弹窗 --><el-drawer v-model="showCart" title="购物车" size="40%"><div v-if="cartItems.length === 0"><p>购物车是空的</p></div><div v-else><div v-for="item in cartItems" :key="item.product.id" class="cart-item"><img :src="item.product.imageUrl" class="cart-image" /><div class="cart-details"><h4>{{ item.product.name }}</h4><p>数量: {{ item.quantity }}</p><p>小计: ¥{{ item.totalPrice }}</p></div></div><div class="cart-total"><h3>总计: ¥{{ cartTotal }}</h3><el-button type="success" @click="createOrder" :disabled="cartItems.length === 0">生成订单</el-button></div></div></el-drawer></div>
</template><script>
import axios from 'axios'export default {name: 'App',data() {return {products: [],cartItems: [],showCart: false,userId: 1 // 模拟用户ID}},computed: {cartTotal() {return this.cartItems.reduce((total, item) => total + item.totalPrice, 0).toFixed(2)}},mounted() {this.loadProducts()this.loadCart()},methods: {async loadProducts() {try {const response = await axios.get('http://localhost:8080/api/products')this.products = response.data} catch (error) {console.error('加载商品失败:', error)}},async loadCart() {try {const response = await axios.get(`http://localhost:8080/api/cart/${this.userId}`)this.cartItems = response.data} catch (error) {console.error('加载购物车失败:', error)}},async addToCart(productId, quantity) {try {await axios.post('http://localhost:8080/api/cart/add', null, {params: { userId: this.userId, productId, quantity }})this.$message.success('已加入购物车')this.loadCart() // 刷新购物车} catch (error) {console.error('添加购物车失败:', error)this.$message.error('添加失败')}},async createOrder() {try {const response = await axios.post('http://localhost:8080/api/orders/create', null, {params: { userId: this.userId }})this.$message.success(response.data)this.showCart = falsethis.loadCart() // 清空购物车} catch (error) {console.error('创建订单失败:', error)this.$message.error('创建订单失败')}}}
}
</script><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;
}.el-header {background-color: #409EFF;color: white;display: flex;justify-content: space-between;align-items: center;
}.product-card {margin-bottom: 20px;
}.product-image {width: 100%;height: 200px;object-fit: cover;
}.product-info {padding: 10px 0;
}.price {color: #e74c3c;font-size: 18px;font-weight: bold;
}.stock {color: #999;
}.cart-image {width: 60px;height: 60px;object-fit: cover;margin-right: 10px;
}.cart-item {display: flex;align-items: center;padding: 10px 0;border-bottom: 1px solid #eee;
}.cart-total {position: fixed;bottom: 0;left: 0;right: 0;padding: 20px;background: white;border-top: 1px solid #eee;text-align: right;
}
</style>

▶️ 四、运行验证(2 分钟搞定)

1. 启动后端

cd mall-backend
mvn spring-boot:run

2. 启动前端

cd mall-frontend
npm run dev

3. 访问页面

浏览器打开:http://localhost:5173

4. 测试功能

  • 看到商品列表
  • 点“加入购物车” → 购物车数量增加
  • 点“购物车”图标 → 查看购物车内容
  • 点“生成订单” → 订单创建成功

✅ 后端端口 8080,前端端口 5173,跨域已配置

💡 五、Bonus:电商项目常见坑点

坑 1:库存超卖

当前实现:用 Redis 缓存购物车,数据库扣减库存
生产建议:加分布式锁或数据库悲观锁

坑 2:订单幂等性

建议:生成订单前检查用户是否有未支付订单

坑 3:购物车过期

当前实现:Redis 30分钟过期
生产建议:根据业务调整,可考虑持久化


💬六、写在最后

电商项目,看似复杂,实则是 商品、购物车、订单 三个模块的组合。

记住三句话:

  • 购物车用 Redis,提升性能
  • 订单创建要事务,保证数据一致
  • 库存扣减要防超卖,加锁或乐观锁

学会电商项目,你就掌握了 真实业务场景的完整开发流程

http://www.dtcms.com/a/619012.html

相关文章:

  • h5可以制作公司网站吗网站用什么框架做
  • AlmaLinux9.6 部署 MariaDB10.11 和 Zabbix7.0 完整教程
  • 东莞市手机网站建设怎么样自己如何做微信小程序
  • 怎么提升网站收录编程培训班学费一般多少钱
  • Git 在团队中的最佳实践--如何正确使用Git Flow
  • 燕郊做网站的安卓程序开发用什么软件
  • 汽车网站建设需要多少钱做网站后期费用
  • Leetcode 3748. Count Stable Subarrays
  • LeetCode Hot100 缺失的第一个正数
  • skywalking中TID
  • 设计公司展厅装修长沙网站搭建seo
  • 私有化部署的gitlab的push failed问题,使用http远程连接(使用token或用户、密码)
  • 人工智能技术- 语音语言- 01 语音识别与合成
  • 枣庄企业网站推广用什么软件做网站hao
  • 网站类型分析招投标网站开发费用
  • 【C语言预处理器全解析】宏、条件编译、字符串化、拼接
  • 生物信息学核心算法全解析:从序列比对到 AI 预测的技术全景
  • 好的网站设计特点北京网站建设公司兴田德润活动
  • 第七章 构建你的智能体框架
  • flash类网站开发石家庄装修设计公司
  • 企业网站推广属于付费推广吗网站用cms
  • 嵌入式面试题:CAN 与 I2C 核心对比(含优缺点,实操视角)
  • 商河县做网站公司网络营销师资格证有什么用
  • 揭阳市住房和城乡建设局官方网站一天必赚100元的游戏
  • Python 常用库
  • 【 Java八股文面试 | Java集合 】
  • 青岛网站优化公司哪家好建网站 找个人
  • 网站建设售后服务网站推广排名
  • 线程控制块 (TCB) 与线程内核栈的内存布局关系
  • 现在最常用网站开发工具建设公司网站开发方案