南京特色美食小吃商城系统是一个基于SpringBoot框架开发的B2C电商平台,专注于展示和销售南京本地特色美食。作为一名有5年Java全栈开发经验的工程师,我在实际开发中发现这类区域性垂直电商平台在高校毕业设计中非常受欢迎,因为它既包含了电商系统的通用功能模块,又能体现地方特色。
系统采用经典的三层架构设计:
提示:选择SpringBoot框架而非传统SSM的主要考虑是简化配置,更适合毕业设计这类时间有限的项目。SpringBoot的自动配置和起步依赖能节省大量环境搭建时间。
开发本系统需要准备以下环境(以Windows为例):
JDK安装:
bash复制JAVA_HOME=C:\Program Files\Java\jdk1.8.0_281
Path=%JAVA_HOME%\bin
Maven配置:
xml复制<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
数据库准备:
sql复制CREATE DATABASE nj_food DEFAULT CHARACTER SET utf8mb4;
CREATE USER 'njfood'@'%' IDENTIFIED BY 'Njfood123!';
GRANT ALL PRIVILEGES ON nj_food.* TO 'njfood'@'%';
推荐使用IntelliJ IDEA作为开发工具:
踩坑记录:MySQL 8.0+版本需要修改驱动为com.mysql.cj.jdbc.Driver,且需显式设置时区serverTimezone=Asia/Shanghai。为减少兼容性问题,建议严格使用MySQL 5.7。
| 技术组件 | 选型理由 | 替代方案对比 |
|---|---|---|
| SpringBoot | 快速启动、自动配置 | 传统SSM配置复杂 |
| Thymeleaf | 天然支持HTML5 | JSP需要额外支持 |
| MyBatis | SQL可控性强 | JPA灵活性不足 |
| MySQL 5.7 | 高校实验室普遍环境 | MariaDB兼容但文档少 |
系统采用模块化设计,主要分为六大核心模块:
用户中心模块
商品管理模块
订单系统
java复制public enum OrderStatus {
UNPAID, PAID, SHIPPED, COMPLETED, CANCELLED
}
数据分析模块
内容管理
评价系统
用户表(user)设计要点:
sql复制CREATE TABLE `user` (
`uid` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL COMMENT '登录账号',
`password` CHAR(32) NOT NULL COMMENT 'MD5加密',
`salt` CHAR(6) NOT NULL COMMENT '加密盐值',
`phone` VARCHAR(20) DEFAULT NULL,
`email` VARCHAR(100) DEFAULT NULL,
`avatar` VARCHAR(255) DEFAULT '/images/default-avatar.jpg',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`uid`),
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
商品表(product)关键字段:
sql复制CREATE TABLE `product` (
`pid` INT NOT NULL AUTO_INCREMENT,
`cid` INT NOT NULL COMMENT '分类ID',
`name` VARCHAR(100) NOT NULL,
`sub_title` VARCHAR(200) DEFAULT NULL COMMENT '副标题',
`original_price` DECIMAL(10,2) NOT NULL,
`promote_price` DECIMAL(10,2) NOT NULL,
`stock` INT NOT NULL DEFAULT 0,
`sales` INT DEFAULT 0 COMMENT '销量',
`image_url` VARCHAR(255) DEFAULT NULL,
`detail_html` TEXT COMMENT '商品详情HTML',
`status` TINYINT DEFAULT 1 COMMENT '1-上架 0-下架',
PRIMARY KEY (`pid`),
KEY `idx_cid` (`cid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
订单创建需要保证数据一致性:
java复制@Transactional
public Order createOrder(OrderDTO orderDTO) {
// 1. 扣减库存
productMapper.reduceStock(orderDTO.getPid(), orderDTO.getQuantity());
// 2. 创建订单
Order order = new Order();
BeanUtils.copyProperties(orderDTO, order);
orderMapper.insert(order);
// 3. 清空购物车
cartMapper.deleteByUserAndProduct(orderDTO.getUid(), orderDTO.getPid());
return order;
}
重要提示:@Transactional注解默认只对RuntimeException回滚,建议明确指定:
@Transactional(rollbackFor = Exception.class)
前端实现要点:
javascript复制// 添加购物车
function addToCart(pid) {
$.post('/cart/add', {pid: pid}, function(res) {
if(res.code == 200) {
// 更新角标
var count = parseInt($('#cart-count').text()) || 0;
$('#cart-count').text(count + 1);
}
});
}
后端关键逻辑:
java复制public void addCart(Integer uid, Integer pid, Integer num) {
// 检查商品是否存在且上架
Product product = productMapper.selectByPrimaryKey(pid);
if(product == null || product.getStatus() != 1) {
throw new BusinessException("商品不存在或已下架");
}
// 查询是否已有购物车记录
CartExample example = new CartExample();
example.createCriteria().andUidEqualTo(uid).andPidEqualTo(pid);
List<Cart> carts = cartMapper.selectByExample(example);
if(!carts.isEmpty()) {
// 更新数量
Cart cart = carts.get(0);
cart.setQuantity(cart.getQuantity() + num);
cartMapper.updateByPrimaryKey(cart);
} else {
// 新增记录
Cart cart = new Cart();
cart.setUid(uid);
cart.setPid(pid);
cart.setQuantity(num);
cart.setCreateTime(new Date());
cartMapper.insert(cart);
}
}
典型状态转换设计:
java复制public void payOrder(Integer oid) {
Order order = orderMapper.selectByPrimaryKey(oid);
if(order.getStatus() != OrderStatus.UNPAID) {
throw new BusinessException("订单状态异常");
}
// 模拟支付
order.setStatus(OrderStatus.PAID);
order.setPayTime(new Date());
orderMapper.updateByPrimaryKey(order);
// 记录支付日志
PayLog payLog = new PayLog();
payLog.setOid(oid);
payLog.setAmount(order.getAmount());
payLog.setCreateTime(new Date());
payLogMapper.insert(payLog);
}
问题现象:
当多个用户同时购买同一商品时,可能出现库存减为负数的情况。
解决方案:
sql复制UPDATE product
SET stock = stock - #{quantity}
WHERE pid = #{pid} AND stock >= #{quantity}
java复制public boolean reduceStockWithLock(Integer pid, Integer quantity) {
String lockKey = "product:" + pid;
String clientId = UUID.randomUUID().toString();
try {
// 获取锁
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, clientId, 30, TimeUnit.SECONDS);
if(locked) {
Product product = productMapper.selectByPrimaryKey(pid);
if(product.getStock() >= quantity) {
product.setStock(product.getStock() - quantity);
return productMapper.updateByPrimaryKey(product) > 0;
}
return false;
}
} finally {
// 释放锁
if(clientId.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
return false;
}
java复制@Cacheable(value = "products", key = "#pid")
public Product getProductById(Integer pid) {
return productMapper.selectByPrimaryKey(pid);
}
@CacheEvict(value = "products", key = "#pid")
public void updateProduct(Product product) {
productMapper.updateByPrimaryKey(product);
}
java复制// 错误写法:N+1查询问题
List<Order> orders = orderMapper.selectByUser(uid);
for(Order order : orders) {
Product product = productMapper.selectByPrimaryKey(order.getPid());
order.setProduct(product);
}
// 正确写法:关联查询
<select id="selectWithProduct" resultMap="OrderWithProduct">
SELECT o.*, p.name as pname, p.image_url
FROM order o
LEFT JOIN product p ON o.pid = p.pid
WHERE o.uid = #{uid}
</select>
java复制// 基于用户历史的简单推荐
public List<Product> recommendProducts(Integer uid) {
// 1. 获取用户购买记录
List<Order> orders = orderMapper.selectByUser(uid);
// 2. 提取商品类别
Set<Integer> cids = orders.stream()
.map(o -> productMapper.selectByPrimaryKey(o.getPid()).getCid())
.collect(Collectors.toSet());
// 3. 推荐同类别商品
ProductExample example = new ProductExample();
example.createCriteria().andCidIn(new ArrayList<>(cids));
example.setOrderByClause("sales DESC");
example.setLimit(5);
return productMapper.selectByExample(example);
}
个人经验:在指导毕业设计过程中,发现学生最容易忽视的是异常处理和日志记录。建议至少添加:
- 全局异常处理器(@ControllerAdvice)
- 操作日志(AOP实现)
- SQL执行日志(mybatis-plus性能分析插件)