
简介这套JavaWeb医药管理系统是为计算机专业期末大作业定制的高分源码包评审分98分内容经过导师指导与助教审定。系统基于JavaWeb技术栈涵盖客户管理、药品管理、进货商管理、采购记录等核心业务模块适合正在完成课程设计、期末大作业或毕业设计的学生也适合需要项目实战练习的中级学习者。压缩包共119个文件大小约3.01MB包含33个Java源码文件、33个已编译的class文件、30个JSP页面、若干XML配置、SQL数据库脚本、JAR依赖库及项目工程文件源码均已在本地编译并通过严格调试导入开发环境即可运行。资源已有94人学习项目按照MVC思路分层从数据库表设计到Dao、Controller、JSP页面形成完整闭环能帮助阅读者快速理解医药管理场景下的增删改查、权限与业务逻辑实现。既可作为期末报告和答辩演示的支撑材料也可作为独立完成Web项目的练习蓝本。1. 一个JavaWeb医药管理系统为什么值得当作期末大作业来做如果你正在为Java期末大作业发愁选一个医药管理系统是最稳妥的方向之一。它不像电商系统那么绕也不像图书管理那么烂大街业务边界清晰药品有增删改查、库存有出入库、用户有登录权限正好把JavaWeb的核心知识点全部覆盖了一遍。市面上流传的所谓“高分项目”源码绝大多数就是Servlet JSP MySQL这套组合少数会用SSM框架包装一层但底层逻辑是一样的。关键在于你拿到的源码能不能跑起来、表结构是不是完整、答辩时你能不能讲清楚——这三件事决定了你是拿高分还是被老师一眼看穿是抄的。这篇笔记我不打算带你“鉴赏”某个神秘源码包而是直接讲清楚一套能拿高分、能跑通、能讲明白的方案数据库怎么设计、代码怎么组织、哪些地方最容易翻车以及答辩时怎么把项目说出花来。目标就一个——你拿着这篇笔记就能复现而不是只会解压别人的压缩包。2. 数据库先行医药管理系统的表怎么设计才经得起老师追问2.1 为什么先设计表而不是先写代码很多同学的习惯是打开IDE先建项目写完几个页面发现不知道数据放哪回头再补数据库表结果表字段和页面表单对不上改到崩溃。我一般会先把SQL脚本写完整再动代码原因很现实期末答辩时老师第一个问题大概率是“你这个系统有哪些表、每张表存什么”如果你连字段都答不利索后面代码讲得再好也白搭。医药管理系统的核心需求无非这么几块用户登录与权限区分管理员、普通员工、药品信息管理名称、厂商、有效期、价格、库存管理入库批次、当前库存量、以及销售出库记录。围绕这四个需求最少需要四张表sys_user用来存登录账号drug用来存药品基本信息drug_stock用来存每个批次的库存sale_record用来存销售流水。订单功能可以不做做了就要加order表和order_item表复杂度立刻翻倍。2.2 建表SQL脚本字段、类型和外键一次到位下面这段SQL是我常用的精简版注释已经标了每张表的用途。需要注意的是字符集统一用utf8mb4排序规则用utf8mb4_general_ci避免中文乱码。-- 用户表管理员和普通员工共用一个表 CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 密码建议MD5加密后存储, real_name VARCHAR(50) COMMENT 真实姓名用于显示和打印单据, role TINYINT NOT NULL DEFAULT 1 COMMENT 角色1管理员 2普通员工, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; -- 药品表保存药品的基础信息 CREATE TABLE drug ( id INT PRIMARY KEY AUTO_INCREMENT, drug_code VARCHAR(30) NOT NULL UNIQUE COMMENT 药品编码由字母和数字组成, drug_name VARCHAR(100) NOT NULL COMMENT 药品通用名, specification VARCHAR(50) COMMENT 规格如 10mg*10片, manufacturer VARCHAR(100) COMMENT 生产厂商, price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 零售单价元, expire_date DATE COMMENT 有效期截止日, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT药品信息表; -- 库存表每个批次一条记录 CREATE TABLE drug_stock ( id INT PRIMARY KEY AUTO_INCREMENT, drug_id INT NOT NULL COMMENT 关联药品表主键, batch_no VARCHAR(30) COMMENT 批次号如 B20240601, quantity INT NOT NULL DEFAULT 0 COMMENT 当前库存数量, purchase_price DECIMAL(10,2) COMMENT 进货单价, CONSTRAINT fk_stock_drug FOREIGN KEY (drug_id) REFERENCES drug(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT药品库存批次表; -- 销售记录表 CREATE TABLE sale_record ( id INT PRIMARY KEY AUTO_INCREMENT, drug_id INT NOT NULL, sale_quantity INT NOT NULL COMMENT 销售数量, sale_price DECIMAL(10,2) NOT NULL COMMENT 成交单价, operator_id INT NOT NULL COMMENT 操作人关联sys_user, sale_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_sale_drug FOREIGN KEY (drug_id) REFERENCES drug(id), CONSTRAINT fk_sale_operator FOREIGN KEY (operator_id) REFERENCES sys_user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT销售记录表;表设计里有三个点容易被老师问到要提前想清楚。第一个是药品和库存为什么分开——因为同一款药可能分多个批次入库有效期和生产日期不同混在一张表里没法做批次管理。第二个是为什么要外键——如果不加外键删掉一个药品后销售记录就成了孤儿数据老师问“数据一致性怎么保证”你就直接答外键约束。第三个是DECIMAL而不是FLOAT存价格——FLOAT有精度丢失问题药品价格算错哪怕一分钱也说不过去。2.3 初始化数据里埋的两个加分细节建完表一定要插入初始数据否则老师运行你的项目还要自己建账号第一印象就差了。初始数据里我建议做两个小细节。第一个是管理员账号用admin、密码用admin的MD5值并在SQL注释里写明“首次登录后请修改密码”这能体现安全意识。第二个是准备10条以上药品数据覆盖不同厂商、不同规格其中至少要有一条库存不足10的药品方便你演示库存预警功能。-- 预置账号admin / adminMD5值0192023a7bbd73250516f069df18b500 INSERT INTO sys_user (username, password, real_name, role) VALUES (admin, 0192023a7bbd73250516f069df18b500, 系统管理员, 1); -- 预置药品和库存示例药品编码请按实际规则编写 INSERT INTO drug (drug_code, drug_name, specification, manufacturer, price, expire_date) VALUES (AMOX001, 阿莫西林胶囊, 0.5g*24粒, 华北制药, 15.80, 2026-12-31), (VITC002, 维生素C片, 100mg*100片, 东北制药, 3.50, 2025-10-31), (IBU003, 布洛芬缓释胶囊, 0.3g*20粒, 中美史克, 18.20, 2026-05-31);脚本导入时如果用的是Navicat或DataGrip直接执行整个SQL文件就行。主流的黑马JavaWeb课程资料里连接数据库用的方式要么是JDBC连接池要么是MyBatis框架但这套SQL脚本两种方式都能直接复用——它不依赖任何ORM语法是纯标准SQL。3. 代码分层与最小闭环Servlet JSP把登录和药品管理跑通3.1 分层结构长什么样为什么照着这个结构搭就行拿到一套JavaWeb项目源码第一件事不是跑起来而是看包结构。如果它把所有类都扔在src根目录下说明代码质量不会太好。我常用的结构是Controller层负责接收请求和返回页面跳转Service层放业务逻辑Dao层写JDBC或MyBatis访问数据库entity包放实体类。这在期末项目里已经是天花板级别的规范度了再往上就是Maven多模块没必要。一个能过查重的源代码包目录结构通常长这样src/main/java/com/example/hospital/ ├── controller/ // 登录、药品、库存、销售四个Controller ├── service/ // 业务逻辑接口和实现类 ├── dao/ // 数据访问层 ├── entity/ // 对应数据库表的实体类 ├── util/ // DBUtil、MD5Util等工具类 └── filter/ // 登录状态过滤器 src/main/resources/ └── db.properties // 数据库连接配置 web/ ├── pages/ // JSP页面login.jsp、drug_list.jsp、stock_in.jsp └── WEB-INF/web.xml // Servlet配置3.2 登录功能从页面到数据库的完整代码闭环登录是老师必看的第一个功能也是整个项目跑起来的前提。下面这段代码是登录Controller的核心逻辑我用的是原生Servlet方式不依赖Spring等框架方便你理解请求是怎么从JSP走到数据库再跳回页面的。// LoginServlet.java WebServlet(/login) public class LoginServlet extends HttpServlet { private UserService userService new UserServiceImpl(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 设置请求编码解决中文乱码 req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password MD5Util.encrypt(req.getParameter(password)); // 2. 调用Service层验证账号密码 User user userService.login(username, password); if (user null) { // 登录失败把错误信息带回登录页 req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/pages/login.jsp).forward(req, resp); return; } // 3. 登录成功把用户对象存进Session HttpSession session req.getSession(); session.setAttribute(loginUser, user); // 4. 按角色跳到不同首页 if (user.getRole() 1) { resp.sendRedirect(req.getContextPath() /pages/admin_index.jsp); } else { resp.sendRedirect(req.getContextPath() /pages/employee_index.jsp); } } }密码一定要MD5加密后存数据库、加密后再比对不能把明文密码直接拿来equals。原因是数据库脚本一旦泄露明文密码等于把系统权限全交出去了。MD5虽然不算强加密但应付期末项目足够前提是你别自己乱改盐值——改了盐值你预置的admin密码就验证不过了。3.3 药品列表的查询与分页把DAO层写明白药品列表页是整个系统最核心的页面老师会看你能不能查数据、能不能翻页。分页用MySQL的LIMIT关键字实现逻辑是计算总记录数然后算总页数。注意这里有一个坑如果你用SQL拼接分页参数参数一定要用PreparedStatement的占位符不能直接拼进SQL字符串否则就是SQL注入漏洞答辩时被问“安全方面做了什么”就直接卡壳。// DrugDaoImpl.java 中的分页查询方法 public ListDrug findPage(int pageNum, int pageSize, String keyword) { ListDrug list new ArrayList(); String sql SELECT id, drug_code, drug_name, specification, manufacturer, price, expire_date FROM drug WHERE drug_name LIKE ? OR drug_code LIKE ? ORDER BY id DESC LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { // 参数1和参数2是模糊查询的关键字 ps.setString(1, % keyword %); ps.setString(2, % keyword %); // 参数3是跳过多少条参数4是每页多少条 ps.setInt(3, (pageNum - 1) * pageSize); ps.setInt(4, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Drug drug new Drug(); drug.setId(rs.getInt(id)); drug.setDrugCode(rs.getString(drug_code)); drug.setDrugName(rs.getString(drug_name)); drug.setSpecification(rs.getString(specification)); drug.setManufacturer(rs.getString(manufacturer)); drug.setPrice(rs.getBigDecimal(price)); list.add(drug); } } } catch (SQLException e) { e.printStackTrace(); } return list; }pageNum参数从页面传过来默认值是1pageSize我固定设成10也可以给个下拉框让用户选。LIMIT的两个参数含义要记牢第一个是起始下标第二个是条数很多同学把两个参数写反导致第二页起数据错位。DAU层用try-with-resources语法自动关闭连接这比写finally手动关Connection、PreparedStatement、ResultSet干净得多也显得你懂资源释放。3.4 增删改功能与事务边界药品的新增、修改、删除必须走事务。原因很简单如果A药调价后库存跟着受影响两步操作只完成了一步数据就坏了。Servlet里调Service层的一个方法方法上加事务控制的临界点是最容易翻车的——有些人把事务写在了Servlet里控制不了Service层方法的原子性。下面这段Service层的删除方法做了两件事先删库存记录再删药品基本信息中间任何一步失败就回滚。// DrugServiceImpl.java public boolean deleteDrug(int drugId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 DrugStockDao stockDao new DrugStockDaoImpl(); DrugDao drugDao new DrugDaoImpl(); // 两步删除顺序不能反先删子表数据再删主表数据 stockDao.deleteByDrugId(conn, drugId); drugDao.deleteById(conn, drugId); conn.commit(); // 全部成功才提交 return true; } catch (SQLException e) { DBUtil.rollback(conn); // 失败回滚数据回到删除前 e.printStackTrace(); return false; } finally { DBUtil.close(conn); } }这里事务回滚的触发条件是抛出SQLException但实际开发中业务逻辑异常也需要回滚比如库存不足抛出运行时异常。期末阶段你只要能在答辩时说清楚“为什么删除要开事务”就够用了因为很多同学的删除功能连事务都不开直接一删了之。4. 高分项目的三个真实加分层库存预警、权限控制与演示脚本4.1 库存预警一条SQL解决但必须知道为什么它准库存预警是医药管理系统最容易出彩的地方因为它是业务上的刚需——药卖完了还没补影响营业。实现方式很简单查询drug_stock表里quantity小于阈值的药品关联drug表拿到药品名称和价格在前端用红色字体标出来。SELECT d.drug_name, d.specification, s.quantity, d.price FROM drug_stock s JOIN drug d ON s.drug_id d.id WHERE s.quantity 10 ORDER BY s.quantity ASC;阈值10你可以做成系统参数在web.xml里配置或在数据库里建一张sys_config表。用10当硬编码演示没问题但老师如果追问“如果不同药品预警阈值不同怎么办”你就说可以把阈值字段扩展到drug表里每款药单独设定。这里提一句数据一致性阈值判断不能只看批量库存加总如果同一药品有多个批次需要先按drug_id做SUM再用HAVING过滤否则库存会被拆散判断出错的预警。4.2 过滤器实现登录拦截三行代码解决权限问题没有登录过滤器的项目直接输URL就能绕过登录页进系统这是期末答辩的致命伤。用Servlet Filter可以把所有需要登录才能访问的页面统一拦住。下面是权限过滤器的核心代码// AuthFilter.java WebFilter(/*) public class AuthFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; // 放行登录页、登录接口、静态资源 String uri req.getRequestURI(); if (uri.endsWith(login.jsp) || uri.endsWith(/login) || uri.contains(/static/)) { chain.doFilter(request, response); return; } // 检查Session里有没有登录用户 HttpSession session req.getSession(); if (session.getAttribute(loginUser) null) { resp.sendRedirect(req.getContextPath() /pages/login.jsp); return; } chain.doFilter(request, response); } }一段代码把整个系统的权限骨架撑起来了。注意过滤器的url-pattern如果用“/*”会把JSP、JS、CSS都拦住所以放行条件里必须把静态资源和登录页排除掉。否则你辛辛苦苦写的样式全部加载不了页面丑成一坨还找不出原因。4.3 答辩演示脚本按这个顺序点5分钟讲完不冷场很多同学项目做完了答辩时不知道先点哪里结果老师看到的第一个页面就是登录失败报错。我会在项目提交前给自己写一个演示脚本固定操作顺序打开登录页故意输错一次密码展示错误提示逻辑。用admin登录成功进到管理员首页主动提“这里我用了Session保存登录状态并由AuthFilter统一控制访问权限”。打开药品列表页展示分页和搜索边点边说“分页通过LIMIT偏移量实现搜索条件通过PreparedStatement占位符拼接避免了SQL注入”。演示新增药品填一个表单提交后列表第一页能看到新数据。演示库存预警找一个库存10的药品展示红色预警标出。演示删除药品删除刚才新增的测试数据说明“这里涉及级联删除我用了事务保证要么全删要么不删”。最后退出登录点浏览器回退按钮展示过滤器会把未登录用户踢回登录页。这套流程走完老师基本能确认这个东西是你自己写的因为每一步你都能讲出“为什么这样做”。最怕的就是只演示了一遍增删改查紧张时一句话说不出来。5. 换台电脑就崩项目迁移与运行时的5个避坑记录5.1 数据库连接配置不生效改了db.properties还是连不上现象代码在别人电脑上跑得好好的拿到自己电脑上运行报错Communications link failure或者Access denied for user。原因db.properties文件里的URL、用户名、密码没改对或者IDEA没有把resources目录标记为资源根目录导致配置文件没有打进产物。解决打开WEB-INF下或resources目录里的db.properties检查jdbc.url是否写成了192.168.开头——那是别人电脑的IP你本机要用localhost。账号和密码确认和你的MySQL一致。如果确认都没问题右键resources目录选择Mark Directory as Resources Root然后重新部署Tomcat。最直接的排查方法是用Navicat先用这个账号连一次数据库端能连上再怀疑代码端。5.2 JSP页面中文全部变成问号现象页面上的中文药品名、厂商名称显示为一串问号。原因数据库连接的URL没有指定characterEncodingutf8或者Tomcat的URI编码没设置。解决在jdbc.url后面追加?useUnicodetruecharacterEncodingutf8并且确认MySQL的my.ini里default-character-set是utf8。JSP页面最顶部要有% page contentTypetext/html;charsetUTF-8 %声明。如果按照黑马的JavaWeb笔记的配置流程走这一步往往是漏了URL参数的百分号转义——在XML里要被写成直接写会解析报错。5.3 IDEA部署工件后找不到访问路径现象Tomcat启动成功但浏览器访问http://localhost:8080/报404或者访问项目名还是报404。原因IDEA里没有正确配置Artifact的Deployment项目没有以exploded形式部署到Tomcat的webapps目录下。解决在IDEA的Run Configuration里找到Tomcat Server切到Deployment标签点击选择Artifact然后确认Application context这里填的项目名和你的URL一致。Application context填/hospital则访问地址是http://localhost:8080/hospital。这个坑通常是IDEA版本不同导致的界面差异不要死记按钮位置。5.4 Tomcat版本高但项目还是老写法现象用Tomcat 10或11跑Servlet项目启动正常但访问接口直接500报错ClassNotFound或Servlet无法实例化。原因Tomcat 10以上把Java EE打包方式从javax.换成了jakarta.你代码里import的javax.servlet.*在新版本里不存在了。解决看你的IDE引入的依赖是否匹配Tomcat版本。如果代码包以及Servlet注解里面用的是javax那就换成Tomcat 9。期末老项目大多是Tomcat 8/9的产物强行上Tomcat 10会白白浪费一晚上。作为参考老牌JavaWeb项目的运行环境Tomcat 8.5配JDK 8最稳。5.5 数据库脚本导入时报错密匙与非法字符合法性现象导入SQL脚本时中途报错比如Unknown collation或Illegal mix of collations。原因SQL文件里有中文字符但文件编码不是UTF-8导入时用了系统默认编码比如GBK导致字符串前后的引号匹配错乱。另一个可能原因是你的MySQL旧版本不支持utf8mb4_0900_ai_ci这个排序规则。解决用Notepad或VS Code把SQL文件转成UTF-8编码后重新导入。脚本里CREATE TABLE语句的排序规则显式写成utf8mb4_general_ci兼容性最好。MySQL 5.7完全不认识_0900_ai_ci如果你用小版本的MySQL就只能用_general_ci。6. 源码包提交前的最后一道工序把代码和数据脚本收干净项目做完只是第一步交一个让人愿意打开的源码包才是拿高分的前提。老师每天要解压几十个zip包如果你的包打开来全是target目录和IDE配置文件第一印象就毁了。提交前我一般会做三件事第一删掉所有IDE和工作目录垃圾。target文件夹、.idea、.iml文件全部清掉保留src、web、pom.xml或build.gradle、数据库脚本目录就行。整个项目压缩后在10MB以内才算正常如果压缩包几十MB基本是把你本地的Tomcat或Maven仓库一并打包了。第二写一个README文件。不要小看这一张文本两三分钟就能写完信息量极大JDK版本、Tomcat版本、数据库版本、部署步骤、初始账号密码、演示顺序。我见过太多人源码包里的数据库脚本命名成final_v2_最终版.sql打开一看三份内容还不一样你让老师用哪个所以只保留一份最新脚本其余全删。第三亲手在干净环境里跑一遍。用另一台电脑或者把刚才删掉的target目录清掉后重新部署按演示脚本完整走一遍。确认登录、增删改查、分页搜索、预警、退出这几个核心链路都没问题再压缩提交。这里的教训是“能跑”和“能交给别人跑”是两码事——你电脑上装了七八个版本的MySQL和Tomcat环境早就乱套了凭本地经验判断可复现性不靠谱。每次换环境都从零配置一遍既是验证也是熟练度提升答辩时被问“部署遇到过什么问题”你都有现成素材讲。医药管理系统作为Java期末大作业的价值不在于功能多炫而在于它逼着你把Servlet、JSP、JDBC、MySQL、会话管理、过滤器、事务这些核心技能串起来走一遍。读懂自己项目里的每一行代码比下载一百个所谓高分源码包更重要。希望这篇笔记帮你把这条路走顺交出一份自己真的能讲清楚的项目。本文还有配套的精品资源点击获取