基于MD5算法的连锁超市物资供应管理系统设计与实现
2026/9/9 2:37:10 网站建设 项目流程

1. 项目概述与整体设计思路

1.1 这个系统到底解决什么问题

连锁超市在运营里最头疼的事情,其实不是货架上的东西卖不出去,而是"该补的货不知道什么时候补""不该囤的货占了一堆资金"。我当年做毕业设计时就盯准了这个真实痛点,于是把课题定为"基于 MD5 算法的连锁超市物资供应管理系统",核心落在三个字:供应、采购、库存。具体一点就是做一个智能连锁超市物资供应管理平台,覆盖门店要货、总部采购、供应商发货、仓库入库、门店库存变动的完整链路,再从数据里长出报表,让管理员一眼看清哪些商品滞销、哪些商品快断货。

这个系统能解决的问题非常具体:第一,采购计划从门店销售数据和库存下限自动生成,不再靠人工拍脑袋;第二,入库、出库、盘点操作全部留痕,账实不一致的时候能追溯到单据;第三,不同角色分权限登录,登录密码用 MD5 加密存储,避免数据库泄露后密码裸奔。对计算机专业的同学来说,这个课题最大的价值在于它把 Java 技术栈、数据库设计、加密算法、业务逻辑、前端交互全都串起来了,是一个能讲清楚、能演示、能答辩的完整闭环项目。

1.2 为什么选 Java + MD5 这套组合

选 Java 不是因为它最潮,而是因为它最稳。连锁超市物资供应管理系统本质上是典型的企业级 CRUD + 流程审批 + 报表统计,这种场景下 Spring Boot + MyBatis 的开发效率极高,事务控制成熟,部署也方便。JDK 8 以上就能跑,不用折腾环境;Java 社区里关于权限、分页、导出、定时任务的轮子一抓一大把,遇到问题搜到的解决方案也最多。对毕设来说,技术难度适中,但又能体现完整的工程能力。

MD5 这个点需要说清楚。我是把 MD5 用在两个地方:一是用户登录密码的摘要存储,二是物资编码或者单据编号的防篡改校验。毕设题目既然挂了"基于 MD5 算法",那在系统里就必须有它不可替代的位置,不能只拿来做个登录就完事。我在设计里给每个物资批次生成唯一的 MD5 校验码,入库时把关键字段拼接后计算摘要,后续出库时重新计算比对,一旦中间有人改过数量或者批次信息,校验就不通过,这样就把"加密"从口号变成了实实在在的业务功能。

1.3 功能模块拆解

整个平台按照角色分成四类:系统管理员、总部采购员、仓库管理员、门店店长。每个角色看到的菜单和操作权限不同,权限控制用拦截器 + 角色字段实现,不需要引入特别重的权限框架。

核心模块有五块。第一块是基础数据管理,包含供应商档案、商品类别、计量单位、门店信息;第二块是采购管理,包含采购计划生成、采购单创建、供应商发货确认;第三块是库存管理,包含入库单、出库单、库存盘点、库存预警;第四块是门店要货管理,门店根据实时库存提交要货申请,总部审核后转入采购流程;第五块是系统管理,包含用户管理、角色管理、MD5 校验工具、操作日志。

门店要货和总部采购之间的联动是这个项目的业务亮点。门店提交要货单后,总部汇总所有门店的需求量,减去仓库现有可用库存,再结合供应商起订量生成采购计划。这样做的好处是既避免每个门店各买各的导致库存分散,又能通过合并采购压低单价,逻辑上完全站得住脚,答辩时老师问业务设计思路,你能把这条链路说清楚就已经赢了一半。

2. 技术选型与核心原理分析

2.1 技术栈与项目结构规划

