在当前的电商浪潮中,垂直领域的精细化运营正成为提升竞争力的关键。针对零食这一高频次、多样化的消费品类,传统的单商户电商模式往往受限于品类单一、运营成本高、市场风险集中等问题。为此,我们设计并实现了一个基于SSM(Spring + SpringMVC + MyBatis)技术栈的多角色协作电商平台——"零食云市",旨在为中小型零食品牌商、个体经营者及区域代理商构建一个高效、灵活、可扩展的线上交易生态系统。
该系统通过引入多商户入驻机制,将平台角色清晰地划分为平台管理员、入驻商户和终端消费者,实现了资源的有效整合与职责的明确分离。商户可以专注于商品运营与客户服务,平台方则负责生态治理与流量分配,共同为消费者提供丰富的一站式购物体验。
系统架构与技术栈
"零食云市"采用经典的三层架构设计,严格遵循MVC模式,确保了代码的高内聚、低耦合。
后端技术栈:
- Spring Framework:作为核心控制容器,管理所有Bean的生命周期,通过依赖注入(DI)和面向切面编程(AOP)实现业务组件的解耦。
- SpringMVC:作为Web层框架,通过DispatcherServlet统一处理HTTP请求路由,结合自定义拦截器实现精细化的权限控制与会话管理。
- MyBatis:作为持久层框架,通过XML映射文件编写灵活的动态SQL,高效完成复杂的数据操作。
- Maven:用于项目构建和依赖管理,确保项目结构的标准化和第三方库的版本一致性。
前端技术栈:
- JSP + JSTL:用于服务端页面渲染,结合JSTL标签库简化页面逻辑。
- jQuery + AJAX:实现前端表单验证、动态数据加载和无刷新交互,提升用户体验。
数据库:
- MySQL 5.7+:作为关系型数据库,存储系统所有业务数据,采用InnoDB引擎保障事务安全。
数据库设计亮点
数据库设计是系统稳定性的基石。"零食云市"的数据库包含22张表,以下重点分析几个核心表的设计亮点。
1. 商品表(product)设计分析
CREATE TABLE `product` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`productno` varchar(255) DEFAULT NULL COMMENT '商品编号',
`productname` varchar(255) DEFAULT NULL COMMENT '商品名称',
`filename` varchar(255) DEFAULT NULL COMMENT '商品图片',
`price` decimal(10,2) DEFAULT NULL COMMENT '销售价格',
`tprice` decimal(10,2) DEFAULT NULL COMMENT '市场价格',
`fid` varchar(255) DEFAULT NULL COMMENT '一级分类',
`sid` varchar(255) DEFAULT NULL COMMENT '二级分类',
`content` text DEFAULT NULL COMMENT '商品描述',
`delstatus` varchar(255) DEFAULT NULL COMMENT '删除状态',
`issj` varchar(255) DEFAULT NULL COMMENT '是否上架',
`istj` varchar(255) DEFAULT NULL COMMENT '是否推荐',
`saver` varchar(255) DEFAULT NULL COMMENT '操作人',
`productid` varchar(255) DEFAULT NULL COMMENT '商品ID',
`leibie` varchar(255) DEFAULT NULL COMMENT '类别',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=139 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='商品表'
设计亮点:
- 价格精度控制:使用
decimal(10,2)类型存储价格,精确到分,避免浮点数计算误差。 - 分类体系设计:通过
fid(一级分类)和sid(二级分类)字段实现多级商品分类,支持灵活的商品组织方式。 - 软删除机制:
delstatus字段实现软删除,保留历史数据的同时避免物理删除带来的关联问题。 - 商品状态管理:
issj(是否上架)和istj(是否推荐)字段实现商品的多维度状态控制。 - 索引优化建议:在实际生产环境中,建议对
productno(商品编号)、fid、sid等查询频繁的字段建立索引。
2. 订单主表(ordermsg)设计分析
CREATE TABLE `ordermsg` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`ddno` varchar(255) DEFAULT NULL COMMENT '订单号',
`memberid` varchar(255) DEFAULT NULL COMMENT '会员ID',
`productid` varchar(255) DEFAULT NULL COMMENT '商品ID',
`num` int(11) DEFAULT NULL COMMENT '购买数量',
`total` double(255,2) DEFAULT NULL COMMENT '订单总额',
`fkstatus` varchar(255) DEFAULT NULL COMMENT '付款状态',
`shstatus` varchar(11) DEFAULT NULL COMMENT '收货状态',
`addr` varchar(255) DEFAULT NULL COMMENT '收货地址',
`savetime` varchar(255) DEFAULT NULL COMMENT '下单时间',
`delstatus` varchar(255) DEFAULT NULL COMMENT '删除状态',
`shfs` varchar(255) DEFAULT NULL COMMENT '配送方式',
`zffs` varchar(255) DEFAULT NULL COMMENT '支付方式',
`saver` varchar(255) DEFAULT NULL COMMENT '操作人',
`isdd` varchar(255) DEFAULT NULL COMMENT '是否订单',
`fid` varchar(255) DEFAULT NULL COMMENT '父级ID',
`goodsid` varchar(255) DEFAULT NULL COMMENT '商品ID',
`goodstype` varchar(255) DEFAULT NULL COMMENT '商品类型',
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='订单主表'
设计亮点:
- 订单状态跟踪:通过
fkstatus(付款状态)、shstatus(收货状态)等多状态字段,完整记录订单生命周期。 - 订单号生成策略:
ddno字段存储唯一订单号,建议采用"时间戳+随机数+商户ID"的生成策略避免重复。 - 配送与支付方式:
shfs(配送方式)和zffs(支付方式)字段支持多种业务场景的扩展。 - 父子订单设计:通过
fid(父级ID)和isdd(是否订单)字段支持拆分订单和合并支付等复杂业务逻辑。

