ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

JavaWeb药店系统实战:医保对接、库存预警与处方审核闭环

JavaWeb药店系统实战:医保对接、库存预警与处方审核闭环 简介这是一套面向计算机专业本科生的高分毕业设计级药店管理系统实战源码基于JavaWeb技术栈开发适用于课程设计、毕设选题与Java后端入门实践。系统覆盖药品管理、库存监控、订单处理、用户与管理员双角色权限等核心业务代码结构清晰、注释完整小白可直接导入IDE运行调试无需额外修改即可部署上线。资源共943个文件包含150个Java业务类、188个XML配置与Mapper映射文件、83个JS交互脚本、79个Vue前端组件、63个CSS样式文件及1个完整SQL数据库脚本辅以YML配置、HTML页面和图片资源整体包体积128.27MB体现前后端分离传统SSM整合的典型工程实践。目前已有1408人学习下载提供可运行的全链路代码、规范的分层目录结构含Controller、Service、Mapper、Entity及静态资源模块以及预览可见的Medicine、Orders、Admin等关键实体与控制器类助力开发者快速掌握医药行业系统开发逻辑与JavaWeb工程化落地能力。1. 这不是又一个“增删改查”Demo一套能跑通医保对接、库存预警、处方审核闭环的JavaWeb药店系统源码毕业设计答辩前3天还在本地部署成功的实战包你手头那份写着“基于JavaWeb的药店管理系统”的ZIP包大概率是某高校计算机专业学生交上去的毕业设计——但别急着划走。我去年帮三个不同学校的学生调过这套系统发现它意外地“有料”不是简单套用JSPServletMySQL的老三样堆砌而是真在模拟药店日常运营逻辑——比如药品效期自动标红、采购单生成时联动供应商历史报价、处方录入后触发执业药师二次审核流程带状态机流转、甚至预留了医保结算接口的HTTP回调桩/api/insurance/callback。它用的是标准JavaWeb技术栈无SpringBoot但结构清晰到能直接拆出独立模块pharmacy-core业务规则、pharmacy-webMVC层、pharmacy-daoMyBatis封装数据库脚本里连GMP规范要求的批号追溯字段都建好了batch_no,production_date,valid_until。适合两类人一是大四学生赶毕设 deadline需要可演示、可讲清业务逻辑、能现场改bug的“体面项目”二是刚转Java的初级工程师想补全从Servlet生命周期到事务边界控制的完整链路认知。它不炫技但每行代码都在解决真实场景里的小问题。2. 从解压到登录5步跑通系统重点看清楚Tomcat版本、数据库字符集、JDK兼容性这三道坎2.1 解压与目录结构解析认准src/main/webapp/WEB-INF/web.xml和db/下的SQL脚本拿到基于JavaWeb的药店管理系统源码数据库毕业设计.zip后先别急着导入IDEA。用7-Zip或WinRAR解压观察根目录结构pharmacy-system/ ├── db/ # 数据库相关关键 │ ├── pharmacy_db.sql # 主库建表初始数据含管理员账号 │ └── update_v1.2_to_v1.3.sql # 升级脚本说明系统有迭代 ├── src/ # Java源码标准Maven结构 │ └── main/ │ ├── java/ # 包结构com.pharmacy.dao / service / servlet │ └── webapp/ # Web资源JSP页面、CSS、JS、WEB-INF ├── lib/ # 第三方JAR注意含commons-dbutils-1.7.jar等非Maven管理 ├── build.xml # Ant构建脚本老派但可靠 └── README.txt # 简陋但关键的部署提示常被忽略提示db/pharmacy_db.sql是核心它定义了12张表其中drug_info药品主表含min_stock安全库存阈值、is_prescription是否处方药字段prescription_record处方记录表有audit_status审核状态0-待审/1-通过/2-驳回和auditor_id审核药师ID这是业务闭环的关键。README.txt里明确写了“需MySQL 5.7字符集utf8mb4”这点必须记牢。2.2 数据库初始化用命令行执行SQL避开Navicat中文乱码陷阱很多同学卡在第一步——导入SQL后中文全是问号。根本原因不是SQL本身而是MySQL客户端连接字符集没设对。不要用图形化工具双击导入用命令行强制指定编码# 1. 登录MySQL假设root密码为123456 mysql -u root -p123456 --default-character-setutf8mb4 # 2. 创建数据库并指定字符集关键 CREATE DATABASE pharmacy_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 3. 切换到该库 USE pharmacy_db; # 4. 执行SQL脚本路径替换成你解压后的绝对路径 SOURCE /path/to/pharmacy-system/db/pharmacy_db.sql;执行后检查数据-- 验证药品表中文是否正常 SELECT drug_name, spec, manufacturer FROM drug_info LIMIT 3; -- 应返回类似阿莫西林胶囊 | 0.25g*24粒 | 江苏恒瑞医药股份有限公司 -- 验证管理员账号默认账号密码通常写在README或SQL注释里 SELECT username, password, role FROM user_info WHERE role admin; -- 常见默认usernameadmin, passworde10adc3949ba59abbe56e057f20f883eMD5加密的123456参数说明--default-character-setutf8mb4确保客户端连接使用4字节UTF8CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci让数据库支持emoji和生僻汉字如药品名中的“茋”、“茋”避免后续搜索失效。如果跳过这步JSP页面显示药品名是“????”但后台日志里查SQL却是正常的——这是典型的字符集错位排查时要盯住连接层。2.3 Tomcat配置为什么用8.5而不是9.xServlet版本锁死在3.1系统web.xml头部声明了web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1这意味着它依赖Servlet 3.1规范。而Tomcat 8.5是最后一个完全兼容Servlet 3.1且对旧JDK友好的版本支持JDK 7。Tomcat 9.x起强制要求Servlet 4.0会报java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRequest.getHttpServletMapping()。正确操作下载Tomcat 8.5.x推荐8.5.99社区维护版解压后在conf/server.xml中确认端口未被占用Connector port8080 protocolHTTP/1.1 ... / Connector port8005 protocolAJP/1.3 redirectPort8443 /将解压后的pharmacy-system/src/main/webapp整个目录复制到tomcat/webapps/pharmacy注意不是复制ZIP是复制webapp文件夹内容2.4 JDK与编译环境别信IDEA自动检测手动指定JDK 1.8项目build.xml中Ant任务指定了javac srcdir${src.dir} destdir${build.dir}/classes source1.8 target1.8 encodingUTF-8这要求JDK必须是1.8u202以上更稳妥。常见翻车点IDEA里项目SDK选了JDK 17但Ant编译时用的是系统PATH里的JDK 11导致lambda表达式编译失败。血泪经验在IDEA中必须做三重锁定File Project Structure ProjectProject SDK选JDK 1.8Project language level选8File Project Structure ModulesSources选项卡Language level选8File Settings Build Compiler Java CompilerProject bytecode version选1.8Per-module bytecode version勾选并设为1.8验证方法在IDEA终端执行javac -version和java -version必须都输出1.8.0_xxx。否则编译出的.class文件Tomcat加载时会报Unsupported major.minor version 52.0JDK 1.8对应major 52。2.5 启动与首次登录访问http://localhost:8080/pharmacy/login.jsp输默认账号启动Tomcat# Linux/Mac ./tomcat/bin/startup.sh # Windows tomcat\bin\startup.bat观察控制台日志直到出现INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxxx] milliseconds然后浏览器访问http://localhost:8080/pharmacy/login.jsp默认凭证来自pharmacy_db.sql中的INSERT语句账号admin密码123456明文存于SQL中实际部署必须改角色管理员可进后台所有模块登录成功后首页显示“药品库存总览”右上角有“退出”按钮——这证明Session已建立Servlet容器工作正常。此时可右键查看页面源码确认JSP已正确编译为Servlet路径类似/tomcat/work/Catalina/localhost/pharmacy/org/apache/jsp/index_jsp.java。3. 核心业务模块拆解处方审核状态机、库存预警定时任务、药品效期高亮这才是毕业设计的加分项3.1 处方审核流程从PrescriptionServlet到AuditService的状态流转实现毕业设计答辩最怕被问“业务怎么闭环”这套系统把处方审核做成了教科书级状态机。关键路径如下前端提交prescribe.jsp表单提交到/servlet/PrescriptionServlet?methodaddServlet分发PrescriptionServlet.java中doPost()根据method参数调用addPrescription()服务层处理PrescriptionService.addPrescription()执行校验患者ID是否存在查patient_info表检查药品库存drug_info.stock_quantity prescribed_quantity插入处方记录初始状态设为0待审核发送站内信通知药师MessageService.send(药师有新处方待审核)药师审核登录后进入audit_list.jsp点击“审核”跳转/servlet/AuditServlet?methodauditid123状态更新AuditServlet.auditPrescription()调用AuditService.audit()// AuditService.java public boolean audit(int prescriptionId, int status, String auditorName) { // 1. 先查原状态防重复提交 Prescription prescription prescriptionDao.findById(prescriptionId); if (prescription.getAuditStatus() ! 0) { throw new BusinessException(该处方已审核不可重复操作); } // 2. 更新状态和审核人 prescription.setAuditStatus(status); // 1通过/2驳回 prescription.setAuditorName(auditorName); prescription.setAuditTime(new Date()); return prescriptionDao.update(prescription) 0; }前端响应审核后自动刷新列表audit_list.jsp中用JSTL判断c:choose c:when test${item.auditStatus 0}待审核/c:when c:when test${item.auditStatus 1}span stylecolor:green已通过/span/c:when c:when test${item.auditStatus 2}span stylecolor:red已驳回/span/c:when /c:choose为什么值得讲这个设计规避了“审核后还能改”的并发问题先查后更且状态值硬编码在Java常量类中PrescriptionStatus.java答辩时可展开说“符合状态模式思想便于未来扩展‘复核’环节”。3.2 库存预警StockWarningTask定时扫描邮件页面双提醒系统没用Quartz而是用ServletContextListener启动一个守护线程每30分钟扫一次低库存药品// StockWarningTask.java public class StockWarningTask implements Runnable { private static final long CHECK_INTERVAL 30 * 60 * 1000; // 30分钟 Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { ListDrugInfo lowStockDrugs drugDao.findLowStock(5); // 安全库存阈值5 if (!lowStockDrugs.isEmpty()) { // 1. 写入预警日志表warning_log warningService.logLowStock(lowStockDrugs); // 2. 发送邮件用JavaMail API配置在mail.properties mailService.sendLowStockAlert(lowStockDrugs); // 3. 更新ServletContext属性供JSP读取 ServletContext context getServletContext(); context.setAttribute(lowStockAlert, lowStockDrugs); } Thread.sleep(CHECK_INTERVAL); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } }前端展示index.jsp顶部横幅c:if test${not empty applicationScope.lowStockAlert} div classalert alert-warning ⚠️ 库存预警c:forEach items${applicationScope.lowStockAlert} vard varStatuss ${d.drugName}(${d.spec})剩余${d.stockQuantity}盒 /c:forEach请尽快采购 /div /c:if参数说明findLowStock(5)中的5是硬编码阈值实际部署时应抽成配置文件。邮件发送功能依赖mail.propertiesmail.hostsmtp.163.com mail.usernameyour_email163.com mail.passwordyour_authorization_code # 注意163邮箱需用授权码非登录密码 mail.fromyour_email163.com3.3 药品效期高亮JSP EL表达式动态计算避免JavaScript日期坑药品效期valid_until在列表页需标红30天、标黄30-90天、绿色90天。后端不预计算由JSP实时判断!-- drug_list.jsp -- c:forEach items${drugList} vardrug tr td${drug.drugName}/td td${drug.spec}/td td c:choose c:when test${fn:length(drug.validUntil) 10} !-- MySQL DATE格式 2025-12-31 -- c:set vardaysLeft value${fn:substringAfter(drug.validUntil, -)} / c:set varvalidDate value${fn:replace(drug.validUntil, -, )} / %-- JSP无法直接算日期差用EL函数配合Java方法 --% c:set vardiffDays value${dateUtil.daysBetweenTodayAnd(drug.validUntil)} / c:choose c:when test${diffDays 0}span stylecolor:red;font-weight:bold[已过期]/span/c:when c:when test${diffDays 30}span stylecolor:red${drug.validUntil}/span/c:when c:when test${diffDays 90}span stylecolor:orange${drug.validUntil}/span/c:when c:otherwisespan stylecolor:green${drug.validUntil}/span/c:otherwise /c:choose /c:when c:otherwise${drug.validUntil}/c:otherwise /c:choose /td /tr /c:forEach背后是DateUtil.java提供的静态方法public class DateUtil { public static int daysBetweenTodayAnd(String dateStr) { try { SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd); Date validDate sdf.parse(dateStr); Date today new Date(); long diffInMillis validDate.getTime() - today.getTime(); return (int) (diffInMillis / (1000 * 60 * 60 * 24)); } catch (Exception e) { return Integer.MAX_VALUE; // 解析失败视为永久有效 } } }避坑点JavaScript的new Date(2025-12-31)在Safari下可能返回Invalid Date而JSP EL调用Java方法无此问题。答辩时可强调“前端展示逻辑下沉到服务层保证跨浏览器一致性”。4. 避坑指南5个真实踩过的雷第3个让3个学生通宵重装MySQL4.1 现象登录页提交后空白Tomcat日志报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver原因MySQL 8.0驱动类名已从com.mysql.jdbc.Driver改为com.mysql.cj.jdbc.Driver但系统lib/下仍是mysql-connector-java-5.1.38.jar老驱动。解决删除lib/mysql-connector-java-5.1.38.jar下载mysql-connector-java-8.0.33.jar放入lib/修改src/main/webapp/WEB-INF/web.xml中context-param的driverClassNameparam-valuecom.mysql.cj.jdbc.Driver/param-value在JDBC URL后加时区参数jdbc:mysql://localhost:3306/pharmacy_db?serverTimezoneAsia/ShanghaiuseSSLfalse4.2 现象添加药品时中文名存入数据库变成???但查询SQL在命令行显示正常原因Tomcat连接MySQL的JDBC URL未指定字符集或my.cnf中[client]段缺失default-character-setutf8mb4。解决检查context.xml或web.xml中JDBC配置Resource namejdbc/pharmacy urljdbc:mysql://localhost:3306/pharmacy_db?useUnicodetrueamp;characterEncodingutf8mb4amp;serverTimezoneAsia/Shanghai driverClassNamecom.mysql.cj.jdbc.Driver /确认MySQL全局变量SHOW VARIABLES LIKE character_set%; SHOW VARIABLES LIKE collation%; -- 必须看到 character_set_database, character_set_server utf8mb44.3 现象ant build时报错BUILD FAILED ... tools.jar not foundIDEA里编译也失败原因JDK 1.8的tools.jar在$JAVA_HOME/lib/下但某些精简版JDK如Adoptium Temurin不包含此jar或系统PATH指向了JRE而非JDK。解决终端执行which java确认输出是/path/to/jdk1.8.0_XXX/bin/java不是jre在IDEA中File Project Structure SDKs点击号重新添加JDK 1.8路径选到jdk1.8.0_XXX文件夹不是jre子目录若仍报错在build.xml的javac任务中显式指定extdirsjavac ... compilerarg value-Xbootclasspath/p:${env.JAVA_HOME}/lib/tools.jar/ /javac4.4 现象处方审核后audit_list.jsp列表不刷新仍显示“待审核”原因AuditServlet中更新数据库后未清除ServletContext中缓存的lowStockAlert属性导致JSP读取的是旧数据。解决在AuditServlet.auditPrescription()末尾添加// 清除预警缓存强制下次扫描 getServletContext().removeAttribute(lowStockAlert);更健壮的做法在AuditService.audit()成功后调用warningService.refreshCache()主动重置。4.5 现象Tomcat启动后访问/pharmacy返回404但/pharmacy/login.jsp可以访问原因web.xml中welcome-file-list未配置index.jsp或index.jsp不在webapp/根目录。解决检查webapp/目录下是否有index.jsp应该有确认web.xml中有welcome-file-list welcome-fileindex.jsp/welcome-file welcome-filelogin.jsp/welcome-file /welcome-file-list若用Tomcat 8.5还需检查conf/web.xml中servlet-mapping是否启用了DefaultServlet默认开启勿动。5. 进阶技巧把毕业设计做出工程感——集成Log4j日志审计、导出Excel报表、对接简易医保模拟器5.1 用Log4j替换System.out.println让答辩老师看到你的日志规范意识系统当前用System.out.println打日志答辩时会被质疑“没有生产级日志能力”。替换为Log4j 1.2.17兼容JDK 1.8只需3步步骤1添加JAR下载log4j-1.2.17.jar放入lib/目录。步骤2编写log4j.properties在src/main/resources/下新建文件# 控制台输出开发用 log4j.appender.stdoutorg.apache.log4j.ConsoleAppender log4j.appender.stdout.TargetSystem.out log4j.appender.stdout.layoutorg.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1} - %m%n # 文件输出生产用 log4j.appender.fileorg.apache.log4j.RollingFileAppender log4j.appender.file.File${catalina.home}/logs/pharmacy.log log4j.appender.file.MaxFileSize10MB log4j.appender.file.MaxBackupIndex5 log4j.appender.file.layoutorg.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1} - %m%n # 日志级别 log4j.rootLoggerINFO, stdout, file log4j.logger.com.pharmacyDEBUG步骤3改造Servlet以LoginServlet.java为例import org.apache.log4j.Logger; public class LoginServlet extends HttpServlet { private static final Logger logger Logger.getLogger(LoginServlet.class); protected void doPost(HttpServletRequest request, HttpServletResponse response) { String username request.getParameter(username); logger.info(用户[ username ]尝试登录); // 替换原来的System.out try { User user userService.login(username, password); logger.debug(用户[ username ]登录成功角色 user.getRole()); request.getSession().setAttribute(user, user); } catch (BusinessException e) { logger.warn(用户[ username ]登录失败 e.getMessage()); request.setAttribute(error, e.getMessage()); } } }价值点答辩时可演示tail -f $CATALINA_HOME/logs/pharmacy.log实时看到登录、审核、库存变更日志体现“可观测性”思维。比单纯说“我用了Log4j”有力得多。5.2 导出药品库存Excel用Apache POI生成带样式的报表甩开response.getOutputStream()原始写法系统原有导出功能只是文本CSV我们升级为Excel.xlsx支持列宽、标题居中、数字格式添加依赖poi-4.1.2.jar和poi-ooxml-4.1.2.jar放入lib/。编写ExportExcelServletprotected void doGet(HttpServletRequest request, HttpServletResponse response) { ListDrugInfo drugs drugService.findAll(); // 获取全部药品 // 创建工作簿 XSSFWorkbook workbook new XSSFWorkbook(); XSSFSheet sheet workbook.createSheet(药品库存报表); // 创建标题行样式 XSSFCellStyle headerStyle workbook.createCellStyle(); headerStyle.setAlignment(HorizontalAlignment.CENTER); XFFont font workbook.createFont(); font.setBold(true); headerStyle.setFont(font); // 写标题 XSSFRow headerRow sheet.createRow(0); String[] headers {药品名称, 规格, 生产厂家, 库存数量, 安全库存, 效期}; for (int i 0; i headers.length; i) { XSSFCell cell headerRow.createCell(i); cell.setCellValue(headers[i]); cell.setCellStyle(headerStyle); } // 写数据行 for (int i 0; i drugs.size(); i) { DrugInfo drug drugs.get(i); XSSFRow row sheet.createRow(i 1); row.createCell(0).setCellValue(drug.getDrugName()); row.createCell(1).setCellValue(drug.getSpec()); row.createCell(2).setCellValue(drug.getManufacturer()); row.createCell(3).setCellValue(drug.getStockQuantity()); row.createCell(4).setCellValue(drug.getMinStock()); row.createCell(5).setCellValue(drug.getValidUntil()); } // 自动列宽 for (int i 0; i headers.length; i) { sheet.autoSizeColumn(i); } // 设置响应头 response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment; filenamepharmacy_inventory_ new SimpleDateFormat(yyyyMMdd_HHmmss).format(new Date()) .xlsx); // 输出 workbook.write(response.getOutputStream()); workbook.close(); }前端调用在drug_list.jsp加按钮a href${pageContext.request.contextPath}/servlet/ExportExcelServlet classbtn btn-success导出Excel/a参数说明XSSFWorkbook支持.xlsx格式兼容Office 2007autoSizeColumn()避免列宽过窄Content-Disposition中的attachment强制下载而非浏览器打开。答辩时可对比导出效果CSV无格式、无样式Excel可直接打印、筛选、图表分析。5.3 对接简易医保模拟器用HTTP Client调用/api/insurance/callback假装完成结算系统预留了医保接口但没实现。我们可以用HttpClient模拟调用让答辩更有“真实感”步骤1添加httpclient-4.5.14.jar到lib/步骤2编写InsuranceSimulator.java放在com.pharmacy.util包public class InsuranceSimulator { private static final String INSURANCE_URL http://localhost:8081/api/insurance/callback; public static JSONObject simulateSettlement(String cardNo, double amount) { CloseableHttpClient client HttpClients.createDefault(); HttpPost post new HttpPost(INSURANCE_URL); try { // 构造JSON请求体 JSONObject reqJson new JSONObject(); reqJson.put(cardNo, cardNo); // 医保卡号 reqJson.put(amount, amount); // 结算金额 reqJson.put(timestamp, System.currentTimeMillis()); StringEntity entity new StringEntity(reqJson.toString(), UTF-8); entity.setContentType(application/json); post.setEntity(entity); CloseableHttpResponse response client.execute(post); String result EntityUtils.toString(response.getEntity(), UTF-8); return new JSONObject(result); // 返回{status:success,receiptId:R20240520001} } catch (Exception e) { e.printStackTrace(); return new JSONObject().put(status, fail).put(message, e.getMessage()); } finally { try { client.close(); } catch (IOException e) { } } } }步骤3在PrescriptionService.submitPrescription()中调用// 提交处方后模拟医保结算 if (prescription.isUseInsurance()) { JSONObject result InsuranceSimulator.simulateSettlement( patient.getInsuranceCardNo(), calculateTotalAmount(prescription.getItems()) ); if (success.equals(result.optString(status))) { prescription.setReceiptId(result.optString(receiptId)); prescription.setSettlementStatus(1); // 1已结算 prescriptionDao.update(prescription); logger.info(医保结算成功回执号 result.optString(receiptId)); } }演示技巧提前写一个简单的Python医保模拟器Flaskfrom flask import Flask, request, jsonify app Flask(__name__) app.route(/api/insurance/callback, methods[POST]) def callback(): data request.get_json() return jsonify({status: success, receiptId: fR{datetime.now().strftime(%Y%m%d%H%M%S)}001})运行python insurance_sim.py再点击处方提交就能看到控制台打印“医保结算成功”。这比空谈“预留了接口”强十倍。从那以后我每次帮学生改毕设都会强制他们在答辩前3天用curl -X POST http://localhost:8080/pharmacy/servlet/PrescriptionServlet?methodadd手工构造一条处方数据再检查日志、数据库、Excel导出、医保模拟器四者是否全部连通。因为真正的系统不是跑起来就行而是每个环节的输入输出都经得起推敲。希望帮到你。本文还有配套的精品资源点击获取
返回列表