我的项目用的是 Spring Boot 2.7 + MyBatis Plus + MySQL 8.0,前端采用 Thymeleaf 模板引擎 + Bootstrap + jQuery。没有拆前后端分离,因为毕设场景下单体应用更容易把控,部署也简单,一个 jar 包丢到服务器上就能跑。如果你对 Vue 更熟,也可以拆成 Spring Boot 后端 + Vue 前端,但那样要多处理跨域、Token 认证、接口文档这些事,工作量会明显上涨。

Java 环境建议直接用 JDK 8 或者 JDK 11。很多同学卡在环境配置上,其实核心就三步:装 JDK、配 JAVA_HOME、把 bin 目录加到 Path。配完之后在命令行敲java -version,能输出版本号就算成功。IDE 我用的是 IntelliJ IDEA,社区版就够用;数据库工具方面,Navicat 和 DataGrip 都行,个人习惯用 Navicat 建表导数据比较快。

项目包结构建议这样规划:

com.supermarket ├── controller ├── service ├── mapper ├── entity ├── dto ├── vo ├── utils ├── config └── interceptor

实体类对应数据库表,DTO 用于接收前端请求参数,VO 用于返回给页面的组装数据,utils 里放 MD5 工具类、日期工具类、导出工具类。分层清晰之后,写代码和写论文都省事,论文里的架构图、流程图直接照着画就行。

2.2 MD5 算法原理、加盐方案与安全边界

MD5 是 Message-Digest Algorithm 5 的缩写,输入任意长度的字节数据,输出固定 128 位,一般表示为 32 位的十六进制字符串。它的核心特点是雪崩效应:哪怕原文只改一个字符,生成的摘要也完全不同。所以它常被用于校验文件完整性、判断数据是否被篡改。

不过必须说清楚,MD5 在密码存储这个场景下已经不算安全。彩虹表攻击可以很快反查常见弱口令,网上甚至直接有 MD5 解密网站。所以我在毕设里做了两个改进:一是加盐,每个用户注册时生成一段随机盐值,存储密码时计算MD5(盐值 + 明文密码),这样相同密码在不同用户下的摘要不同,彩虹表直接失效;二是在代码里把"MD5 + 盐值"封装成独立工具类,后续如果要升级成 SHA-256 或者 BCrypt,只需要改动一个类,不影响业务代码。

MD5 校验物资编码的做法是这样的:把商品编号、批次号、入库数量、入库时间拼接成一个字符串,然后计算 MD5 值,将该值作为该批次的"指纹"存到表里。出库时重新按相同规则计算,与库里存的指纹比对,不一致就说明单据在流转过程中被改过。这个设计在答辩时非常加分,因为它体现了"技术服务于业务防篡改"的思考,而不只是机械地调用一个加密函数。

2.3 数据库表结构设计思路

数据库设计决定了这个项目能写到什么深度。我的核心表有九张,简单列一下:

表名作用关键字段
sys_user用户表id, username, password, salt, role_id, store_id
supplier供应商表id, supplier_name, contact, phone
category商品分类表id, category_name, parent_id
product商品表id, product_code, name, category_id, unit, price
store门店表id, store_name, address, manager
purchase_order采购单表id, order_no, supplier_id, status, total_amount
purchase_order_item采购单明细表id, order_id, product_id, quantity, price
stock库存表id, store_id, product_id, quantity, md5_code
stock_log库存流水表id, store_id, product_id, change_type, change_qty, operator_id

库存表设计值得多说两句。很多初学者会把库存字段直接放在商品表里,这样很简单,但一遇到多门店就废了。连锁超市一定是有多个门店的,每个门店对同一商品有独立库存,所以库存表必须以store_id + product_id作为唯一维度。库存流水表则记录每一次变动,入库、出库、盘点调整都写流水,这样既能追溯又能对账。

采购单用主表 + 明细表的设计,是为了支持一张采购单包含多种商品,同时每种商品的数量和价格各自独立存储。主表存供应商、总金额、状态这些公共信息,明细表存具体商品项。表之间用外键逻辑关联,但物理上不建外键约束,为了性能和删除方便,靠程序保证数据一致性。