3. 地址表(address)设计分析
CREATE TABLE `address` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(255) DEFAULT NULL COMMENT '收货人姓名',
`tel` varchar(255) DEFAULT NULL COMMENT '联系电话',
`addr` text DEFAULT NULL COMMENT '详细地址',
`ismr` varchar(255) DEFAULT NULL COMMENT '是否默认地址',
`delstatus` varchar(255) DEFAULT NULL COMMENT '删除状态',
`memberid` varchar(255) DEFAULT NULL COMMENT '会员ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='地址表'
设计亮点:
- 默认地址标识:
ismr字段实现默认地址管理,简化用户下单流程。 - 软删除支持:
delstatus字段确保地址数据的历史可追溯性。 - 用户关联设计:
memberid字段建立与用户表的关联,支持用户多地址管理。
核心功能实现
1. 多商户商品管理机制
"零食云市"的核心特色在于多商户协作模式。每个入驻商户拥有独立的后台管理系统,可以自主完成商品上架、价格调整、库存管理等操作。
商品上架控制器实现:
@Controller
@RequestMapping("/seller")
public class ProductManageController {
@Resource
private ProductDAO productDAO;
@Resource
private CategoryDAO categoryDAO;
@RequestMapping("/productAdd")
public String productAdd(HttpServletRequest request) {
// 获取当前登录商户信息
Seller seller = (Seller) request.getSession().getAttribute("seller");
List<Category> categoryList = categoryDAO.selectAll();
request.setAttribute("categoryList", categoryList);
return "seller/productadd";
}
@RequestMapping("/productInsert")
public String productInsert(Product product, HttpServletRequest request) {
Seller seller = (Seller) request.getSession().getAttribute("seller");
// 生成商品编号:商户ID+时间戳
String productNo = seller.getId() + "_" + System.currentTimeMillis();
product.setProductno(productNo);
product.setSaver(seller.getUsername());
product.setDelstatus("0");
product.setIssj("1");
productDAO.add(product);
return "redirect:productList.do";
}
@RequestMapping("/productList")
public String productList(HttpServletRequest request) {
Seller seller = (Seller) request.getSession().getAttribute("seller");
String key = request.getParameter("key");
Map<String, Object> map = new HashMap<>();
map.put("key", key);
map.put("saver", seller.getUsername());
List<Product> list = productDAO.selectAll(map);
request.setAttribute("list", list);
request.setAttribute("key", key);
return "seller/productlist";
}
}
商品实体类设计:
package com.entity;
import java.math.BigDecimal;
public class Product {
private int id;
private String productno;
private String productname;
private String filename;
private BigDecimal price;
private BigDecimal tprice;
private String fid;
private String sid;
private String content;
private String delstatus;
private String issj;
private String istj;
private String saver;
private String productid;
private String leibie;
// Getter和Setter方法
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getProductno() { return productno; }
public void setProductno(String productno) { this.productno = productno; }
public BigDecimal getPrice() { return price; }
public void setPrice(BigDecimal price) { this.price = price; }
// 其他Getter/Setter方法...
@Override
public String toString() {
return "Product [id=" + id + ", productname=" + productname +
", price=" + price + ", issj=" + issj + "]";
}
}

