1. 项目概述与背景
中小型商户在日常经营中面临商品管理效率低下的痛点。我曾接手过一家社区超市的数字化改造项目,店主王姐用Excel表格管理2000多种商品,每次盘点库存都要加班到凌晨。这正是当前中小商户的典型困境——他们既用不起淘宝京东那样的重型系统,又无法忍受手工管理的低效。
这个基于SSM框架的商品管理系统,正是为解决这类问题而生。它采用Spring+SpringMVC+MyBatis技术栈,实现了用户权限管理、商品分类体系构建和商品信息维护三大核心功能。相比市面上的大型电商系统,我们的设计有三大差异化优势:部署成本降低80%(只需1核2G服务器)、功能聚焦核心需求(去除冗余模块)、学习曲线平缓(界面操作10分钟可上手)。
2. 技术选型与架构设计
2.1 为什么选择SSM框架
在技术选型阶段,我们对比了三种主流方案:
- SpringBoot(约定大于配置,适合快速开发)
- SSH(Struts2+Spring+Hibernate,较陈旧)
- SSM(Spring+SpringMVC+MyBatis,灵活可控)
最终选择SSM框架组合主要基于以下考量:
- 教学价值:Spring的IoC/AOP、SpringMVC的请求分发、MyBatis的SQL优化,能完整展示JavaEE核心技术栈
- 可控性:XML+注解的混合配置方式,比SpringBoot的自动配置更利于理解底层机制
- 生态成熟:国内企业级应用占比超60%,社区资源丰富
实际开发中发现:Spring 5.2.6 + SpringMVC 5.2.6 + MyBatis 3.5.4的组合兼容性最佳,需特别注意spring-webmvc和mybatis-spring的版本匹配
2.2 系统分层架构
系统采用经典三层架构,但做了针对性优化:
code复制表现层:SpringMVC + JSP/Vue.js
业务层:Spring Service + 自定义AOP日志
持久层:MyBatis + PageHelper分页插件
数据库设计采用"一业务一事务"原则:
- 用户表(user)增加salt字段增强密码安全
- 分类表(category)使用path枚举法存储层级关系
- 商品表(product)与分类表建立N:1关联
3. 核心功能实现细节
3.1 多级商品分类实现
传统邻接表模型(parent_id方式)在查询子分类时需要递归查询,我们改进为path枚举模式:
sql复制CREATE TABLE `category` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`path` varchar(255) DEFAULT '0', -- 存储如"0,1,5"表示1>5>当前
`level` tinyint(4) DEFAULT 1,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
查询所有子分类只需一次SQL:
java复制@Select("SELECT * FROM category WHERE path LIKE '${parentPath}%'")
List<Category> findSubCategories(@Param("parentPath") String parentPath);
3.2 商品分页查询优化
结合MyBatis的PageHelper和覆盖索引技术,实现毫秒级响应:
xml复制<!-- 商品Mapper.xml -->
<select id="selectByCondition" resultMap="ProductResult">
SELECT
p.id, p.name, p.price, c.name as categoryName
FROM
product p
LEFT JOIN
category c ON p.category_id = c.id
<where>
<if test="name != null">p.name LIKE CONCAT('%',#{name},'%')</if>
<if test="categoryId != null">AND p.category_id = #{categoryId}</if>
</where>
ORDER BY p.create_time DESC
</select>
Service层调用示例:
java复制public PageInfo<ProductVO> queryProducts(ProductQuery query, int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<Product> products = productMapper.selectByCondition(query);
return new PageInfo<>(convertToVOList(products));
}
4. 开发踩坑实录
4.1 文件上传漏洞防护
初期实现的文件上传功能存在安全风险,我们通过三重防护加固:
- 前端校验:限制文件类型为jpg/png,大小<2MB
javascript复制// Vue组件
watch: {
'uploadFile'(file) {
if(!['image/jpeg','image/png'].includes(file.type)) {
this.$message.error('仅支持JPEG/PNG格式');
return false;
}
if(file.size > 2 * 1024 * 1024) {
this.$message.error('文件大小超过2MB限制');
return false;
}
}
}
- 后端校验:双重检查文件魔数
java复制public boolean isImage(InputStream is) throws IOException {
byte[] header = new byte[8];
is.read(header);
return Arrays.equals(header, JPEG_HEADER) ||
Arrays.equals(header, PNG_HEADER);
}
- 存储安全:使用UUID重命名+独立存储目录
properties复制# application.properties
file.upload-dir=/var/upload/
file.access-prefix=/static/
4.2 事务管理陷阱
商品入库操作涉及多表更新,最初未加事务导致数据不一致。解决方案:
java复制@Service
public class ProductServiceImpl implements ProductService {
@Transactional(rollbackFor = Exception.class)
public void addProduct(Product product, List<Inventory> inventories) {
productMapper.insert(product); // 插入商品
inventoryBatchMapper.insertBatch(inventories); // 批量插入库存
categoryMapper.updateProductCount(product.getCategoryId()); // 更新分类商品数
}
}
关键配置:
xml复制<!-- spring-context.xml -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
5. 部署与性能调优
5.1 生产环境部署方案
推荐使用Docker Compose一键部署:
dockerfile复制version: '3'
services:
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql-data:/var/lib/mysql
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
关键优化参数:
properties复制# Tomcat配置
server.tomcat.max-threads=200
server.tomcat.accept-count=100
# MyBatis缓存
mybatis.configuration.cache-enabled=true
mybatis.configuration.local-cache-scope=statement
5.2 性能测试对比
使用JMeter模拟100并发测试:
| 场景 | 平均响应时间 | 错误率 |
|---|---|---|
| 无缓存商品查询 | 320ms | 0.2% |
| 开启二级缓存后 | 45ms | 0% |
| 分页查询(页大小20) | 68ms | 0% |
缓存配置示例:
java复制@CacheNamespace(implementation = RedisCache.class)
public interface ProductMapper {
@Select("SELECT * FROM product WHERE id=#{id}")
@Options(useCache = true)
Product selectById(Integer id);
}
6. 项目扩展建议
6.1 功能扩展方向
- 移动端适配:增加微信小程序端,使用uni-app跨平台方案
- 数据分析:集成ECharts实现销售趋势可视化
- 库存预警:设置阈值自动邮件通知
6.2 教学应用建议
适合作为计算机专业下列课程的实践项目:
- 《JavaEE开发》SSM框架整合实训
- 《数据库原理》索引优化案例分析
- 《软件工程》从需求到部署的全流程实践
我在实际教学中发现,学生最容易在以下环节出错:
- MyBatis的#{}和${}混淆使用导致SQL注入
- Spring事务注解未正确配置导致不回滚
- 前端表单未做二次验证直接提交
建议采用"分阶段验收"方式:
- 第一阶段验收数据库设计和MyBatis基础CRUD
- 第二阶段验收SpringMVC接口和页面交互
- 最终验收完整业务流程和性能优化