乐于分享
好东西不私藏

信息科程序猿出手:传染病前置软件告别专用客户端,实现 Web 单点登录!

信息科程序猿出手:传染病前置软件告别专用客户端,实现 Web 单点登录!
写在前面:国家传染病智能监测预警前置软件通常通过专用客户端访问。对于医院信息科来说,客户端安装、升级和故障处理都会增加维护成本。本文分享一种院内 Web 化思路:保留原系统和后端接口,只增加一个轻量级登录入口(仅仅一个html页面),让 HIS 能够通过 URL 拉起前置软件并完成单点登录。

01我们到底解决了什么问题?

传统使用方式需要工作人员先打开专用客户端,再输入账号、选择科室,最后进入数据上报页面。频繁切换系统时,这几步很容易打断工作流程。

改造后的使用体验:
  1. 用户在 HIS 中点击“传染病上报”;
  2. HIS 将用户工号、科室编码拼接到访问地址;
  3. Web 页面调用前置软件原有登录接口;
  4. 保存 Token 和菜单权限;
  5. 自动进入数据上报页面。
    完整代码见:https://gist.github.com/N3verL4nd/26cbe44435df3ecab0b94de7d10c2d17
HIS 点击入口 → URL 携带身份 → 获取请求 ID → 插件登录 → 加载权限 → 数据上报

整个方案没有修改原 Vue 业务代码,核心是复用前置软件已经提供的 getSecretKeypluginLogin和菜单权限接口。

02最终访问地址

页面部署到前置软件 Tomcat 的 ROOT 应用后,可直接通过下面的地址访问:

http://前置机IP:8881/infDiseaseAdvance-demo.html?userId=005590&deptCode=0141
参数说明:userId是前置软件用户 ID,deptCode是当前登录科室编码。这两个参数应由 HIS 根据当前登录用户动态生成。

03单点登录关键代码

页面首先从 URL 读取用户和科室参数:

const params = new URLSearchParams(window.location.search);
const userId = params.get("userId");
const deptCode = params.get("deptCode");

if (userId && deptCode) {
  sessionStorage.clear();
  doLogin();
}

随后获取本次登录使用的请求 ID。正式使用时,请将许可证配置替换为本院实际值,不要把真实配置发布到互联网:

const PLATFORM_BASE = window.location.origin;
const SECRET_LICENSE = "请填写本院许可证标识";

async function getSecretKey() {
  const url = PLATFORM_BASE
    + "/appBackend/hisSecret/getSecretKey?license="
    + encodeURIComponent(SECRET_LICENSE);

  const response = await fetch(url, {
    method: "GET",
    credentials: "include"
  });

  const result = await response.json();

  if (!response.ok || !result.result || !result.secretKey) {
    throw new Error(result.desc || "获取请求 ID 失败");
  }

  return result.secretKey;
}

拿到请求 ID 后,调用插件登录接口:

async function pluginLogin(secretKey, userId, deptCode) {
  const response = await fetch(
    PLATFORM_BASE + "/appBackend/hisSecret/pluginLogin",
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify({
        id: secretKey,
        userId: userId,
        orgCode: "请填写本院机构编码",
        deptCode: deptCode
      })
    }
  );

  const result = await response.json();

  if (!response.ok || result.result !== true
      || Number(result.code) !== 1000) {
    throw new Error(result.desc || "登录验证失败");
  }

  return result.data;
}

登录成功后,将原系统运行所需的 Token 和用户信息写入 sessionStorage

function saveUserSession(loginData) {
  const user = loginData.user || {};

  sessionStorage.setItem("token", loginData.token);
  sessionStorage.setItem("userId", user.userId || "");
  sessionStorage.setItem("loginName", user.loginName || "");
  sessionStorage.setItem("realName", user.realName || "");
  sessionStorage.setItem("orgCode", user.orgCode || "");
  sessionStorage.setItem("departmentName", user.departmentName || "");
  sessionStorage.setItem("userType", user.userType || "");
}

完成权限加载后,重新载入页面并进入数据上报路由:

window.location.replace(
  window.location.pathname + "?autologin=1#/dataReport"
);

04部署到前置软件目录

将最终 HTML 文件命名为:

infDiseaseAdvance-demo.html

目标目录为:

/opt/hclient/client/webapps/ROOT/

确认原 Vue 系统的静态资源仍然存在:

复制代码
ls -l /opt/hclient/client/webapps/ROOT/infDiseaseAdvanceDist
ls -l /opt/hclient/client/webapps/ROOT/infDiseaseAdvanceDist/js
ls -l /opt/hclient/client/webapps/ROOT/infDiseaseAdvanceDist/css
HTML 属于静态文件,一般覆盖后立即生效,不需要重启前置软件。浏览器如仍显示旧页面,可使用 Ctrl + F5强制刷新。

05上线前必须完成的验证

  1. 不带参数打开页面,确认能够显示快捷登录界面;
  2. 带正确工号和科室访问,确认自动进入数据上报;
  3. 切换不同科室,确认菜单权限与用户身份正确;
  4. 点击注销,确认返回快捷登录页而不是循环登录;
  5. 关闭浏览器重新打开,确认旧 Token 不会串号;
  6. 同时测试前置机直连地址和 Nginx 代理地址;
  7. 查看浏览器控制台,确认 JS、CSS 和图片没有 404。

06安全边界不能忽略

这类 URL 单点登录适合受控的医院内网,不应直接暴露到互联网。URL 参数只是用户身份线索,不应被当作绝对可信的身份凭证。正式使用时,还应结合内网访问控制、来源系统校验、HTTPS、访问日志和最小权限策略。
  • 不要在公开文章或代码仓库中泄露真实许可证标识、机构编码和接口响应;
  • 不要在页面中设置真实的默认工号、科室或管理账号;
  • 建议由服务端签发短时效、一次性登录凭据,避免只凭可修改的 URL 参数识别用户;
  • 记录发起系统、用户、科室、时间、来源 IP 和登录结果,方便审计追踪;
  • 必须保证 HIS 当前用户与前置软件用户之间存在可靠映射。

07写在最后

医院信息化改造不一定都要推倒重来。很多时候,只要把现有系统的登录流程、Token 保存方式和路由机制梳理清楚,就能用一个轻量页面改善使用体验。

这次改造没有触碰前置软件的核心业务,只是在 HIS 与前置软件之间补上了一座桥:用户少点几次鼠标,信息科也少维护一套桌面入口。前置软件开发商目前已无人维保,了解一点技术细节还是很有必要的。

技术的价值,不只是把系统“跑起来”,更是让业务人员用得顺、信息科管得住、出了问题查得到。
如果这篇文章对你有帮助,欢迎转发给同样奋战在医院信息化一线的朋友。