ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Java的药房购药系统设计与实现:库存、订单与权限全解析

基于Java的药房购药系统设计与实现:库存、订单与权限全解析 其实做这类系统最烦的就是“毕设项目”变成“摆设项目”——数据库建好了、页面能跳转、演示一过就吃灰。这篇我打算换个思路聊不是说怎么凑出一个能答辩的药房购药系统而是把一个选题拆成一套真正有逻辑的业务闭环从需求梳理到表结构、从库存并发到权限设计尽量站在“以后还能往下做”的角度把每一步讲透。尤其适合正在做JAVA毕业设计、或者想拿现成源码却看不懂模块之间怎么协作的同学。我自己用JAVA做过好几个类似的进销存、订单类系统药房购药跟普通商城最大的区别是它沾着“药品”两个字就意味着库存要准、效期要盯、特殊药品要有记录这不是单纯加个购物车那么容易。所以这篇文章会按照我自己写这类系统的思路来展开包括为什么选用Spring Boot JSP的单体结构、库存为什么用预扣减而不是直接改数量、金额为什么必须用BigDecimal、权限为什么不能只靠菜单隐藏。文章里所有代码片段都是从本项目里提炼出来的核心逻辑骨架完整你可以直接照着扩展。1. 项目定位与整体设计思路1.1 这个系统到底要解决什么问题买药和买衣服不一样。买衣服你加入购物车、下单、支付流程结束可能就完事了。买药牵扯的东西更多得能看到药品的规格、厂家、批准文号得知道库存还剩多少得区分处方药和非处方药有的药还要登记购买者的基本信息。这些约束决定了药房购药系统不是一个简简单单的“商城demo”而是一个带行业约束的业务系统。这个项目的选题叫“基于JAVA的药房购药系统的设计与实现”核心要做的事可以拆成几条线面向顾客的在线购药流程注册登录、浏览药品、搜索筛选、加入购物车、提交订单、模拟支付、查看订单状态。面向药房工作人员的药品管理药品上架、分类维护、库存盘点、效期预警、订单审核与发货。面向系统管理员的权限与数据维护用户管理、角色分配、订单全流程跟踪、基础数据配置。支撑这些业务的基础能力用户认证、统一异常处理、日志记录、数据统计。这几个模块凑齐了才叫一个“药房购药系统”而不是一个“购物页面”。1.2 为什么选JAVA Spring Boot JSP这套技术栈JAVA在毕业设计里出现频率最高的原因一是教学体系里JAVA是主力语言二是JAVA的生态足够成熟网上资料、框架文档、排错经验一抓一大把。但“JAVA”和“JAVA框架”是不同的概念选型时我建议直接用Spring Boot而不是Servlet JSP全手写。Spring Boot的好处不用多说内嵌Tomcat、自动配置、起步依赖写个接口几行代码就完事。很多同学担心用Spring Boot显得“不够底层”答辩时老师问几句框架原理就答不上来。我给个实在建议你可以用Spring Boot做项目主体但你得把Spring MVC的请求处理流程、依赖注入的原理、MyBatis的Mapper代理机制弄清楚——毕设答辩不看你用了多新的技术而是问你为什么这么设计、遇到问题怎么排查。JSP在这套系统里承担的是视图层。现在前后端分离很流行但毕设场景下JSP反而有优势不用处理跨域、不用单独部署前端项目、开发调试直接改页面就能看到效果。药房管理后台这类系统对交互要求没那么高JSP Bootstrap就够用了。如果你想体现一点工程化意识可以加一个Thymeleaf替代JSP或者把Vue单独拎出来做前端这要看你自己的精力。1.3 单机单体架构还是微服务听到“微服务”三个字很多同学就兴奋但药房购药系统的真实业务量单体架构完全能扛住。我甚至建议你在论文里写清楚本系统采用单体架构后续若业务量增长可按照模块边界拆分为独立的用户服务、订单服务、库存服务。这句话既体现了设计思考也避免了自找麻烦。单体架构的模块划分依然要按照业务的边界来组织否则代码很快就变成大泥球。我的习惯是先按功能分层再按业务模块分包。举个例子com.pharmacy.system ├── common // 通用工具、统一返回、异常定义 ├── config // 配置类拦截器、全局异常、跨域配置 ├── controller // 控制层各业务模块的controller ├── service // 服务层业务逻辑接口与实现 ├── mapper // 持久层MyBatis的Mapper接口与XML ├── entity // 数据库实体对象 ├── vo // 视图对象给页面或接口返回的数据结构 ├── dto // 数据传输对象接收前端参数 └── interceptor // 自定义拦截器这样一个包里装了什么从目录名就能看出来。模块之间通过service互相调用避免controller直接操作别人的mapper。分层清楚的好处是出了问题知道去哪翻代码答辩时讲起架构也明明白白。2. 核心业务模块与数据库设计2.1 业务模块的全景拆解药房购药系统的功能模块我在设计时从来不上来就写代码而是先列出一张业务流程图。画图不是为了交文档而是为了理顺“谁、在什么条件下、能干什么”。下面是我整理出的核心模块清单请对照着你的项目核对一下是不是都覆盖到了。用户端模块注册、登录、个人信息维护、收货地址管理、购药记录、收藏药品。药品浏览模块药品分类展示、关键字搜索、按价格和销量排序、药品详情查看。购物车模块加入购物车、修改数量、删除药品、清空购物车、计算总价。订单模块提交订单、订单确认、取消订单、订单支付状态更新、订单删除。药品管理模块药品新增、编辑、上下架、分类管理、库存设置、效期管理。销售管理模块订单列表查询、订单发货、销售统计、热销药品排行。客户管理模块客户列表、客户等级、购药记录跟踪。系统管理模块用户管理、角色管理、菜单权限、操作日志。如果你拿到的是这套系统的源码打开首页导航就能看到这些入口的雏形。每个模块之间不是孤立的比如药品一上架用户端马上能搜到用户下单库存就跟着扣减支付成功订单状态就流转到待发货。这就是模块之间“咬合”的地方也是设计难点所在。2.2 数据库表结构怎么设计才算合理数据库设计是这类项目能不能落到实处的关键。药房购药系统的核心表我认为有八张外加几张辅助表下面给你逐个过一遍第一是用户表。除了基本的用户名、密码、手机号要留一个role_id字段做角色区分。密码存储用加密方式不要明文存毕设答辩老师看到明文密码容易追问安全性。第二是药品分类表。一级分类和二级分类最好分开设计比如感冒用药下面还有中成药和西药用parent_id做自关联就可以支持无限层级。第三是药品表。字段要包含药品名称、通用名、规格、厂家、批准文号、零售价、进货价、库存、销量、上架状态、图片路径。零售价和进货价为什么分开因为统计报表要看毛利的随便写个total_price后期根本算不出利润。第四是购物车表。设计要点是用户ID 药品ID联合唯一用户同一个药品只能存在一条记录数量字段单独维护这样加减数量时不用反复插删数据。第五是订单表。订单编号、用户ID、总金额、优惠金额、实付金额、订单状态、收货人信息、下单时间、支付时间。这里要提示一个常见错误订单表里不要只存一个收件人姓名电话、地址都要冗余进来因为订单是历史快照用户后来改了地址并不影响已下的订单。第六是订单明细表。每条明细对应订单里的一种药品包含药品名称快照、单价、数量、小计。同样药品名称、价格也要快照存储否则药品改名或调价后历史订单显示会出现错乱。第七是库存表。很多同学把库存字段直接堆到药品表里这样做不是不能用但盘点、入库流水都不好追溯。我的习惯是单独建一张库存表一个药品一条记录字段包括总库存、锁定库存、可用库存。锁定的含义后面讲订单流程时会详细说。第八是操作日志表。谁在什么时间操作了什么保底留一条记录这在答辩时可以讲成“系统具备操作追溯能力满足合规审计需求”。下面是建表时可以抄作业的简化骨架实际字段你在源码里能看到更完整的版本CREATE TABLE drug ( id int NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 药品名称, generic_name varchar(100) DEFAULT NULL COMMENT 通用名, category_id int DEFAULT NULL COMMENT 分类ID, specification varchar(50) DEFAULT NULL COMMENT 规格, manufacturer varchar(100) DEFAULT NULL COMMENT 生产厂家, approval_number varchar(50) DEFAULT NULL COMMENT 批准文号, retail_price decimal(10,2) NOT NULL COMMENT 零售价, cost_price decimal(10,2) DEFAULT NULL COMMENT 进货价, image varchar(255) DEFAULT NULL COMMENT 药品图片, status tinyint DEFAULT 1 COMMENT 1上架 0下架, prescription_flag tinyint DEFAULT 0 COMMENT 0非处方 1处方药, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT药品信息表;价格字段用decimal(10,2)这是常识但也是高发错误。用float和double存金额算术运算会出现0.1 0.2 ! 0.3这类问题药房订单金额一旦产生小数尾差对账就会对不上。JAVA侧对应使用BigDecimal这个细节下面还会再讲。2.3 数据库字段与页面展示的对应关系设计和实现脱节是常见问题表现为页面想要的数据查不出来表里存的字段页面没用上。我的办法是做表结构时在注释里直接写明这个字段会在哪个页面、哪个位置展示。比如药品表的prescription_flag用户端详情页就要靠它显示“处方药”标签同时前端要限制处方药不能直接点“立即购买”页面要弹出提示框“该药品为处方药请咨询药师后下单”。这个逻辑如果表和页面不对应实现时很容易漏掉。分类表和药品表之间的关联通过category_id完成用户端导航栏的分类列表就是从分类表实时查出来的不是写死的HTML。好处是运营人员在后台上架新分类前台导航会自动更新。这种“一个改动全链路生效”的体验是判断系统设计得好不好的直观标准。3. 库存扣减与订单流转的核心流程3.1 订单提交时库存是怎么一步步扣减的药房库存管理比普通商品库存更敏感药品卖完了就得等补货超卖更不行——顾客付款后你去调货体验极其糟糕。所以在订单提交这个环节我设计了一套预扣减流程你可以直接参考它的思路。整个流程分三步走。第一步用户提交订单时系统检查每个药品的“可用库存”是否充足。第二步如果充足同时把“锁定库存”加上对应数量这个操作叫预占库存相当于把货先给你留住了。第三步订单支付成功后锁定库存正式转为已扣减状态也就是“总库存减少、锁定库存减少”同时增加销量。这样做的好处是从用户下单到支付完成这中间药品不会被人抢走而且不会出现库存负数的脏数据。如果用“直接扣减库存”的粗暴方式用户下单还没支付库存就减了结果他最后没付款库存却白白少了每天跑一遍对账会发现差了超多货。对应的可用库存计算SQL可以这么写UPDATE stock SET locked_stock locked_stock #{quantity}, available_stock available_stock - #{quantity} WHERE drug_id #{drugId} AND available_stock #{quantity}注意这个SQL是带条件的available_stock #{quantity}就是一道保险锁如果库存不够受影响行数为0代码里通过判断影响行数来决定是否回滚事务。这比先查库存、再更新这种“先查后改”的方式安全得多。3.2 订单状态的流转设计药房购药系统的订单状态我经常看到有人用一串数字0到5表示页面里到处switch判断代码看着乱逻辑也容易漏。我建议用一个常量类或者枚举把状态集中管理public enum OrderStatusEnum { PENDING_PAYMENT(0, 待付款), PENDING_DELIVERY(1, 待发货), DELIVERED(2, 待收货), COMPLETED(3, 已完成), CANCELLED(4, 已取消), REFUNDING(5, 退款中); private final Integer code; private final String desc; // 构造方法和getter省略 }状态机里的合法流转路径是固定的待付款能走到待发货支付成功或者已取消用户取消待发货只能走到待收货发货完成待收货只能走到已完成确认收货。不该允许待收货直接跳已完成由用户操作应该给一个“确认收货”接口由前端按钮触发后台校验当前状态后再更新。每个状态变更时我强烈建议同步写一条日志。比如“用户在2025-05-20 14:30把订单从待付款变更为已取消原因为超时未支付”。后来做运营统计分析或者排查纠纷时这些日志的价值远比你想象的大。3.3 支付环节怎么模拟才显得专业毕设项目对接真实支付通道不太现实但也不能用“点击支付直接改成已支付”这么糊弄。我推荐的方案是引入一个支付单的概念把支付动作和订单状态解耦。核心思路是建立payment_record表包含订单号、流水号、支付金额、支付方式、支付状态。用户点击支付时后台先创建一条支付单状态为待支付然后模拟一个“第三方支付网关处理中”处理成功后回调更新支付单状态再联动更新订单状态。这样做的好处是代码结构里payment、order两层分开将来如果真要接入微信支付或支付宝只需要替换支付网关的实现类业务代码不用大动。模拟支付的页面你可以做成一个二维码占位图旁边显示“请扫码支付演示环境自动支付成功”几秒后前端轮询订单状态发现已支付就跳转订单详情页。这个交互比“啪一下变成已支付”专业得多答辩时也有东西讲。3.4 金额计算的细节处理金额计算在订单流程里的地位很容易被低估。小计、运费、优惠、合计每个环节都可能出现精度问题。我定下的铁律是所有金额字段一律用BigDecimal禁止使用double和float。数据库里用decimalJAVA实体里用BigDecimalJSON传输时用字符串或者做好序列化配置避免前端拿到浮点数丢失精度。如果系统支持优惠券一定要把优惠分摊到每个订单明细上而不是只改订单总金额。因为后续如果做“订单退款”功能退单里某一个药品时要能算清楚这单个药品实际付了多少钱。同一个订单如果包含甲乙两种药整单减了10元退款时退甲的金额就不能瞎写。订单总金额的计算我建议放在服务端的service层完成不要信任前端传过来的金额参数。前端的合计仅供参考服务端根据数据库里的单价和数量重新计算一遍再做校验这是防止篡改价格的基础手段。4. 权限模型与项目安全设计4.1 用户角色怎么划分才够用药房购药系统至少需要四类角色顾客、药师、仓库管理员、系统管理员。有的系统里还会拆出收银员和店长毕设里四个足够了。每类角色的菜单权限和操作权限不同核心控制点如下顾客只能操作个人中心、购物车、订单和自己的收货地址不能碰管理后台。药师除了基础功能还能查看药品详情、处方药信息、审核特殊订单。仓库管理员可以管理药品库存、处理发货但看不到销售统计报表。系统管理员拥有一切权限包括用户管理、角色分配、日志查询。关于权限的落地方式市面上成熟的做法是RBAC模型——用户-角色-权限三层结构。数据库设计里加一张菜单表或权限表一张角色菜单关联表用户登录后把菜单权限加载进Session或Redis缓存前端根据权限动态渲染菜单后端根据权限拦截非法请求。我这里想强调一个很多毕设里容易漏掉的点前端隐藏菜单只是面子工程后端必须做真正的权限校验。别人拿个工具直接调你的接口菜单隐藏根本挡不住。所以服务端的拦截器里要校验用户角色是否匹配接口权限或者直接在敏感操作的Controller方法上使用自定义注解做权限标记比如RequiresRole(ADMIN)。这部分代码完全可以在答辩时作为亮点提。4.2 登录状态管理怎么做登录状态管理涉及安全性也是面试题和答辩常见追问点。用HttpSession存登录状态是最省事的方案但存在一个隐患——用户关掉浏览器后Session失效又要重新登录。更轻量、更常见的做法是使用JWT或者Redis Token的认证方式。毕设场景我用的是JWT方案原因是不用额外搭Redis。用户登录成功后服务端签发一个带有效期比如2小时的Token令牌返回给前端前端存到localStorage里每次请求在Header里带上。后端配置一个拦截器通过拦截器解析Token拿到当前用户信息再放行请求。JWT的组成你可以这么理解头部声明加密算法和Token类型载荷部分存用户ID、用户名、角色和过期时间签名部分用服务端密钥对前两部分做签名防止伪造。写论文时把这套机制讲清楚就是一个很好的“安全性设计”章节素材。4.3 常见安全漏洞的防范意识就算是个毕设项目一些基本的防攻击意识也要有。第一是SQL注入。如果你用了MyBatis的#{}它预编译参数可以防注入但如果你图省事写${}拼字符串那等于给攻击者开门。源码里所有动态查询请检查一遍禁止出现${}拼接入参的情况。第二是密码明文存储问题。我见过不少系统的数据库里password字段直接存123456答辩时一旦被问到就很尴尬。至少要使用MD5加盐或者BCrypt做加密。Spring Security自带BCryptPasswordEncoder单独引一个工具类也行。尽管是演示项目你也要在论文里体现“密码加密存储”的安全考虑。第三是越权漏洞。水平越权的典型场景用户A查看或修改用户B的订单。解决办法是查询订单时必须同时带上当前登录用户的ID作为条件不能只通过前端传的订单号查询。垂直越权的典型场景是普通用户访问管理员接口解决办法就是上面说的后端权限校验。这两类问题在真实项目里是高频漏洞你能在自己的毕设里规避并且讲得出方案是很大的加分项。4.4 操作日志如何记录操作日志这个模块往往被当成“锦上添花”但药房系统这类涉及药品销售、库存调拨的业务系统操作留痕是基本要求。我的实现方式是使用Spring AOP做一个切面通过自定义注解标记需要日志记录的方法方法执行完自动记录操作人、操作对象、操作类型、操作时间、请求参数和IP地址。不用把日志和业务代码写在一起避免代码到处都是System.out打印。AOP的好处是侵入性低业务代码不用关心日志怎么记只需要在方法上加一个OperationLog(修改药品库存)注解。整理日志查询页面时按用户、按时间区间、按操作类型筛选一套就成型了。5. 部署运行与高频坑位排查5.1 环境准备与快速启动步骤拿到源码之后最怕的就是运行不起来。环境排查第一步确认JDK版本和Spring Boot版本的匹配关系。如果源码用的Spring Boot 2.x推荐JDK8或JDK11如果Spring Boot 3.x就必须JDK17及以上用错版本启动直接报错。第二步确保Maven配置了国内镜像阿里云中央仓库否则依赖下载能卡到你怀疑人生。第三步配置MySQL数据库版本建议5.7或8.0导入项目里自带的sql文件。第四步确认数据库连接信息在application.yml里的配置账号密码不要写错。启动成功跑起来之后先不要急着点功能建议按我刚才说的业务模块逐个验证注册一个新用户登录看药品列表能不能加载加几个药品到购物车走一遍下单流程重点看库存变化和订单状态登录管理员账号看看药品管理、订单管理、用户管理页面各自是否正常。5.2 常见错误与排查清单我在跑这类项目时踩过不少坑也帮人排查过不少类似项目的问题把高频问题整理成一张速查表希望能帮你省点时间常见报错根本原因排查方向启动报端口被占用8080被其他进程占用或Tomcat配置冲突命令行输入 netstat -ano | findstr :8080找到进程PID后结束任务或改yml里的server.port数据库连接失败密码错误、数据库没启动、库名不对核对application.yml的url、username、password先通过数据库客户端连接验证中文乱码字符集配置不一致数据库连接URL加useUnicodetruecharacterEncodingUTF-8页面统一设置UTF-8JSP顶部确认pageEncoding页面报404请求路径或静态资源路径错误检查Controller的RequestMapping映射值是否和页面访问路径一致检查静态资源是否放在static目录下实体映射字段报null数据库下划线字段和实体驼峰字段没匹配MyBatis开启mapUnderscoreToCamelCasetrue配置或XML里给查询列加别名启动报ClassNotFoundException依赖没有正确导入在Maven面板执行reload或者删掉本地仓库对应依赖目录重新下载这里再补充一个比较隐蔽的坑多个用户同时下单商品库存出现负数或者超卖说明你用的是先查询再更新没有做好并发控制。解决思路前面已经提过就是用带条件的UPDATE语句来判断库存是否充足结合事务处理。5.3 代码里值得深挖的切入点拿到源码后如果只想准备答辩我的建议是把这几块代码吃透面试和论文都用得上一是登录认证的完整链路从前端提交账号密码到后端生成Token再到拦截器的校验过程二是订单提交的事务处理流程怎么保证“订单生成库存扣减状态变更”同时成功或者同时回滚三是MyBatis的Mapper接口和XML文件是怎么关联的为什么接口方法名要和XML的id一致四是Spring Boot启动类的注解SpringBootApplication组合了哪些注解。如果源码里有Redis缓存药品列表、或者用了Spring AOP写日志那更是加分点优先把这两块读懂。能把“项目里用了什么技术”和“为什么要用”讲清楚答辩基本稳了。还有一个加分技巧你可以给药品列表加一个简单的缓存设计查询药品列表时先从Redis检查是否存在没有则查数据库并写入Redis设置5分钟过期。这在答辩时就是“提升系统性能的缓存策略设计”。注意如果用缓存药品上下架时要记得删除对应缓存否则会出现前台已经下架但页面还能买到的逻辑错误。5.4 后续扩展空间与升级路线最后说点实在的。项目跑到这一步你完全可以做几个延伸方向难度递增但工作量可控。第一方向是增加统计报表模块基于订单数据按日、按月统计销售额、热销药品Top10、库存周转率用ECharts画几张图表放到首页这就是一个完整的“数据可视化”亮点。第二方向是接入WebSocket顾客下单或退款后后台收到实时通知能体现你对实时通信的理解。第三方向是把单体项目按模块拆分成微服务虽然工作量不小但可以只做“订单服务药品服务”两个服务的拆分演示配合Nacos注册中心在架构上又是高阶加分项。所有扩展方向的前提是先把基础功能跑稳主流程不出bug再谈优化。我个人在这些项目的实践里最大的体会是很多看起来“高大上”的系统最后跑不起来的核心原因都不是缺技术而是没有把业务逻辑跟数据设计对齐没有把关键的边界条件考虑进去。药房购药系统也是一样你多花点时间在库存扣减的条件、订单状态的流转、金额精度的处理上跑起来一定比那些只搭了CRUD框架的demo稳重得多。希望这篇能把你的思路理顺一些接下来的代码调试阶段遇到具体的报错再去查对应资料效率反而最高。
返回列表