SSM框架药店进销存系统:前台商城+后台库存管理全解析
做毕业设计和课程设计这些年SSM框架的管理系统我见得实在太多了但“java_ssm131龙康药店库存进销存管理系统”这个项目我第一次拿到手的时候还是觉得有点意思。一般的课设系统大多是单后台、纯增删改查这个项目不一样它带了一个完整的前台商城药品分类展示、搜索、购物车、模拟下单全都有后台还要管采购入库、销售出库、库存预警、统计报表这已经算是“进销存 电商前台”的双端闭环了。整份源码基于Spring SpringMVC MyBatis这套经典SSM组合开发工具是IntelliJ IDEA数据库用的MySQL非常适合Java Web刚入门、想系统看一个完整项目的同学。你在github或网盘上搜“龙康药店”也能找到不少流传版本但网上大部分资源要么缺文档要么跑不起来真正能把项目导入IDEA、配置好环境、把页面调通的人其实不多。这篇文章就围绕这套源码从项目设计、技术选型、核心业务逻辑、IDEA部署实操到常见报错排查完整拆一遍。无论你是要做课程设计、毕业设计还是想练手一套带前台的进销存系统这篇文章都能帮你少走不少弯路。1. 项目到底做什么龙康药店进销存系统的构建思路1.1 医药零售场景下的库存管理痛点刚开始看这类系统很多人会把它当成普通超市进销存来理解但药店和超市有个本质区别药品是严格受监管的商品有批号、有效期、生产日期而且库存多了会过期库存少了影响销售所以对“进出有据、账实相符”的要求比一般商品高得多。龙康药店这套系统虽然没有做GSP药品经营质量管理规范级别的复杂效期管理但从表结构和业务设计上已经具备了一套进销存系统最核心的骨架。系统要解决的问题其实很朴素药店里面每天有采购进来的药品有顾客买走的药有退货有临期处理如果全用Excel记日子一久必然对不上账。所以这套系统把“采购入库 - 库存增加 - 前台销售 - 库存扣减 - 库存预警 - 报表统计”这条链路用代码串了起来。后台管理员负责维护药品、供应商、采购单、库存前台用户注册登录之后可以浏览药品、加入购物车、下单整个流程都是通的。1.2 功能模块全景图与用户角色这套系统从前台到后台核心角色分三类管理员、员工、普通用户。管理员是后台的最高权限负责药品分类、药品信息、供应商、采购单、销售单、库存管理员工可以帮忙处理订单发货但一般不能动系统基础配置普通用户在商城前台注册登录浏览商品、加购物车、确认订单。功能模块拆开来看大概是这样系统登录与权限控制后台入口需要管理员/员工登录前台用户单独注册登录。药品分类管理维护中药、西药、医疗器械等分类方便前台分类展示。药品信息管理包括药品名称、规格、生产厂家、批准文号、进价、零售价、库存上下限。供应商管理记录供应商名称、联系人、联系电话采购单要从这里选供应商。采购入库管理创建采购单选择药品、填写数量、进价入库后自动增加库存。销售出库管理前台用户下单生成销售单后台管理员/员工处理订单出库后扣减库存。库存查询与预警库存列表展示当前数量低于预警阈值的药品高亮显示。销售统计报表按日、按月统计销售金额与数量用于了解店铺经营情况。这些模块放在一起就是一个非常标准的药店进销存闭环。你如果把这个项目吃透市面上绝大多数后台管理系统的开发套路基本就能摸清了。2. 技术选型与设计思路SSM框架为什么至今仍是经典2.1 SSM三层架构与项目代码结构先聊聊技术栈。现在很多人一谈起Java Web张嘴就是Spring Boot Vue但为什么这套源码还在用SSM其实理由很简单大多数高校的Java Web课程、毕业设计至今仍然以SSM为教学主线。SSM本身并不老旧它只是把Spring、SpringMVC、MyBatis这三个框架组合起来各司其职分工非常清晰。Spring负责对象管理把Service层、Mapper层、事务管理等对象交给Spring容器管理。你不用到处new对象依赖注入就够了。SpringMVC负责Web层的请求转发与参数绑定。前端页面发来请求DispatcherServlet分发到对应的Controller方法处理完再返回视图或JSON。MyBatis负责持久层 SQL 操作。SQL写在XML文件里面Mapper接口定义方法调用时动态拼SQL既灵活也直观。这套项目打开之后典型的目录结构长这样src/main/java ├── com.longkang.controller // Controller层 ├── com.longkang.service // Service接口 ├── com.longkang.service.impl // Service实现类 ├── com.longkang.mapper // MyBatis Mapper接口 ├── com.longkang.pojo // 实体类 ├── com.longkang.common // 通用工具、常量 └── com.longkang.interceptor // 拦截器 src/main/resources ├── mapper // MyBatis XML映射文件 ├── jdbc.properties // 数据库连接配置 ├── spring-mybatis.xml // Spring整合MyBatis └── spring-mvc.xml // SpringMVC配置 src/main/webapp ├── jsp / pages // 后台JSP页面 ├── front / 前台页面 ├── static / css, js, images └── WEB-INF / web.xml新手面对这个结构最容易犯的一个错误是拿到源码直接往IDEA里扔然后找不到启动入口。SSM项目的入口不在某个main方法而在web.xml配置的DispatcherServlet以及Spring的ApplicationContext。理解这一点后面部署排查才有方向。2.2 数据库表设计进销存的核心是“流水账”很多课设项目的数据库就三五张表但进销存系统如果想做到逻辑自洽表设计至少要覆盖“主数据 单据 流水 关系表”这四类。龙康药店这套项目的表大致是这个思路主数据表admin管理员/员工、user前台用户、supplier供应商、medicine_type药品分类、medicine药品单据表purchase_order采购单、purchase_order_detail采购单明细、sale_order销售单、sale_order_detail销售单明细购物车表cart购物车库存与流水表stock库存表、inventory_log库存流水这里最核心的设计点是“单据和单据明细分离”。为什么不能直接在一张采购表里存药品名称、数量、进价因为一张采购单可能会同时采购几十种药一行完全存不下而且单据有表头信息供应商、采购员、总金额、下单时间和明细信息哪批次、哪种药、多少数量、多少钱这两者天然是一对多的关系所以必须拆成主表和子表。另一个很关键的设计是库存流水表。每次采购入库、销售出库、退货除了改库存表里的数量还应该往库存流水表里插一条记录。这样做的好处是哪天库存对不上了可以通过流水倒查是哪个环节出的问题。很多半吊子源码没有流水表出库就UPDATE一下库存看起来没问题但一旦出现并发订单或程序报错账目就成一锅粥了。药品表里通常会设几个关键字段stock_min最低库存、stock_max最高库存、price_in进价、price_out零售价。预警功能就是拿当前库存和stock_min做比较低于阈值就提醒补货。2.3 前后台分离的设计取舍既然是“带前台”的药店系统就涉及前台和后台的展示区分。SSM项目里最常见的做法是后台用一套JSP放在WEB-INF或者单独/admin目录下面前台商城用另一套JSP放在根目录下面两边共用同一套Service和Mapper但Controller分开。这样做的好处首先是权限隔离。后台入口设置管理员登录拦截普通用户访问不了前台页面则允许游客浏览只有在加购物车、下单时才要求登录。其次两套页面的风格完全不同后台是密集的表格表单前台是商品卡片购物车放在不同目录下代码维护起来不会互相干扰。要注意的是这套源码的前台和后台前端渲染都由JSP完成并没有单独做前后端分离接口。如果你以后想改成Vue前端可以把Controller改成返回JSON再配合前端的axios请求这个改造也不算难后面我会单独讲。3. 核心业务实现详解从采购入库到前台下单3.1 采购入库库存更新与事务控制采购入库这个功能表面上就是“填一张采购单点一下提交库存增加”但代码实现上必须保证原子性。我看了源码里的流程大致是这样管理员在后台选择供应商填写采购单表头信息。在明细列表中添加一种或多种药品输入采购数量和进价。提交时后端先insert采购单主表再批量insert采购单明细表。最后循环遍历明细UPDATE库存表让库存数量加上采购数量。这个流程里最容易出问题的是第四步。如果三个药品的采购明细前两个都执行成功第三个UPDATE库存时报错了那数据库里就会留下“采购单存在、但库存只加了一半”的脏数据。所以Service层必须加Transactional事务注解让这四个步骤要么全部成功要么全部回滚。Service实现里大概是这样的写法Transactional(rollbackFor Exception.class) public boolean addPurchaseOrder(PurchaseOrder order, ListPurchaseOrderDetail details) { int count purchaseOrderMapper.insertSelective(order); if (count 0) { return false; } for (PurchaseOrderDetail detail : details) { purchaseOrderDetailMapper.insert(detail); stockMapper.increaseStock(detail.getMedicineId(), detail.getPurchaseNum()); } return true; }这里还有一个容易踩的坑库存表初始数据哪里来正常系统在第一次采购入库之前库存表里应该已经有药品的基础库存或者在采购入库时判断库存不存在则新增一条。很多新手在测试时发现采购单建成功了库存却没变多半就是库存表里根本没有对应药品的初始记录UPDATE语句影响行数为0。代码里最好处理成“有则加、无则insert”否则会让人排查很久。3.2 前台购物购物车、下单与库存扣减前台的商城流程比较接近真实电商的简化版游客浏览药品 - 点击加入购物车 - 跳转到购物车页面 - 确认结算 - 生成订单 - 后台订单列表中能看到。这里有几个细节值得说一说。购物车表的核心设计是userId medicineId quantity三要素一个用户对同一种药品只保留一条记录。用户重复加入同一个药品时不应该在购物车中新增一行而是把quantity累加。我见过不少新手源码在这里直接insert导致购物车里出现两行一模一样的药这种低级错误很影响体验。结账生成销售单的时候要考虑库存是否充足。用户下单数量不能超过当前库存否则应该提示“库存不足”。严谨一点的写法是在SQL层面做条件更新UPDATE stock SET stock_num stock_num - #{num} WHERE medicine_id #{medicineId} AND stock_num #{num}这条语句是利用数据库的行锁做并发控制即使两个人同时下同一款药的订单也不会把库存扣成负数。先查询库存再在Java里判断在高并发场景下是有风险的所以写销售出库的update时最好带上stock_num #{num}这个条件然后判断影响行数是否为0。如果影响行数为0就说明库存已经被别人抢光了。3.3 库存预警与销售统计报表库存预警的实现不复杂核心还是SQL聚合。后台每个页面加载后会去查一遍库存表中所有低于最低库存阈值的药品SELECT m.medicine_name, s.stock_num, m.stock_min FROM stock s LEFT JOIN medicine m ON s.medicine_id m.id WHERE s.stock_num m.stock_min这类预警一般放在首页仪表盘显示一个红色提醒列表同时在库存管理页面把低库存药品标红。对药店来说这个功能很重要因为药品断货会直接影响门店营收所以做演示的时候一定要把预警做明显一点答辩时也容易成为加分项。销售统计报表这块源码里一般是统计订单的聚合数据比如当天营业额、本月营业额、销量Top5药品。SQL用GROUP BY SUM DATE_FORMAT函数即可。例如统计每天的销售总额SELECT DATE_FORMAT(create_time, %Y-%m-%d) AS day, SUM(total_price) AS total_sales FROM sale_order WHERE order_status 2 -- 已完成 GROUP BY day ORDER BY day DESC如果页面需要更直观的图表展示通常会引入ECharts把聚合结果转成JSON返回前端画折线图或柱状图。基础课设做到这一步已经绰绰有余了。3.4 权限控制与会话管理后台访问控制通常靠拦截器实现。写一个LoginInterceptor实现HandlerInterceptor接口在preHandle方法里判断session里有没有登录用户没有就重定向到登录页。public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(admin); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return false; } return true; }然后在spring-mvc.xml里配置拦截规则最核心的一条是要把登录页、静态资源、前台页面放行否则会出现“登录页都进不去”的尴尬。我见过有同学把拦截器配置成/**结果静态CSS、JS全部被拦页面样式全丢了排查半天才发现是拦截器放行路径没写好。mvc:interceptors mvc:interceptor mvc:mapping path/admin/**/ mvc:exclude-mapping path/admin/login/ mvc:exclude-mapping path/static/**/ /mvc:interceptor /mvc:interceptors前台部分则相对宽松游客可以浏览药品但加入购物车和结算时Controller判断用户是否登录。session里没有userId时直接跳回登录页登录成功再返回购物车。4. 源码落地IDEA导入部署的完整实操记录4.1 环境准备清单拿到源码第一件事不是急着打开IDEA而是先把环境准备好。这套项目属于传统SSM依赖的运行时环境跟Spring Boot不太一样Java版本、Tomcat版本、Maven版本如果对不上启动时会冒出各种奇奇怪怪的错误。建议环境如下JDK 1.8SSM的老项目几乎都基于JDK8开发用太高版本反而可能编译不过。Maven 3.6.x用于拉取Spring、MyBatis等依赖版本太新偶尔会跟旧插件不兼容。Tomcat 8.5或9.0重要的事说三遍不要直接用Tomcat 10Tomcat 10把javax.包改成了jakarta.老SSM项目会直接报ClassNotFoundError。MySQL 5.7或8.0最好版本不低于5.5另外连接MySQL8要记得用新驱动。IntelliJ IDEA社区版或正式版都可以社区版装个Tomcat插件就能跑。注意不要在这类老项目上强行追求“所有版本最新”。JDK17 Tomcat10 MySQL8.0 最新Maven组合对于SSM项目来说不仅没有性能提升反而会让你在环境配置上耗费大量时间。4.2 IDEA导入源码到运行IDEA导入SSM项目最高效的方式是直接打开项目的pom.xml让IDEA识别为Maven项目。具体步骤很简单打开IDEA选择File - Open找到项目根目录下的pom.xml点击OK。IDEA询问“Open as Project or File?”时选择Open as Project。确认Project SDK选择1.8如果没有JDK1.8先在Project Structure - SDKs里添加。等待右侧Maven面板把依赖全部下载完毕。这一步取决于网络环境依赖下载失败的话后面启动就会疯狂报错。配置Tomcat下拉框选择Edit Configurations - Add New - Tomcat Server - Local在Deployment选项卡里添加Artifact: xxx:war exploded。这里有个细节如果是第一次用IDEA跑传统Web项目很可能发现Deployment里没有Artifact可选。这是因为项目还没有被识别为Web项目。解决办法是打开Project Structure - Facets点击加号添加Web把Web Resource Directory指定到src/main/webapp然后在Artifacts里新建一个Web Application: Exploded。配好之后IDEA才能把项目打成可部署的war包。Tomcat配置中Application context通常设置为/这样访问地址直接是http://localhost:8080不用额外拼接项目名。如果项目里用相对路径写了很多跳转context设置为空会省很多麻烦。4.3 数据库初始化与连接配置接下来要创建数据库并导入表结构和初始数据。一般源码包里会带一个db.sql或longkang.sql文件。打开Navicat或MySQL命令行mysql -uroot -p CREATE DATABASE longkang DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; USE longkang; SOURCE D:/path/to/longkang.sql;导入成功之后修改项目里的jdbc.properties把数据库的账号密码改成你自己的。这里有两个非常典型的版本差异MySQL 5.7版本jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/longkang?characterEncodingutf8 jdbc.usernameroot jdbc.password123456MySQL 8.0版本jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/longkang?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password你的密码MySQL8的密码加密规则是caching_sha2_password老驱动连接时会报Public Key Retrieval is not allowed所以URL里必须带上allowPublicKeyRetrievaltrue和useSSLfalse。如果密码配置不对启动时日志里会出现Access denied for user rootlocalhost这时候不要去改代码先去确认密码。4.4 启动验证与关键节点检查配置完成后点击Tomcat旁边的绿色三角启动。第一次启动成功后浏览器会自动打开首页。这时候建议按下面几个节点做一次完整验证前台首页能不能打开正常的药店前台应该有分类菜单、药品列表、搜索框。用户注册/登录能注册新账号登录后session能正常写入。加入购物车并下单能生成销售订单库存数量对应扣减。后台登录用admin账号进入后台看能否看到带预警信息的首页仪表盘。新增一笔采购单后台增加供应商、选择药品、提交采购单库存能不能同步增加。如果这些流程都通了说明项目已经完整跑起来了。但现实往往是第一次启动就会卡在某个报错上。所以接下来这部分我把跑项目过程中最常见的坑整理出来你对照排查即可。5. 从0到1跑通项目的各类坑与排查技巧5.1 数据库连接类问题这类报错是启动时出现频率最高的。常见问题与定位手段如下Access denied for user rootlocalhost (using password: YES)连接数据库的账号密码不对。用Navicat测试能否用同一个账号密码连上数据库。UnknownHostException或Communications link failure数据库服务没启动或者URL里的localhost写错检查3306端口是否被占用。Public Key Retrieval is not allowedMySQL8的URL里少了allowPublicKeyRetrievaltrue。Unknown database longkang数据库没有创建成功或者名字跟jdbc.properties里的不一致。Table xxx doesnt exist说明.sql文件没有完全导入或者导到了别的库。用SHOW TABLES;检查一遍。排查数据库问题的黄金命令复制jdbc.properties里的URL、用户名、密码在Navicat新建连接里试一次。如果Navicat能连项目连不上大概率是驱动或URL问题如果Navicat都连不上那就是MySQL服务或密码问题。5.2 Tomcat启动与404问题启动报java.lang.NoClassDefFoundError: javax/servlet/...Tomcat版本太高换成Tomcat 8.5或9.0。启动报ClassNotFoundException: org.springframework.web.context.ContextLoaderListenerSpring的jar包没打包进Artifact。打开Project Structure - Artifacts - Output Layout把Available Elements里的Maven依赖全部加入。启动正常但访问后台404检查Application context是不是/同时确认Controller里的请求映射路径和前端跳转路径是否一致。SSM项目经常出现前端写/admin/toAdd.jsp但实际Controller映射是/admin/add的情况这类路径问题只能靠仔细对比。页面能打开但样式全丢多半是JSP里引入CSS用了绝对路径但项目部署context没带项目名或者在拦截器里把静态资源拦截了。建议统一用${pageContext.request.contextPath}拼接静态资源路径。5.3 中文乱码问题中文乱码在这个项目里很容易出现因为涉及的环节太多JSP页面编码、数据库编码、请求编码、响应编码任何一个环节不一致都会乱。数据库层面建库时用DEFAULT CHARACTER SET utf8表字段也统一用utf8。JSP页面pageEncodingUTF-8和contentTypetext/html; charsetUTF-8都写上。请求参数乱码在web.xml里配置Spring自带的CharacterEncodingFilter强制请求和响应都走UTF-8。filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping如果上述都配置了还是乱码再看看数据库连接URL里有没有characterEncodingutf8SQL文件导入时是否选择了正确的字符集。5.4 Maven依赖与构建问题SSM项目常见的Maven报错一是依赖迟迟下载不下来二是jar包冲突。依赖下载慢在settings.xml里配置阿里云镜像mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirrorjar包冲突最常见的是javax.servlet-api和Tomcat自带的servlet-api冲突会报ClassCastException或NoSuchMethodError。解决办法是把pom.xml里servlet-api的scope改成provided让它只在编译时存在运行时不打包进war。dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency我当时跑的时候还遇到过一个问题IDEA里Maven面板一片红所有依赖都显示invalid。后来发现是项目里pom.xml的groupId和artifactId有特殊字符加上本地Maven仓库里有坏包删掉C:\Users\xxx\.m2\repository下对应的目录重新下载就好了。6. 常见问题速查表与避坑清单这里把部署和运行时最常见的问题整理成一张表方便你直接对照。这些内容都是我在实际跑这套源码时踩过的坑网上很多教程不会提到。现象根本原因解决方案启动报NoClassDefFoundError: javax/servletTomcat 10不兼容旧servlet包换Tomcat 8.5/9.0访问后台404Application context或Controller路径不对将context设为/检查请求映射前端样式丢失静态资源被拦截或路径写死拦截器放行static目录用${pageContext.request.contextPath}数据库中文乱码字符集不一致统一UTF-8URL加characterEncodingutf8MySQL8连接报Public Key RetrievalMySQL8新加密协议JDBC URL加allowPublicKeyRetrievaltrue事务不生效忘了加Transactional或类没被Spring扫描Service实现类加事务注解与Component库存为负扣减库存未加数量条件UPDATE带stock_num #{num}采购单成功但库存没变库存表无对应药品初始记录入库逻辑处理为“有则加、无则insert”页面提示包不存在Maven依赖未完整下载配置阿里云镜像后重新reimport避坑清单总的来说就三条第一不要盲目追求最新版本Tomcat选对JDK老老实实用8第二外部资源要先确认可用再启动数据库能连上再点运行第三改任何配置前先备份尤其是jdbc.properties和web.xml这两个文件是系统能跑起来的命根子。7. 个人经验与项目扩展思考7.1 这类管理系统最容易忽略的几个设计细节把这套系统完整跑通之后我回过头想了一下其实进销存类项目要做得好“能跑”只是第一步真正拉开差距的是几个容易被忽略的细节。第一个是幂等性。用户连续点两次提交订单系统不应该生成两笔一模一样的订单。严谨做法是在后端生成唯一订单号并在数据库订单表中给订单号加唯一索引重复提交时直接报错或忽略。第二个是库存流水可追溯。有的版本没有库存流水表销售出库后无法知道历史库存变动。我建议你在这个项目里自己加一张inventory_log表每次出入库都记录操作时间、操作类型、变动数量、操作人哪怕只是给答辩加分这个表都很值。第三个是搜索和筛选条件。药品列表页如果只能翻页没有任何条件筛选系统会显得特别业余。加一个按药品名称模糊查询、按分类下拉筛选代码量不大但用户体验提升非常明显。第四个是前端校验与后端校验的结合。我在测试这个项目的时候试着在价格输入框里填了负数如果后端没有做校验进价是-100的药也能入库。正规系统必须做“前端提示 后端校验”双重保障后端的校验绝对不能省。7.2 如果重新做一遍我会怎么升级如果你已经把这套SSM源码跑通想进一步练手我建议可以往这几个方向升级难度逐渐递增引入Maven多模块拆分把pojo、mapper、service、controller拆成独立模块虽然工程结构变复杂但能让你对分层架构的理解更深刻。把JSP替换成前后端分离后端只返回JSON前端用Vue或纯HTMLAjax。改造过程中你会更清楚Controller返回数据与返回视图的区别。增加药品效期管理给药品加生产日期、有效期字段在库存查询时自动计算临期药品并预警这才是药店系统真正的业务难点。引入Redis缓存热点药品前台首页的爆款药品列表可以缓存起来体验一下传统SSM项目集成Redis的过程。用Spring Boot重写一遍这是最经典的进阶路线把XML配置改成注解和application.yml你会发现Spring Boot对开发者有多友好。做了这么多套管理系统我个人最深的体会是SSM框架本身并不难难的是把“数据在页面、Controller、Service、Mapper、数据库之间完整流转”这件事理解透。龙康药店这套项目虽然代码量不算少但胜在业务链路完整从前台购物车到后台库存管理每一步都有真实的业务动机。你能把它完整跑起来、能说清每个表为什么存在、每个事务为什么加这套系统才算真正变成你自己的东西。第一次跑不通过很正常不要急着删代码先看日志、再查配置、最后看路径绝大部分问题都是这三个环节里的低级错误。