1. 项目背景与核心需求
超市进货管理系统是零售行业最基础也最关键的数字化工具之一。我去年为本地连锁超市实施的这套系统,上线后帮助他们将库存周转率提升了37%。这个基于JSP的解决方案特别适合中小型商超,既能满足基础业务需求,又避免了过度复杂的技术架构。
传统手工管理进货流程存在几个痛点:采购员需要反复核对纸质单据、库存数据更新滞后、供应商对账周期长。我们设计的系统主要解决以下问题:
- 实时库存可视化(避免断货/积压)
- 自动化采购建议(基于历史销售数据)
- 供应商全生命周期管理
- 多维度报表分析(ABC分类、周转率等)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型解析
2.1 为什么选择JSP+SSM组合
在2023年仍然选择JSP技术栈主要基于几点考量:
- 维护成本:超市IT人员普遍更熟悉传统JavaWeb技术
- 快速开发:配合SSM框架可实现日均3-4个功能模块的开发速度
- 稳定性:经测试单服务器可稳定支撑200+并发请求
技术栈具体构成:
- 前端:JSP+JSTL+Bootstrap3(兼容IE11)
- 后端:Spring5+SpringMVC+MyBatis3
- 数据库:MySQL8.0(必须使用InnoDB集群方案)
- 服务器:Tomcat9+jdk1.8
注意:避免在JSP中直接写Java代码,我们采用TagLib+EL表达式实现前后端分离,这是保持代码可维护性的关键
2.2 数据库设计要点
进货系统的核心表结构设计有几个特殊处理:
sql复制CREATE TABLE `purchase_order` (
`id` varchar(20) NOT NULL COMMENT 'PO20230701-001格式',
`supplier_id` int(11) NOT NULL,
`total_amount` decimal(12,2) DEFAULT 0.00,
`payment_status` tinyint(4) DEFAULT 0 COMMENT '0-未付 1-部分付 2-已结清',
`warehouse_id` int(11) NOT NULL,
`creator_id` int(11) NOT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_supplier` (`supplier_id`),
KEY `idx_creator` (`creator_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特别设计的字段包括:
- 采购单号包含日期流水号(方便线下沟通)
- 金额使用DECIMAL(12,2)避免浮点误差
- 建立供应商和创建人索引加速查询
3. 核心功能实现细节
3.1 智能补货算法
在PurchaseService中实现的补货逻辑:
java复制public List<PurchaseItem> generateAutoPurchase(List<InventoryAlert> alerts) {
return alerts.stream().map(alert -> {
PurchaseItem item = new PurchaseItem();
// 安全库存 = 日均销量 × 采购周期 × 系数1.2
double dailySales = salesStatDao.getAvgDailySales(alert.getGoodsId());
int leadTime = supplierDao.getLeadTime(alert.getMainSupplierId());
int safetyStock = (int)Math.ceil(dailySales * leadTime * 1.2);
item.setGoodsId(alert.getGoodsId());
item.setQuantity(safetyStock - alert.getCurrentStock());
item.setSuggestedPrice(purchaseDao.getLastPrice(alert.getGoodsId()));
return item;
}).collect(Collectors.toList());
}
这个算法在实际使用中需要注意:
- 系数1.2需要根据商品保质期调整(短保商品适当降低)
- 采购周期应取供应商承诺时间和历史实际时间的最大值
- 需要定期人工校准日均销量数据
3.2 采购审批工作流
我们采用状态机模式实现多级审批:
mermaid复制stateDiagram-v2
[*] --> DRAFT
DRAFT --> MANAGER_APPROVAL: 提交
MANAGER_APPROVAL --> FINANCE_REVIEW: 通过
MANAGER_APPROVAL --> REJECTED: 拒绝
FINANCE_REVIEW --> APPROVED: 通过
FINANCE_REVIEW --> REJECTED: 拒绝
APPROVED --> [*]
对应的状态转移代码:
java复制public void submitApproval(String orderId, int userId) {
PurchaseOrder order = orderDao.get(orderId);
if (order.getStatus() != Status.DRAFT) {
throw new IllegalStateException("只有草稿状态可提交");
}
order.setStatus(Status.MANAGER_APPROVAL);
order.setUpdateTime(new Date());
orderDao.update(order);
// 发送企业微信通知
wechatNotify.sendToManager(order.getCreatorId());
}
4. 性能优化实践
4.1 库存快照设计
为解决实时库存查询压力,我们采用双写机制:
- 实时更新inventory表(InnoDB)
- 每天凌晨生成inventory_snapshot(MyISAM压缩表)
查询优化对比:
| 查询类型 | 原方案响应时间 | 快照方案响应时间 |
|---|---|---|
| 单商品查询 | 120ms | 35ms |
| 全仓盘点 | 2800ms | 400ms |
| 分类统计 | 1500ms | 200ms |
4.2 MyBatis二级缓存陷阱
我们在商品分类查询中遇到的典型问题:
xml复制<cache eviction="LRU" size="1024" readOnly="true"/>
问题现象:分类变更后,前端仍显示旧数据
解决方案:
- 对配置类数据关闭二级缓存
- 改用手动缓存控制:
java复制@CacheEvict(value="categoryCache", key="#root.targetClass")
public void updateCategory(Category category) {
categoryDao.update(category);
}
5. 部署与运维要点
5.1 Tomcat连接池配置
在server.xml中关键参数:
xml复制<Resource name="jdbc/InventoryDB"
auth="Container"
type="javax.sql.DataSource"
maxTotal="100"
maxIdle="30"
maxWaitMillis="10000"
validationQuery="SELECT 1"
testOnBorrow="true"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
username="dbuser"
password="加密后的密码"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://cluster-01:3306/inventory?useSSL=false&serverTimezone=Asia/Shanghai"/>
必须调整的参数:
- maxTotal不要超过数据库最大连接数的80%
- 生产环境必须配置validationQuery
- 使用连接池加密工具处理密码
5.2 日志分割策略
采用logback的按日滚动策略:
xml复制<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/purchase.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/purchase.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
关键技巧:
- 对支付相关日志单独配置appender
- 使用MDC记录操作人信息
- 日志文件保留周期与财务对账周期对齐
6. 典型问题排查记录
6.1 中文乱码问题
现象:供应商名称显示为"???"
解决方案链:
- 确认数据库连接串添加characterEncoding=utf8mb4
- 检查Tomcat的server.xml中URIEncoding="UTF-8"
- 验证JSP页面头部声明<%@ page contentType="text/html;charset=UTF-8"%>
- 确保MySQL的character_set_server=utf8mb4
6.2 日期显示异常
问题代码:
jsp复制<fmt:formatDate value="${order.createTime}" pattern="yyyy-MM-dd"/>
正确做法:
jsp复制<fmt:parseDate value="${order.createTime}" pattern="yyyy-MM-dd HH:mm:ss" var="parsedDate"/>
<fmt:formatDate value="${parsedDate}" pattern="yyyy-MM-dd"/>
根本原因:MySQL返回的datetime格式与JSTL预期格式不匹配
7. 安全防护方案
7.1 SQL注入防护
除了使用MyBatis预编译,额外添加的防护措施:
- 在web.xml配置过滤器:
xml复制<filter>
<filter-name>sqlInjectionFilter</filter-name>
<filter-class>com.util.SqlInjectionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sqlInjectionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 关键业务接口添加注解校验:
java复制@Validated
public void createOrder(@SQLInjectionCheck String remarks) {
// ...
}
7.2 XSS防护
采用三层防护:
- 前端使用DOMPurify过滤
- 后端使用Spring HtmlUtils
- 数据库存储原始数据+转义后数据双份
在JSP中的安全输出:
jsp复制<c:out value="${userInput}" escapeXml="true"/>
8. 项目演进建议
系统上线后可以考虑的扩展方向:
- 移动端验收功能(PDA扫码)
- 与财务系统对接(自动生成凭证)
- 供应商门户(自助对账)
- 引入Redis缓存热销商品数据
性能优化路线图:
- 第一阶段:SQL优化+索引调整
- 第二阶段:引入读写分离
- 第三阶段:热点数据缓存
- 最终阶段:分库分表(当SKU超过500万时)
这套系统在3家超市的实际运行数据显示,平均帮助减少15%的库存资金占用,采购员工作效率提升约40%。特别提醒在实施时要注意与现有ERP系统的接口规范,我们吃过数据格式不统一的亏