3. 实操过程:从建表到入库扣库存的完整实现

3.1 环境准备与项目初始化

先把开发环境跑通。我用的是 JDK 8,Maven 3.8,MySQL 8.0。新建一个 Spring Boot 工程,pom.xml里引入必要依赖:spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、thymeleaf、lombok、druid 连接池。

配置文件application.yml里核心内容如下:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/supermarket?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource thymeleaf: cache: false mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

这里有一个特别容易踩的坑:数据库连接 URL 必须显式指定characterEncoding=utf8,否则前后端传中文会出现乱码。MySQL 8 还要加serverTimezone=Asia/Shanghai,不然日期类型会报时区错误。

建表时把公共字段加上:create_timeupdate_timedeleted逻辑删除标记。MyBatis Plus 支持逻辑删除,可以避免物理删除带来的数据追溯问题,对毕设项目来说也是个加分项。初始化几条演示数据,包括两个门店、三个供应商、十几样商品,方便后面测试。

3.2 MD5 工具类与用户登录认证实现

MD5 工具类是整个项目里复用率最高的类,我把它写得尽量通用。代码核心逻辑是这样的:

public class Md5Util { public static String md5(String input) { if (input == null || input.length() == 0) { return null; } try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(input.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte b : digest) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) { sb.append('0'); } sb.append(hex); } return sb.toString(); } catch (Exception e) { throw new RuntimeException("MD5 encryption failed", e); } } public static String addSalt(String password, String salt) { return md5(salt + password); } public static String generateSalt() { return UUID.randomUUID().toString().replace("-", "").substring(0, 16); } public static String generateMd5Code(String... fields) { String raw = String.join("|", fields); return md5(raw); } }

generateSalt()用 UUID 生成 16 位随机盐值,addSalt把盐拼在明文前面再计算摘要,generateMd5Code用竖线分隔符拼接多个字段,生成物资校验码。竖线分隔符很重要,如果直接拼接字符串,"12" + "3""1" + "23"会得到同样的结果,加了分隔符就不会混淆。

登录认证流程这样写:前端提交用户名和密码,后端先用用户名查出用户及其 salt,然后用Md5Util.addSalt(rawPassword, salt)计算结果,与数据库里的 password 字段比对。比对成功后把用户信息放进 Session,同时记录一条登录日志。

这里我想强调一个细节:永远不要在日志里打印用户的原始密码和完整摘要。有一次我调试时打印了用户信息对象,因为实体类重写了 toString 方法,结果把密码摘要全部打到了控制台。在毕设阶段问题不大,但养成好习惯很重要。

3.3 采购入库与库存扣减的核心事务实现

采购入库是最能体现事务控制能力的环节。业务流程是:仓库管理员对状态为"已发货"的采购单执行入库,然后系统遍历采购单明细,逐条增加对应门店的库存,同时写库存流水,最后把采购单状态改成"已完成"。

这个操作必须加@Transactional注解,否则遍历明细时一旦某条失败,前面已经加的库存没法回滚,数据就乱了。事务方法大致长这样:

