ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于MD5算法的连锁超市物资供应管理系统设计与实现

基于MD5算法的连锁超市物资供应管理系统设计与实现 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_idsupplier供应商表id, supplier_name, contact, phonecategory商品分类表id, category_name, parent_idproduct商品表id, product_code, name, category_id, unit, pricestore门店表id, store_name, address, managerpurchase_order采购单表id, order_no, supplier_id, status, total_amountpurchase_order_item采购单明细表id, order_id, product_id, quantity, pricestock库存表id, store_id, product_id, quantity, md5_codestock_log库存流水表id, store_id, product_id, change_type, change_qty, operator_id库存表设计值得多说两句。很多初学者会把库存字段直接放在商品表里这样很简单但一遇到多门店就废了。连锁超市一定是有多个门店的每个门店对同一商品有独立库存所以库存表必须以store_id product_id作为唯一维度。库存流水表则记录每一次变动入库、出库、盘点调整都写流水这样既能追溯又能对账。采购单用主表 明细表的设计是为了支持一张采购单包含多种商品同时每种商品的数量和价格各自独立存储。主表存供应商、总金额、状态这些公共信息明细表存具体商品项。表之间用外键逻辑关联但物理上不建外键约束为了性能和删除方便靠程序保证数据一致性。3. 实操过程从建表到入库扣库存的完整实现3.1 环境准备与项目初始化先把开发环境跑通。我用的是 JDK 8Maven 3.8MySQL 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?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/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 必须显式指定characterEncodingutf8否则前后端传中文会出现乱码。MySQL 8 还要加serverTimezoneAsia/Shanghai不然日期类型会报时区错误。建表时把公共字段加上create_time、update_time、deleted逻辑删除标记。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(采购单不存在或状态不允许入库); } ListPurchaseOrderItem 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 有没有characterEncodingutf8表结构是不是 utf8mb4页面 meta 标签是否设置了charsetutf-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 展示图表然后模拟一个门店库存低于预警线演示要货单创建再到总部审核要货、生成采购单最后到仓库做入库回到门店看库存增加。每一步在哪个页面、点哪个按钮、预期出现什么数据都提前走一遍。毕业设计答辩本质上是一场演示加讲解流程顺了问题自然就少了。
返回列表