尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JSP医疗系统如何用链式哈希实现防篡改数据模型

JSP医疗系统如何用链式哈希实现防篡改数据模型 简介一套基于JSPJava技术的医疗数据管理系统源码面向Java Web开发者与健康医疗领域的课程设计、毕业设计人群。系统采用B/S模式整合区块链数据模型用于医疗数据的安全管理可在Tomcat7.0、Myeclipse8.5环境下搭配MySQL数据库运行有助于理解JSP前后端交互与数据库设计。资源共608个文件除核心JSP页面与Java类外还包含大量JS脚本、CSS/LESS样式、PNG/JPG图片及SQL数据库脚本目录结构清晰便于按功能模块阅读和二次开发压缩包仅3.97MB方便快速下载。已有119人学习下载适合作为医疗信息化入门参考。配套源码包含完整的登录注册与权限管理管理员账号及数据库配置文件DBO.java等均已提供并展示了区块链数据结构在业务系统中的落地方式具备一定的工程实践参考价值。1. 为什么一个 2015 年的 JSP 项目会讲区块链一个典型的 JSP 医疗数据管理系统项目名 ylsj数据库名 jspylsj登录入口是 login.jsp管理员账号是 admin/admin。如果只看源码包里的文件列表只有 DesUtil、DBO、StaticMethod 和一堆 bootstrap.css似乎没什么值得关注的。但摘要里提到“数据模型进行区块链设计”这很关键它说明项目不是在页面上贴区块链概念而是在数据表的组织方式上做了链式哈希。对想理解 JSP 请求怎么走、JDBC 连接怎么管、数据被篡改如何被发现的人来说这是一个可以反复拆解的样本不依赖 Spring不依赖 Maven只用 MyEclipse 8.5、Tomcat 7、MySQL 和原生 Java 就搭起了一套可运行的 B/S 模式系统。下面我会从环境搭建、连接边界、链式哈希落地和上线排查四个方向拆开讲。2. jspylsj 数据链路搭建Tomcat 7、MyEclipse 8.5 与 MySQL 版本选型先说明一个原则拿到这套项目时不要急着在你的新电脑上装 MySQL 8.0也不要一上来就用 JDK 17。老项目里的 JDBC 驱动、MySQL 认证插件和 JSP 编译方式都是有时代背景的版本差异会直接转换成“能跑”和“不能跑”的差别。2.1 为什么医疗数据的完整性要先于业务功能被考虑医疗数据管理系统里最重要的事情不是某个页面写得有多花哨而是数据不能被随意改动。普通关系型数据库用主键和事务保证一致性但一个拥有管理员权限的人直接执行 UPDATE数据库不会拒绝。区块链设计在这里做的事情是把原本独立的行记录变成一条可校验的链每条新记录都保存前一条记录的哈希谁改了中间的某一行后续所有哈希都会失配。这套 jspylsj 项目用的是单机 MySQL没有分布式节点也没有共识机制它只是把区块链里最核心的“哈希指针”思想移植到了数据模型层。对于 JSP 医疗系统这种内部系统来说做到这一步已经足够让篡改行为暴露。2.2 版本对照表避免踩进环境坑我按项目实际使用的组合整理了一张版本对照表方便你判断手边的环境组件推荐版本原因JDK1.8兼容 MyEclipse 8.5避免 JDK 11 以上模块化问题MyEclipse8.5项目中的 .classpath、WebRoot 结构按该版本生成Tomcat7.0.x支持 Servlet 3.0JSP 2.2适合原生 JSP 项目MySQL5.7.x与 mysql-connector-java 5.1.x 配合最稳定驱动mysql-connector-java-5.1.49驱动类名是 com.mysql.jdbc.Driver这个组合不是追求新版本而是为了贴近项目原始运行环境。如果只有 MySQL 8.0你需要在 JDBC 连接串里追加allowPublicKeyRetrievaltrueuseSSLfalse并且改用 mysql-connector-java 8.0.33但这属于额外调试成本不建议新手一上来就走这条路。2.3 建库、导库与 MyEclipse 发布路径拿到源码后第一步不是直接开 Tomcat而是先把数据库恢复出来。数据库名是 jspylsj源码包里通常有一个同名 SQL 文件。使用命令行或 Navicat 执行导入mysql -uroot -p -e CREATE DATABASE IF NOT EXISTS jspylsj DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; mysql -uroot -p jspylsj jspylsj.sql第一行命令先建库指定utf8字符集是为了防止 JSP 页面插入中文时出现乱码。第二行命令把 SQL 文件导入到jspylsj库中。导入完成后执行mysql -uroot -p -e USE jspylsj; SHOW TABLES;检查表是否齐全。如果 SQL 文件不在压缩包根目录需要先去database或sql目录找。接下来是在 MyEclipse 里关联项目。选择 File - Import - Existing Projects into Workspace选中 ylsj 根目录。发布目录是 WebRoot你要在项目属性里确认 Web Context Root 设置为/ylsj。然后在 Servers 窗口添加 Tomcat 7.0把项目 Deploy 到 Tomcat启动后访问 http://127.0.0.1:8080/ylsj/login.jsp。能出现登录页说明 Tomcat 与 web 目录配置正确下一步才需要检查数据库连接。这里有一个容易遗漏的细节MyEclipse 8.5 默认会把项目编译到build/classes而不是 Maven 的target/classes。如果改了 Java 代码后 Tomcat 仍显示旧内容多半是发布目录里没有同步最新的.class文件。做法是手动执行 Project - Clean再重新部署。3. DBO.java 的连接边界以及 JSP 里管理员和用户权限的落地方式项目启动后有两个位置决定了整个系统是否安全可用数据库连接怎么建立以及登录后的 session 怎么控制角色访问。老 JSP 项目不使用 Spring 管理连接而是把驱动加载和连接获取写在一个工具类里这个工具类就是 DBO.java。3.1 从 DBO.java 看 JDBC 连接管理的典型写法我一般会先打开 DBO.java搜索getConnection确认数据库地址、用户名、密码都集中在哪几个常量里。常见的代码结构是这样的public class DBO { private static final String DRIVER com.mysql.jdbc.Driver; private static final String URL jdbc:mysql://127.0.0.1:3306/jspylsj ?useUnicodetruecharacterEncodingutf-8; private static final String USERNAME root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConn() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } }这段代码非常典型Class.forName(DRIVER)只需要在类加载时执行一次所以放进静态块。getConn()每次调用都会创建物理连接在高并发场景下性能并不理想但作为学习型项目足够。URL里的characterEncodingutf-8控制从 MySQL 读取中文时的编码如果漏掉这个参数即使数据库本身是 utf8JSP 页面也可能看到问号。如果你想把密码外置常见做法是把这些参数挪到jdbc.properties然后用Properties.load()读取。这样换数据库时不用重新编译 Java 类。后面我会在最后一章给出具体写法。3.2 登录逻辑里的权限角色如何设计系统权限包括管理员和用户两种。管理员账号是 admin密码是 admin这个信息在摘要里已经明确。登录时login.jsp 把用户名和密码提交给一个 ServletServlet 从 request 里取出参数再通过 DBO 查询数据库。典型的登录核对逻辑如下String sql SELECT role FROM t_user WHERE login_name? AND password?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); ResultSet rs ps.executeQuery(); if (rs.next()) { session.setAttribute(role, rs.getString(role)); response.sendRedirect(index.jsp); } else { response.sendRedirect(login.jsp?error1); }这里使用?占位符而不是字符串拼接目的是防止 SQL 注入。t_user是常见表名实际表名要以你手上源码的 SQL 为准。登录成功后将角色写入 session后面所有需要鉴权的页面都能读到这个值。DesUtil类在这条链路里通常负责密码的加密或解密如果项目里明文存储密码就可以绕过它。3.3 没有拦截器老项目怎么控制页面访问这套系统没有 Spring Security也没有独立的过滤器而是用 JSP 的 include 机制做权限控制。常见做法是在 WebRoot 下放一个check.jsp内容只有几行% String role (String) session.getAttribute(role); if (role null) { response.sendRedirect(login.jsp?msgtimeout); return; } %每个需要保护的业务页面在顶部加上% include filecheck.jsp %。这样当用户直接访问业务页面时如果 session 里没有 role就会被强制送回登录页。理解这个机制比背代码更重要JSP 页面本质上会被容器翻译成 Servletinclude 的内容最终会合并进同一个输出类里因此变量和response都是共享的。下面这张表概括了权限边界的常见设置角色可访问页面越权表现未登录仅 login.jsp跳转 login.jsp?msgtimeout用户数据录入、查询页面管理员页面不可见管理员用户管理、数据审核页面全量功能这种控制的优点是简单缺点是如果某个 JSP 忘了 include页面就没有任何保护。你在学习时可以把这段逻辑摘出来改成 Filter 实现能同时提升对 Servlet 规范的理解。4. 在业务表上做链式哈希SHA-256 区块校验的 JSP 落地摘要里写的“数据模型进行区块链设计”真正落地位置就在这里。医疗记录需要防篡改但系统里没有专门的区块链中间件只能在业务表上增加哈希字段把记录组织成一条哈希链。4.1 在原有业务表上补出区块结构不要重建表而是在原表上增加四个字段block_height表示当前是第几个数据块prev_hash指向前一条记录的哈希record_hash保存当前记录的哈希created_at保存区块生成时间。SQL 如下ALTER TABLE t_medical_record ADD COLUMN block_height INT NOT NULL DEFAULT 0, ADD COLUMN prev_hash VARCHAR(64) NOT NULL DEFAULT , ADD COLUMN record_hash VARCHAR(64) NOT NULL DEFAULT , ADD COLUMN created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;只增加字段而不改主键是为了不破坏原有业务代码。旧代码仍然用 id 做更新和删除新增字段不会影响这些操作。record_hash使用长度为 64 的字符串正好能存下 SHA-256 的十六进制结果。4.2 Java 里实现 SHA-256 工具类JDK 自带MessageDigest不需要额外引入 jar 包。我封装了一个工具类专门负责哈希计算和取链上最后一条哈希。public class BlockChainUtil { public static String sha256(String data) throws Exception { MessageDigest md MessageDigest.getInstance(SHA-256); byte[] digest md.digest(data.getBytes(UTF-8)); StringBuilder hex new StringBuilder(); for (byte b : digest) { hex.append(String.format(%02x, b)); } return hex.toString(); } public static String getLastHash(Connection conn) throws SQLException { String sql SELECT record_hash FROM t_medical_record ORDER BY block_height DESC LIMIT 1; try (Statement st conn.createStatement(); ResultSet rs st.executeQuery(sql)) { if (rs.next()) { return rs.getString(record_hash); } } return ; } }sha256方法把任意字符串转成 64 位十六进制哈希。getLastHash按block_height倒序取最新一条记录的record_hash作为新记录插入时的prev_hash。特别注意拼接字符串时使用的分隔符必须是固定的如果插入时用|校验时也要用|否则哈希无法对上。4.3 插入记录时把新哈希写进业务表改造后的 DAO 插入逻辑是这样的先拿到上一条区块哈希然后把当前记录的关键字段拼接计算当前哈希最后写入数据库。public void addMedicalRecord(Connection conn, String patientId, String diagnosis) throws Exception { String prevHash BlockChainUtil.getLastHash(conn); String raw patientId | diagnosis | prevHash; String currentHash BlockChainUtil.sha256(raw); String sql INSERT INTO t_medical_record (patient_id, diagnosis, block_height, prev_hash, record_hash) VALUES (?, ?, ?, ?, ?); PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, patientId); ps.setString(2, diagnosis); ps.setInt(3, getNextBlockHeight(conn)); ps.setString(4, prevHash); ps.setString(5, currentHash); ps.executeUpdate(); }getNextBlockHeight是在插入前执行SELECT IFNULL(MAX(block_height),0)1 FROM t_medical_record。这段代码的坑在于并发如果两个人同时调用可能取到相同的prevHash导致链分叉。原项目是医院内部系统同时录入人数有限所以问题不明显如果要做成高并发版可以在查询后对表加写锁或者把整段插入逻辑放进事务并串行执行。4.4 校验脚本快速定位被篡改的行校验逻辑与插入逻辑正好相反。从第一条开始用同样的拼接规则重新计算哈希再与库里保存的record_hash对比。如果某一行不匹配就把这一行的 id 收集起来。public static ListInteger verifyChain(Connection conn) throws Exception { ListInteger broken new ArrayList(); String sql SELECT id, patient_id, diagnosis, prev_hash, record_hash, block_height FROM t_medical_record ORDER BY block_height ASC; String expectedPrev ; try (Statement st conn.createStatement(); ResultSet rs st.executeQuery(sql)) { while (rs.next()) { String raw rs.getString(patient_id) | rs.getString(diagnosis) | expectedPrev; String calcHash sha256(raw); if (!calcHash.equals(rs.getString(record_hash))) { broken.add(rs.getInt(id)); } expectedPrev rs.getString(record_hash); } } return broken; }这段代码里有一个关键变量expectedPrev。初始为空字符串因为第一条记录的prev_hash在插入时取到的也是空字符串。每处理完一条记录就把当前行的record_hash赋给expectedPrev作为下一行的比对基准。这样一来如果第 3 行被改动第 3 行的哈希会失配第 4 行用第 3 行旧哈希也能发现链路断裂。这里放一张表能更清楚看到加不加哈希链的差别对比维度普通业务表带链式哈希的业务表单条查询速度快略慢篡改识别无法感知哈希比对立刻发现插入成本低多一次哈希计算与一次前序查询适用场景内部日志医疗、合同、审计类数据这个模型距离真正的区块链还很远它没有去中心化也没有共识算法。但对于一套 JSP 医疗系统它已经足够让管理员在数据被修改后立刻发现问题。5. 不碰页面源码把登录审计与异常排查做成可维护的小动作最后我要讲的不是在系统里再加一个显示区块列表的页面而是两个可以不动 JSP 页面的改造分别针对登录审计和数据库连接配置。这些改动都集中在 Java 类里适合在上线前快速完成。5.1 给登录操作也增加一个审计块医疗系统里除了业务数据登录行为本身也需要被记录。更糟糕的情况是管理员直接改数据库里的 admin 密码然后正常登录这时普通日志不会发现异常。常见做法是单独建一张t_login_log表字段为user_name、login_time、prev_hash、record_hash。登录成功后按时间顺序把登录记录也做成哈希链。String raw username | loginTime | prevHash; String hash BlockChainUtil.sha256(raw); PreparedStatement ps conn.prepareStatement( INSERT INTO t_login_log (user_name, login_time, prev_hash, record_hash) VALUES (?, ?, ?, ?)); ps.setString(1, username); ps.setString(2, loginTime); ps.setString(3, prevHash); ps.setString(4, hash); ps.executeUpdate();这里的prevHash需要从t_login_log中取上一条记录的record_hash方法与业务表完全一致。要注意登录事务必须放在同一个数据库事务里避免业务提交成功但审计日志落库失败。5.2 把数据库配置从 DBO.java 移到 properties 文件DBO.java 里的常量虽然简单但改环境就要重新编译整个项目。更好的做法是在 src 目录下建立jdbc.propertiesjdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://127.0.0.1:3306/jspylsj?useUnicodetruecharacterEncodingutf-8 jdbc.usernameroot jdbc.password123456然后在 DBO 中改为读取该文件。需要提醒的是properties 文件依然保留明文密码这只解决了“不用重新编译”的问题不解决安全问题。如果你是给医院做真实交付应该把密码放入环境变量或配置中心。5.3 启动异常对照与定位顺序下面这张表列出来自这套环境最常见的启动异常适合在部署时逐条对照现象原因处理方式ClassNotFoundException: com.mysql.jdbc.Driver驱动 jar 不在 WEB-INF/lib放入 mysql-connector-java-5.1.49.jar重新部署Access denied for user rootlocalhostDBO.java 里的密码与实际 MySQL 密码不符修改配置文件重启 Tomcatlogin.jsp 404项目上下文路径不是 /ylsj在 MyEclipse 中检查 Web Context Root 是否为 /ylsj遇到这类问题的定位顺序我会先看 Tomcat 控制台里Caused by后面的提示再根据提示去检查 DBO.java 和发布目录。能正常打开登录页说明 web 层已经就绪剩下的问题几乎都在数据库连接和 SQL 语句上。如果你要部署到新机器这套链式哈希表结构不需要额外初始化因为校验逻辑会自动对比已经存在的记录旧数据在第一次插入新记录时会自动补齐引用关系。本文还有配套的精品资源点击获取
返回列表