@Transactional(rollbackFor = Exception.class) public void doStockIn(Long orderId, Long operatorId) { PurchaseOrder order = purchaseOrderMapper.selectById(orderId); if (order == null || !"SHIPPED".equals(order.getStatus())) { throw new ServiceException("采购单不存在或状态不允许入库"); } List<PurchaseOrderItem> items = purchaseOrderItemMapper.selectByOrderId(orderId); for (PurchaseOrderItem item : items) { Stock stock = stockMapper.selectByStoreAndProduct(order.getStoreId(), item.getProductId()); if (stock == null) { stock = Stock.builder() .storeId(order.getStoreId()) .productId(item.getProductId()) .quantity(0) .build(); stockMapper.insert(stock); } // 用数据库乐观锁版本号控制并发 int updated = stockMapper.increaseQuantityWithVersion(stock.getId(), item.getQuantity(), stock.getVersion()); if (updated == 0) { throw new ServiceException("库存更新冲突,请重试"); } stockLogMapper.insert(StockLog.builder() .storeId(stock.getStoreId()) .productId(item.getProductId()) .changeType("IN") .changeQty(item.getQuantity()) .operatorId(operatorId) .orderNo(order.getOrderNo()) .build()); } order.setStatus("FINISHED"); purchaseOrderMapper.updateById(order); }

库存更新的 SQL 我用了乐观锁版本号机制,而不是简单的quantity = quantity + ?。原因是连锁超市场景下可能存在多个仓库管理员同时操作,如果不做并发控制,两条事务同时读到库存 0,各自加 10,最后库存只变成 10,实际应该是 20。加上 version 字段后,更新时带上旧版本号,更新成功才把版本号加一,失败就说明有人改过了,需要重试。这个点无论在代码评审还是毕业答辩里都是非常亮眼的加分项。

门店要货出库的逻辑类似,只是把数量做减法。出库前要先判断库存是否充足,不够就直接抛异常提示,由前端捕获后弹出错误消息。这样就能有效避免"负库存"这种在真实业务中最低级的错误。

3.4 报表统计与库存预警的简易实现

报表部分我用的是 ECharts 展示图表,后端提供统计数据接口。核心思路是写 SQL 按时间聚合,比如查询近 7 天各门店的采购金额趋势:

SELECT DATE(create_time) AS day, store_id, SUM(total_amount) AS amount FROM purchase_order WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND status = 'FINISHED' GROUP BY DATE(create_time), store_id

返回 JSON 给前端,前端用 ECharts 折线图展示。这个功能不复杂,但视觉效果好,演示的时候一打开 dashboard 就有科技感。

库存预警是在库存表加一个low_limit字段,定时任务每天扫描一次,把库存低于下限的商品写入预警表。也可以用更简单的方式:门店登录首页时实时查询,在页面上用红色标出预警商品。考虑到毕设演示需要立刻见效,我选择在登录后实时查询,不依赖定时任务,展示效果更好。

MD5 校验码的展示也放在库存管理页面。每一行库存记录都有一个"校验码"按钮,点击后重新计算该批次的 MD5 值,与数据库中存储的原始校验码做比对,一致则显示"数据正常",不一致则提示"数据疑似被篡改"。这个功能演示时非常有冲击力,你可以先正常查看,然后在数据库里把某条库存数量改掉,再回到页面点校验,系统立刻报错。老师看到这个效果,基本都会认可你对 MD5 技术的理解和应用深度。

4. 常见问题与排查技巧实录

4.1 密码加密后为什么一直登录失败

这是做加密登录时最常遇到的问题。原因一般有三个:第一,注册时加盐计算和登录时加盐计算用的拼接顺序不一致,比如注册时用salt + password,登录时却用了password + salt,计算结果自然不同;第二,盐值没有正确保存或查出,用户表里 salt 字段为空;第三,数据库中密码字段长度不够,MD5 摘要固定 32 位,但如果你把加盐前的原始值直接存了,字段长度设短了会被截断。

我的排查方法很朴素:在登录接口里临时打印对比一下"数据库密文"和"重新计算的密文"。如果两个值前半段一样、后半段不一样,多半是盐值或拼接顺序问题;如果完全不一样,先确认查出来的用户对象是不是对的那个,再看密码字段有没有被截断。定位之后,把注册和登录的逻辑统一抽到一个 Service 方法里,不要在两处各写一套,就能彻底避免这类问题。

4.2 库存并发扣减导致数据错误

我在测试时用两个账号同时对同一个商品出库,发现库存偶尔会多扣或少扣。原因是库存更新语句没有加条件控制。解决方法是前面提到的乐观锁。如果你不想引入 version 字段,也可以用 SQL 原子更新:

UPDATE stock SET quantity = quantity - #{qty} WHERE store_id = #{storeId} AND product_id = #{productId} AND quantity >= #{qty}

quantity >= #{qty}这个条件既保证库存充足,又通过受影响行数判断是否更新成功。如果返回 0,说明库存不够或者数据冲突,程序直接抛出友好提示。这种方法代码更简单,也足够应付毕设场景。

4.3 中文乱码和插入数据报错

数据库乱码问题在 Windows 环境特别常见。检查顺序:数据库连接 URL 有没有characterEncoding=utf8,表结构是不是 utf8mb4,页面 meta 标签是否设置了charset=utf-8,前端 Ajax 提交时是否指定了contentType。大多数情况都是第一项漏了,加上就好。

还有一个很隐蔽的问题:MySQL 5.7 或 8.0 里,商品编码字段如果设置了唯一索引,插入重复编码会直接抛 DuplicateKeyException。我在代码里没有捕获这个异常,导致前端直接崩到 500 页面。后来统一在全局异常处理器里捕获DuplicateKeyException,返回"编号已存在"的提示,体验立刻好了很多。

4.4 毕业答辩中老师最爱问的问题

这个项目在答辩时被问到的频率最高的几个问题,我提前理一下思路。

第一个问题是"MD5 不是已经不安全了吗,你为什么还要用?"回答思路是:MD5 确实存在碰撞风险和彩虹表风险,但本项目通过加盐提升了安全性,同时把 MD5 用于数据完整性校验仍然是非常合适的场景;如果生产环境要求更高,可以替换为 SHA-256 或 BCrypt,而封装好的工具类让替换成本很低。

第二个问题是"采购计划自动生成的具体规则是什么?"回答思路是:先获取所有门店库存低于安全库存的商品,计算总需求量 = 安全库存 + 预测日均销量 × 采购提前期 - 当前库存 - 在途库存,再向上取整到供应商起订量的整数倍。你说出这套公式,老师就能看出你不是随便写的。

第三个问题是"库存表为什么要分门店?"回答思路是:连锁超市的库存天然是按门店物理隔离的,总仓配送到门店后,各门店独立销售、独立盘点,如果只用一个总库存数字,既无法支撑门店要货,也无法统计各门店的损耗和销售情况。

第四个问题是"项目里最复杂的点是什么?"不要说是 MD5,而是说多门店库存一致性和采购流程状态流转,然后引出事务和乐观锁。这样会显得你对系统有全局理解。

5. 项目扩展方向与个人经验总结

做完这个项目之后,其实还有很多可以继续深入的方向。比如给系统加一个简单的 Redis 缓存,把热门的商品信息和库存数量缓存起来,减轻数据库查询压力;比如引入 RabbitMQ 或者消息队列,让门店要货和总部采购异步解耦,而不是实时同步;比如做一个小程序端,让门店店长在手机上就能查看库存和提交要货申请。这些扩展方向不用真做出来,写进论文的"展望"章节就能提升项目高度。

我个人的建议是,毕设项目不要贪大求全,核心链路做扎实比功能数量更重要。这个连锁超市物资供应管理系统,如果能把"门店要货 -> 总部采购 -> 供应商发货 -> 仓库入库 -> 门店库存更新 -> 图表展示"这条链路完整跑通,再把 MD5 在密码存储和数据校验两个场景里讲明白,就足够拿到一个不错的结果。

最后分享一个小技巧。演示之前一定要准备一套"剧本":先登录管理员账号,打开 dashboard 展示图表;然后模拟一个门店库存低于预警线,演示要货单创建;再到总部审核要货、生成采购单;最后到仓库做入库,回到门店看库存增加。每一步在哪个页面、点哪个按钮、预期出现什么数据,都提前走一遍。毕业设计答辩本质上是一场演示加讲解,流程顺了,问题自然就少了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询