2. 智能购物车与订单处理
系统实现了完整的购物车功能和订单处理流程,支持多商品合并下单、库存校验、价格计算等复杂业务逻辑。
购物车服务实现:
@Service
public class CartService {
@Resource
private ProductDAO productDAO;
@Resource
private OrderMsgDAO orderMsgDAO;
/**
* 添加商品到购物车
*/
public void addToCart(HttpServletRequest request, String productId, int quantity) {
HttpSession session = request.getSession();
Map<String, CartItem> cart = (Map<String, CartItem>) session.getAttribute("cart");
if (cart == null) {
cart = new HashMap<>();
session.setAttribute("cart", cart);
}
Product product = productDAO.findById(Integer.parseInt(productId));
if (product != null && "1".equals(product.getIssj())) {
CartItem item = cart.get(productId);
if (item != null) {
item.setQuantity(item.getQuantity() + quantity);
} else {
item = new CartItem(product, quantity);
cart.put(productId, item);
}
}
}
/**
* 生成订单
*/
public String createOrder(HttpServletRequest request, String addressId, String payType) {
Member member = (Member) request.getSession().getAttribute("member");
Map<String, CartItem> cart = (Map<String, CartItem>) request.getSession().getAttribute("cart");
if (cart == null || cart.isEmpty()) {
return "购物车为空";
}
// 生成订单号
String orderNo = generateOrderNo(member.getId());
double totalAmount = 0.0;
for (CartItem item : cart.values()) {
OrderMsg order = new OrderMsg();
order.setDdno(orderNo);
order.setMemberid(String.valueOf(member.getId()));
order.setProductid(String.valueOf(item.getProduct().getId()));
order.setNum(item.getQuantity());
order.setTotal(item.getProduct().getPrice().doubleValue() * item.getQuantity());
order.setFkstatus("待付款");
order.setShstatus("待发货");
order.setZffs(payType);
order.setSavetime(new Date().toString());
orderMsgDAO.add(order);
totalAmount += order.getTotal();
}
// 清空购物车
request.getSession().removeAttribute("cart");
return "订单生成成功,总金额:" + totalAmount;
}
private String generateOrderNo(int memberId) {
return memberId + "_" + System.currentTimeMillis();
}
}

3. 权限管理与会话控制
系统通过自定义拦截器实现精细化的权限控制,确保不同角色只能访问其授权范围内的功能。
权限拦截器实现:
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
HttpSession session = request.getSession();
// 卖家后台权限校验
if (uri.contains("/seller/")) {
Seller seller = (Seller) session.getAttribute("seller");
if (seller == null) {
response.sendRedirect(request.getContextPath() + "/seller/login.jsp");
return false;
}
}
// 管理员后台权限校验
if (uri.contains("/admin/")) {
Admin admin = (Admin) session.getAttribute("admin");
if (admin == null) {
response.sendRedirect(request.getContextPath() + "/admin/login.jsp");
return false;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
// 后处理逻辑
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// 请求完成后的清理工作
}
}
SpringMVC配置:
<!-- springmvc-servlet.xml -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/seller/**"/>
<mvc:mapping path="/admin/**"/>
<bean class="com.interceptor.AuthInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>

4. 数据持久层优化
系统采用MyBatis作为ORM框架,通过动态SQL和结果映射实现高效的数据访问。
商品DAO接口与映射文件:
public interface ProductDAO {
List<Product> selectAll(Map<String, Object> map);
void add(Product product);
void update(Product product);
Product findById(int id);
void delete(int id);
List<Product> searchByKeywords(String keywords);
}
<!-- ProductMapper.xml -->
<mapper namespace="com.dao.ProductDAO">
<select id="selectAll" parameterType="map" resultType="com.entity.Product">
SELECT * FROM product
WHERE delstatus='0'
<if test="key != null and key != ''">
AND (productname LIKE CONCAT('%',#{key},'%')
OR productno LIKE CONCAT('%',#{key},'%'))
</if>
<if test="saver != null and saver != ''">
AND saver = #{saver}
</if>
ORDER BY id DESC
</select>
<insert id="add" parameterType="com.entity.Product">
INSERT INTO product(
productno, productname, filename, price, tprice,
fid, sid, content, delstatus, issj, istj, saver
) VALUES(
#{productno}, #{productname}, #{filename}, #{price}, #{tprice},
#{fid}, #{sid}, #{content}, '0', #{issj}, #{istj}, #{saver}
)
</insert>
<update id="update" parameterType="com.entity.Product">
UPDATE product SET
productname=#{productname}, filename=#{filename},
price=#{price}, tprice=#{tprice}, fid=#{fid}, sid=#{sid},
content=#{content}, issj=#{issj}, istj=#{istj}
WHERE id=#{id}
</update>
</mapper>
5. 前台页面数据渲染
系统使用JSP结合JSTL标签库实现服务端页面渲染,确保数据展示的准确性和一致性。
商品列表页面示例:
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>商品列表 - 零食云市</title>
</head>
<body>
<div class="product-container">
<c:forEach items="${productList}" var="product" varStatus="status">
<div class="product-item">
<div class="product-image">
<img src="${pageContext.request.contextPath}/upload/${product.filename}"
alt="${product.productname}