feat: 支持中转平台 direct 路由模式
- gateway 新增 local/direct/edge 多模式路由 - 邹平改为 direct 模式,由 gateway 主动访问前置机地址 - 更新 gateway/zpfb 环境变量示例和部署说明
This commit is contained in:
parent
11c141fc21
commit
b8a6e9a8e1
@ -1,9 +1,12 @@
|
||||
CONFIG_NODE_PORT=8082
|
||||
CONFIG_ADAPTER_ROLE=standalone
|
||||
CONFIG_ADAPTER_ROLE=gateway
|
||||
CONFIG_MONGO_ENABLED=false
|
||||
|
||||
# 测试环境: 前端按 HIS 选项传固定 corpId,网关按 corpId 路由到 gk 映射。
|
||||
CONFIG_HOSPITAL_ADAPTER_CORP_MAP={"wwa54dfba0b5441ef1":"gk"}
|
||||
# 测试环境: 前端按 HIS 选项传固定 corpId,网关按 corpId 路由到医院编码。
|
||||
CONFIG_HOSPITAL_ADAPTER_CORP_MAP={"wwa54dfba0b5441ef1":"gk","zpfb-corp-id":"zpfb"}
|
||||
CONFIG_HOSPITAL_ADAPTER_ROUTE_MODE_MAP={"gk":"local","zpfb":"direct"}
|
||||
CONFIG_HOSPITAL_ADAPTER_DIRECT_URL_MAP={"zpfb":"http://120.224.145.13:8082"}
|
||||
|
||||
CONFIG_GK_JHIDS_PROXY_URL=https://crm.gykqyy.com/ykt/getYoucanData/gkJhids
|
||||
|
||||
CONFIG_RPC_TIMEOUT_MS=30000
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
CONFIG_NODE_PORT=8082
|
||||
CONFIG_NODE_PORT=18082
|
||||
CONFIG_ADAPTER_ROLE=standalone
|
||||
CONFIG_GATEWAY_URL=
|
||||
CONFIG_EDGE_ID=zpfb-edge-1
|
||||
CONFIG_EDGE_HOSPITAL_CODE=zpfb
|
||||
CONFIG_HOSPITAL_ADAPTER_CORP_MAP={}
|
||||
CONFIG_MONGO_ENABLED=false
|
||||
|
||||
|
||||
15
.env.zpfb.example
Normal file
15
.env.zpfb.example
Normal file
@ -0,0 +1,15 @@
|
||||
CONFIG_NODE_PORT=18082
|
||||
CONFIG_ADAPTER_ROLE=standalone
|
||||
CONFIG_HOSPITAL_ADAPTER_CORP_MAP={}
|
||||
CONFIG_MONGO_ENABLED=false
|
||||
|
||||
CONFIG_ZPFB_SOURCE_TYPE=view
|
||||
CONFIG_ZPFB_DB_TYPE=oracle_sqlplus
|
||||
CONFIG_ZPFB_DB_HOST=<db-host>
|
||||
CONFIG_ZPFB_DB_PORT=1521
|
||||
CONFIG_ZPFB_DB_NAME=<db-name>
|
||||
CONFIG_ZPFB_DB_USER=<db-user>
|
||||
CONFIG_ZPFB_DB_PASSWORD=
|
||||
CONFIG_ZPFB_DB_ORACLE_CONNECT_MODE=service
|
||||
CONFIG_ZPFB_DB_SQLPLUS_PATH=<sqlplus-path>
|
||||
CONFIG_ZPFB_DB_SQLPLUS_ENCODING=gbk
|
||||
59
README.md
59
README.md
@ -19,7 +19,7 @@ npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
默认端口: `8082`
|
||||
默认端口: `8082`,邹平子程序端口使用 `18082`。
|
||||
|
||||
测试 gateway 本地启动:
|
||||
|
||||
@ -27,13 +27,19 @@ npm run dev
|
||||
npm run gateway
|
||||
```
|
||||
|
||||
邹平子程序本地启动。默认按 `standalone` 模式启动,内部监听 `18082`;gateway 通过前置机/Nginx 暴露的地址访问,例如 `http://120.224.145.13:8082`:
|
||||
|
||||
```cmd
|
||||
npm run zp
|
||||
```
|
||||
|
||||
前置机轻量部署可以不安装 Mongo,只安装 Node.js 和 PM2。Mongo 默认关闭,查询接口仍可直接访问医院视图。
|
||||
|
||||
## 运行角色
|
||||
|
||||
- `standalone`: 单机调试/院内直接部署,标准资源接口和 YKT 兼容接口都由本机查询 HIS。
|
||||
- `gateway`: 中心网关,给 `ytk-customer-service` 调用,并通过 WebSocket 转发到院内 Agent。
|
||||
- `edge`: 院内 Agent,主动连接中心网关,只负责访问院内 HIS。
|
||||
- `gateway`: 中心网关,给 `ytk-customer-service` 调用,并按医院配置选择 `local`、`direct` 或 `edge` 路由。
|
||||
- `edge`: 院内 Agent,主动连接中心网关,只负责访问院内 HIS,适合医院不开放入站端口的场景。
|
||||
|
||||
关键配置:
|
||||
|
||||
@ -43,6 +49,8 @@ CONFIG_GATEWAY_URL=http://中心网关地址
|
||||
CONFIG_EDGE_ID=zpfb-edge-1
|
||||
CONFIG_EDGE_HOSPITAL_CODE=zpfb
|
||||
CONFIG_HOSPITAL_ADAPTER_CORP_MAP={"corpId":"zpfb"}
|
||||
CONFIG_HOSPITAL_ADAPTER_ROUTE_MODE_MAP={"zpfb":"direct"}
|
||||
CONFIG_HOSPITAL_ADAPTER_DIRECT_URL_MAP={"zpfb":"http://120.224.145.13:8082"}
|
||||
```
|
||||
|
||||
## Windows 前置机部署
|
||||
@ -52,7 +60,7 @@ CONFIG_HOSPITAL_ADAPTER_CORP_MAP={"corpId":"zpfb"}
|
||||
```cmd
|
||||
cd /d C:\code\yk\ykt\hospital-adapter-service
|
||||
npm install
|
||||
npm run build -- --env zpfb
|
||||
npm run build:zp
|
||||
```
|
||||
|
||||
构建输出:
|
||||
@ -79,26 +87,30 @@ PM2 启动示例:
|
||||
pm2 start bundle.js --name hospital-adapter-service
|
||||
```
|
||||
|
||||
edge bundle 也是同一个构建命令,只是每家医院用自己的 env 文件。比如 `.env.zpfb`:
|
||||
邹平子程序 bundle 也是同一个构建命令,只是每家医院用自己的 env 文件。比如 `.env.zpfb`:
|
||||
|
||||
```bash
|
||||
CONFIG_ADAPTER_ROLE=edge
|
||||
CONFIG_GATEWAY_URL=http://中心网关地址
|
||||
CONFIG_EDGE_ID=zpfb-edge-1
|
||||
CONFIG_EDGE_HOSPITAL_CODE=zpfb
|
||||
CONFIG_NODE_PORT=18082
|
||||
CONFIG_ADAPTER_ROLE=standalone
|
||||
CONFIG_MONGO_ENABLED=false
|
||||
```
|
||||
|
||||
然后重新构建:
|
||||
|
||||
```cmd
|
||||
npm run build -- --env zpfb
|
||||
npm run build:zp
|
||||
```
|
||||
|
||||
把生成的 `dist\bundle.zpfb.js` 复制到前置机后启动,可以复制时改名为 `bundle.js`:
|
||||
|
||||
```cmd
|
||||
pm2 start bundle.js --name hospital-adapter-edge-zpfb
|
||||
pm2 start bundle.js --name hospital-adapter-zpfb
|
||||
```
|
||||
|
||||
也可以不改名直接启动:
|
||||
|
||||
```cmd
|
||||
pm2 start bundle.zpfb.js --name hospital-adapter-zpfb
|
||||
```
|
||||
|
||||
中心 gateway 用自己的 env,例如 `.env.gateway`:
|
||||
@ -113,10 +125,12 @@ npm run build:gateway
|
||||
dist\bundle.gateway.js
|
||||
```
|
||||
|
||||
当前 `.env.gateway` 用 standalone 模式,前端 HIS 选项会传固定广口 `corpId`,网关按 `corpId` 路由到本地 `gk` adapter;`gk` adapter 再调用现有 dev-jcpt 服务获取原始数据,并由中继平台完成字段映射:
|
||||
当前 `.env.gateway` 用 `gateway` 模式,前端 HIS 选项会传固定 `corpId`,网关按 `corpId` 路由医院编码,再按医院编码选择调用模式。广口走本地 `gk` adapter,邹平走 `direct` 访问前置机公网地址:
|
||||
|
||||
```bash
|
||||
CONFIG_HOSPITAL_ADAPTER_CORP_MAP={"wwa54dfba0b5441ef1":"gk"}
|
||||
CONFIG_HOSPITAL_ADAPTER_CORP_MAP={"wwa54dfba0b5441ef1":"gk","zpfb-corp-id":"zpfb"}
|
||||
CONFIG_HOSPITAL_ADAPTER_ROUTE_MODE_MAP={"gk":"local","zpfb":"direct"}
|
||||
CONFIG_HOSPITAL_ADAPTER_DIRECT_URL_MAP={"zpfb":"http://120.224.145.13:8082"}
|
||||
CONFIG_GK_JHIDS_PROXY_URL=https://crm.gykqyy.com/ykt/getYoucanData/gkJhids
|
||||
```
|
||||
|
||||
@ -191,8 +205,10 @@ CONFIG_GK_JHIDS_TLS_REJECT_UNAUTHORIZED=true
|
||||
|
||||
## 查询接口
|
||||
|
||||
以下 curl 用于 `standalone` 或院内直连调试。邹平正式接中心网关时使用 `direct` 模式:邹平子程序内部监听 `18082`,前置机/Nginx 对外暴露 `http://120.224.145.13:8082`,gateway 主动访问这个对外地址。
|
||||
|
||||
```bash
|
||||
curl -X POST "http://127.0.0.1:8082/api/zpfb/patient/query" \
|
||||
curl -X POST "http://127.0.0.1:18082/api/zpfb/patient/query" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id_num":"110101199001011234","page":1,"pageSize":20}'
|
||||
```
|
||||
@ -200,7 +216,7 @@ curl -X POST "http://127.0.0.1:8082/api/zpfb/patient/query" \
|
||||
YKT HIS 兼容入口:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://127.0.0.1:8082/api/ykt/customerHisSync" \
|
||||
curl -X POST "http://127.0.0.1:18082/api/ykt/customerHisSync" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"hospitalCode":"zpfb","type":"getHisCustomerArchive","idCard":"身份证号"}'
|
||||
```
|
||||
@ -254,6 +270,10 @@ CONFIG_HOSPITAL_ADAPTER_GATEWAY_URL=http://127.0.0.1:8082
|
||||
邹平妇保示例:
|
||||
|
||||
```bash
|
||||
CONFIG_NODE_PORT=18082
|
||||
CONFIG_ADAPTER_ROLE=standalone
|
||||
CONFIG_MONGO_ENABLED=false
|
||||
|
||||
CONFIG_ZPFB_SOURCE_TYPE=view
|
||||
CONFIG_ZPFB_DB_TYPE=oracle_sqlplus
|
||||
CONFIG_ZPFB_DB_HOST=<db-host>
|
||||
@ -266,14 +286,17 @@ CONFIG_ZPFB_DB_SQLPLUS_PATH=<sqlplus-path>
|
||||
CONFIG_ZPFB_DB_SQLPLUS_ENCODING=gbk
|
||||
```
|
||||
|
||||
前置机 CMD 临时启动示例仅用于未打包时排查。正常单文件部署时,相关变量应写入本机 `.env.production` 后重新 `npm run build`。
|
||||
前置机 CMD 临时启动示例仅用于未打包时排查。正常单文件部署时,相关变量应写入本机 `.env.zpfb` 后重新 `npm run build:zp`。
|
||||
|
||||
```cmd
|
||||
set CONFIG_NODE_PORT=8082
|
||||
set CONFIG_NODE_PORT=18082
|
||||
set CONFIG_ADAPTER_ROLE=standalone
|
||||
set CONFIG_MONGO_ENABLED=false
|
||||
node index.js
|
||||
```
|
||||
|
||||
说明: 邹平正式接中心网关时使用 `direct`,由 gateway 主动访问 `http://120.224.145.13:8082`;前置机内部子程序仍监听 `18082`。
|
||||
|
||||
邹平第一版默认只启用:
|
||||
|
||||
- `patient` -> `v_youcan_patient`
|
||||
@ -284,7 +307,7 @@ node index.js
|
||||
## 同步标准快照
|
||||
|
||||
```bash
|
||||
curl -X POST "http://127.0.0.1:8082/api/his/sync" \
|
||||
curl -X POST "http://127.0.0.1:18082/api/his/sync" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"hospitalCode":"zpfb","resource":"patient","maxPages":1,"pageSize":200}'
|
||||
```
|
||||
|
||||
7
index.js
7
index.js
@ -8,7 +8,8 @@ const { handleQuery, queryStandardResource } = require("./src/core/query-control
|
||||
const { handleSync } = require("./src/sync/sync-controller");
|
||||
const { connectMongo, ensureIndexes, isMongoAvailable, getMongoDisabledReason } = require("./src/mongo");
|
||||
const { handleYktCustomerHisSync } = require("./src/ykt/customer-his-sync");
|
||||
const { attachGateway, queryEdgeResource, queryEdgeCustomerHisSync, listEdges } = require("./src/gateway/edge-registry");
|
||||
const { attachGateway, listEdges } = require("./src/gateway/edge-registry");
|
||||
const { queryGatewayResource, queryGatewayCustomerHisSync } = require("./src/gateway/router");
|
||||
const { startEdgeClient } = require("./src/edge/client");
|
||||
|
||||
loadEnv();
|
||||
@ -41,11 +42,11 @@ app.get("/healthz", (req, res) => {
|
||||
app.post("/api/ykt/customerHisSync", async (req, res) => {
|
||||
const queryResource =
|
||||
config.role === "gateway"
|
||||
? queryEdgeResource
|
||||
? queryGatewayResource
|
||||
: ({ hospitalCode, resource, query }) => queryStandardResource({ hospitalCode, resource, query, saveSnapshot: false }).then((result) => ({ status: "success", message: "查询成功", ...result }));
|
||||
return handleYktCustomerHisSync(req, res, {
|
||||
queryResource,
|
||||
forwardCustomHisSync: config.role === "gateway" ? queryEdgeCustomerHisSync : undefined,
|
||||
forwardCustomHisSync: config.role === "gateway" ? queryGatewayCustomerHisSync : undefined,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -6,12 +6,15 @@
|
||||
"scripts": {
|
||||
"dev": "node index.js",
|
||||
"gateway": "cross-env NODE_ENV=gateway node index.js",
|
||||
"zp": "cross-env NODE_ENV=zpfb node index.js",
|
||||
"pro": "cross-env NODE_ENV=production node index.js",
|
||||
"build": "node esbuild.config.js",
|
||||
"build:gateway": "node esbuild.config.js --env gateway --out dist/bundle.gateway.js",
|
||||
"build:zp": "node esbuild.config.js --env zpfb --out dist/bundle.zpfb.js",
|
||||
"build:pro": "cross-env NODE_ENV=production node esbuild.config.js",
|
||||
"start": "node dist/bundle.js",
|
||||
"start:gateway": "node dist/bundle.gateway.js"
|
||||
"start:gateway": "node dist/bundle.gateway.js",
|
||||
"start:zp": "node dist/bundle.zpfb.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.7",
|
||||
|
||||
@ -42,6 +42,8 @@ function getConfig() {
|
||||
edgeReconnectMs: parsePositiveInteger(process.env.CONFIG_EDGE_RECONNECT_MS, 5000),
|
||||
rpcTimeoutMs: parsePositiveInteger(process.env.CONFIG_RPC_TIMEOUT_MS, 30000),
|
||||
corpMap: parseMap(process.env.CONFIG_HOSPITAL_ADAPTER_CORP_MAP || process.env.CONFIG_CORP_HOSPITAL_MAP || ""),
|
||||
routeModeMap: parseMap(process.env.CONFIG_HOSPITAL_ADAPTER_ROUTE_MODE_MAP || ""),
|
||||
directUrlMap: parseMap(process.env.CONFIG_HOSPITAL_ADAPTER_DIRECT_URL_MAP || ""),
|
||||
mongoEnabled: parseBoolean(process.env.CONFIG_MONGO_ENABLED, false),
|
||||
mongoOptional: parseBoolean(process.env.CONFIG_MONGO_OPTIONAL, true),
|
||||
mongoUri: process.env.CONFIG_MONGO_URI || "",
|
||||
|
||||
96
src/gateway/router.js
Normal file
96
src/gateway/router.js
Normal file
@ -0,0 +1,96 @@
|
||||
const axios = require("axios");
|
||||
const { getConfig } = require("../core/config");
|
||||
const { queryStandardResource } = require("../core/query-controller");
|
||||
const { getAdapter } = require("../adapters");
|
||||
const { queryEdgeResource, queryEdgeCustomerHisSync } = require("./edge-registry");
|
||||
|
||||
async function queryGatewayResource({ hospitalCode, resource, query }) {
|
||||
const mode = getRouteMode(hospitalCode);
|
||||
if (mode === "local") {
|
||||
const result = await queryStandardResource({ hospitalCode, resource, query, saveSnapshot: false });
|
||||
return { status: "success", message: "查询成功", ...result };
|
||||
}
|
||||
if (mode === "direct") return queryDirectResource({ hospitalCode, resource, query });
|
||||
return queryEdgeResource({ hospitalCode, resource, query });
|
||||
}
|
||||
|
||||
async function queryGatewayCustomerHisSync({ hospitalCode, event }) {
|
||||
const mode = getRouteMode(hospitalCode);
|
||||
if (mode === "local") {
|
||||
const adapter = await getAdapter(hospitalCode);
|
||||
if (adapter && typeof adapter.customerHisSync === "function") return adapter.customerHisSync(event);
|
||||
throwGatewayError("本地医院适配器未提供 HIS 兼容接口", "LOCAL_ADAPTER_UNSUPPORTED");
|
||||
}
|
||||
if (mode === "direct") return queryDirectCustomerHisSync({ hospitalCode, event });
|
||||
return queryEdgeCustomerHisSync({ hospitalCode, event });
|
||||
}
|
||||
|
||||
async function queryDirectResource({ hospitalCode, resource, query }) {
|
||||
const baseUrl = getDirectBaseUrl(hospitalCode);
|
||||
const url = `${baseUrl}/api/${encodeURIComponent(hospitalCode)}/${encodeURIComponent(resource)}/query`;
|
||||
const response = await requestDirect(url, query || {});
|
||||
return normalizeStandardResponse(response);
|
||||
}
|
||||
|
||||
async function queryDirectCustomerHisSync({ hospitalCode, event }) {
|
||||
const baseUrl = getDirectBaseUrl(hospitalCode);
|
||||
const url = `${baseUrl}/api/ykt/customerHisSync`;
|
||||
const response = await requestDirect(url, { ...(event || {}), hospitalCode });
|
||||
if (response && response.success === false) return { success: false, message: response.message || "医院子程序查询失败", list: [], err: response.err || response.code };
|
||||
if (response && response.success === true) return response;
|
||||
if (response && response.status === "fail") return { success: false, message: response.message || "医院子程序查询失败", list: [], err: response.code };
|
||||
return response;
|
||||
}
|
||||
|
||||
async function requestDirect(url, payload) {
|
||||
try {
|
||||
const res = await axios.post(url, payload, {
|
||||
timeout: getConfig().timeoutMs,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
return res.data;
|
||||
} catch (err) {
|
||||
const message = (err.response && err.response.data && err.response.data.message) || err.message || "医院子程序调用失败";
|
||||
throwGatewayError(message, (err.response && err.response.data && err.response.data.code) || err.code || "DIRECT_REQUEST_FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeStandardResponse(response) {
|
||||
if (!response) return { status: "fail", message: "医院子程序无响应", code: "DIRECT_EMPTY_RESPONSE", data: [] };
|
||||
if (response.status === "success" || response.status === "fail") return response;
|
||||
if (response.success === false) return { status: "fail", message: response.message || "医院子程序查询失败", code: response.err || response.code || "DIRECT_QUERY_FAILED", data: [] };
|
||||
return {
|
||||
status: "success",
|
||||
message: response.message || "查询成功",
|
||||
data: Array.isArray(response.data) ? response.data : Array.isArray(response.list) ? response.list : [],
|
||||
page: response.page || 1,
|
||||
pageSize: response.pageSize || 20,
|
||||
total: response.total || 0,
|
||||
};
|
||||
}
|
||||
|
||||
function getRouteMode(hospitalCode) {
|
||||
const config = getConfig();
|
||||
const raw = config.routeModeMap[hospitalCode] || config.routeModeMap["*"] || "edge";
|
||||
const mode = String(raw || "").trim().toLowerCase();
|
||||
return ["local", "direct", "edge"].includes(mode) ? mode : "edge";
|
||||
}
|
||||
|
||||
function getDirectBaseUrl(hospitalCode) {
|
||||
const config = getConfig();
|
||||
const url = config.directUrlMap[hospitalCode] || config.directUrlMap["*"];
|
||||
if (!url) throwGatewayError(`未配置医院 direct 地址: ${hospitalCode}`, "DIRECT_URL_MISSING");
|
||||
return String(url).replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function throwGatewayError(message, code) {
|
||||
const err = new Error(message);
|
||||
err.code = code;
|
||||
throw err;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
queryGatewayResource,
|
||||
queryGatewayCustomerHisSync,
|
||||
getRouteMode,
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user