Compare commits

..
11 Commits
Author SHA1 Message Date
lianlonggangandClaude Code 97be68db61 feat: 新增 /api/admin/httpProxy 环回代理端点,供 B 端接口测试直连调用(自动附加 Basic/X-Admin-Token,路径白名单限 /order 与 /api/admin,读超时 120s)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-11 17:50:34 +08:00
lianlonggangandClaude Code 8fff1db294 docs: README 重写对齐现状(Java17+Solon、延安 TLS1.0 启动命令、外挂配置与安全注意事项);tls10.security 移入 resources 随 jar 打包留底
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-10 14:19:36 +08:00
lianlonggangandClaude Code 04e4d66a4c feat: 结果状态接口切换 getResultStatus、全局异常分级细化、日期序列化对齐、Oracle NLS/orai18n 与 BSM 结果视图改造、API 访问日志过滤器
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-10 14:03:08 +08:00
lianlonggangandClaude Code b871409361 fix: 提交预约补充成功 peId 与失败 errorMsg 日志,日志 pattern 去掉 %file:%line 消除 ?:?
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-10 14:02:53 +08:00
lianlonggangandClaude Code a3566c1f0c fix: health 接口 DB 探测改后台缓存,避免阻塞被误判宕机
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-09 09:35:26 +08:00
lianlonggangandClaude Code ae2b585df9 feat: 前置机安全加固(404 路由降级 INFO + IP 白名单过滤器)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-09 09:35:22 +08:00
lianlonggangandClaude Code b7283db0bd fix: nx SQL 对齐线上旧版出参(补 CAST 与 getEmpItem 缺失列)
- getSuggestAll/getConclusionAll 恢复 text→varchar(8000) CAST(text 列不能直接 REPLACE)
- getEmpItem 补 PE_DEPT_CODE/PE_DEPT_NAME/REGISTER_DATE(对齐线上 8082 出参)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-04 19:25:20 +08:00
lianlonggangandClaude Code aeed30f014 refactor: 延安归并宁夏方言(删除 ya 独立实现)
延安数据库结构已与宁夏完全一致(V_PHYEXAM_* 视图 + P_ADD_* 存储过程):
- YananAdapter 改继承 NxAdapterBase,hmType 归并为 nx
- 删除 YaAdapterBase / YaOrderMapper / YaOrderMapper.xml
- app.yml 去掉 ya 的 mapper 注册

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-04 18:59:51 +08:00
lianlonggangandClaude Code 51b050bd4c feat: 全面对齐原 hospitalmiddle 出参契约
- Result 对齐旧结构 {"success","msg","data"}(移除 code 体系,异常统一 error(msg))
- nullAsWriteable=true:VO 序列化保留 null 字段(对齐 Jackson 默认)
- getPeStatus/getExamListByDate 强类型化(PeStatusPhyVo/ExamListVo,契约不变)
- getResultByPeId/getResultByIdcardAndYearAll 补报告组装(hospitalId/结论/建议/item子表)
- bsm SQL 换回旧视图(V_PHYEXAM_PE_STATUS/V_PHYEXAM_PE_PERSONAL_ITEM),修复
  PE_TRANSFER_HOSPITAL 列缺失与 peItemList 字段差异
- OkHttp → JDK HttpClient;shade 过滤重叠资源;排除重复 mybatis-plus-core 3.5.5

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-04 18:54:11 +08:00
lianlonggangandClaude 754448e370 feat: 接口测试工具 - @ApiMeta 注解扫描生成接口清单
- 新增 @ApiMeta/@ApiParam 注解,OrderApi 每个方法标注 desc+参数描述
- 新增 ApiScanner 反射扫描器:从 @Mapping+方法签名自动提取 method/path/params
- AdminApi /apis 改为 ApiScanner.scan(OrderApi.class),替代手写清单
- 新增接口自动同步:OrderApi 加 @ApiMeta 即可,无需维护写死列表

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-02 13:49:39 +08:00
lianlonggangandClaude 8b32335eee fix: 启动/激活日志改走 logback 落盘(System.out 服务运行时不可见)
- HospitalFrontApp:启动完成日志带 PID/激活医院/端口,改 SLF4J;停机钩子同步改
- HospitalRouter:构造器激活医院日志改 SLF4J
- 移除上轮联调遗留的异常因果链诊断代码

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-27 17:16:14 +08:00
38 changed files with 1550 additions and 1823 deletions
+2 -1
View File
@@ -2,6 +2,7 @@
target/ target/
*.class *.class
*.jar *.jar
dist/
# 运行时目录(本地联调用,不入 Git) # 运行时目录(本地联调用,不入 Git)
bin/ bin/
@@ -54,4 +55,4 @@ mvnw.cmd
# JDK # JDK
*.jdk *.jdk
*.jre *.jredist/
+82 -88
View File
@@ -1,10 +1,8 @@
# Hospital Front — 医院前置机程序 # Hospital Front — 医院前置机程序
> 基于 Kotlin + Solon 4.0.6 重构的医院前置机对接系统 > 基于 Java 17 + Solon 4.0.6 的医院前置机对接系统
> >
> 重构源:[hospitalmiddle](../hospitalmiddle)SpringBoot 2.7 + MyBatis-Plus + Oracle/SqlServer > 重构源:hospitalmiddleSpringBoot 2.7 + MyBatis-Plus + Oracle/SqlServer
>
> 完整方案:[E:\data\ob_data\myob\11 健康长庆\医院前置机重构\](../ob_data)
--- ---
@@ -12,34 +10,35 @@
体检预约系统通过本程序与多家医院 HIS 系统对接,实现: 体检预约系统通过本程序与多家医院 HIS 系统对接,实现:
- 体检预约的提交、取消 - 体检预约的提交、取消
- 体检项目查询、状态查询 - 体检项目 / 组合查询、体检状态查询
- 体检结果/结论/建议的获取 - 体检结果 / 结论 / 建议的获取
- 单位信息同步 - 单位信息同步
**支持 5 家医院**(拼音编码): **支持医院**(拼音编码,由 `hospital.active` 指定激活):
| 编码 | 医院名 | 数据库 |
|---|---|---| | 编码 | 医院名 | 数据库 | Adapter |
| `xinglongyuan` | 兴隆园医院 | Oracle | |---|---|---|---|
| `qingyang` | 庆阳医院 | Oracle | | `xinglongyuan` | 兴隆园医院 | Oracle | `XingLongYuanAdapter` |
| `ningxia` | 宁夏宝石花医院 | SqlServer | | `qingyang` | 庆阳医院 | Oracle | `QingyangAdapter` |
| `yanan` | 延安医院 | SqlServer | | `ningxia` | 宁夏宝石花医院 | SqlServer | `NingxiaAdapter` |
| `jinghe` | 泾河医院 | Oracle | | `yanan` | 延安医院 | SqlServer | `YananAdapter` |
| `jinghe` | 泾河医院 | Oracle | `JingheAdapter` |
医院通过 `HospitalAdapter` 接口 + `HospitalRouter` 路由(策略模式),方言基类:
- `BsmAdapterBase`Oracle 方言(bsm Mapper
- `NxAdapterBase`SqlServer 方言(nx Mapper
--- ---
## 2. 技术栈 ## 2. 技术栈
- **语言**Kotlin 2.0.21`jvmTarget=17` - **语言**Java 17
- **框架**Solon 4.0.6(国产轻量级 Java 框架) - **框架**Solon 4.0.6
- **JDK**17 LTS(推荐)或 21 LTS
- **ORM**MyBatis-Plus 3.5.9 - **ORM**MyBatis-Plus 3.5.9
- **数据库**Oracle / SqlServer(直连医院库,不可修改) - **数据库**Oracleojdbc11 + orai18nZHS16GBK 字符集支持)/ SqlServer(直连医院库,只读对接,不可修改表结构
- **日志**Logback 1.5.12 - **日志**Logback(异步 appenderinfo.log / error.log 分文件滚动)
- **HTTP/WS**OkHttp 4.12.0
- **构建**Maven 3.9+ - **构建**Maven 3.9+
> ✅ **依赖瘦身**:不接入 Knife4j / Druid / Lombok / Jasypt(详见 [01-技术栈选型.md](../ob_data/01-技术栈选型.md)
--- ---
## 3. 项目结构 ## 3. 项目结构
@@ -48,31 +47,22 @@
hospital-front/ hospital-front/
├── pom.xml ├── pom.xml
├── README.md ├── README.md
├── .gitignore ├── conf/ # 各医院外置配置(含生产凭据,不入 Git)
── src/ ── src/main/
├── main/ ├── java/com/hospital/front/
│ ├── kotlin/com/hospital/front/ │ ├── HospitalFrontApp.java # 启动类
│ │ │ ├── HospitalFrontApp.kt # @SolonMain 启动类 │ ├── adapter/ # 医院适配器(策略模式 + 方言基类)
│ │ │ ├── adapter/ # 医院适配器(策略模式 │ ├── api/ # HTTP 接口(order / admin
│ │ │ ├── api/ # HTTP 接口层(按业务域) │ ├── service/ # 业务服务层
│ │ │ ├── service/ # 业务服务层 │ ├── mapper/bsm|nx/ # 数据访问层(按方言分包)
│ │ │ ├── mapper/ # 数据访问层 │ ├── vo/ dto/ # 值对象 / 传输对象
│ │ │ ├── vo/ # 值对象 │ ├── infra/ # 基础设施(过滤器、异常处理、扫描器)
│ │── dto/ # 数据传输对象 ── task/ # 定时任务(Uptime-Kuma 上报)
│ │ │ ├── infra/ # 基础设施 └── resources/
── task/ # 定时任务 ── app.yml # 主配置(框架级 + 公共配置)
── resources/ ── logback.xml # 日志配置
│ │ ├── app.yml # Solon 主配置 ├── tls10.security # 延安专用 JDK 安全策略(放开 TLSv1/弱算法)
│ │ ── logback.xml # 日志配置 ── mapper/ # MyBatis XML
│ │ └── mapper/ # MyBatis XML
│ └── test/kotlin/ # 单元测试
└── pac/ # 部署包(运行时外置)
├── bin/ # 启动脚本
├── conf/ # 外置配置
├── backup/ # 历史版本
├── log/ # 日志
├── pid/ # PID 文件
└── tmp/ # 临时文件
``` ```
--- ---
@@ -89,57 +79,61 @@ mvn clean package -DskipTests
## 5. 启动(开发环境) ## 5. 启动(开发环境)
```bash ```bash
# 拷贝外置配置(首次启动 # 方式一:默认加载 classpath 的 app.ymlhospital.active 默认 jinghe
cp -r pac/conf-example pac/conf java -jar target/hospital-front-1.0.0.jar
# 编辑配置
vim pac/conf/app-jinghe.yml
# 启动(默认加载 app.yml + conf/app-{HOSPITAL}.yml
HOSPITAL=jinghe java -jar target/hospital-front-1.0.0.jar
``` ```
数据源等医院差异配置在 `app-{医院}.yml` 中,开发期可用 `--solon.config.add` 外挂。
--- ---
## 6. 部署 ## 6. 生产部署注意事项(重要)
详细部署、升级、回滚 SOP[05-部署运维.md](../ob_data/05-部署运维.md) ### 6.1 外挂医院配置
**Windows Server 简版** 生产环境配置文件**外置于 jar 同级或 `conf/` 目录**,通过 `--solon.config.add` 追加加载,
```batch 修改数据库地址 / 鉴权等无需重新打包:
D:
cd D:\hospital-front\bin ```bash
call start.bat start java -jar hospital-front.jar --solon.config.add=conf/app-yanan.yml --server.port=8003
``` ```
- 配置模板见仓库 `conf/` 目录(app-jinghe / app-ningxia / app-yanan
- `--server.port` 覆盖默认端口 8082
- 外挂文件中必须指定 `hospital.active``hm.type`bsm / nx / ya),并定义 `solon.dataSources.db1`
### 6.2 延安医院:TLS 1.0 特殊处理
延安 SqlServer 为老版本(仅支持 TLS 1.0 / 弱加密算法),JDK 17 默认禁用,
必须外挂 JDK 安全属性文件启动,否则连接报 `handshake failure` / `No appropriate protocol`
```bash
java -Dfile.encoding=UTF-8 -Djava.security.properties=tls10.security -jar hospital-front.jar --solon.config.add=conf/app-yanan.yml --server.port=8003
```
- `tls10.security` 已打包在 jar 内(`src/main/resources/`),上述命令按 classpath 加载,无需单独分发文件;也可用文件系统绝对路径覆盖
- `-Dfile.encoding=UTF-8`Windows 服务器默认 GBK,防止日志与接口中文乱码
- 其余医院**不要**带 `tls10.security`(会全局放开弱算法,降低安全基线)
### 6.3 安全配置(生产必读)
`app.yml` 中以下配置生产环境必须核对(可在外挂配置中覆盖):
| 配置 | 说明 |
|---|---|
| `order.basic` | `/order/**` 的 HTTP Basic 认证(`user:password`),生产必须配置 |
| `admin.token` | `/api/admin/**``X-Admin-Token`Agent 环回调用 |
| `security.ipWhitelistEnabled` / `ipWhitelist` | IP 白名单(支持精确 IP / 通配段 / CIDR),端口暴露公网时务必开启 |
| `security.trustProxy` | nginx 反代部署时开启,从 X-Forwarded-For 取真实客户端 IP |
### 6.4 日志
- 日志目录默认 `./log``-Dlog.path=xxx` 可覆盖)
- `info.log`INFO 及以上)/ `error.log`(仅 ERROR)分文件,50MB 按天滚动,保留 30 天
- pattern 不含 `%file:%line`AsyncAppender 下取不到调用者信息,会打成 `?:?`
--- ---
## 7. 文档 ## 7. 文档
完整设计文档位于:`E:\data\ob_data\myob\11 健康长庆\医院前置机重构\` 完整设计文档位于:`E:\data\ob_data\myob\11 健康长庆\医院前置机重构\`
| 文档 | 内容 |
|---|---|
| [00-总体方案.md](../ob_data/00-总体方案.md) | 整体方案 |
| [01-技术栈选型.md](../ob_data/01-技术栈选型.md) | Solon 选型、版本、注解对照 |
| [02-医院差异化矩阵.md](../ob_data/02-医院差异化矩阵.md) | 5 家医院差异、Adapter 拆分 |
| [03-架构设计.md](../ob_data/03-架构设计.md) | 系统架构、模块、时序 |
| [04-数据流与接口契约.md](../ob_data/04-数据流与接口契约.md) | 接口清单、入参出参 |
| [05-部署运维.md](../ob_data/05-部署运维.md) | 部署、升级、回滚 SOP |
| [06-变更记录.md](../ob_data/06-变更记录.md) | 所有变更登记 |
---
## 8. 任务进度
| Step | 内容 | 状态 |
|---|---|---|
| 1 | 工程骨架 | ✅ |
| 2 | 5 医院配置中心 | ⏳ |
| 3 | 业务接口 Kotlin 重写 | ⏳ |
| 4 | 5 套 Mapper 适配 | ⏳ |
| 5 | 主程序被控端 API | ⏳ |
| 6 | Agent 工程 | ⏳ |
| 7 | 升级流程 | ⏳ |
| 8 | 日志推送 | ⏳ |
| 9 | B 端管理后台 | ⏳ |
| 10 | 联调 + 文档 | ⏳ |
+26 -10
View File
@@ -79,6 +79,14 @@
<dependency> <dependency>
<groupId>org.noear</groupId> <groupId>org.noear</groupId>
<artifactId>mybatis-plus-solon-plugin</artifactId> <artifactId>mybatis-plus-solon-plugin</artifactId>
<!-- 排除插件传递的 core 3.5.5:显式声明的 mybatis-plus 3.5.9 已内含 core 类,
双版本共存既造成 shade 同类冲突(随机取一边混搭),也白白多出 ~4MB 体积 -->
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
@@ -99,6 +107,12 @@
<artifactId>ojdbc11</artifactId> <artifactId>ojdbc11</artifactId>
<version>${ojdbc.version}</version> <version>${ojdbc.version}</version>
</dependency> </dependency>
<!-- Oracle NLS 字符集支持:ZHS16GBK 等中文/多字节字符集转换表,缺失报 ORA-17056 -->
<dependency>
<groupId>com.oracle.database.nls</groupId>
<artifactId>orai18n</artifactId>
<version>${ojdbc.version}</version>
</dependency>
<!-- SqlServer --> <!-- SqlServer -->
<dependency> <dependency>
@@ -111,15 +125,8 @@
- sqlite-jdbc:原为 B 端 Agent 用,Agent 已拆分为独立 Python 项目 - sqlite-jdbc:原为 B 端 Agent 用,Agent 已拆分为独立 Python 项目
- h2:开发 Mock 数据源,需要时本地临时加回 - h2:开发 Mock 数据源,需要时本地临时加回
- sa-token-solon-plugin:鉴权已改为 OrderApi 手写 HTTP Basic - sa-token-solon-plugin:鉴权已改为 OrderApi 手写 HTTP Basic
- okhttpUptimeKuma 心跳已改用 JDK 内置 java.net.http.HttpClient
--> -->
<!-- ========== HTTPUptimeKuma 心跳用) ========== -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<!-- ========== JSON 解析(Solon 生态标准库) ========== -->
<dependency> <dependency>
<groupId>org.noear</groupId> <groupId>org.noear</groupId>
<artifactId>snack3</artifactId> <artifactId>snack3</artifactId>
@@ -134,7 +141,7 @@
</dependencies> </dependencies>
<build> <build>
<finalName>hospital-front-${project.version}</finalName> <finalName>hospital-front</finalName>
<plugins> <plugins>
<!-- 打 fat jar(包含所有依赖) --> <!-- 打 fat jar(包含所有依赖) -->
@@ -163,10 +170,19 @@
<exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude> <exclude>META-INF/*.RSA</exclude>
<!-- 依赖自带的模块描述文件与 fat jar 不兼容,剔除以消除 shade 警告 -->
<exclude>module-info.class</exclude>
<exclude>META-INF/versions/*/module-info.class</exclude>
<!-- 以下重叠资源均为 IDE 提示/native-image 元数据/许可证文本,
运行时不读取,剔除以消除 shade 的 overlapping 警告 -->
<exclude>META-INF/solon/solon-configuration-metadata.json</exclude>
<exclude>META-INF/native-image/**</exclude>
<exclude>META-INF/LICENSE</exclude>
<exclude>META-INF/MANIFEST.MF</exclude>
</excludes> </excludes>
</filter> </filter>
</filters> </filters>
<finalName>hospital-front-${project.version}</finalName> <finalName>hospital-front</finalName>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
@@ -1,12 +1,13 @@
package com.hospital.front; package com.hospital.front;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import org.noear.solon.Solon; import org.noear.solon.Solon;
import org.noear.solon.annotation.SolonMain; import org.noear.solon.annotation.SolonMain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* 医院前置机主程序启动类。 * 医院前置机主程序启动类。
@@ -20,29 +21,19 @@ import org.noear.solon.annotation.SolonMain;
@SolonMain @SolonMain
public class HospitalFrontApp { public class HospitalFrontApp {
private static final Logger log = LoggerFactory.getLogger(HospitalFrontApp.class);
/** /**
* 主入口。 * 主入口。
*/ */
public static void main(String[] args) { public static void main(String[] args) {
// TODO 临时诊断代码:打印完整异常因果链,定位注入失败根因后移除 Solon.start(HospitalFrontApp.class, args);
try {
Solon.start(HospitalFrontApp.class, args);
} catch (Throwable e) {
Throwable t = e;
int depth = 0;
while (t != null && depth < 10) {
System.err.println("=== 因果链[" + depth + "]: " + t.getClass().getName() + ": " + t.getMessage());
StackTraceElement[] stackTrace = t.getStackTrace();
for (int i = 0; i < Math.min(10, stackTrace.length); i++) {
System.err.println(" at " + stackTrace[i]);
}
t = t.getCause();
depth++;
}
System.exit(1);
}
long pid = ProcessHandle.current().pid(); long pid = ProcessHandle.current().pid();
System.out.println("[HospitalFront] 启动完成,PID=" + pid); // 走 logback 落盘(服务方式运行时控制台不可见,System.out 进不了日志文件)
log.info("启动完成,PID={},激活医院={},端口={}",
pid,
Solon.cfg().get("hospital.active", "?"),
Solon.cfg().get("server.port", "?"));
// 写 PID 文件(供 start.bat stop / Agent 升级流程强杀使用) // 写 PID 文件(供 start.bat stop / Agent 升级流程强杀使用)
try { try {
File dir = new File("pid"); File dir = new File("pid");
@@ -58,7 +49,7 @@ public class HospitalFrontApp {
new File("pid/hospital-front.pid").delete(); new File("pid/hospital-front.pid").delete();
} catch (Exception ignored) { } catch (Exception ignored) {
} }
System.out.println("[HospitalFront] 优雅停机中..."); log.info("优雅停机中...");
})); }));
} }
} }
@@ -1,5 +1,11 @@
package com.hospital.front.adapter; package com.hospital.front.adapter;
import com.hospital.front.vo.ConclusionVo;
import com.hospital.front.vo.ExamListVo;
import com.hospital.front.vo.MedicalMiddleResultVo;
import com.hospital.front.vo.PeStatusVo;
import com.hospital.front.vo.SuggestVo;
import com.hospital.front.dto.ProcResult; import com.hospital.front.dto.ProcResult;
import com.hospital.front.dto.Result; import com.hospital.front.dto.Result;
import com.hospital.front.mapper.bsm.BsmOrderMapper; import com.hospital.front.mapper.bsm.BsmOrderMapper;
@@ -32,7 +38,7 @@ public abstract class BsmAdapterBase implements HospitalAdapter {
try { try {
return Result.success(mapper.ping() == 1); return Result.success(mapper.ping() == 1);
} catch (Exception e) { } catch (Exception e) {
return Result.error(Result.CODE_DB_UNREACHABLE, "数据库连接失败:" + e.getMessage()); return Result.error("数据库连接失败:" + e.getMessage());
} }
} }
@@ -87,7 +93,7 @@ public abstract class BsmAdapterBase implements HospitalAdapter {
} }
@Override @Override
public List<Map<String, Object>> getPhyexamStatus(String peIds) { public List<PeStatusVo> getPhyexamStatus(String peIds) {
return mapper.getPhyexamStatus(peIds); return mapper.getPhyexamStatus(peIds);
} }
@@ -122,65 +128,45 @@ public abstract class BsmAdapterBase implements HospitalAdapter {
} }
@Override @Override
public List<Map<String, Object>> getConclusion(String peId) { public List<ConclusionVo> getConclusionAll(String peId) {
return mapper.getConclusion(peId);
}
@Override
public List<Map<String, Object>> getConclusionAll(String peId) {
return mapper.getConclusionAll(peId); return mapper.getConclusionAll(peId);
} }
@Override @Override
public List<Map<String, Object>> getSuggest(String peId) { public List<SuggestVo> getSuggestAll(String peId) {
return mapper.getSuggest(peId);
}
@Override
public List<Map<String, Object>> getSuggestAll(String peId) {
return mapper.getSuggestAll(peId); return mapper.getSuggestAll(peId);
} }
@Override @Override
public Map<String, Object> getPeStatusByIdnoAndYear(String idNo, String year) { public PeStatusVo getPeStatusByIdnoAndYear(String idNo, String year) {
return mapper.getPeStatusByIdnoAndYear(idNo, year); return mapper.getPeStatusByIdnoAndYear(idNo, year);
} }
@Override @Override
public List<Map<String, Object>> getResultByIdcardAndYear(String idNo, String year) { public List<MedicalMiddleResultVo> getResultByIdcardAndYear(String idNo, String year) {
return mapper.getResultByIdcardAndYear(idNo, year); return mapper.getResultByIdcardAndYear(idNo, year);
} }
@Override @Override
public List<Map<String, Object>> getResultByIdcardAndYearAll(String idNo, String year) { public List<MedicalMiddleResultVo> getResultByIdcardAndYearAll(String idNo, String year) {
return mapper.getResultByIdcardAndYearAll(idNo, year); return mapper.getResultByIdcardAndYearAll(idNo, year);
} }
@Override @Override
public List<Map<String, Object>> getResultByPeId(String peId) { public List<MedicalMiddleResultVo> getResultByPeId(String peId) {
return mapper.getResultByPeId(peId); return mapper.getResultByPeId(peId);
} }
@Override @Override
public List<Map<String, Object>> getDisResultByAuditDate(String auditDate) { public List<MedicalMiddleResultVo> getItemByPeId(String peId) {
return mapper.getDisResultByAuditDate(auditDate);
}
@Override
public List<Map<String, Object>> getItemByPeId(String peId) {
return mapper.getItemByPeId(peId); return mapper.getItemByPeId(peId);
} }
@Override @Override
public List<Map<String, Object>> getItemByPeIdAll(String peId) { public List<MedicalMiddleResultVo> getItemByPeIdAll(String peId) {
return mapper.getItemByPeIdAll(peId); return mapper.getItemByPeIdAll(peId);
} }
@Override
public List<Map<String, Object>> getResultByIdNoOrPeId(String idNo, String peId, String auditYear) {
return mapper.getResultByIdNoOrPeId(idNo, peId, auditYear);
}
@Override @Override
public Map<String, Object> getPeIdAndDateByIdcard(String idNo) { public Map<String, Object> getPeIdAndDateByIdcard(String idNo) {
return mapper.getPeIdAndDateByIdcard(idNo); return mapper.getPeIdAndDateByIdcard(idNo);
@@ -192,7 +178,7 @@ public abstract class BsmAdapterBase implements HospitalAdapter {
} }
@Override @Override
public List<Map<String, Object>> getExamListByDate(String auditDate) { public List<ExamListVo> getExamListByDate(String auditDate) {
return mapper.getExamListByDate(auditDate); return mapper.getExamListByDate(auditDate);
} }
@@ -1,5 +1,11 @@
package com.hospital.front.adapter; package com.hospital.front.adapter;
import com.hospital.front.vo.ConclusionVo;
import com.hospital.front.vo.ExamListVo;
import com.hospital.front.vo.MedicalMiddleResultVo;
import com.hospital.front.vo.PeStatusVo;
import com.hospital.front.vo.SuggestVo;
import com.hospital.front.dto.ProcResult; import com.hospital.front.dto.ProcResult;
import com.hospital.front.dto.Result; import com.hospital.front.dto.Result;
import com.hospital.front.vo.UnitVo; import com.hospital.front.vo.UnitVo;
@@ -8,93 +14,76 @@ import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* 医院适配器接口(策略模式)。 * 医院适配器接口(各医院方言实现:bsm/nx,延安复用 nx)。
* *
* 5 家医院各自实现本接口,通过 HospitalRouter 按医院编码路由。 * getConclusion/getSuggest 与其 All 版 SQL 完全重复,已合并为 All 版
* 所有数据库差异(Oracle/SqlServer 方言、存储过程名)均封装在实现内; * (对外接口路径 /getConclusion、/getSuggest 保留,内部走 All 版)。
* 存储过程的入参出参契约由医院 HIS 系统约定,不可修改。
*
* 查询类方法统一返回 Map 列表(列名与医院视图/存储过程保持一致)。
*/ */
public interface HospitalAdapter { public interface HospitalAdapter {
/** 医院编码(拼音):xinglongyuan / qingyang / ningxia / yanan / jinghe / dev */ /** 医院编码(唯一,如 jinghe */
String code(); String code();
/** 医院中文名(日志与展示用) */ /** 医院显示名 */
String displayName(); String displayName();
/** Mapper 方言类型bsmOracle/ nx / yaSqlServer */ /** 医院方言类型bsm/nx/ya */
String hmType(); String hmType();
/** 轻量健康检查SELECT 1 探测) */ /** 数据库健康检查 */
Result<Boolean> healthCheck(); Result<Boolean> healthCheck();
// ================= 存储过程(写) ================= /** 提交预约(存储过程) */
/** 提交预约(P_ADD_PE_REGIST */
ProcResult saveOrder(String peInfoStr, String peItemStr); ProcResult saveOrder(String peInfoStr, String peItemStr);
/** 取消预约(P_DEL_PE_REGIST */ /** 取消预约(存储过程 */
ProcResult cancelOrder(String peId, String name, String idcard); ProcResult cancelOrder(String peId, String name, String idcard);
/** 添加/修改单位(P_ADD_UNIT */ /** 添加/修改单位(存储过程,V1/V2 参数不同 */
ProcResult addUnit(UnitVo unit, boolean useV2); ProcResult addUnit(UnitVo unit, boolean useV2);
// ================= 查询(视图) ================= /** 体检状态查询(多个 peId 逗号分隔) */
List<PeStatusVo> getPhyexamStatus(String peIds);
/** 体检预约 ID(逗号分隔多个)查询体检状态 */ /** 体检项目查询 */
List<Map<String, Object>> getPhyexamStatus(String peIds);
/** 医院所有体检项目(组合项) */
List<Map<String, Object>> getItemList(); List<Map<String, Object>> getItemList();
/** 医院所有体检项目及指标项 */ /** 体检指标项及体检组合数据 */
List<Map<String, Object>> getAllItemList(); List<Map<String, Object>> getAllItemList();
/** 医院所有体检指标项 */ /** 体检指标项数据 */
List<Map<String, Object>> getPeItemList(); List<Map<String, Object>> getPeItemList();
/** 按指标项 CODE 查询所属体检组合 */ /** 按体检指标项查所属组合 */
List<Map<String, Object>> getItemInfo(String peItemCode); List<Map<String, Object>> getItemInfo(String peItemCode);
/** 员工体检项目 */ /** 员工体检项目 */
List<Map<String, Object>> getEmpItem(String peId); List<Map<String, Object>> getEmpItem(String peId);
/** 员工体检结果 */ /** 按体检编号查体检结果 */
List<Map<String, Object>> getResult(String peId); List<Map<String, Object>> getResult(String peId);
/** 体检结论(列表 / 全量 */ /** 体检结论(列表) */
List<Map<String, Object>> getConclusion(String peId); List<ConclusionVo> getConclusionAll(String peId);
List<Map<String, Object>> getConclusionAll(String peId); /** 体检建议(全量,内部组装用) */
List<SuggestVo> getSuggestAll(String peId);
/** 体检建议(列表 / 全量) */
List<Map<String, Object>> getSuggest(String peId);
List<Map<String, Object>> getSuggestAll(String peId);
/** 按身份证号 + 年份查询体检状态 */ /** 按身份证号 + 年份查询体检状态 */
Map<String, Object> getPeStatusByIdnoAndYear(String idNo, String year); PeStatusVo getPeStatusByIdnoAndYear(String idNo, String year);
/** 按身份证号 + 年份查询体检结果(默认 / 全量) */ /** 按身份证号 + 年份查询体检结果(默认 / 全量) */
List<Map<String, Object>> getResultByIdcardAndYear(String idNo, String year); List<MedicalMiddleResultVo> getResultByIdcardAndYear(String idNo, String year);
List<Map<String, Object>> getResultByIdcardAndYearAll(String idNo, String year); List<MedicalMiddleResultVo> getResultByIdcardAndYearAll(String idNo, String year);
/** 按体检编号查询体检结果 */ /** 按体检编号查询体检结果 */
List<Map<String, Object>> getResultByPeId(String peId); List<MedicalMiddleResultVo> getResultByPeId(String peId);
/** 按审核日期查询体检报告(去重主表) */
List<Map<String, Object>> getDisResultByAuditDate(String auditDate);
/** 按体检编号查询体检项目子表(默认 / 全量) */ /** 按体检编号查询体检项目子表(默认 / 全量) */
List<Map<String, Object>> getItemByPeId(String peId); List<MedicalMiddleResultVo> getItemByPeId(String peId);
List<Map<String, Object>> getItemByPeIdAll(String peId); List<MedicalMiddleResultVo> getItemByPeIdAll(String peId);
/** 按身份证号或体检编号 + 审核年份查询体检结果 */
List<Map<String, Object>> getResultByIdNoOrPeId(String idNo, String peId, String auditYear);
/** 按身份证号查询当年 peId 与预约日期(单条 / 列表) */ /** 按身份证号查询当年 peId 与预约日期(单条 / 列表) */
Map<String, Object> getPeIdAndDateByIdcard(String idNo); Map<String, Object> getPeIdAndDateByIdcard(String idNo);
@@ -102,5 +91,5 @@ public interface HospitalAdapter {
List<Map<String, Object>> getPeIdAndDateByIdcardList(String idNo); List<Map<String, Object>> getPeIdAndDateByIdcardList(String idNo);
/** 按审核日期查询当天体检人员列表(去重;日期空则当天) */ /** 按审核日期查询当天体检人员列表(去重;日期空则当天) */
List<Map<String, Object>> getExamListByDate(String auditDate); List<ExamListVo> getExamListByDate(String auditDate);
} }
@@ -2,6 +2,8 @@ package com.hospital.front.adapter;
import org.noear.solon.Solon; import org.noear.solon.Solon;
import org.noear.solon.annotation.Component; import org.noear.solon.annotation.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -17,6 +19,8 @@ import java.util.Set;
@Component @Component
public class HospitalRouter { public class HospitalRouter {
private static final Logger log = LoggerFactory.getLogger(HospitalRouter.class);
/** 启动激活的医院编码(默认 jinghe) */ /** 启动激活的医院编码(默认 jinghe) */
private final String activeCode = Solon.cfg().get("hospital.active", "jinghe"); private final String activeCode = Solon.cfg().get("hospital.active", "jinghe");
@@ -24,7 +28,8 @@ public class HospitalRouter {
private volatile Map<String, HospitalAdapter> map; private volatile Map<String, HospitalAdapter> map;
public HospitalRouter() { public HospitalRouter() {
System.out.println("[HospitalRouter] 启动激活医院:" + activeCode); // 走 logback 落盘(服务方式运行时 System.out 进不了日志文件)
log.info("启动激活医院:{}", activeCode);
} }
/** /**
@@ -1,5 +1,11 @@
package com.hospital.front.adapter; package com.hospital.front.adapter;
import com.hospital.front.vo.ConclusionVo;
import com.hospital.front.vo.ExamListVo;
import com.hospital.front.vo.MedicalMiddleResultVo;
import com.hospital.front.vo.PeStatusVo;
import com.hospital.front.vo.SuggestVo;
import com.hospital.front.dto.ProcResult; import com.hospital.front.dto.ProcResult;
import com.hospital.front.dto.Result; import com.hospital.front.dto.Result;
import com.hospital.front.mapper.nx.NxOrderMapper; import com.hospital.front.mapper.nx.NxOrderMapper;
@@ -13,7 +19,7 @@ import java.util.Map;
/** /**
* NX 方言适配器基类(SqlServer)。 * NX 方言适配器基类(SqlServer)。
* *
* 适用医院:ningxia(宁夏宝石花)。 * 适用医院:ningxia(宁夏宝石花)/ yanan(延安,库结构已与宁夏完全一致)
*/ */
public abstract class NxAdapterBase implements HospitalAdapter { public abstract class NxAdapterBase implements HospitalAdapter {
@@ -31,7 +37,7 @@ public abstract class NxAdapterBase implements HospitalAdapter {
try { try {
return Result.success(mapper.ping() == 1); return Result.success(mapper.ping() == 1);
} catch (Exception e) { } catch (Exception e) {
return Result.error(Result.CODE_DB_UNREACHABLE, "数据库连接失败:" + e.getMessage()); return Result.error("数据库连接失败:" + e.getMessage());
} }
} }
@@ -83,7 +89,7 @@ public abstract class NxAdapterBase implements HospitalAdapter {
} }
@Override @Override
public List<Map<String, Object>> getPhyexamStatus(String peIds) { public List<PeStatusVo> getPhyexamStatus(String peIds) {
return mapper.getPhyexamStatus(peIds); return mapper.getPhyexamStatus(peIds);
} }
@@ -118,65 +124,45 @@ public abstract class NxAdapterBase implements HospitalAdapter {
} }
@Override @Override
public List<Map<String, Object>> getConclusion(String peId) { public List<ConclusionVo> getConclusionAll(String peId) {
return mapper.getConclusion(peId);
}
@Override
public List<Map<String, Object>> getConclusionAll(String peId) {
return mapper.getConclusionAll(peId); return mapper.getConclusionAll(peId);
} }
@Override @Override
public List<Map<String, Object>> getSuggest(String peId) { public List<SuggestVo> getSuggestAll(String peId) {
return mapper.getSuggest(peId);
}
@Override
public List<Map<String, Object>> getSuggestAll(String peId) {
return mapper.getSuggestAll(peId); return mapper.getSuggestAll(peId);
} }
@Override @Override
public Map<String, Object> getPeStatusByIdnoAndYear(String idNo, String year) { public PeStatusVo getPeStatusByIdnoAndYear(String idNo, String year) {
return mapper.getPeStatusByIdnoAndYear(idNo, year); return mapper.getPeStatusByIdnoAndYear(idNo, year);
} }
@Override @Override
public List<Map<String, Object>> getResultByIdcardAndYear(String idNo, String year) { public List<MedicalMiddleResultVo> getResultByIdcardAndYear(String idNo, String year) {
return mapper.getResultByIdcardAndYear(idNo, year); return mapper.getResultByIdcardAndYear(idNo, year);
} }
@Override @Override
public List<Map<String, Object>> getResultByIdcardAndYearAll(String idNo, String year) { public List<MedicalMiddleResultVo> getResultByIdcardAndYearAll(String idNo, String year) {
return mapper.getResultByIdcardAndYearAll(idNo, year); return mapper.getResultByIdcardAndYearAll(idNo, year);
} }
@Override @Override
public List<Map<String, Object>> getResultByPeId(String peId) { public List<MedicalMiddleResultVo> getResultByPeId(String peId) {
return mapper.getResultByPeId(peId); return mapper.getResultByPeId(peId);
} }
@Override @Override
public List<Map<String, Object>> getDisResultByAuditDate(String auditDate) { public List<MedicalMiddleResultVo> getItemByPeId(String peId) {
return mapper.getDisResultByAuditDate(auditDate);
}
@Override
public List<Map<String, Object>> getItemByPeId(String peId) {
return mapper.getItemByPeId(peId); return mapper.getItemByPeId(peId);
} }
@Override @Override
public List<Map<String, Object>> getItemByPeIdAll(String peId) { public List<MedicalMiddleResultVo> getItemByPeIdAll(String peId) {
return mapper.getItemByPeIdAll(peId); return mapper.getItemByPeIdAll(peId);
} }
@Override
public List<Map<String, Object>> getResultByIdNoOrPeId(String idNo, String peId, String auditYear) {
return mapper.getResultByIdNoOrPeId(idNo, peId, auditYear);
}
@Override @Override
public Map<String, Object> getPeIdAndDateByIdcard(String idNo) { public Map<String, Object> getPeIdAndDateByIdcard(String idNo) {
return mapper.getPeIdAndDateByIdcard(idNo); return mapper.getPeIdAndDateByIdcard(idNo);
@@ -188,7 +174,7 @@ public abstract class NxAdapterBase implements HospitalAdapter {
} }
@Override @Override
public List<Map<String, Object>> getExamListByDate(String auditDate) { public List<ExamListVo> getExamListByDate(String auditDate) {
return mapper.getExamListByDate(auditDate); return mapper.getExamListByDate(auditDate);
} }
@@ -1,202 +0,0 @@
package com.hospital.front.adapter;
import com.hospital.front.dto.ProcResult;
import com.hospital.front.dto.Result;
import com.hospital.front.mapper.ya.YaOrderMapper;
import com.hospital.front.vo.UnitVo;
import org.noear.solon.annotation.Inject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* YA 方言适配器基类(SqlServer)。
*
* 适用医院:yanan(延安)。
*/
public abstract class YaAdapterBase implements HospitalAdapter {
/** 数据库 MapperSolon 按类型注入) */
@Inject
protected YaOrderMapper mapper;
@Override
public String hmType() {
return "ya";
}
@Override
public Result<Boolean> healthCheck() {
try {
return Result.success(mapper.ping() == 1);
} catch (Exception e) {
return Result.error(Result.CODE_DB_UNREACHABLE, "数据库连接失败:" + e.getMessage());
}
}
@Override
public ProcResult saveOrder(String peInfoStr, String peItemStr) {
Map<String, Object> map = new HashMap<>();
map.put("peInfoStr", peInfoStr);
map.put("peItemStr", peItemStr);
mapper.saveOrder(map);
return new ProcResult(
str(map.get("resultCode")),
str(map.get("errorMsg")),
str(map.get("peId"))
);
}
@Override
public ProcResult cancelOrder(String peId, String name, String idcard) {
Map<String, Object> map = new HashMap<>();
map.put("peId", peId);
map.put("name", name);
map.put("idcard", idcard);
mapper.cancelOrder(map);
return new ProcResult(
str(map.get("resultCode")),
str(map.get("errorMsg")),
null
);
}
@Override
public ProcResult addUnit(UnitVo unit, boolean useV2) {
Map<String, Object> map = new HashMap<>();
map.put("unitCode", unit.getUnitCode());
map.put("unitName", unit.getUnitName());
map.put("parentUnitCode", unit.getParentUnitCode());
map.put("address", unit.getAddress());
map.put("connecter1", unit.getConnecter1());
map.put("phone1", unit.getPhone1());
map.put("connecter2", unit.getConnecter2());
map.put("phone2", unit.getPhone2());
if (useV2) {
mapper.addUnitV2(map);
} else {
mapper.addUnit(map);
}
return new ProcResult(
str(map.get("resultCode")),
str(map.get("errorMsg")),
null
);
}
@Override
public List<Map<String, Object>> getPhyexamStatus(String peIds) {
return mapper.getPhyexamStatus(peIds);
}
@Override
public List<Map<String, Object>> getItemList() {
return mapper.getItemList();
}
@Override
public List<Map<String, Object>> getAllItemList() {
return mapper.getAllItemList();
}
@Override
public List<Map<String, Object>> getPeItemList() {
return mapper.getPeItemList();
}
@Override
public List<Map<String, Object>> getItemInfo(String peItemCode) {
return mapper.getItemInfo(peItemCode);
}
@Override
public List<Map<String, Object>> getEmpItem(String peId) {
return mapper.getEmpItem(peId);
}
@Override
public List<Map<String, Object>> getResult(String peId) {
return mapper.getResult(peId);
}
@Override
public List<Map<String, Object>> getConclusion(String peId) {
return mapper.getConclusion(peId);
}
@Override
public List<Map<String, Object>> getConclusionAll(String peId) {
return mapper.getConclusionAll(peId);
}
@Override
public List<Map<String, Object>> getSuggest(String peId) {
return mapper.getSuggest(peId);
}
@Override
public List<Map<String, Object>> getSuggestAll(String peId) {
return mapper.getSuggestAll(peId);
}
@Override
public Map<String, Object> getPeStatusByIdnoAndYear(String idNo, String year) {
return mapper.getPeStatusByIdnoAndYear(idNo, year);
}
@Override
public List<Map<String, Object>> getResultByIdcardAndYear(String idNo, String year) {
return mapper.getResultByIdcardAndYear(idNo, year);
}
@Override
public List<Map<String, Object>> getResultByIdcardAndYearAll(String idNo, String year) {
return mapper.getResultByIdcardAndYearAll(idNo, year);
}
@Override
public List<Map<String, Object>> getResultByPeId(String peId) {
return mapper.getResultByPeId(peId);
}
@Override
public List<Map<String, Object>> getDisResultByAuditDate(String auditDate) {
return mapper.getDisResultByAuditDate(auditDate);
}
@Override
public List<Map<String, Object>> getItemByPeId(String peId) {
return mapper.getItemByPeId(peId);
}
@Override
public List<Map<String, Object>> getItemByPeIdAll(String peId) {
return mapper.getItemByPeIdAll(peId);
}
@Override
public List<Map<String, Object>> getResultByIdNoOrPeId(String idNo, String peId, String auditYear) {
return mapper.getResultByIdNoOrPeId(idNo, peId, auditYear);
}
@Override
public Map<String, Object> getPeIdAndDateByIdcard(String idNo) {
return mapper.getPeIdAndDateByIdcard(idNo);
}
@Override
public List<Map<String, Object>> getPeIdAndDateByIdcardList(String idNo) {
return mapper.getPeIdAndDateByIdcardList(idNo);
}
@Override
public List<Map<String, Object>> getExamListByDate(String auditDate) {
return mapper.getExamListByDate(auditDate);
}
/** 等价于 Kotlin 的 map[key]?.toString():值为 null 时返回 null,否则转字符串 */
private static String str(Object value) {
return value == null ? null : String.valueOf(value);
}
}
@@ -3,10 +3,13 @@ package com.hospital.front.adapter;
import org.noear.solon.annotation.Component; import org.noear.solon.annotation.Component;
/** /**
* 延安医院适配器(SqlServerya 方言)。 * 延安医院适配器(SqlServernx 方言)。
*
* 延安数据库结构已与宁夏完全一致(V_PHYEXAM_* 视图 + P_ADD_* 存储过程),
* 仅连接配置不同,直接复用 NxAdapterBase。
*/ */
@Component @Component
public class YananAdapter extends YaAdapterBase { public class YananAdapter extends NxAdapterBase {
@Override @Override
public String code() { public String code() {
@@ -2,6 +2,8 @@ package com.hospital.front.api.admin;
import java.io.File; import java.io.File;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.net.URI;
import java.util.Base64;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
@@ -23,6 +25,7 @@ import org.noear.solon.core.handle.MethodType;
import com.hospital.front.adapter.HospitalRouter; import com.hospital.front.adapter.HospitalRouter;
import com.hospital.front.dto.Result; import com.hospital.front.dto.Result;
import com.hospital.front.infra.DbHealthMonitor;
import com.hospital.front.infra.exception.BusinessException; import com.hospital.front.infra.exception.BusinessException;
/** /**
@@ -43,6 +46,10 @@ public class AdminApi {
@Inject @Inject
private HospitalRouter router; private HospitalRouter router;
/** DB 健康缓存监测(health 接口读缓存秒回,避免 DB 慢时阻塞被 Agent 误判宕机) */
@Inject
private DbHealthMonitor dbHealthMonitor;
/** 日志目录(与 logback.xml 的 log.path 保持一致) */ /** 日志目录(与 logback.xml 的 log.path 保持一致) */
private File getLogDir() { private File getLogDir() {
return new File(Solon.cfg().get("log.path", "./log")); return new File(Solon.cfg().get("log.path", "./log"));
@@ -53,26 +60,27 @@ public class AdminApi {
private void checkToken(Context ctx) { private void checkToken(Context ctx) {
String expected = Solon.cfg().get("admin.token", ""); String expected = Solon.cfg().get("admin.token", "");
if (expected != null && !expected.trim().isEmpty() && !expected.equals(ctx.header("X-Admin-Token"))) { if (expected != null && !expected.trim().isEmpty() && !expected.equals(ctx.header("X-Admin-Token"))) {
throw BusinessException.of(Result.CODE_AUTH_FAILED, "管理接口鉴权失败"); throw BusinessException.of("管理接口鉴权失败");
} }
} }
// ================= 1. 健康与指标 ================= // ================= 1. 健康与指标 =================
/** 健康检查(含数据库探测 */ /** 健康检查(进程健康即时返回;DB 状态读后台探测缓存,不阻塞 */
@Mapping("/health") @Mapping("/health")
public Result<Map<String, Object>> health(Context ctx) { public Result<Map<String, Object>> health(Context ctx) {
checkToken(ctx); checkToken(ctx);
Runtime mem = Runtime.getRuntime(); Runtime mem = Runtime.getRuntime();
Result<Boolean> db = router.active().healthCheck(); Result<Boolean> db = dbHealthMonitor.last();
Map<String, Object> out = new LinkedHashMap<>(); Map<String, Object> out = new LinkedHashMap<>();
out.put("status", db.getCode() == 0 ? "UP" : "DEGRADED"); // db=null 表示后台首轮探测未完成(启动/唤醒初期),进程本身健康,报 STARTING 而非 DEGRADED
out.put("status", db == null ? "STARTING" : (db.isSuccess() ? "UP" : "DEGRADED"));
out.put("hospital", router.activeCode()); out.put("hospital", router.activeCode());
out.put("hospitalName", router.active().displayName()); out.put("hospitalName", router.active().displayName());
out.put("registeredHospitals", router.registeredCodes()); out.put("registeredHospitals", router.registeredCodes());
Map<String, Object> database = new HashMap<>(); Map<String, Object> database = new HashMap<>();
database.put("ok", db.getCode() == 0); database.put("ok", db != null && db.isSuccess());
database.put("msg", db.getMsg()); database.put("msg", db != null ? db.getMsg() : "DB 探测进行中");
out.put("database", database); out.put("database", database);
Map<String, Object> jvm = new HashMap<>(); Map<String, Object> jvm = new HashMap<>();
jvm.put("totalMemoryMb", mem.totalMemory() / 1048576); jvm.put("totalMemoryMb", mem.totalMemory() / 1048576);
@@ -273,4 +281,136 @@ public class AdminApi {
t.start(); t.start();
return Result.success(); return Result.success();
} }
// ================= 5. 接口代理(供 B 端接口测试工具直连调用) =================
/** httpProxy 请求体(Solon 自动绑定 JSON 请求体) */
public static class HttpProxyReq {
/** HTTP 方法:GET/POST/PUT/DELETE */
public String method = "GET";
/** 接口路径,如 /order/getItemList */
public String path;
/** query 参数(拼到 URL 上) */
public Map<String, String> query;
/** 请求体字符串(POST/PUT 时使用) */
public String body;
/** 额外请求头 */
public Map<String, String> headers;
}
/** 代理专用 HttpClient:环回调用本机接口,读超时 120s 适配大接口 */
private final java.net.http.HttpClient proxyHttp = java.net.http.HttpClient.newBuilder()
.connectTimeout(java.time.Duration.ofSeconds(5))
.build();
/**
* HTTP 代理:在 main 进程内环回调用本机接口,原样透传响应。
*
* B 端接口测试工具经此代理访问业务接口,免去 Agent WS 通道倒手
* (WS 通道有 2MB 消息上限与 60s 等待超时,大接口会超时/掉线)。
* 自动按 path 附加鉴权头:/order/** → Basicorder.basic 配置),
* /api/admin/** → X-Admin-Token。
*/
@Mapping(value = "/httpProxy", method = {MethodType.POST})
public Result<Map<String, Object>> httpProxy(Context ctx, HttpProxyReq req) {
checkToken(ctx);
// 参数校验与路径白名单:只允许代理本机 /order/** 与 /api/admin/**
if (req == null || req.path == null || req.path.isEmpty()) {
throw BusinessException.paramMissing("path 不能为空");
}
String path = req.path.trim();
if (!path.startsWith("/order/") && !path.startsWith("/api/admin/")) {
throw BusinessException.businessFailed("仅允许代理 /order/** 与 /api/admin/** 路径:" + path);
}
// 拼接环回 URLserver.port + path + query
String port = Solon.cfg().get("server.port", "8080");
StringBuilder url = new StringBuilder("http://127.0.0.1:").append(port).append(path);
if (req.query != null && !req.query.isEmpty()) {
StringBuilder qs = new StringBuilder();
for (Map.Entry<String, String> e : req.query.entrySet()) {
if (e.getKey() == null || e.getValue() == null) {
continue;
}
if (qs.length() > 0) {
qs.append('&');
}
qs.append(java.net.URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8))
.append('=')
.append(java.net.URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8));
}
if (qs.length() > 0) {
url.append('?').append(qs);
}
}
// 构建请求:按 path 附加鉴权头
java.net.http.HttpRequest.Builder rb = java.net.http.HttpRequest.newBuilder(URI_COMPAT.apply(url.toString()));
String m = req.method == null ? "GET" : req.method.toUpperCase();
java.net.http.HttpRequest.BodyPublisher pub = req.body == null || req.body.isEmpty()
? java.net.http.HttpRequest.BodyPublishers.noBody()
: java.net.http.HttpRequest.BodyPublishers.ofString(req.body, StandardCharsets.UTF_8);
switch (m) {
case "POST" -> rb.POST(pub);
case "PUT" -> rb.PUT(pub);
case "DELETE" -> rb.DELETE();
default -> rb.GET();
}
if (path.startsWith("/order/")) {
String basic = Solon.cfg().get("order.basic", "");
boolean authFlag = !"false".equals(Solon.cfg().get("order.authFlag", "true"));
if (authFlag && basic != null && !basic.isEmpty()) {
// 对齐 OrderApi checkBasic 的校验格式:Basic + Base64(user:password)
rb.header("Authorization", "Basic "
+ Base64.getEncoder().encodeToString(basic.getBytes(StandardCharsets.UTF_8)));
}
} else {
rb.header("X-Admin-Token", Solon.cfg().get("admin.token", ""));
}
if (req.headers != null) {
for (Map.Entry<String, String> e : req.headers.entrySet()) {
if (e.getKey() != null && e.getValue() != null) {
rb.header(e.getKey(), e.getValue());
}
}
}
rb.timeout(java.time.Duration.ofSeconds(120));
// 执行并透传
try {
java.net.http.HttpResponse<String> resp = proxyHttp.send(rb.build(),
java.net.http.HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
Map<String, Object> out = new LinkedHashMap<>();
out.put("status", resp.statusCode());
out.put("body", resp.body());
return Result.success(out);
} catch (java.io.IOException e) {
throw BusinessException.businessFailed("环回调用失败(本机 " + path + "):" + e.getMessage());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw BusinessException.businessFailed("环回调用被中断:" + path);
}
}
/** URI 构建容错(URL 已校验,异常时给出可读错误) */
private static final java.util.function.Function<String, URI> URI_COMPAT = s -> {
try {
return URI.create(s);
} catch (Exception e) {
throw BusinessException.paramMissing("非法 URL" + s);
}
};
// ================= 6. 接口清单(供 B 端接口测试工具加载) =================
/**
* 返回 /order 业务接口元数据清单。
*
* 反射扫描 OrderApi 的 @ApiMeta 注解自动生成,无需手写维护。
* B 端因前置机白名单无法直连,通过 Agent 通道拉取本接口清单,
* 再经 Agent HTTP_PROXY 代理调用具体接口。
* 走 X-Admin-Token 鉴权(与其它管理接口一致)。
*/
@Mapping("/apis")
public Result<List<Map<String, Object>>> apis(Context ctx) {
checkToken(ctx);
return Result.success(com.hospital.front.infra.ApiScanner.scan(com.hospital.front.api.order.OrderApi.class));
}
} }
@@ -1,6 +1,7 @@
package com.hospital.front.api.order; package com.hospital.front.api.order;
import java.util.ArrayList; import com.hospital.front.vo.MedicalMiddleResultVo;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -14,11 +15,14 @@ import org.noear.solon.annotation.Path;
import org.noear.solon.core.handle.Context; import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.MethodType; import org.noear.solon.core.handle.MethodType;
import org.noear.solon.Solon; import org.noear.solon.Solon;
import org.noear.snack.ONode;
import com.hospital.front.dto.Result; import com.hospital.front.dto.Result;
import com.hospital.front.infra.annotation.ApiMeta;
import com.hospital.front.infra.annotation.ApiParam;
import com.hospital.front.infra.exception.BusinessException; import com.hospital.front.infra.exception.BusinessException;
import com.hospital.front.service.OrderService; import com.hospital.front.service.OrderService;
import com.hospital.front.vo.ExamListVo;
import com.hospital.front.vo.PeStatusPhyVo;
/** /**
* 体检预约业务接口,路径前缀 /order。 * 体检预约业务接口,路径前缀 /order。
@@ -29,6 +33,8 @@ import com.hospital.front.service.OrderService;
* 鉴权:对齐原程序 Sa-Token 的 SaBasicUtil.check(),对 /order/** 做 HTTP Basic 认证。 * 鉴权:对齐原程序 Sa-Token 的 SaBasicUtil.check(),对 /order/** 做 HTTP Basic 认证。
* 配置 order.basic(格式 "user:password",未配置时放行——与原程序 authFlag=true 且 * 配置 order.basic(格式 "user:password",未配置时放行——与原程序 authFlag=true 且
* basic 为空时行为一致,仅建议生产环境必配)。 * basic 为空时行为一致,仅建议生产环境必配)。
*
* 每个业务方法标注 @ApiMeta,供 /api/admin/apis 反射扫描自动生成接口清单(B 端接口测试工具用)。
*/ */
@Controller @Controller
@Mapping("/order") @Mapping("/order")
@@ -70,7 +76,7 @@ public class OrderApi {
// 认证失败:401 + 质询头(对齐 Sa-Token SaBasicUtil.check 的响应行为) // 认证失败:401 + 质询头(对齐 Sa-Token SaBasicUtil.check 的响应行为)
ctx.status(401); ctx.status(401);
ctx.headerSet("WWW-Authenticate", "Basic realm=\"hospital-front\""); ctx.headerSet("WWW-Authenticate", "Basic realm=\"hospital-front\"");
throw BusinessException.of(Result.CODE_AUTH_FAILED, "Basic 认证失败"); throw BusinessException.of("Basic 认证失败");
} }
/** 恒定时间字符串比较(防时序攻击) */ /** 恒定时间字符串比较(防时序攻击) */
@@ -83,16 +89,22 @@ public class OrderApi {
// ================= 1. 预约提交 / 取消 ================= // ================= 1. 预约提交 / 取消 =================
/** 4. 提交预约保存 */ /** 4. 提交预约保存 */
@ApiMeta(desc = "提交预约保存", params = {
@ApiParam(name = "body", desc = "预约 JSON 体")
})
@Mapping(value = "/saveOrder", method = {MethodType.POST}) @Mapping(value = "/saveOrder", method = {MethodType.POST})
public Result<String> saveOrder(Context ctx, @Body String body) { public Result<String> saveOrder(Context ctx, @Body com.hospital.front.vo.OrderV2Vo body) {
checkBasic(ctx); checkBasic(ctx);
// Solon 对 Map 泛型 body 反序列化支持不佳,用 snack3 手动解析 // 强类型 DTO 直收(Solon 对 DTO 反序列化无问题),避免 Map 中转
@SuppressWarnings("unchecked") return Result.success(service.saveOrder(body));
Map<String, Object> map = (Map<String, Object>) ONode.load(body).toObject(Map.class);
return Result.success(service.saveOrder(map));
} }
/** 5. 取消体检预约 */ /** 5. 取消体检预约 */
@ApiMeta(desc = "取消体检预约", params = {
@ApiParam(name = "peId", desc = "体检编号"),
@ApiParam(name = "name", desc = "姓名"),
@ApiParam(name = "idcard", desc = "身份证号")
})
@Mapping(value = "/cancelOrder/{peId}", method = {MethodType.POST}) @Mapping(value = "/cancelOrder/{peId}", method = {MethodType.POST})
public Result<?> cancelOrder( public Result<?> cancelOrder(
Context ctx, Context ctx,
@@ -107,13 +119,20 @@ public class OrderApi {
// ================= 2. 体检状态 ================= // ================= 2. 体检状态 =================
/** 3. 体检状态查询(多个 peId 逗号分隔) */ /** 3. 体检状态查询(多个 peId 逗号分隔) */
@ApiMeta(desc = "体检状态查询(多个 peId 逗号分隔)", params = {
@ApiParam(name = "peIds", desc = "体检编号逗号分隔")
})
@Mapping("/getPeStatus/{peIds}") @Mapping("/getPeStatus/{peIds}")
public Result<List<Map<String, String>>> getPeStatus(Context ctx, @Path("peIds") String peIds) { public Result<List<PeStatusPhyVo>> getPeStatus(Context ctx, @Path("peIds") String peIds) {
checkBasic(ctx); checkBasic(ctx);
return Result.success(service.getPhyexamStatus(peIds)); return Result.success(service.getPhyexamStatus(peIds));
} }
/** 13. 根据身份证号及年份获取当前体检状态及体检项状态 */ /** 13. 根据身份证号及年份获取当前体检状态及体检项状态 */
@ApiMeta(desc = "体检状态及体检项状态", params = {
@ApiParam(name = "idNo", desc = "身份证号"),
@ApiParam(name = "year", desc = "年份")
})
@Mapping("/getPeStatusAndItemList/{idNo}/{year}") @Mapping("/getPeStatusAndItemList/{idNo}/{year}")
public Result<Map<String, Object>> getPeStatusAndItemList(Context ctx, @Path("idNo") String idNo, @Path("year") String year) { public Result<Map<String, Object>> getPeStatusAndItemList(Context ctx, @Path("idNo") String idNo, @Path("year") String year) {
checkBasic(ctx); checkBasic(ctx);
@@ -123,6 +142,9 @@ public class OrderApi {
// ================= 3. 体检项目 ================= // ================= 3. 体检项目 =================
/** 1. 查询员工体检项目 */ /** 1. 查询员工体检项目 */
@ApiMeta(desc = "员工体检项目", params = {
@ApiParam(name = "peId", desc = "体检编号")
})
@Mapping("/getEmpItem/{peId}") @Mapping("/getEmpItem/{peId}")
public Result<List<Map<String, Object>>> getEmpItem(Context ctx, @Path("peId") String peId) { public Result<List<Map<String, Object>>> getEmpItem(Context ctx, @Path("peId") String peId) {
checkBasic(ctx); checkBasic(ctx);
@@ -130,6 +152,7 @@ public class OrderApi {
} }
/** 2. 体检项目查询 */ /** 2. 体检项目查询 */
@ApiMeta(desc = "体检项目查询")
@Mapping("/getItemList") @Mapping("/getItemList")
public Result<List<Map<String, Object>>> getItemList(Context ctx) { public Result<List<Map<String, Object>>> getItemList(Context ctx) {
checkBasic(ctx); checkBasic(ctx);
@@ -137,6 +160,7 @@ public class OrderApi {
} }
/** 6. 体检指标项数据获取 */ /** 6. 体检指标项数据获取 */
@ApiMeta(desc = "体检指标项数据获取")
@Mapping("/getPeItemList") @Mapping("/getPeItemList")
public Result<List<Map<String, Object>>> getPeItemList(Context ctx) { public Result<List<Map<String, Object>>> getPeItemList(Context ctx) {
checkBasic(ctx); checkBasic(ctx);
@@ -144,6 +168,7 @@ public class OrderApi {
} }
/** 6.1 体检指标项及体检组合数据获取 */ /** 6.1 体检指标项及体检组合数据获取 */
@ApiMeta(desc = "体检指标项及体检组合数据获取")
@Mapping("/getAllItemList") @Mapping("/getAllItemList")
public Result<List<Map<String, Object>>> getAllItemList(Context ctx) { public Result<List<Map<String, Object>>> getAllItemList(Context ctx) {
checkBasic(ctx); checkBasic(ctx);
@@ -151,6 +176,9 @@ public class OrderApi {
} }
/** 7. 根据体检指标项查询所属体检组合项 */ /** 7. 根据体检指标项查询所属体检组合项 */
@ApiMeta(desc = "按体检指标项查所属组合", params = {
@ApiParam(name = "peItemCode", desc = "体检指标项编码")
})
@Mapping("/getItemInfo/{peItemCode}") @Mapping("/getItemInfo/{peItemCode}")
public Result<List<Map<String, Object>>> getItemInfo(Context ctx, @Path("peItemCode") String peItemCode) { public Result<List<Map<String, Object>>> getItemInfo(Context ctx, @Path("peItemCode") String peItemCode) {
checkBasic(ctx); checkBasic(ctx);
@@ -160,92 +188,59 @@ public class OrderApi {
// ================= 4. 体检结果 / 结论 / 建议 ================= // ================= 4. 体检结果 / 结论 / 建议 =================
/** 8. 根据体检编号获取员工体检结果 */ /** 8. 根据体检编号获取员工体检结果 */
@ApiMeta(desc = "按体检编号查员工体检结果", params = {
@ApiParam(name = "peId", desc = "体检编号")
})
@Mapping("/getResult/{peId}") @Mapping("/getResult/{peId}")
public Result<List<Map<String, Object>>> getResult(Context ctx, @Path("peId") String peId) { public Result<List<Map<String, Object>>> getResult(Context ctx, @Path("peId") String peId) {
checkBasic(ctx); checkBasic(ctx);
return Result.success(service.getResult(peId)); return Result.success(service.getResult(peId));
} }
/** 9. 体检结论(汇总) */ // 9/10/11/12(体检结论/建议 汇总与列表)不再对外暴露:结论/建议仅作为
@Mapping("/getTotalConclusion/{peId}") // 体检报告的组成部分在应用层组装,相关查询(getConclusionAll/getSuggestAll)为内部使用
public Result<Map<String, String>> getTotalConclusion(Context ctx, @Path("peId") String peId) {
checkBasic(ctx);
return Result.success(service.getTotalConclusion(peId));
}
/** 10. 体检建议(汇总) */
@Mapping("/getTotalSuggest/{peId}")
public Result<Map<String, String>> getTotalSuggest(Context ctx, @Path("peId") String peId) {
checkBasic(ctx);
return Result.success(service.getTotalSuggest(peId));
}
/** 11. 体检结论(列表) */
@Mapping("/getConclusion/{peId}")
public Result<List<Map<String, Object>>> getConclusion(Context ctx, @Path("peId") String peId) {
checkBasic(ctx);
return Result.success(service.getConclusion(peId));
}
/** 12. 体检建议(列表) */
@Mapping("/getSuggest/{peId}")
public Result<List<Map<String, Object>>> getSuggest(Context ctx, @Path("peId") String peId) {
checkBasic(ctx);
return Result.success(service.getSuggest(peId));
}
// ================= 5. 按身份证 / 日期查询 ================= // ================= 5. 按身份证 / 日期查询 =================
/** 14. 根据身份证及年份获取体检结果 */ // 14(按身份证及年份查结果)/ 15.3(按审查日期查报告)/ 16(按身份证或编号+年份查报告)
@Mapping("/getResultByIdcardAndYear/{idNo}/{year}") // 不再对外暴露:相关查询为内部使用(如 getPeStatusByIdnoAndYear 的宁夏兜底)
public Result<List<Map<String, Object>>> getResultByIdcardAndYear(Context ctx, @Path("idNo") String idNo, @Path("year") String year) { // 14.1 全量版保留对外
checkBasic(ctx);
return Result.success(service.getResultByIdcardAndYear(idNo, year));
}
/** 14.1 根据身份证号及年份获取体检结果(全量) */ /** 14.1 根据身份证号及年份获取体检结果(全量) */
@ApiMeta(desc = "按身份证及年份查全量结果", params = {
@ApiParam(name = "idNo", desc = "身份证号"),
@ApiParam(name = "year", desc = "年份")
})
@Mapping("/getResultByIdcardAndYearAll/{idNo}/{year}") @Mapping("/getResultByIdcardAndYearAll/{idNo}/{year}")
public Result<List<Map<String, Object>>> getResultByIdcardAndYearAll(Context ctx, @Path("idNo") String idNo, @Path("year") String year) { public Result<List<MedicalMiddleResultVo>> getResultByIdcardAndYearAll(Context ctx, @Path("idNo") String idNo, @Path("year") String year) {
checkBasic(ctx); checkBasic(ctx);
return Result.success(service.getResultByIdcardAndYearAll(idNo, year)); return Result.success(service.getResultByIdcardAndYearAll(idNo, year));
} }
/** 15.1 根据体检编号获取体检结果 */ /** 15.1 根据体检编号获取体检结果 */
@ApiMeta(desc = "按体检编号查体检结果", params = {
@ApiParam(name = "peId", desc = "体检编号")
})
@Mapping("/getResultByPeId/{peId}") @Mapping("/getResultByPeId/{peId}")
public Result<List<Map<String, Object>>> getResultByPeId(Context ctx, @Path("peId") String peId) { public Result<List<MedicalMiddleResultVo>> getResultByPeId(Context ctx, @Path("peId") String peId) {
checkBasic(ctx); checkBasic(ctx);
return Result.success(service.getResultByPeId(peId)); return Result.success(service.getResultByPeId(peId));
} }
/** 15.2 根据体检编号获取体检状态(简) */ /** 15.2 根据体检编号获取体检状态(简) */
@ApiMeta(desc = "按体检编号查体检状态(简)", params = {
@ApiParam(name = "peId", desc = "体检编号")
})
@Mapping("/getResultStatus/{peId}") @Mapping("/getResultStatus/{peId}")
public Result<List<Map<String, Object>>> getResultStatus(Context ctx, @Path("peId") String peId) { public Result<List<MedicalMiddleResultVo>> getResultStatus(Context ctx, @Path("peId") String peId) {
checkBasic(ctx); checkBasic(ctx);
return Result.success(service.getResultByPeId(peId)); return Result.success(service.getResultStatus(peId));
}
/** 15.3 根据 audit_date 查询当天的体检报告 */
@Mapping("/getResultByAuditDate/{auditDate}")
public Result<List<Map<String, Object>>> getResultByAuditDate(Context ctx, @Path("auditDate") String auditDate) {
checkBasic(ctx);
return Result.success(service.getResultByAuditDate(auditDate));
}
/** 16. 根据 id_code 或 pe_id 和审查年份查询体检报告 */
@Mapping(value = "/getResultByIdNoOrPeId", method = {MethodType.POST})
public Result<List<Map<String, Object>>> getResultByIdNoOrPeId(
Context ctx,
@Param(value = "idNo", required = false) String idNo,
@Param(value = "peId", required = false) String peId,
@Param("auditYear") String auditYear) {
checkBasic(ctx);
if (isNullOrBlank(idNo) && isNullOrBlank(peId)) {
throw BusinessException.paramMissing("身份证号或体检编号");
}
return Result.success(service.getResultByIdNoOrPeId(idNo, peId, auditYear));
} }
/** 17. 根据身份证号查询当前年的 peId 和预约日期 */ /** 17. 根据身份证号查询当前年的 peId 和预约日期 */
@ApiMeta(desc = "按身份证查当年 peId 和预约日期", params = {
@ApiParam(name = "idNo", desc = "身份证号")
})
@Mapping("/getPeIdAndDateByIdcard/{idNo}") @Mapping("/getPeIdAndDateByIdcard/{idNo}")
public Result<Map<String, Object>> getPeIdAndDateByIdcard(Context ctx, @Path("idNo") String idNo) { public Result<Map<String, Object>> getPeIdAndDateByIdcard(Context ctx, @Path("idNo") String idNo) {
checkBasic(ctx); checkBasic(ctx);
@@ -253,6 +248,9 @@ public class OrderApi {
} }
/** 17.1 同上(数组) */ /** 17.1 同上(数组) */
@ApiMeta(desc = "按身份证查 peId 和预约日期(数组)", params = {
@ApiParam(name = "idNo", desc = "身份证号")
})
@Mapping("/getPeIdAndDateByIdcardList/{idNo}") @Mapping("/getPeIdAndDateByIdcardList/{idNo}")
public Result<List<Map<String, Object>>> getPeIdAndDateByIdcardList(Context ctx, @Path("idNo") String idNo) { public Result<List<Map<String, Object>>> getPeIdAndDateByIdcardList(Context ctx, @Path("idNo") String idNo) {
checkBasic(ctx); checkBasic(ctx);
@@ -262,25 +260,23 @@ public class OrderApi {
// ================= 6. 单位 / 人员列表 ================= // ================= 6. 单位 / 人员列表 =================
/** 18. 添加修改单位信息 */ /** 18. 添加修改单位信息 */
@ApiMeta(desc = "添加修改单位信息", params = {
@ApiParam(name = "body", desc = "单位 JSON 数组")
})
@Mapping(value = "/addUnit", method = {MethodType.POST}) @Mapping(value = "/addUnit", method = {MethodType.POST})
public Result<?> addUnit(Context ctx, @Body String body) { public Result<?> addUnit(Context ctx, @Body String body) {
checkBasic(ctx); checkBasic(ctx);
// Solon 对 List 泛型 body 反序列化支持不佳,此处用 snack3 手动解析 // Solon 对 List 泛型 body 支持不佳,用 JsonUtils 统一解析(空串返回空列表)
List<Map<String, Object>> units; String errors = service.addUnits(com.hospital.front.infra.util.JsonUtils.parseList(body));
if (body == null || body.trim().isEmpty()) {
units = new ArrayList<>();
} else {
@SuppressWarnings("unchecked")
List<Map<String, Object>> parsed = (List<Map<String, Object>>) ONode.load(body).toObject(List.class);
units = parsed;
}
String errors = service.addUnits(units);
return errors != null ? Result.error(errors) : Result.success(); return errors != null ? Result.error(errors) : Result.success();
} }
/** 19. 根据日期查询当天体检人员列表(去重),日期为空则查当天 */ /** 19. 根据日期查询当天体检人员列表(去重),日期为空则查当天 */
@ApiMeta(desc = "按日期查当天体检人员列表(去重)", params = {
@ApiParam(name = "auditDate", desc = "审查日期(空则查当天)")
})
@Mapping("/getExamListByDate") @Mapping("/getExamListByDate")
public Result<List<Map<String, Object>>> getExamListByDate(Context ctx, @Param("auditDate") String auditDate) { public Result<List<ExamListVo>> getExamListByDate(Context ctx, @Param("auditDate") String auditDate) {
checkBasic(ctx); checkBasic(ctx);
return Result.success(service.getExamListByDate(auditDate)); return Result.success(service.getExamListByDate(auditDate));
} }
@@ -1,114 +1,49 @@
package com.hospital.front.dto; package com.hospital.front.dto;
import lombok.Data;
/** /**
* 统一 API 响应包装。 * 统一 API 响应包装。
* *
* 业务接口均使用此结构返回,便于前端统一处理。 * 结构与原 hospitalmiddle 的 Result 保持一致(对外 JSON 契约,不可修改):
* {"success": true/false, "msg": "成功", "data": ...}
* *
* @param <T> 数据类型 * @param <T> 数据类型
* @author hospital-front重构
*/ */
@Data
public class Result<T> { public class Result<T> {
/** 业务码(0=成功 */ /** 是否成功 */
private final int code; private boolean success = false;
/** 提示信息 */ /** 提示信息 */
private final String msg; private String msg;
/** 业务数据 */ /** 业务数据 */
private final T data; private T data;
/** /** 成功响应(无数据),msg 固定"成功"(对齐原项目) */
* 全参构造器。 public static Result success() {
* Result result = new Result();
* @param code 业务码(0=成功) result.setSuccess(true);
* @param msg 提示信息 result.setMsg("成功");
* @param data 业务数据(可为 null return result;
*/
public Result(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
} }
/** /** 成功响应(带数据) */
* 两参构造器(data 默认为 null,与 Kotlin 默认参数语义一致)。 public static <T> Result success(T t) {
* Result result = new Result();
* @param code 业务码 result.setSuccess(true);
* @param msg 提示信息 result.setMsg("成功");
*/ result.setData(t);
public Result(int code, String msg) { return result;
this(code, msg, null);
} }
/** 业务成功码 */ /** 错误响应 */
public static final int SUCCESS_CODE = 0; public static Result error(String msg) {
Result result = new Result();
/** 业务失败默认码 */ result.setSuccess(false);
public static final int ERROR_CODE = 5001; result.setMsg(msg);
return result;
/** 参数缺失 */
public static final int CODE_PARAM_MISSING = 1001;
/** 参数格式错误 */
public static final int CODE_PARAM_FORMAT = 1002;
/** 业务校验失败 */
public static final int CODE_BUSINESS = 1003;
/** 存储过程调用失败 */
public static final int CODE_PROC_FAILED = 2001;
/** 数据库连接失败 */
public static final int CODE_DB_UNREACHABLE = 2002;
/** 医院系统返回业务失败 */
public static final int CODE_HOSPITAL_FAILED = 2003;
/** 鉴权失败 */
public static final int CODE_AUTH_FAILED = 3001;
/** Token 过期 */
public static final int CODE_TOKEN_EXPIRED = 3002;
/**
* 成功响应(无数据)。
*/
public static <T> Result<T> success() {
return new Result<>(SUCCESS_CODE, "success", null);
}
/**
* 成功响应。
*/
public static <T> Result<T> success(T data) {
return new Result<>(SUCCESS_CODE, "success", data);
}
/**
* 错误响应(默认错误码)。
*/
public static <T> Result<T> error(String msg) {
return new Result<>(ERROR_CODE, msg, null);
}
/**
* 错误响应(指定错误码)。
*/
public static <T> Result<T> error(int code, String msg) {
return new Result<>(code, msg, null);
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public T getData() {
return data;
} }
} }
@@ -0,0 +1,58 @@
package com.hospital.front.infra;
import java.util.Set;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;
import lombok.extern.slf4j.Slf4j;
/**
* 全局接口访问日志过滤器。
*
* 统一打印每个请求的调用日志:HTTP 方法、路径、来源 IP、请求参数、耗时、响应状态码。
* 覆盖所有 Controller/order、/api/admin),新增接口自动生效,无需逐个手写日志。
*
* 参数覆盖说明:
* - @Paramquery/form)与 @Path(路径变量)统一通过 ctx.paramMap() 取;@Path 的值同时也在 ctx.path() 里可见。
* - @Body 参数(saveOrder/addUnit)不在此读取,避免消费 body 流破坏 @Body 反序列化;
* 由 service 层日志覆盖(saveOrder 打印 peInfoStr/peItemStraddUnit 打印 units)。
*
* 顺序:index = -1 保证本过滤器在 GlobalExceptionHandler / IpWhitelistFilter 之前执行(最外层),
* 从而能记录到异常被处理后写入 ctx 的最终状态码(401/500 等)。
*/
@Slf4j
@Component(index = -1)
public class ApiAccessLogFilter implements Filter {
/** 无需打印访问日志的路径(高频健康探测等,避免刷屏) */
private static final Set<String> SKIP_LOG_PATHS = Set.of("/api/admin/health");
@Override
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
long start = System.currentTimeMillis();
try {
chain.doFilter(ctx);
} finally {
long cost = System.currentTimeMillis() - start;
if (!SKIP_LOG_PATHS.contains(ctx.path())) {
log.info("[接口调用] {} {} ip={} 参数=[{}] cost={}ms status={}",
ctx.method(), ctx.path(), ctx.remoteIp(), paramsOf(ctx), cost, ctx.status());
}
}
}
/** 汇总请求参数(query/form/path@Body 除外),形如 key1=v1, key2=v2 */
private String paramsOf(Context ctx) {
StringBuilder sb = new StringBuilder();
for (var kv : ctx.paramMap()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(kv.getKey()).append('=').append(kv.getFirstValue());
}
return sb.toString();
}
}
@@ -0,0 +1,131 @@
package com.hospital.front.infra;
import com.hospital.front.infra.annotation.ApiMeta;
import com.hospital.front.infra.annotation.ApiParam;
import org.noear.solon.annotation.Body;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.annotation.Param;
import org.noear.solon.annotation.Path;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 接口元数据扫描器。
*
* 反射扫描指定 Controller 类的所有 @ApiMeta 标注方法,自动提取:
* - method:从 @Mapping.method()(空数组视为 GET
* - path:类 @Mapping 前缀 + 方法 @Mapping.value()
* - params:从方法签名 @Path/@Param/@Body 提取 name/in/required/type@ApiMeta.params 补充 desc
*
* 替代手写接口清单,新增/调整接口时只需维护 @ApiMeta 注解即可自动同步。
*/
public final class ApiScanner {
private ApiScanner() {
}
/**
* 扫描指定 Controller 类,返回带 @ApiMeta 的接口元数据清单。
*
* @param controllerClass Controller 类(如 OrderApi.class
* @return 接口元数据列表(不可变)
*/
public static List<Map<String, Object>> scan(Class<?> controllerClass) {
// 类级路径前缀(如 /order
String prefix = "";
Mapping classMapping = controllerClass.getAnnotation(Mapping.class);
if (classMapping != null && !classMapping.value().isEmpty()) {
prefix = classMapping.value();
}
List<Map<String, Object>> list = new ArrayList<>();
for (Method method : controllerClass.getDeclaredMethods()) {
ApiMeta meta = method.getAnnotation(ApiMeta.class);
Mapping methodMapping = method.getAnnotation(Mapping.class);
if (meta == null || methodMapping == null) {
continue;
}
// 拼接完整路径
String path = prefix + (methodMapping.value() == null ? "" : methodMapping.value());
// HTTP 方法:Solon @Mapping 不显式指定时 method() 返回 ALL(支持所有方法),
// 接口测试场景下默认按 GET 处理(OrderApi 的 GET 接口均未显式指定 method)
String httpMethod = "GET";
MethodType[] mtypes = methodMapping.method();
if (mtypes != null && mtypes.length > 0) {
String name = mtypes[0].name();
if (!"ALL".equals(name) && !"UNKNOWN".equals(name)) {
httpMethod = name;
}
}
// @ApiMeta.params 转 name→desc 映射,供参数补充描述
Map<String, String> descMap = new LinkedHashMap<>();
for (ApiParam ap : meta.params()) {
descMap.put(ap.name(), ap.desc());
}
// 扫描方法参数(跳过 Context)
List<Map<String, Object>> params = new ArrayList<>();
for (Parameter p : method.getParameters()) {
if (p.getType() == Context.class) {
continue;
}
Map<String, Object> param = extractParam(p, descMap);
if (param != null) {
params.add(param);
}
}
Map<String, Object> item = new LinkedHashMap<>();
item.put("method", httpMethod);
item.put("path", path);
item.put("desc", meta.desc());
item.put("params", params);
list.add(item);
}
return Collections.unmodifiableList(list);
}
/** 从单个方法参数提取元数据 */
private static Map<String, Object> extractParam(Parameter p, Map<String, String> descMap) {
Map<String, Object> param = new LinkedHashMap<>();
Path pathAnno = p.getAnnotation(Path.class);
Param paramAnno = p.getAnnotation(Param.class);
Body bodyAnno = p.getAnnotation(Body.class);
if (pathAnno != null) {
String name = pathAnno.value().isEmpty() ? p.getName() : pathAnno.value();
param.put("name", name);
param.put("in", "path");
param.put("type", "string");
param.put("required", true);
} else if (bodyAnno != null) {
param.put("name", "body");
param.put("in", "body");
param.put("type", "json");
param.put("required", true);
} else if (paramAnno != null) {
String name = paramAnno.value().isEmpty() ? p.getName() : paramAnno.value();
param.put("name", name);
param.put("in", "query");
param.put("type", "string");
param.put("required", paramAnno.required());
} else {
// 无 Solon 参数注解,跳过(如注入的 bean 等)
return null;
}
param.put("desc", descMap.getOrDefault((String) param.get("name"), ""));
return param;
}
}
@@ -0,0 +1,45 @@
package com.hospital.front.infra;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.scheduling.annotation.Scheduled;
import com.hospital.front.adapter.HospitalRouter;
import com.hospital.front.dto.Result;
import java.util.concurrent.atomic.AtomicReference;
/**
* 数据库健康后台监测(缓存式)。
*
* 后台周期探测 DB(默认 30 秒),/api/admin/health 直接读缓存秒回——
* 避免 DB 建连慢(如机器休眠唤醒后网络未就绪,实测可达 4 分钟)时
* 健康接口长时间阻塞,导致 Agent 看门狗 30s 超时误判主程序宕机而反复重启。
*
* 启动初期缓存为空视为 STARTING(探测线程尚未跑完第一轮),不算故障。
*/
@Component
public class DbHealthMonitor {
@Inject
private HospitalRouter router;
/** 最近一次探测结果(null = 尚未完成首轮探测) */
private final AtomicReference<Result<Boolean>> last = new AtomicReference<>();
/** 周期探测(首轮延迟由调度器决定,失败不影响后续轮次) */
@Scheduled(cron = "*/30 * * * * *")
public void probe() {
try {
last.set(router.active().healthCheck());
} catch (Exception e) {
// healthCheck 内部已捕获,此处兜底防止调度线程中断
last.set(Result.error("探测异常:" + e.getMessage()));
}
}
/** 最近一次探测结果;未完成首轮探测时返回 null(调用方按 STARTING 处理) */
public Result<Boolean> last() {
return last.get();
}
}
@@ -0,0 +1,174 @@
package com.hospital.front.infra;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;
import lombok.extern.slf4j.Slf4j;
/**
* IP 白名单过滤器(应对前置机端口被公网扫描器探测)。
*
* 配置(app.yml,支持医院配置覆盖):
* security:
* ipWhitelistEnabled: false # 白名单开关(默认关,行为不变)
* ipWhitelist: "10.10.10.228,10.0.0.0/24" # IP 池,逗号分隔
*
* 规则:
* - 开关关闭时全放行(默认)
* - 127.0.0.1 / ::1Agent 环回访问 /api/admin)永远放行,无需配置
* - 直连部署:取连接对端 IP(remoteIp)判定
* - nginx 反代部署(security.trustProxy=true):连接对端必须是环回或 nginx 所在 IP,
* 然后取 X-Forwarded-For 的最右侧 IP 判定(最右侧由可信 nginx 追加,无法伪造;
* 不能取最左侧——客户端可自带 XFF 冒充白名单)
* - 支持三种写法:精确 IP10.0.0.5)、通配段(112.113.*.*,每段可单独 *)、
* CIDR 网段(10.0.0.0/24
* - 非白名单一律 403 打回,并记 WARN 一行(含来源 IP 与路径,便于追踪扫描源)
*/
@Slf4j
@Component
public class IpWhitelistFilter implements Filter {
/** 环回地址(永远放行) */
private static final Set<String> LOOPBACK = new HashSet<>(Arrays.asList("127.0.0.1", "0:0:0:0:0:0:0:1", "::1"));
@Override
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
if (!enabled()) {
chain.doFilter(ctx);
return;
}
String peer = ctx.remoteIp() != null ? ctx.remoteIp() : "";
String ip = peer;
if (trustProxy()) {
// 反代模式:对端必须是环回(同机 nginx)或白名单中的代理 IP,
// 否则视为伪造直连,直接打回
if (!LOOPBACK.contains(peer)) {
log.warn("[IP白名单] 代理模式下非可信代理直连:ip={} {} {}", peer, ctx.method(), ctx.path());
reject(ctx, peer);
return;
}
// X-Forwarded-For 最右侧为本机 nginx 追加的真实客户端 IP(不可伪造)
String xff = ctx.header("X-Forwarded-For");
if (xff != null && !xff.trim().isEmpty()) {
String[] hops = xff.split(",");
ip = hops[hops.length - 1].trim();
}
// 无 XFF 的环回请求 = Agent 本机访问,保持环回 IP(放行)
}
if (isAllowed(ip)) {
chain.doFilter(ctx);
return;
}
reject(ctx, ip);
}
/** 非白名单:403 打回(一行日志,避免扫描器刷爆日志) */
private void reject(Context ctx, String ip) {
log.warn("[IP白名单] 拒绝访问:ip={} {} {}", ip, ctx.method(), ctx.path());
ctx.status(403);
ctx.contentType("application/json;charset=UTF-8");
ctx.output("{\"success\":false,\"msg\":\"禁止访问\",\"data\":null}");
}
/** 白名单开关(惰性读取,支持热更新) */
private boolean enabled() {
return "true".equals(Solon.cfg().get("security.ipWhitelistEnabled", "false"));
}
/** 代理模式开关:nginx 反代部署时置 true,从 X-Forwarded-For 取真实客户端 IP */
private boolean trustProxy() {
return "true".equals(Solon.cfg().get("security.trustProxy", "false"));
}
/** IP 是否放行:环回 / 精确匹配 / 通配段匹配 / CIDR 网段匹配 */
private boolean isAllowed(String ip) {
if (LOOPBACK.contains(ip)) {
return true;
}
for (String entry : pool()) {
if (entry.contains("/")) {
if (cidrMatch(ip, entry)) {
return true;
}
} else if (entry.contains("*")) {
if (wildcardMatch(ip, entry)) {
return true;
}
} else if (entry.equals(ip)) {
return true;
}
}
return false;
}
/**
* 通配段匹配(仅 IPv4):entry 每段可为 * 或具体数字,如 112.113.*.*。
*/
private static boolean wildcardMatch(String ip, String entry) {
String[] ipParts = ip.split("\\.");
String[] entryParts = entry.split("\\.");
if (ipParts.length != 4 || entryParts.length != 4) {
return false;
}
for (int i = 0; i < 4; i++) {
if (!"*".equals(entryParts[i]) && !entryParts[i].equals(ipParts[i])) {
return false;
}
}
return true;
}
/** 白名单 IP 池(逗号分隔,惰性读取) */
private Set<String> pool() {
String raw = Solon.cfg().get("security.ipWhitelist", "");
Set<String> set = new HashSet<>();
if (raw != null && !raw.trim().isEmpty()) {
for (String s : raw.split(",")) {
if (!s.trim().isEmpty()) {
set.add(s.trim());
}
}
}
return set;
}
/** IPv4 CIDR 网段匹配(如 10.0.0.0/24 */
private static boolean cidrMatch(String ip, String cidr) {
try {
String[] parts = cidr.split("/");
byte[] net = java.net.InetAddress.getByName(parts[0]).getAddress();
int prefix = Integer.parseInt(parts[1]);
byte[] addr = java.net.InetAddress.getByName(ip).getAddress();
if (net.length != addr.length || net.length != 4) {
return false; // 仅支持 IPv4 网段
}
int fullBytes = prefix / 8;
int restBits = prefix % 8;
for (int i = 0; i < fullBytes; i++) {
if (net[i] != addr[i]) {
return false;
}
}
if (restBits > 0 && fullBytes < 4) {
int mask = 0xFF << (8 - restBits);
if ((net[fullBytes] & mask) != (addr[fullBytes] & mask)) {
return false;
}
}
return true;
} catch (Exception e) {
return false; // 网段配置非法视为不匹配
}
}
}
@@ -0,0 +1,25 @@
package com.hospital.front.infra.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 接口元数据注解(标注在 Controller 方法上)。
*
* 用于 /api/admin/apis 反射扫描自动生成接口清单,供 B 端接口测试工具加载。
* 标注后无需手写清单,新增/调整接口时自动同步。
*
* 参数的 name/in/required/type 由方法签名(@Path/@Param/@Body)反射自动提取,
* 此处仅补充方法描述与参数描述(按 name 匹配)。
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiMeta {
/** 接口描述 */
String desc();
/** 参数描述(按 name 匹配方法参数补充 desc) */
ApiParam[] params() default {};
}
@@ -0,0 +1,21 @@
package com.hospital.front.infra.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
/**
* 接口参数描述注解(仅作为 {@link ApiMeta#params()} 数组成员,不单独标注)。
*
* 扫描时按 name 匹配方法参数,补充其 desc。
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiParam {
/** 参数名(须与方法签名 @Path/@Param/@Body 的参数名一致) */
String name();
/** 参数描述 */
String desc() default "";
}
@@ -1,11 +1,10 @@
package com.hospital.front.infra.exception; package com.hospital.front.infra.exception;
import com.hospital.front.dto.Result;
/** /**
* 业务异常。 * 业务异常。
* *
* 用于业务层显式抛出指定错误码的异常,会被 GlobalExceptionHandler 捕获并包装为 Result。 * 对外契约与原 hospitalmiddle 一致:异常统一以 {"success":false,"msg":...} 返回,
* 不携带错误码(code 体系已随 Result 对齐移除,分类信息仅用于日志排查)。
* *
* @author hospital-front重构 * @author hospital-front重构
*/ */
@@ -13,46 +12,32 @@ public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** 业务错误码(参见 Result 中的常量) */
private final int code;
/** /**
* 构造业务异常。 * 构造业务异常。
* *
* @param code 业务错误码
* @param message 错误描述 * @param message 错误描述
*/ */
public BusinessException(int code, String message) { public BusinessException(String message) {
super(message); super(message);
this.code = code;
} }
/** /** 静态工厂方法 */
* 静态工厂方法。 public static BusinessException of(String message) {
* return new BusinessException(message);
* @param code 业务错误码
* @param message 错误描述
*/
public static BusinessException of(int code, String message) {
return new BusinessException(code, message);
} }
/** 参数缺失异常 */ /** 参数缺失异常 */
public static BusinessException paramMissing(String field) { public static BusinessException paramMissing(String field) {
return new BusinessException(Result.CODE_PARAM_MISSING, "参数缺失:" + field); return new BusinessException("参数缺失:" + field);
} }
/** 业务校验失败异常 */ /** 业务校验失败异常 */
public static BusinessException businessFailed(String msg) { public static BusinessException businessFailed(String msg) {
return new BusinessException(Result.CODE_BUSINESS, msg); return new BusinessException(msg);
} }
/** 存储过程调用失败异常 */ /** 存储过程调用失败异常 */
public static BusinessException procFailed(String msg) { public static BusinessException procFailed(String msg) {
return new BusinessException(Result.CODE_PROC_FAILED, msg); return new BusinessException(msg);
}
public int getCode() {
return code;
} }
} }
@@ -23,36 +23,63 @@ public class GlobalExceptionHandler implements Filter {
public void doFilter(Context ctx, FilterChain chain) { public void doFilter(Context ctx, FilterChain chain) {
try { try {
chain.doFilter(ctx); chain.doFilter(ctx);
} catch (org.noear.solon.core.exception.StatusException e) {
int code = e.getCode();
if (code >= 500) {
// 5xx 服务端状态异常:真实故障,ERROR 记堆栈
log.error("服务端状态异常:{} {}(HTTP {}", ctx.method(), ctx.path(), code, e);
} else if (code == 400) {
// 400 请求解析失败(多为 multipart/Content-Type 错误):真实调用失败,WARN 并带 Content-Type 便于定位
log.warn("请求解析失败(HTTP 400):{} {}Content-Type={}", ctx.method(), ctx.path(), ctx.header("Content-Type"));
} else if (code == 404) {
// 404 路由未匹配:多为扫描器探测,DEBUG 静默(INFO 级别下不再输出)
log.debug("路由未匹配:{} {}HTTP 404", ctx.method(), ctx.path());
} else {
// 其它 4xx401/403/405/415 等)
log.info("客户端状态异常:{} {}(HTTP {}", ctx.method(), ctx.path(), code);
}
renderStatus(ctx, code);
} catch (BusinessException e) { } catch (BusinessException e) {
String msg = e.getMessage() != null ? e.getMessage() : "业务异常"; String msg = e.getMessage() != null ? e.getMessage() : "业务异常";
log.warn("业务异常:code={}, msg={}", e.getCode(), e.getMessage()); log.warn("业务异常:{}", e.getMessage());
renderResult(ctx, Result.error(e.getCode(), msg)); renderResult(ctx, Result.error(msg));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
String msg = e.getMessage() != null ? e.getMessage() : "参数错误"; String msg = e.getMessage() != null ? e.getMessage() : "参数错误";
log.warn("参数异常:{}", e.getMessage()); log.warn("参数异常:{}", e.getMessage());
renderResult(ctx, Result.error(Result.CODE_PARAM_FORMAT, msg)); renderResult(ctx, Result.error(msg));
} catch (Exception e) { } catch (Exception e) {
log.error("系统异常", e); log.error("系统异常", e);
String msg = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); String msg = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName();
renderResult(ctx, Result.error(Result.ERROR_CODE, "系统异常:" + msg)); renderResult(ctx, Result.error("系统异常:" + msg));
} catch (Throwable t) { } catch (Throwable t) {
// 兜底:捕获 Error 及其它 Throwable(如 OOM),保证响应不悬挂 // 兜底:捕获 Error 及其它 Throwable(如 OOM),保证响应不悬挂
log.error("严重异常(Throwable", t); log.error("严重异常(Throwable", t);
renderResult(ctx, Result.error(Result.ERROR_CODE, "严重异常:" + t.getClass().getSimpleName())); renderResult(ctx, Result.error("严重异常:" + t.getClass().getSimpleName()));
} }
} }
/** /**
* 输出 Result JSON 响应。 * 输出路由状态异常(404/405 等)响应。
*/
private void renderStatus(Context ctx, int code) {
ctx.status(code);
ctx.contentType("application/json;charset=UTF-8");
ctx.output("{\"success\":false,\"msg\":\"Not Found\",\"data\":null}");
}
/**
* 输出 Result JSON 响应(与原 hospitalmiddle 契约一致:{"success":false,"msg":...})。
*/ */
private void renderResult(Context ctx, Result<?> result) { private void renderResult(Context ctx, Result<?> result) {
ctx.status(200); // 保留上游预设的 4xx/5xx 状态码(如 checkBasic 认证失败已设 401),否则默认 200。
// 若强行刷成 200,Basic 认证失败将不再返回 401,浏览器不会弹出账号密码框。
if (ctx.status() < 400) {
ctx.status(200);
}
ctx.contentType("application/json;charset=UTF-8"); ctx.contentType("application/json;charset=UTF-8");
// 简易 JSON 序列化(避免引入 fastjson 依赖) ctx.output("{\"success\":" + result.isSuccess()
String dataJson = result.getData() != null ? toJsonValue(result.getData()) : "null";
ctx.output("{\"code\":" + result.getCode()
+ ",\"msg\":" + toJsonValue(result.getMsg()) + ",\"msg\":" + toJsonValue(result.getMsg())
+ ",\"data\":" + dataJson + "}"); + ",\"data\":null}");
} }
/** /**
@@ -0,0 +1,42 @@
package com.hospital.front.infra.util;
import org.noear.snack.ONode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* JSON 解析工具(snack3 封装)。
*
* Solon 对 Map/List 泛型 body 反序列化支持不佳(接口层只能拿 String body),
* 此处集中封装手动解析,接口层一行调用;单 DTO 结构应优先用 @Body 强类型直收。
*/
public final class JsonUtils {
private JsonUtils() {
}
/** JSON 对象字符串 → Map(解析失败抛 IllegalArgumentException */
@SuppressWarnings("unchecked")
public static Map<String, Object> parseMap(String json) {
try {
return (Map<String, Object>) ONode.load(json).toObject(Map.class);
} catch (Exception e) {
throw new IllegalArgumentException("JSON 对象格式错误:" + e.getMessage());
}
}
/** JSON 数组字符串 → List&lt;Map&gt;(空串返回空列表) */
@SuppressWarnings("unchecked")
public static List<Map<String, Object>> parseList(String json) {
if (json == null || json.trim().isEmpty()) {
return new ArrayList<>();
}
try {
return (List<Map<String, Object>>) ONode.load(json).toObject(List.class);
} catch (Exception e) {
throw new IllegalArgumentException("JSON 数组格式错误:" + e.getMessage());
}
}
}
@@ -1,5 +1,11 @@
package com.hospital.front.mapper.bsm; package com.hospital.front.mapper.bsm;
import com.hospital.front.vo.ConclusionVo;
import com.hospital.front.vo.ExamListVo;
import com.hospital.front.vo.MedicalMiddleResultVo;
import com.hospital.front.vo.PeStatusVo;
import com.hospital.front.vo.SuggestVo;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@@ -33,7 +39,7 @@ public interface BsmOrderMapper {
void addUnitV2(Map<String, Object> map); void addUnitV2(Map<String, Object> map);
/** 按体检预约 ID(逗号分隔)查询体检状态 */ /** 按体检预约 ID(逗号分隔)查询体检状态 */
List<Map<String, Object>> getPhyexamStatus(@Param("peIds") String peIds); List<PeStatusVo> getPhyexamStatus(@Param("peIds") String peIds);
/** 医院所有体检项目(组合项) */ /** 医院所有体检项目(组合项) */
List<Map<String, Object>> getItemList(); List<Map<String, Object>> getItemList();
@@ -53,45 +59,32 @@ public interface BsmOrderMapper {
/** 员工体检结果 */ /** 员工体检结果 */
List<Map<String, Object>> getResult(@Param("peId") String peId); List<Map<String, Object>> getResult(@Param("peId") String peId);
/** 体检结论(列表) */
List<Map<String, Object>> getConclusion(@Param("peId") String peId);
/** 体检结论(全量) */ /** 体检结论(全量) */
List<Map<String, Object>> getConclusionAll(@Param("peId") String peId); List<ConclusionVo> getConclusionAll(@Param("peId") String peId);
/** 体检建议(列表) */
List<Map<String, Object>> getSuggest(@Param("peId") String peId);
/** 体检建议(全量) */ /** 体检建议(全量) */
List<Map<String, Object>> getSuggestAll(@Param("peId") String peId); List<SuggestVo> getSuggestAll(@Param("peId") String peId);
/** 按身份证号及年份查询体检状态 */ /** 按身份证号及年份查询体检状态 */
Map<String, Object> getPeStatusByIdnoAndYear(@Param("idNo") String idNo, @Param("year") String year); PeStatusVo getPeStatusByIdnoAndYear(@Param("idNo") String idNo, @Param("year") String year);
/** 按身份证号及年份查询体检结果 */ /** 按身份证号及年份查询体检结果 */
List<Map<String, Object>> getResultByIdcardAndYear(@Param("idNo") String idNo, @Param("year") String year); List<MedicalMiddleResultVo> getResultByIdcardAndYear(@Param("idNo") String idNo, @Param("year") String year);
/** 按身份证号及年份查询体检结果(全量) */ /** 按身份证号及年份查询体检结果(全量) */
List<Map<String, Object>> getResultByIdcardAndYearAll(@Param("idNo") String idNo, @Param("year") String year); List<MedicalMiddleResultVo> getResultByIdcardAndYearAll(@Param("idNo") String idNo, @Param("year") String year);
/** 按体检编号查询体检结果 */ /** 按体检编号查询体检结果 */
List<Map<String, Object>> getResultByPeId(@Param("peId") String peId); List<MedicalMiddleResultVo> getResultByPeId(@Param("peId") String peId);
/** 按审核日期查询体检报告(去重) */
List<Map<String, Object>> getDisResultByAuditDate(@Param("auditDate") String auditDate);
/** 按体检编号查询体检项目子表 */ /** 按体检编号查询体检项目子表 */
List<Map<String, Object>> getItemByPeId(@Param("peId") String peId); List<MedicalMiddleResultVo> getItemByPeId(@Param("peId") String peId);
/** 按体检编号查询体检项目子表(全量) */ /** 按体检编号查询体检项目子表(全量) */
List<Map<String, Object>> getItemByPeIdAll(@Param("peId") String peId); List<MedicalMiddleResultVo> getItemByPeIdAll(@Param("peId") String peId);
/** 按身份证号或体检编号 + 审核年份查询体检结果 */
List<Map<String, Object>> getResultByIdNoOrPeId(
@Param("idNo") String idNo,
@Param("peId") String peId,
@Param("auditYear") String auditYear
);
/** 按身份证号查询当年 peId 与预约日期 */ /** 按身份证号查询当年 peId 与预约日期 */
Map<String, Object> getPeIdAndDateByIdcard(@Param("idNo") String idNo); Map<String, Object> getPeIdAndDateByIdcard(@Param("idNo") String idNo);
@@ -100,5 +93,5 @@ public interface BsmOrderMapper {
List<Map<String, Object>> getPeIdAndDateByIdcardList(@Param("idNo") String idNo); List<Map<String, Object>> getPeIdAndDateByIdcardList(@Param("idNo") String idNo);
/** 按审核日期查询当天体检人员列表(去重) */ /** 按审核日期查询当天体检人员列表(去重) */
List<Map<String, Object>> getExamListByDate(@Param("auditDate") String auditDate); List<ExamListVo> getExamListByDate(@Param("auditDate") String auditDate);
} }
@@ -1,5 +1,11 @@
package com.hospital.front.mapper.nx; package com.hospital.front.mapper.nx;
import com.hospital.front.vo.ConclusionVo;
import com.hospital.front.vo.ExamListVo;
import com.hospital.front.vo.MedicalMiddleResultVo;
import com.hospital.front.vo.PeStatusVo;
import com.hospital.front.vo.SuggestVo;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@@ -7,91 +13,70 @@ import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* BSM 医院 MapperOracle 方言)。 * 宁夏医院 MapperSqlServer 方言)。
* * getConclusion/getSuggest 与 All 版 SQL 完全重复,已合并为 All 版。
* 适用医院:ningxia(宁夏宝石花,SqlServer
* XML 位置:classpath:mapper/nx/OrderMapper.xml
*
* 存储过程参数与医院 HIS 系统约定,不可修改。
*/ */
@Mapper @Mapper
public interface NxOrderMapper { public interface NxOrderMapper {
/** 健康探测SELECT 1 */ /** 健康探测 */
int ping(); int ping();
/** 预约提交(存储过程 P_ADD_PE_REGIST,出参 resultCode/errorMsg/peId */ /** 提交预约(存储过程) */
void saveOrder(Map<String, Object> map); void saveOrder(Map<String, Object> map);
/** 取消预约(存储过程 P_DEL_PE_REGIST */ /** 取消预约(存储过程) */
void cancelOrder(Map<String, Object> map); void cancelOrder(Map<String, Object> map);
/** 添加/修改单位 V1(存储过程 P_ADD_UNIT */ /** 添加/修改单位 V2(存储过程) */
void addUnit(Map<String, Object> map);
/** 添加/修改单位 V2(存储过程 P_ADD_UNIT8 入参版) */
void addUnitV2(Map<String, Object> map); void addUnitV2(Map<String, Object> map);
/** 体检预约 ID(逗号分隔)查询体检状态 */ /** 体检状态查询(多个 peId 逗号分隔) */
List<Map<String, Object>> getPhyexamStatus(@Param("peIds") String peIds); List<PeStatusVo> getPhyexamStatus(@Param("peIds") String peIds);
/** 医院所有体检项目(组合项) */ /** 体检项目查询 */
List<Map<String, Object>> getItemList(); List<Map<String, Object>> getItemList();
/** 医院所有体检项目及指标项 */ /** 体检指标项及体检组合数据 */
List<Map<String, Object>> getAllItemList(); List<Map<String, Object>> getAllItemList();
/** 医院所有体检指标项 */ /** 体检指标项数据 */
List<Map<String, Object>> getPeItemList(); List<Map<String, Object>> getPeItemList();
/** 按体检指标项查所属组合 */
List<Map<String, Object>> getItemInfo(@Param("peItemCode") String peItemCode);
/** 员工体检项目 */ /** 员工体检项目 */
List<Map<String, Object>> getEmpItem(@Param("peId") String peId); List<Map<String, Object>> getEmpItem(@Param("peId") String peId);
/** 按指标项 CODE 查询体检组合 */ /** 按体检编号查体检结果 */
List<Map<String, Object>> getItemInfo(@Param("peItemCode") String peItemCode);
/** 员工体检结果 */
List<Map<String, Object>> getResult(@Param("peId") String peId); List<Map<String, Object>> getResult(@Param("peId") String peId);
/** 体检结论(列表) */ /** 体检结论(列表) */
List<Map<String, Object>> getConclusion(@Param("peId") String peId); List<ConclusionVo> getConclusionAll(@Param("peId") String peId);
/** 体检结论(全量) */
List<Map<String, Object>> getConclusionAll(@Param("peId") String peId);
/** 体检建议(列表) */
List<Map<String, Object>> getSuggest(@Param("peId") String peId);
/** 体检建议(全量) */ /** 体检建议(全量) */
List<Map<String, Object>> getSuggestAll(@Param("peId") String peId); List<SuggestVo> getSuggestAll(@Param("peId") String peId);
/** 按身份证号及年份查询体检状态 */ /** 按身份证号及年份查询体检状态 */
Map<String, Object> getPeStatusByIdnoAndYear(@Param("idNo") String idNo, @Param("year") String year); PeStatusVo getPeStatusByIdnoAndYear(@Param("idNo") String idNo, @Param("year") String year);
/** 按身份证号及年份查询体检结果 */ /** 按身份证号及年份查询体检结果 */
List<Map<String, Object>> getResultByIdcardAndYear(@Param("idNo") String idNo, @Param("year") String year); List<MedicalMiddleResultVo> getResultByIdcardAndYear(@Param("idNo") String idNo, @Param("year") String year);
/** 按身份证号及年份查询体检结果(全量) */ /** 按身份证号及年份查询体检结果(全量) */
List<Map<String, Object>> getResultByIdcardAndYearAll(@Param("idNo") String idNo, @Param("year") String year); List<MedicalMiddleResultVo> getResultByIdcardAndYearAll(@Param("idNo") String idNo, @Param("year") String year);
/** 按体检编号查询体检结果 */ /** 按体检编号查询体检结果 */
List<Map<String, Object>> getResultByPeId(@Param("peId") String peId); List<MedicalMiddleResultVo> getResultByPeId(@Param("peId") String peId);
/** 按审核日期查询体检报告(去重) */
List<Map<String, Object>> getDisResultByAuditDate(@Param("auditDate") String auditDate);
/** 按体检编号查询体检项目子表 */ /** 按体检编号查询体检项目子表 */
List<Map<String, Object>> getItemByPeId(@Param("peId") String peId); List<MedicalMiddleResultVo> getItemByPeId(@Param("peId") String peId);
/** 按体检编号查询体检项目子表(全量) */ /** 按体检编号查询体检项目子表(全量) */
List<Map<String, Object>> getItemByPeIdAll(@Param("peId") String peId); List<MedicalMiddleResultVo> getItemByPeIdAll(@Param("peId") String peId);
/** 按身份证号或体检编号 + 审核年份查询体检结果 */
List<Map<String, Object>> getResultByIdNoOrPeId(
@Param("idNo") String idNo,
@Param("peId") String peId,
@Param("auditYear") String auditYear
);
/** 按身份证号查询当年 peId 与预约日期 */ /** 按身份证号查询当年 peId 与预约日期 */
Map<String, Object> getPeIdAndDateByIdcard(@Param("idNo") String idNo); Map<String, Object> getPeIdAndDateByIdcard(@Param("idNo") String idNo);
@@ -100,5 +85,5 @@ public interface NxOrderMapper {
List<Map<String, Object>> getPeIdAndDateByIdcardList(@Param("idNo") String idNo); List<Map<String, Object>> getPeIdAndDateByIdcardList(@Param("idNo") String idNo);
/** 按审核日期查询当天体检人员列表(去重) */ /** 按审核日期查询当天体检人员列表(去重) */
List<Map<String, Object>> getExamListByDate(@Param("auditDate") String auditDate); List<ExamListVo> getExamListByDate(@Param("auditDate") String auditDate);
} }
@@ -1,104 +0,0 @@
package com.hospital.front.mapper.ya;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* BSM 医院组 MapperOracle 方言)。
*
* 适用医院:yanan(延安,SqlServer
* XML 位置:classpath:mapper/ya/OrderMapper.xml
*
* 存储过程参数与医院 HIS 系统约定,不可修改。
*/
@Mapper
public interface YaOrderMapper {
/** 健康探测(SELECT 1 */
int ping();
/** 预约提交(存储过程 P_ADD_PE_REGIST,出参 resultCode/errorMsg/peId */
void saveOrder(Map<String, Object> map);
/** 取消预约(存储过程 P_DEL_PE_REGIST */
void cancelOrder(Map<String, Object> map);
/** 添加/修改单位 V1(存储过程 P_ADD_UNIT */
void addUnit(Map<String, Object> map);
/** 添加/修改单位 V2(存储过程 P_ADD_UNIT8 入参版) */
void addUnitV2(Map<String, Object> map);
/** 按体检预约 ID(逗号分隔)查询体检状态 */
List<Map<String, Object>> getPhyexamStatus(@Param("peIds") String peIds);
/** 医院所有体检项目(组合项) */
List<Map<String, Object>> getItemList();
/** 医院所有体检项目及指标项 */
List<Map<String, Object>> getAllItemList();
/** 医院所有体检指标项 */
List<Map<String, Object>> getPeItemList();
/** 员工体检项目 */
List<Map<String, Object>> getEmpItem(@Param("peId") String peId);
/** 按指标项 CODE 查询体检组合 */
List<Map<String, Object>> getItemInfo(@Param("peItemCode") String peItemCode);
/** 员工体检结果 */
List<Map<String, Object>> getResult(@Param("peId") String peId);
/** 体检结论(列表) */
List<Map<String, Object>> getConclusion(@Param("peId") String peId);
/** 体检结论(全量) */
List<Map<String, Object>> getConclusionAll(@Param("peId") String peId);
/** 体检建议(列表) */
List<Map<String, Object>> getSuggest(@Param("peId") String peId);
/** 体检建议(全量) */
List<Map<String, Object>> getSuggestAll(@Param("peId") String peId);
/** 按身份证号及年份查询体检状态 */
Map<String, Object> getPeStatusByIdnoAndYear(@Param("idNo") String idNo, @Param("year") String year);
/** 按身份证号及年份查询体检结果 */
List<Map<String, Object>> getResultByIdcardAndYear(@Param("idNo") String idNo, @Param("year") String year);
/** 按身份证号及年份查询体检结果(全量) */
List<Map<String, Object>> getResultByIdcardAndYearAll(@Param("idNo") String idNo, @Param("year") String year);
/** 按体检编号查询体检结果 */
List<Map<String, Object>> getResultByPeId(@Param("peId") String peId);
/** 按审核日期查询体检报告(去重) */
List<Map<String, Object>> getDisResultByAuditDate(@Param("auditDate") String auditDate);
/** 按体检编号查询体检项目子表 */
List<Map<String, Object>> getItemByPeId(@Param("peId") String peId);
/** 按体检编号查询体检项目子表(全量) */
List<Map<String, Object>> getItemByPeIdAll(@Param("peId") String peId);
/** 按身份证号或体检编号 + 审核年份查询体检结果 */
List<Map<String, Object>> getResultByIdNoOrPeId(
@Param("idNo") String idNo,
@Param("peId") String peId,
@Param("auditYear") String auditYear
);
/** 按身份证号查询当年 peId 与预约日期 */
Map<String, Object> getPeIdAndDateByIdcard(@Param("idNo") String idNo);
/** 按身份证号查询当年 peId 与预约日期(数组) */
List<Map<String, Object>> getPeIdAndDateByIdcardList(@Param("idNo") String idNo);
/** 按审核日期查询当天体检人员列表(去重) */
List<Map<String, Object>> getExamListByDate(@Param("auditDate") String auditDate);
}
@@ -1,5 +1,7 @@
package com.hospital.front.service; package com.hospital.front.service;
import com.hospital.front.vo.MedicalMiddleResultVo;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList; import java.util.ArrayList;
@@ -17,8 +19,10 @@ import com.hospital.front.adapter.HospitalAdapter;
import com.hospital.front.adapter.HospitalRouter; import com.hospital.front.adapter.HospitalRouter;
import com.hospital.front.infra.exception.BusinessException; import com.hospital.front.infra.exception.BusinessException;
import com.hospital.front.infra.util.DateUtils; import com.hospital.front.infra.util.DateUtils;
import com.hospital.front.vo.ExamListVo;
import com.hospital.front.vo.OrderItemVo; import com.hospital.front.vo.OrderItemVo;
import com.hospital.front.vo.OrderV2Vo; import com.hospital.front.vo.OrderV2Vo;
import com.hospital.front.vo.PeStatusPhyVo;
import com.hospital.front.vo.UnitVo; import com.hospital.front.vo.UnitVo;
/** /**
@@ -79,8 +83,7 @@ public class OrderService {
* *
* @return 成功时返回 peId * @return 成功时返回 peId
*/ */
public String saveOrder(Map<String, Object> body) { public String saveOrder(com.hospital.front.vo.OrderV2Vo vo) {
OrderV2Vo vo = convertOrderV2(body);
if (isAllsecondFlag()) { if (isAllsecondFlag()) {
vo = vo.applyAllsecondFlag(); vo = vo.applyAllsecondFlag();
} }
@@ -97,8 +100,10 @@ public class OrderService {
if (result.getPeId() == null) { if (result.getPeId() == null) {
throw BusinessException.procFailed("医院未返回体检编号"); throw BusinessException.procFailed("医院未返回体检编号");
} }
log.info("提交预约成功:peId={}", result.getPeId());
return result.getPeId(); return result.getPeId();
} else { } else {
log.warn("提交预约失败:errorMsg={}", result.getErrorMsg());
throwHospitalFailed(result.getErrorMsg()); throwHospitalFailed(result.getErrorMsg());
// 不可达,仅为满足 Java 返回类型要求 // 不可达,仅为满足 Java 返回类型要求
throw new IllegalStateException("unreachable"); throw new IllegalStateException("unreachable");
@@ -116,6 +121,7 @@ public class OrderService {
/** 批量添加/修改单位;返回失败信息(全部成功返回 null) */ /** 批量添加/修改单位;返回失败信息(全部成功返回 null) */
public String addUnits(List<Map<String, Object>> units) { public String addUnits(List<Map<String, Object>> units) {
log.info("添加单位:units={}", units);
StringBuilder errors = new StringBuilder(); StringBuilder errors = new StringBuilder();
boolean useV2 = "V2".equals(getHmVer()); boolean useV2 = "V2".equals(getHmVer());
for (int index = 0; index < units.size(); index++) { for (int index = 0; index < units.size(); index++) {
@@ -160,35 +166,27 @@ public class OrderService {
return adapter().getResult(peId); return adapter().getResult(peId);
} }
public List<Map<String, Object>> getConclusion(String peId) { /** 按身份证及年份查体检结果(内部使用:宁夏 getPeStatusByIdnoAndYear 兜底) */
return adapter().getConclusion(peId); public List<MedicalMiddleResultVo> getResultByIdcardAndYear(String idNo, String year) {
}
public List<Map<String, Object>> getSuggest(String peId) {
return adapter().getSuggest(peId);
}
public List<Map<String, Object>> getResultByIdcardAndYear(String idNo, String year) {
return adapter().getResultByIdcardAndYear(idNo, year); return adapter().getResultByIdcardAndYear(idNo, year);
} }
public List<Map<String, Object>> getResultByIdcardAndYearAll(String idNo, String year) { public List<MedicalMiddleResultVo> getResultByIdcardAndYearAll(String idNo, String year) {
return adapter().getResultByIdcardAndYearAll(idNo, year); return enrichResultAll(adapter().getResultByIdcardAndYearAll(idNo, year));
} }
public List<Map<String, Object>> getResultByPeId(String peId) { public List<MedicalMiddleResultVo> getResultByPeId(String peId) {
return enrichResultAll(adapter().getResultByPeId(peId));
}
/**
* 15.2 体检状态(简):返回原始基础字段,不做报告组装(对齐原 hospitalmiddle 的 getResultStatus)。
* 与 getResultByPeId 的区别在于不调用 enrichResultAll,故不含 conclusion/suggest/hospitalId/resultVoList。
*/
public List<MedicalMiddleResultVo> getResultStatus(String peId) {
return adapter().getResultByPeId(peId); return adapter().getResultByPeId(peId);
} }
/** 按审核日期查询体检报告(去重主表) */
public List<Map<String, Object>> getResultByAuditDate(String auditDate) {
return adapter().getDisResultByAuditDate(auditDate);
}
public List<Map<String, Object>> getResultByIdNoOrPeId(String idNo, String peId, String auditYear) {
return adapter().getResultByIdNoOrPeId(idNo, peId, auditYear);
}
public Map<String, Object> getPeIdAndDateByIdcard(String idNo) { public Map<String, Object> getPeIdAndDateByIdcard(String idNo) {
return adapter().getPeIdAndDateByIdcard(idNo); return adapter().getPeIdAndDateByIdcard(idNo);
} }
@@ -200,14 +198,13 @@ public class OrderService {
/** /**
* 体检状态查询:按医院返回的 PE_STATUS 名转字典数字。 * 体检状态查询:按医院返回的 PE_STATUS 名转字典数字。
*/ */
public List<Map<String, String>> getPhyexamStatus(String peIds) { public List<PeStatusPhyVo> getPhyexamStatus(String peIds) {
List<Map<String, String>> out = new ArrayList<>(); List<PeStatusPhyVo> out = new ArrayList<>();
for (Map<String, Object> row : adapter().getPhyexamStatus(peIds)) { for (com.hospital.front.vo.PeStatusVo row : adapter().getPhyexamStatus(peIds)) {
Map<String, String> item = new HashMap<>(); out.add(new PeStatusPhyVo(
item.put("peId", row.get("PE_ID") != null ? String.valueOf(row.get("PE_ID")) : ""); row.getPeId() != null ? row.getPeId() : "",
String statusName = row.get("PE_STATUS") != null ? String.valueOf(row.get("PE_STATUS")) : ""; PE_STATUS_DICT.getOrDefault(
item.put("peStatus", PE_STATUS_DICT.getOrDefault(statusName, "")); row.getPeStatus() != null ? row.getPeStatus() : "", "")));
out.add(item);
} }
return out; return out;
} }
@@ -215,63 +212,39 @@ public class OrderService {
/** /**
* 按日期查询当天体检人员列表(日期空则当天)。 * 按日期查询当天体检人员列表(日期空则当天)。
*/ */
public List<Map<String, Object>> getExamListByDate(String auditDate) { public List<ExamListVo> getExamListByDate(String auditDate) {
return adapter().getExamListByDate(auditDate != null ? auditDate : DateUtils.today()); return adapter().getExamListByDate(auditDate != null ? auditDate : DateUtils.today());
} }
/**
* 体检结论(汇总为单字符串)。
*/
public Map<String, String> getTotalConclusion(String peId) {
Map<String, String> map = new HashMap<>();
map.put("totalConclusion", buildSummary(adapter().getConclusion(peId)));
return map;
}
/** 体检结论(汇总,全量来源) */
public Map<String, String> getTotalConclusionAll(String peId) {
Map<String, String> map = new HashMap<>();
map.put("totalConclusion", buildSummary(adapter().getConclusionAll(peId)));
return map;
}
/**
* 体检建议(汇总)。宁夏(nx)仅取首条 GUIDE_CONTENT。
*/
public Map<String, String> getTotalSuggest(String peId) {
Map<String, String> map = new HashMap<>();
map.put("totalSuggest", buildSuggest(adapter().getSuggest(peId), "nx".equals(adapter().hmType())));
return map;
}
/** 体检建议(汇总,全量来源) */
public Map<String, String> getTotalSuggestAll(String peId) {
Map<String, String> map = new HashMap<>();
map.put("totalSuggest", buildSuggest(adapter().getSuggestAll(peId), "nx".equals(adapter().hmType())));
return map;
}
/** /**
* 按身份证号 + 年份查询体检状态及项目(含宁夏体检报告兜底)。 * 按身份证号 + 年份查询体检状态及项目(含宁夏体检报告兜底)。
*/ */
public Map<String, Object> getPeStatusByIdnoAndYear(String idNo, String year) { public Map<String, Object> getPeStatusByIdnoAndYear(String idNo, String year) {
Map<String, Object> map = adapter().getPeStatusByIdnoAndYear(idNo, year); com.hospital.front.vo.PeStatusVo vo = adapter().getPeStatusByIdnoAndYear(idNo, year);
if (map != null && map.get("PE_ID") != null) { if (vo != null && vo.getPeId() != null) {
Map<String, Object> result = new LinkedHashMap<>(map); Map<String, Object> result = new LinkedHashMap<>();
result.put("peItemList", adapter().getEmpItem(String.valueOf(map.get("PE_ID")))); result.put("PE_ID", vo.getPeId());
result.put("NAME", vo.getName());
result.put("ID_NO", vo.getIdNo());
result.put("UNIT_NAME", vo.getUnitName());
result.put("DEPARTMENT", vo.getDepartment());
result.put("PE_PRE_DATE", vo.getPePreDate());
result.put("PE_QUEUE_DATE", vo.getPeQueueDate());
result.put("PE_STATUS", vo.getPeStatus());
result.put("peItemList", adapter().getEmpItem(vo.getPeId()));
return result; return result;
} else { } else {
// 宁夏:从体检报告继续查 // 宁夏:从体检报告继续查
if ("nx".equals(adapter().hmType())) { if ("nx".equals(adapter().hmType())) {
List<Map<String, Object>> rows = adapter().getResultByIdcardAndYear(idNo, year); List<MedicalMiddleResultVo> rows = adapter().getResultByIdcardAndYear(idNo, year);
if (!rows.isEmpty()) { if (!rows.isEmpty()) {
Map<String, Object> row = rows.get(0); MedicalMiddleResultVo row = rows.get(0);
String auditDate = row.get("AUDIT_DATE") != null ? String.valueOf(row.get("AUDIT_DATE")) : null; String auditDate = row.getAuditDate();
Map<String, Object> out = new LinkedHashMap<>(); Map<String, Object> out = new LinkedHashMap<>();
out.put("PE_ID", row.get("PE_ID")); out.put("PE_ID", row.getPeId());
out.put("PE_PRE_DATE", out.put("PE_PRE_DATE",
auditDate != null && auditDate.length() >= 10 ? auditDate.substring(0, 10) : auditDate); auditDate != null && auditDate.length() >= 10 ? auditDate.substring(0, 10) : auditDate);
out.put("peItemList", adapter().getEmpItem(String.valueOf(row.get("PE_ID")))); out.put("peItemList", adapter().getEmpItem(row.getPeId()));
return out; return out;
} else { } else {
return null; return null;
@@ -284,107 +257,58 @@ public class OrderService {
// ================= 私有工具 ================= // ================= 私有工具 =================
/** 医院侧业务失败:抛出 2003 业务异常 */ /**
* 报告组装(对齐原 hospitalmiddle 的 dealMiddleChildDataAll):
* 为每条结果补 hospitalId、item 子表(全量)、体检结论、体检建议。
*/
private List<MedicalMiddleResultVo> enrichResultAll(List<MedicalMiddleResultVo> rows) {
String hospitalId = Solon.cfg().get("hospital.id", "");
for (MedicalMiddleResultVo row : rows) {
row.setHospitalId(hospitalId);
// item 子表(全量版)
row.setResultVoList(adapter().getItemByPeIdAll(row.getPeId()));
// 体检结论:多条按 "序号.科室:结论文本;<br/>" 拼接
StringBuilder conclusion = new StringBuilder();
int cIndex = 1;
for (com.hospital.front.vo.ConclusionVo c : adapter().getConclusionAll(row.getPeId())) {
String deptName = c.getPeDeptName() != null ? c.getPeDeptName() : "";
String text = c.getConclusionText() != null ? c.getConclusionText() : "";
conclusion.append(cIndex).append(".").append(deptName).append(":").append(text).append(";<br/>");
cIndex++;
}
row.setConclusion(conclusion.toString());
// 体检建议:宁夏取首条 guideContent;其余按 "序号.建议项:建议内容;<br/>" 拼接(跳过建议内容为空的行)
StringBuilder suggest = new StringBuilder();
List<com.hospital.front.vo.SuggestVo> suggestRows = adapter().getSuggestAll(row.getPeId());
if ("nx".equals(adapter().hmType())) {
if (!suggestRows.isEmpty() && suggestRows.get(0).getGuideContent() != null) {
suggest.append(suggestRows.get(0).getGuideContent());
}
} else {
int sIndex = 1;
for (com.hospital.front.vo.SuggestVo s : suggestRows) {
if (s.getGuideContent() != null && !s.getGuideContent().isEmpty()) {
String rec = s.getRecContent() != null ? s.getRecContent() : "";
suggest.append(sIndex).append(".").append(rec).append(":")
.append(s.getGuideContent()).append(";<br/>");
sIndex++;
}
}
}
row.setSuggest(suggest.toString());
}
return rows;
}
/** 医院侧业务失败:抛出业务异常 */
private void throwHospitalFailed(String errorMsg) { private void throwHospitalFailed(String errorMsg) {
if (errorMsg != null && !errorMsg.isEmpty()) { if (errorMsg != null && !errorMsg.isEmpty()) {
throw BusinessException.of(2003, errorMsg); throw BusinessException.of(errorMsg);
} else { } else {
throw BusinessException.procFailed("医院系统返回业务失败"); throw BusinessException.procFailed("医院系统返回业务失败");
} }
} }
/** 结论汇总:1.科室:结论;<br/> */
private String buildSummary(List<Map<String, Object>> rows) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < rows.size(); i++) {
Map<String, Object> row = rows.get(i);
String deptName = row.get("PE_DEPT_NAME") != null ? String.valueOf(row.get("PE_DEPT_NAME")) : "";
String dept = !deptName.isEmpty() ? deptName + ":" : "";
Object conclusion = row.get("CONCLUSION_TEXT");
sb.append(i + 1).append(".").append(dept).append(conclusion != null ? conclusion : "").append(";<br/>");
}
return sb.toString();
}
/** 建议汇总:宁夏仅取首条 GUIDE_CONTENT;其余 1.建议项:内容;<br/> */
private String buildSuggest(List<Map<String, Object>> rows, boolean nxStyle) {
if (nxStyle) {
if (rows.isEmpty() || rows.get(0).get("GUIDE_CONTENT") == null) {
return "";
}
return String.valueOf(rows.get(0).get("GUIDE_CONTENT"));
} else {
StringBuilder sb = new StringBuilder();
int i = 1;
for (Map<String, Object> row : rows) {
String guide = row.get("GUIDE_CONTENT") != null ? String.valueOf(row.get("GUIDE_CONTENT")) : "";
if (guide.isEmpty()) {
continue;
}
Object rec = row.get("REC_CONTENT");
sb.append(i).append(".").append(rec != null ? rec : "").append(":").append(guide).append(";<br/>");
i++;
}
return sb.toString();
}
}
/** Map → OrderV2Vo(数值/空值容错) */
private OrderV2Vo convertOrderV2(Map<String, Object> body) {
List<OrderItemVo> itemList = null;
Object rawList = body.get("itemList");
if (rawList instanceof List) {
itemList = new ArrayList<>();
for (Object item : (List<?>) rawList) {
if (item instanceof Map) {
Map<?, ?> m = (Map<?, ?>) item;
itemList.add(new OrderItemVo(
m.get("itemNo") != null ? String.valueOf(m.get("itemNo")) : null,
m.get("itemName") != null ? String.valueOf(m.get("itemName")) : null));
}
}
}
return new OrderV2Vo(
str(body, "orderDate"),
str(body, "secondDepartId"),
str(body, "secondDepartName"),
str(body, "thirdDepartName"),
str(body, "name"),
str(body, "sexName"),
str(body, "birthDay"),
body.get("age") instanceof Number ? ((Number) body.get("age")).intValue() : null,
str(body, "idCard"),
str(body, "marrigeStatusName"),
str(body, "country"),
str(body, "nationName"),
str(body, "birthplace"),
str(body, "position"),
str(body, "profession"),
str(body, "feeType"),
str(body, "liveSpace"),
str(body, "zipCode"),
str(body, "mobile"),
str(body, "email"),
str(body, "empSysno"),
str(body, "medicalType"),
str(body, "medicalClass"),
str(body, "workShape"),
str(body, "workDate"),
str(body, "jobLevel"),
str(body, "workPlace"),
str(body, "baseSite"),
str(body, "workTypeCode"),
str(body, "workTypeOther"),
str(body, "education"),
str(body, "title"),
str(body, "oldDepartName"),
str(body, "harmReasonCode"),
str(body, "harmReasonOther"),
str(body, "harmDate"),
str(body, "defendCancer"),
itemList);
}
/** 取字符串值(null 安全) */ /** 取字符串值(null 安全) */
private static String str(Map<String, Object> map, String key) { private static String str(Map<String, Object> map, String key) {
Object v = map.get(key); Object v = map.get(key);
@@ -2,11 +2,12 @@ package com.hospital.front.task;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit; import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.noear.solon.Solon; import org.noear.solon.Solon;
import org.noear.solon.annotation.Component; import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject; import org.noear.solon.annotation.Inject;
@@ -33,9 +34,9 @@ public class UptimeKumaTask {
@Inject @Inject
private HospitalRouter router; private HospitalRouter router;
private final OkHttpClient http = new OkHttpClient.Builder() /** JDK 内置 HttpClient(替代 OkHttp,免额外依赖):连接 5s、读取 10s */
.connectTimeout(5, TimeUnit.SECONDS) private final HttpClient http = HttpClient.newBuilder()
.readTimeout(10, TimeUnit.SECONDS) .connectTimeout(Duration.ofSeconds(5))
.build(); .build();
/** /**
@@ -51,15 +52,19 @@ public class UptimeKumaTask {
} }
// 检查数据库连接 // 检查数据库连接
com.hospital.front.dto.Result<Boolean> health = router.active().healthCheck(); com.hospital.front.dto.Result<Boolean> health = router.active().healthCheck();
if (health.getCode() == 0) { if (health.isSuccess()) {
try { try {
Response resp = http.newCall(new Request.Builder().url(kumaUrl).get().build()).execute(); HttpResponse<String> resp = http.send(
if (resp.isSuccessful()) { HttpRequest.newBuilder(URI.create(kumaUrl))
.GET()
.timeout(Duration.ofSeconds(10))
.build(),
HttpResponse.BodyHandlers.ofString());
if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
log.info("[Kuma] 心跳推送成功:{}", kumaUrl); log.info("[Kuma] 心跳推送成功:{}", kumaUrl);
} else { } else {
log.warn("[Kuma] 心跳推送失败:HTTP {}", resp.code()); log.warn("[Kuma] 心跳推送失败:HTTP {}", resp.statusCode());
} }
resp.close();
} catch (Exception e) { } catch (Exception e) {
log.warn("[Kuma] 心跳推送异常:{}", e.getMessage()); log.warn("[Kuma] 心跳推送异常:{}", e.getMessage());
} }
@@ -0,0 +1,22 @@
package com.hospital.front.vo;
import lombok.Data;
/**
* 体检结论明细 VOgetConclusionAll 强类型映射,内部使用)。
*
* 一个 peId 按科室返回多条结论(汇总拼接用),故为列表元素。
*/
@Data
public class ConclusionVo {
/** 体检编号 */
private String peId;
/** 就诊编号 */
private String peVisitId;
/** 科室编码 */
private String peDeptCode;
/** 科室名称 */
private String peDeptName;
/** 结论文本(已去换行) */
private String conclusionText;
}
@@ -0,0 +1,25 @@
package com.hospital.front.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 当天体检人员列表 VOgetExamListByDate 对外强类型出参)。
*
* 三院 SQL 出参统一(列别名即驼峰字段),日期已在 SQL 层转为字符串。
* 字段名与 snack3 默认序列化输出 key 一致,可直接返回 HTTP 调用方。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ExamListVo {
/** 体检编号 */
private String peId;
/** 身份证号 */
private String idNo;
/** 姓名 */
private String name;
/** 审查日期 yyyy-MM-dd */
private String auditDate;
}
@@ -0,0 +1,22 @@
package com.hospital.front.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 体检状态简版 VOgetPhyexamStatus 对外强类型出参)。
*
* 字段名与 snack3 默认序列化输出 key 一致(peId / peStatus),
* 可直接返回给 HTTP 调用方,无需手工 Map 包装。
* peStatus 已由 OrderService 加工为字典数字(与原 Map 契约一致)。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PeStatusPhyVo {
/** 体检编号(null 时输出空字符串,保持与原 Map 契约一致) */
private String peId;
/** 体检状态字典数字(原始状态名查不到时输出空字符串) */
private String peStatus;
}
@@ -0,0 +1,29 @@
package com.hospital.front.vo;
import lombok.Data;
/**
* 体检状态 VOgetPhyexamStatus / getPeStatusByIdnoAndYear 强类型映射,内部中间态)。
*
* 对外出参由 OrderService 加工(key 小写 peId/peStatus + 状态字典转换),
* 本 VO 仅作 adapter → service 的内部传输,不影响 HTTP 契约。
*/
@Data
public class PeStatusVo {
/** 体检编号 */
private String peId;
/** 姓名 */
private String name;
/** 身份证号 */
private String idNo;
/** 单位名称 */
private String unitName;
/** 部门 */
private String department;
/** 预约日期 */
private String pePreDate;
/** 排队日期 */
private String peQueueDate;
/** 状态名(报到/检中/初审/终审/预约/转向) */
private String peStatus;
}
@@ -0,0 +1,18 @@
package com.hospital.front.vo;
import lombok.Data;
/**
* 体检建议 VOgetSuggestAll 强类型映射,内部使用)。
*
* 按 peId 查询仅一条建议记录,adapter 层取首条返回单对象。
*/
@Data
public class SuggestVo {
/** 体检编号 */
private String peId;
/** 建议项(科室/类别描述) */
private String recContent;
/** 建议内容(已去换行) */
private String guideContent;
}
+22 -3
View File
@@ -11,6 +11,13 @@ solon:
app: app:
name: "hospital-front" name: "hospital-front"
group: "hospital" group: "hospital"
# JSON 序列化对齐原 hospitalmiddleJackson 默认保留 null 字段 + 日期格式)
serialization:
json:
nullAsWriteable: true
# 对齐原 spring.jackson.date-format / time-zoneDate/Timestamp 序列化为字符串而非毫秒时间戳
dateAsFormat: "yyyy-MM-dd HH:mm:ss"
dateAsTimeZone: "GMT+8"
# 应用配置 # 应用配置
# 医院注册清单由代码决定(各 Adapter 的 @Component),此处仅指定激活哪家 # 医院注册清单由代码决定(各 Adapter 的 @Component),此处仅指定激活哪家
@@ -40,6 +47,20 @@ order:
admin: admin:
token: "" token: ""
# IP 白名单(应对端口暴露公网被扫描器探测;环回地址 Agent 永远放行)
# 开启后非白名单 IP 一律 403。
# ipWhitelist 三种写法可混用,逗号分隔:
# 精确 IP10.0.0.5
# 通配段:112.113.*.*(每段可单独 *
# CIDR 网段:10.0.0.0/24
security:
ipWhitelistEnabled: false
ipWhitelist: ""
# ipWhitelist: "112.113.*.*,10.20.30.40,10.0.0.0/24"
# nginx 反代部署时开启:从 X-Forwarded-For 最右侧取真实客户端 IP 判定
# (对端必须是本机环回的 nginx,防止客户端伪造 XFF 绕过白名单)
# trustProxy: false
# ==================================== # ====================================
# MyBatis-Plus 公共配置(db1 为唯一数据源,随医院配置注入) # MyBatis-Plus 公共配置(db1 为唯一数据源,随医院配置注入)
# mappers 条目两种形式(Solon mybatis 约定): # mappers 条目两种形式(Solon mybatis 约定):
@@ -52,10 +73,8 @@ mybatis.db1:
mappers: mappers:
- "com.hospital.front.mapper.bsm" - "com.hospital.front.mapper.bsm"
- "com.hospital.front.mapper.nx" - "com.hospital.front.mapper.nx"
- "com.hospital.front.mapper.ya"
- "com/hospital/front/mapper/bsm/BsmOrderMapper.xml" - "com/hospital/front/mapper/bsm/BsmOrderMapper.xml"
- "com/hospital/front/mapper/nx/NxOrderMapper.xml" - "com/hospital/front/mapper/nx/NxOrderMapper.xml"
- "com/hospital/front/mapper/ya/YaOrderMapper.xml"
configuration: configuration:
mapUnderscoreToCamelCase: true mapUnderscoreToCamelCase: true
logImpl: "org.apache.ibatis.logging.slf4j.Slf4jImpl" logImpl: "org.apache.ibatis.logging.slf4j.Slf4jImpl"
@@ -69,7 +88,7 @@ logging:
level: level:
root: INFO root: INFO
com.hospital.front: INFO com.hospital.front: INFO
org.noear: WARN org.noear: INFO
# ==================================== # ====================================
# Uptime-Kuma 监控(沿用原 hospitalmiddle 逻辑) # Uptime-Kuma 监控(沿用原 hospitalmiddle 逻辑)
@@ -7,102 +7,113 @@
SELECT 1 FROM DUAL SELECT 1 FROM DUAL
</select> </select>
<!-- ==================== 存储过程:预约/取消/单位 ==================== -->
<parameterMap id="pm" type="java.util.Map"> <parameterMap id="pm" type="java.util.Map">
<parameter property="peInfoStr" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="peInfoStr" jdbcType="VARCHAR" mode="IN"/>
<parameter property="peItemStr" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="peItemStr" jdbcType="VARCHAR" mode="IN"/>
<parameter property="resultCode" jdbcType="VARCHAR" mode="OUT"></parameter> <parameter property="resultCode" jdbcType="VARCHAR" mode="OUT"/>
<parameter property="errorMsg" jdbcType="VARCHAR" mode="OUT"></parameter> <parameter property="errorMsg" jdbcType="VARCHAR" mode="OUT"/>
<parameter property="peId" jdbcType="VARCHAR" mode="OUT"></parameter> <parameter property="peId" jdbcType="VARCHAR" mode="OUT"/>
</parameterMap> </parameterMap>
<insert id="saveOrder" parameterMap="pm" statementType="CALLABLE"> <insert id="saveOrder" parameterMap="pm" statementType="CALLABLE">
{call P_ADD_PE_REGIST( {call P_ADD_PE_REGIST(
#{peInfoStr,mode=IN,jdbcType=VARCHAR}, #{peInfoStr,mode=IN,jdbcType=VARCHAR},
#{peItemStr,mode=IN,jdbcType=VARCHAR}, #{peItemStr,mode=IN,jdbcType=VARCHAR},
#{resultCode,mode=OUT,jdbcType=VARCHAR}, #{resultCode,mode=OUT,jdbcType=VARCHAR},
#{errorMsg,mode=OUT,jdbcType=VARCHAR}, #{errorMsg,mode=OUT,jdbcType=VARCHAR},
#{peId,mode=OUT,jdbcType=VARCHAR} #{peId,mode=OUT,jdbcType=VARCHAR}
)} )}
</insert> </insert>
<parameterMap id="cancenlPm" type="java.util.Map"> <parameterMap id="cancenlPm" type="java.util.Map">
<parameter property="peId" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="peId" jdbcType="VARCHAR" mode="IN"/>
<parameter property="name" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="name" jdbcType="VARCHAR" mode="IN"/>
<parameter property="idcard" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="idcard" jdbcType="VARCHAR" mode="IN"/>
<parameter property="resultCode" jdbcType="VARCHAR" mode="OUT"></parameter> <parameter property="resultCode" jdbcType="VARCHAR" mode="OUT"/>
<parameter property="errorMsg" jdbcType="VARCHAR" mode="OUT"></parameter> <parameter property="errorMsg" jdbcType="VARCHAR" mode="OUT"/>
</parameterMap> </parameterMap>
<insert id="cancelOrder" parameterMap="cancenlPm" statementType="CALLABLE"> <insert id="cancelOrder" parameterMap="cancenlPm" statementType="CALLABLE">
{call P_DEL_PE_REGIST( {call P_DEL_PE_REGIST(
#{peId,mode=IN,jdbcType=VARCHAR}, #{peId,mode=IN,jdbcType=VARCHAR},
#{name,mode=IN,jdbcType=VARCHAR}, #{name,mode=IN,jdbcType=VARCHAR},
#{idcard,mode=IN,jdbcType=VARCHAR}, #{idcard,mode=IN,jdbcType=VARCHAR},
#{resultCode,mode=OUT,jdbcType=VARCHAR}, #{resultCode,mode=OUT,jdbcType=VARCHAR},
#{errorMsg,mode=OUT,jdbcType=VARCHAR} #{errorMsg,mode=OUT,jdbcType=VARCHAR}
)} )}
</insert> </insert>
<parameterMap id="unitPm" type="java.util.Map"> <parameterMap id="unitPm" type="java.util.Map">
<parameter property="unitCode" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="unitCode" jdbcType="VARCHAR" mode="IN"/>
<parameter property="unitName" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="unitName" jdbcType="VARCHAR" mode="IN"/>
<parameter property="resultCode" jdbcType="VARCHAR" mode="OUT"></parameter> <parameter property="resultCode" jdbcType="VARCHAR" mode="OUT"/>
<parameter property="errorMsg" jdbcType="VARCHAR" mode="OUT"></parameter> <parameter property="errorMsg" jdbcType="VARCHAR" mode="OUT"/>
</parameterMap> </parameterMap>
<insert id="addUnit" parameterMap="unitPm" statementType="CALLABLE"> <insert id="addUnit" parameterMap="unitPm" statementType="CALLABLE">
{call P_ADD_UNIT( {call P_ADD_UNIT(
#{unitCode,mode=IN,jdbcType=VARCHAR}, #{unitCode,mode=IN,jdbcType=VARCHAR},
#{unitName,mode=IN,jdbcType=VARCHAR}, #{unitName,mode=IN,jdbcType=VARCHAR},
#{resultCode,mode=OUT,jdbcType=VARCHAR}, #{resultCode,mode=OUT,jdbcType=VARCHAR},
#{errorMsg,mode=OUT,jdbcType=VARCHAR} #{errorMsg,mode=OUT,jdbcType=VARCHAR}
)} )}
</insert> </insert>
<parameterMap id="unitPmV2" type="java.util.Map"> <parameterMap id="unitPmV2" type="java.util.Map">
<parameter property="unitCode" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="unitCode" jdbcType="VARCHAR" mode="IN"/>
<parameter property="unitName" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="unitName" jdbcType="VARCHAR" mode="IN"/>
<parameter property="parentUnitCode" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="parentUnitCode" jdbcType="VARCHAR" mode="IN"/>
<parameter property="address" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="address" jdbcType="VARCHAR" mode="IN"/>
<parameter property="connecter1" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="connecter1" jdbcType="VARCHAR" mode="IN"/>
<parameter property="phone1" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="phone1" jdbcType="VARCHAR" mode="IN"/>
<parameter property="connecter2" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="connecter2" jdbcType="VARCHAR" mode="IN"/>
<parameter property="phone2" jdbcType="VARCHAR" mode="IN"></parameter> <parameter property="phone2" jdbcType="VARCHAR" mode="IN"/>
<parameter property="resultCode" jdbcType="VARCHAR" mode="OUT"></parameter> <parameter property="resultCode" jdbcType="VARCHAR" mode="OUT"/>
<parameter property="errorMsg" jdbcType="VARCHAR" mode="OUT"></parameter> <parameter property="errorMsg" jdbcType="VARCHAR" mode="OUT"/>
</parameterMap> </parameterMap>
<insert id="addUnitV2" parameterMap="unitPmV2" statementType="CALLABLE"> <insert id="addUnitV2" parameterMap="unitPmV2" statementType="CALLABLE">
{call P_ADD_UNIT( {call P_ADD_UNIT(
#{unitCode,mode=IN,jdbcType=VARCHAR}, #{unitCode,mode=IN,jdbcType=VARCHAR},
#{unitName,mode=IN,jdbcType=VARCHAR}, #{unitName,mode=IN,jdbcType=VARCHAR},
#{parentUnitCode,mode=IN,jdbcType=VARCHAR}, #{parentUnitCode,mode=IN,jdbcType=VARCHAR},
#{address,mode=IN,jdbcType=VARCHAR}, #{address,mode=IN,jdbcType=VARCHAR},
#{connecter1,mode=IN,jdbcType=VARCHAR}, #{connecter1,mode=IN,jdbcType=VARCHAR},
#{phone1,mode=IN,jdbcType=VARCHAR}, #{phone1,mode=IN,jdbcType=VARCHAR},
#{connecter2,mode=IN,jdbcType=VARCHAR}, #{connecter2,mode=IN,jdbcType=VARCHAR},
#{phone2,mode=IN,jdbcType=VARCHAR}, #{phone2,mode=IN,jdbcType=VARCHAR},
#{resultCode,mode=OUT,jdbcType=VARCHAR}, #{resultCode,mode=OUT,jdbcType=VARCHAR},
#{errorMsg,mode=OUT,jdbcType=VARCHAR} #{errorMsg,mode=OUT,jdbcType=VARCHAR}
)} )}
</insert> </insert>
<select id="getPhyexamStatus" resultType="java.util.Map"> <!-- ==================== 体检状态 ==================== -->
<select id="getPhyexamStatus" resultType="com.hospital.front.vo.PeStatusVo">
select PE_ID, NAME, ID_NO, UNIT_NAME, DEPARTMENT, PE_PRE_DATE, PE_QUEUE_DATE, PE_STATUS select PE_ID, NAME, ID_NO, UNIT_NAME, DEPARTMENT, PE_PRE_DATE, PE_QUEUE_DATE, PE_STATUS
from V_PHYEXAM_PE_STATUS from V_PHYEXAM_PE_STATUS
where instr(',' || #{peIds} || ',', ',' || pe_id || ',') > 0 where instr(',' || #{peIds} || ',', ',' || pe_id || ',') > 0
</select> </select>
<select id="getItemList" resultType="java.util.Map"> <select id="getPeStatusByIdnoAndYear" resultType="com.hospital.front.vo.PeStatusVo">
select distinct item_assem_code, item_assem_name select * from V_PHYEXAM_PE_STATUS
from V_PHYEXAM_ITEM_ASSEMBLED where ID_NO = #{idNo}
and to_char(PE_PRE_DATE, 'yyyy') = #{year}
</select> </select>
<!-- ==================== 体检项目 ==================== -->
<select id="getItemList" resultType="java.util.Map">
SELECT DISTINCT ITEM_ASSEM_CODE, ITEM_ASSEM_NAME
FROM V_PHYEXAM_ITEM_ASSEMBLED
</select>
<select id="getAllItemList" resultType="java.util.Map"> <select id="getAllItemList" resultType="java.util.Map">
select ITEM_ASSEM_CODE, ITEM_ASSEM_NAME, PE_ITEM_CODE, PE_ITEM_NAME SELECT ITEM_ASSEM_CODE, ITEM_ASSEM_NAME, PE_ITEM_CODE, PE_ITEM_NAME
from V_PHYEXAM_ITEM_ASSEMBLED FROM V_PHYEXAM_ITEM_ASSEMBLED
</select> </select>
<select id="getPeItemList" resultType="java.util.Map"> <select id="getPeItemList" resultType="java.util.Map">
select * SELECT PE_ITEM_CODE, PE_ITEM_NAME
from V_PHYEXAM_ITEM FROM V_PHYEXAM_ITEM
</select> </select>
<select id="getEmpItem" resultType="java.util.Map"> <select id="getEmpItem" resultType="java.util.Map">
@@ -112,310 +123,217 @@
</select> </select>
<select id="getItemInfo" resultType="java.util.Map"> <select id="getItemInfo" resultType="java.util.Map">
select ITEM_ASSEM_CODE, ITEM_ASSEM_NAME, PE_ITEM_CODE, PE_ITEM_NAME SELECT ITEM_ASSEM_CODE, ITEM_ASSEM_NAME, PE_ITEM_CODE, PE_ITEM_NAME
from V_PHYEXAM_ITEM_ASSEMBLED FROM V_PHYEXAM_ITEM_ASSEMBLED
where PE_ITEM_CODE = #{peItemCode} WHERE PE_ITEM_CODE = #{peItemCode}
</select> </select>
<!-- ==================== 体检结果 ==================== -->
<select id="getResult" resultType="java.util.Map"> <select id="getResult" resultType="java.util.Map">
select PE_ID, SELECT PE_ID,
PE_VISIT_ID, PE_VISIT_ID,
ID_NO, ID_NO,
NAME, NAME,
UNIT_CODE, UNIT_CODE,
UNIT_NAME, UNIT_NAME,
TO_CHAR(PE_QUEUE_DATE, 'yyyy-mm-dd HH24:mi:ss') PE_QUEUE_DATE, TO_CHAR(PE_QUEUE_DATE, 'yyyy-mm-dd HH24:mi:ss') PE_QUEUE_DATE,
TO_CHAR(AUDIT_DATE, 'yyyy-mm-dd HH24:mi:ss') AUDIT_DATE, TO_CHAR(AUDIT_DATE, 'yyyy-mm-dd HH24:mi:ss') AUDIT_DATE,
PE_DEPT_CODE, PE_DEPT_CODE,
PE_DEPT_NAME, PE_DEPT_NAME,
ITEM_ASSEM_CODE, ITEM_ASSEM_CODE,
ITEM_ASSEM_NAME, ITEM_ASSEM_NAME,
PE_ITEM_CODE, PE_ITEM_CODE,
PE_ITEM_NAME, PE_ITEM_NAME,
PE_RESULT, PE_RESULT,
UNIT, UNIT,
PRINT_CONTEXT PRINT_CONTEXT
from V_PHYEXAM_RESULT FROM (
where PE_ID = #{peId} SELECT b.pe_id, b.pe_visit_id, a.id_no, a.name, b.unit_code,
(SELECT t.unit_name FROM pe_unit_dict t WHERE t.unit_code = b.unit_code) unit_name,
b.pe_queue_date, b.audit_date,
c.pe_dept_code, c.pe_dept_name, c.item_assem_code, c.item_assem_name,
c.pe_item_code, c.pe_item_name, c.pe_result, c.unit, c.print_context
FROM pe_master_index a, pe_visit b, pe_result_dict c
WHERE a.pe_id = b.pe_id
AND b.pe_id = c.pe_id
AND b.pe_visit_id = c.pe_visit_id
)
WHERE PE_ID = #{peId}
</select> </select>
<select id="getConclusion" resultType="java.util.Map"> <!-- ==================== 体检结论 / 建议 ==================== -->
select PE_ID,
PE_VISIT_ID,
PE_DEPT_CODE,
PE_DEPT_NAME,
REPLACE(REPLACE(REPLACE(DBMS_LOB.SUBSTR(CONCLUSION_TEXT, 4000, 1), chr(13) || chr(10), ''), chr(13), ''), chr(10), '') CONCLUSION_TEXT
from V_PHYEXAM_DEPT_RESULT
where PE_ID = #{peId}
and CONCLUSION_TEXT is not null
</select>
<select id="getConclusionAll" resultType="java.util.Map">
select PE_ID,
PE_VISIT_ID,
PE_DEPT_CODE,
PE_DEPT_NAME,
REPLACE(REPLACE(REPLACE(DBMS_LOB.SUBSTR(CONCLUSION_TEXT, 4000, 1), chr(13) || chr(10), ''), chr(13), ''), chr(10), '') CONCLUSION_TEXT
from (select a.pe_id,
a.pe_visit_id,
a.pe_dept_code,
a.pe_dept_name,
a.conclusion_text
from pe_dept_result_dict a,pe_visit b
where a.pe_id = b.pe_id
and a.pe_visit_id = b.pe_visit_id) a
where PE_ID = #{peId}
and CONCLUSION_TEXT is not null
</select>
<!-- getTotalConclusion/getTotalSuggest 已删除:原实现依赖 wm_concatOracle 18c 起移除,
且接口未引用);汇总逻辑由 OrderService 基于本文件列表查询在应用层完成 -->
<select id="getSuggest" resultType="java.util.Map">
select PE_ID, PE_VISIT_ID, REC_CONTENT, REPLACE(REPLACE(REPLACE(DBMS_LOB.SUBSTR(GUIDE_CONTENT, 4000, 1), chr(13) || chr(10), ''), chr(13), ''), chr(10), '') GUIDE_CONTENT <select id="getConclusionAll" resultType="com.hospital.front.vo.ConclusionVo">
from V_PHYEXAM_DIAGNOSIS SELECT PE_ID,
where PE_ID = #{peId} PE_VISIT_ID,
and GUIDE_CONTENT is not null PE_DEPT_CODE,
PE_DEPT_NAME,
REPLACE(REPLACE(REPLACE(DBMS_LOB.SUBSTR(CONCLUSION_TEXT, 4000, 1), chr(13) || chr(10), ''), chr(13), ''), chr(10), '') CONCLUSION_TEXT
FROM (
SELECT a.pe_id, a.pe_visit_id, a.pe_dept_code, a.pe_dept_name, a.conclusion_text
FROM pe_dept_result_dict a, pe_visit b
WHERE a.pe_id = b.pe_id
AND a.pe_visit_id = b.pe_visit_id
)
WHERE PE_ID = #{peId}
AND CONCLUSION_TEXT IS NOT NULL
</select> </select>
<select id="getSuggestAll" resultType="java.util.HashMap">
select PE_ID, PE_VISIT_ID, REC_CONTENT, REPLACE(REPLACE(REPLACE(DBMS_LOB.SUBSTR(GUIDE_CONTENT, 4000, 1), chr(13) || chr(10), ''), chr(13), ''), chr(10), '') GUIDE_CONTENT <select id="getSuggestAll" resultType="com.hospital.front.vo.SuggestVo">
from (select a.pe_id, SELECT PE_ID, PE_VISIT_ID, REC_CONTENT,
a.pe_visit_id, REPLACE(REPLACE(REPLACE(DBMS_LOB.SUBSTR(GUIDE_CONTENT, 4000, 1), chr(13) || chr(10), ''), chr(13), ''), chr(10), '') GUIDE_CONTENT
a.rec_content, FROM (
a.guide_content SELECT a.pe_id, a.pe_visit_id, a.rec_content, a.guide_content
from pe_diagnosis_record a,pe_visit b FROM pe_diagnosis_record a, pe_visit b
where a.pe_id = b.pe_id WHERE a.pe_id = b.pe_id
and a.pe_visit_id = b.pe_visit_id) a AND a.pe_visit_id = b.pe_visit_id
where PE_ID = #{peId} )
and GUIDE_CONTENT is not null WHERE PE_ID = #{peId}
</select> AND GUIDE_CONTENT IS NOT NULL
<select id="getPeStatusByIdnoAndYear" resultType="java.util.HashMap">
select *
from V_PHYEXAM_PE_STATUS
where ID_NO = #{idNo}
and PE_PRE_DATE &gt;= TO_DATE(#{year} || '-01-01', 'YYYY-MM-DD')
and PE_PRE_DATE &lt; ADD_MONTHS(TO_DATE(#{year} || '-01-01', 'YYYY-MM-DD'), 12)
</select> </select>
<!-- ==================== 按身份证 / 日期 / 编号查询结果 ==================== -->
<select id="getResultByIdcardAndYear" resultType="com.hospital.front.vo.MedicalMiddleResultVo"> <select id="getResultByIdcardAndYear" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
select DISTINCT SELECT DISTINCT
ID_NO, ID_NO, NAME, PE_ID, PE_VISIT_ID, UNIT_CODE, UNIT_NAME,
NAME, to_char(PE_QUEUE_DATE, 'yyyy-mm-dd HH24:mi:ss') PE_QUEUE_DATE,
PE_ID, to_char(AUDIT_DATE, 'yyyy-mm-dd HH24:mi:ss') AUDIT_DATE
PE_VISIT_ID, FROM (
UNIT_CODE, SELECT b.pe_id, b.pe_visit_id, a.id_no, a.name, b.unit_code,
UNIT_NAME, (SELECT t.unit_name FROM pe_unit_dict t WHERE t.unit_code = b.unit_code) unit_name,
to_char( PE_QUEUE_DATE, 'yyyy-mm-dd HH24:mi:ss' ) PE_QUEUE_DATE, b.pe_queue_date, b.audit_date,
to_char( AUDIT_DATE, 'yyyy-mm-dd HH24:mi:ss' ) AUDIT_DATE c.pe_dept_code, c.pe_dept_name, c.item_assem_code, c.item_assem_name,
from V_PHYEXAM_RESULT c.pe_item_code, c.pe_item_name, c.pe_result, c.unit, c.print_context
where ID_NO = #{idNo} FROM pe_master_index a, pe_visit b, pe_result_dict c
and PE_QUEUE_DATE &gt;= TO_DATE(#{year} || '-01-01', 'YYYY-MM-DD') WHERE a.pe_id = b.pe_id
and PE_QUEUE_DATE &lt; ADD_MONTHS(TO_DATE(#{year} || '-01-01', 'YYYY-MM-DD'), 12) AND b.pe_id = c.pe_id
AND b.pe_visit_id = c.pe_visit_id
)
WHERE ID_NO = #{idNo}
AND PE_QUEUE_DATE &gt;= TO_DATE(#{year} || '-01-01', 'YYYY-MM-DD')
AND PE_QUEUE_DATE &lt; ADD_MONTHS(TO_DATE(#{year} || '-01-01', 'YYYY-MM-DD'), 12)
</select> </select>
<select id="getResultByIdcardAndYearAll" resultType="com.hospital.front.vo.MedicalMiddleResultVo"> <select id="getResultByIdcardAndYearAll" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
SELECT DISTINCT SELECT DISTINCT
ID_NO, ID_NO, NAME, PE_ID, PE_VISIT_ID, UNIT_CODE, UNIT_NAME,
NAME, to_char(PE_QUEUE_DATE, 'yyyy-mm-dd HH24:mi:ss') PE_QUEUE_DATE,
PE_ID, to_char(AUDIT_DATE, 'yyyy-mm-dd HH24:mi:ss') AUDIT_DATE
PE_VISIT_ID, FROM (
UNIT_CODE, SELECT b.pe_id, b.pe_visit_id, a.id_no, a.NAME, b.unit_code, t.unit_name,
UNIT_NAME, b.pe_queue_date, b.audit_date,
to_char ( PE_QUEUE_DATE, 'yyyy-mm-dd HH24:mi:ss' ) PE_QUEUE_DATE, c.pe_dept_code, c.pe_dept_name, c.item_assem_code, c.item_assem_name,
to_char ( AUDIT_DATE, 'yyyy-mm-dd HH24:mi:ss' ) AUDIT_DATE c.pe_item_code, c.pe_item_name, c.pe_result, c.unit, c.print_context
FROM FROM pe_master_index a
( JOIN pe_visit b ON b.pe_id = a.pe_id
SELECT JOIN pe_result_dict c ON c.pe_id = b.pe_id AND c.pe_visit_id = b.pe_visit_id
b.pe_id, LEFT JOIN pe_unit_dict t ON t.unit_code = b.unit_code
b.pe_visit_id, WHERE b.audit_date &gt;= TO_DATE(#{year} || '-01-01', 'YYYY-MM-DD')
a.id_no, AND b.audit_date &lt; ADD_MONTHS(TO_DATE(#{year} || '-01-01', 'YYYY-MM-DD'), 12)
a.NAME, AND a.id_no = #{idNo}
b.unit_code, )
t.unit_name,
b.pe_queue_date,
b.audit_date,
c.pe_dept_code,
c.pe_dept_name,
c.item_assem_code,
c.item_assem_name,
c.pe_item_code,
c.pe_item_name,
c.pe_result,
c.unit,
c.print_context
FROM
pe_master_index a
JOIN pe_visit b ON b.pe_id = a.pe_id
JOIN pe_result_dict c ON c.pe_id = b.pe_id AND c.pe_visit_id = b.pe_visit_id
LEFT JOIN pe_unit_dict t ON t.unit_code = b.unit_code
WHERE
b.audit_date &gt;= TO_DATE(#{year} || '-01-01', 'YYYY-MM-DD')
and b.audit_date &lt; ADD_MONTHS(TO_DATE(#{year} || '-01-01', 'YYYY-MM-DD'), 12)
and a.id_no = #{idNo}
) a
</select>
<select id="getResultByAuditDate" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
select *
from V_PHYEXAM_RESULT
where to_char(AUDIT_DATE,'yyyy-mm-dd')=#{auditDate}
</select>
<select id="getDisResultByAuditDate" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
SELECT DISTINCT
ID_NO,
NAME,
PE_ID,
PE_VISIT_ID,
UNIT_CODE,
UNIT_NAME,
to_char( PE_QUEUE_DATE, 'yyyy-mm-dd HH24:mi:ss' ) PE_QUEUE_DATE,
to_char( AUDIT_DATE, 'yyyy-mm-dd HH24:mi:ss' ) AUDIT_DATE
FROM
V_PHYEXAM_RESULT
WHERE
AUDIT_DATE &gt;= TO_DATE(#{auditDate}, 'YYYY-MM-DD')
and AUDIT_DATE &lt; TO_DATE(#{auditDate}, 'YYYY-MM-DD') + 1
</select> </select>
<select id="getItemByPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo"> <select id="getItemByPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
SELECT SELECT ITEM_ASSEM_CODE, ITEM_ASSEM_NAME, PE_ITEM_CODE, PE_ITEM_NAME, PE_RESULT, UNIT, PRINT_CONTEXT
ITEM_ASSEM_CODE, FROM (
ITEM_ASSEM_NAME, SELECT b.pe_id, b.pe_visit_id, a.id_no, a.NAME, b.unit_code, t.unit_name,
PE_ITEM_CODE, b.pe_queue_date, b.audit_date,
PE_ITEM_NAME, c.pe_dept_code, c.pe_dept_name, c.item_assem_code, c.item_assem_name,
PE_RESULT, c.pe_item_code, c.pe_item_name, c.pe_result, c.unit, c.print_context
UNIT, FROM pe_master_index a
PRINT_CONTEXT JOIN pe_visit b ON b.pe_id = a.pe_id
FROM JOIN pe_result_dict c ON c.pe_id = b.pe_id AND c.pe_visit_id = b.pe_visit_id
( LEFT JOIN pe_unit_dict t ON t.unit_code = b.unit_code
SELECT WHERE b.pe_id = #{peId}
b.pe_id, )
b.pe_visit_id,
a.id_no,
a.NAME,
b.unit_code,
t.unit_name,
b.pe_queue_date,
b.audit_date,
c.pe_dept_code,
c.pe_dept_name,
c.item_assem_code,
c.item_assem_name,
c.pe_item_code,
c.pe_item_name,
c.pe_result,
c.unit,
c.print_context
FROM
pe_master_index a
JOIN pe_visit b ON b.pe_id = a.pe_id
JOIN pe_result_dict c ON c.pe_id = b.pe_id AND c.pe_visit_id = b.pe_visit_id
LEFT JOIN pe_unit_dict t ON t.unit_code = b.unit_code
WHERE
b.pe_id = #{peId}
) a
</select> </select>
<select id="getItemByPeIdAll" resultType="com.hospital.front.vo.MedicalMiddleResultVo"> <select id="getItemByPeIdAll" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
SELECT SELECT ITEM_ASSEM_CODE, ITEM_ASSEM_NAME, PE_ITEM_CODE, PE_ITEM_NAME, PE_RESULT, UNIT, PRINT_CONTEXT
ITEM_ASSEM_CODE, FROM (
ITEM_ASSEM_NAME, SELECT b.pe_id, b.pe_visit_id, a.id_no, a.NAME, b.unit_code, t.unit_name,
PE_ITEM_CODE, b.pe_queue_date, b.audit_date,
PE_ITEM_NAME, c.pe_dept_code, c.pe_dept_name, c.item_assem_code, c.item_assem_name,
PE_RESULT, c.pe_item_code, c.pe_item_name, c.pe_result, c.unit, c.print_context
UNIT, FROM pe_master_index a
PRINT_CONTEXT JOIN pe_visit b ON b.pe_id = a.pe_id
FROM JOIN pe_result_dict c ON c.pe_id = b.pe_id AND c.pe_visit_id = b.pe_visit_id
( LEFT JOIN pe_unit_dict t ON t.unit_code = b.unit_code
SELECT WHERE b.pe_id = #{peId}
b.pe_id, )
b.pe_visit_id,
a.id_no,
a.NAME,
b.unit_code,
t.unit_name,
b.pe_queue_date,
b.audit_date,
c.pe_dept_code,
c.pe_dept_name,
c.item_assem_code,
c.item_assem_name,
c.pe_item_code,
c.pe_item_name,
c.pe_result,
c.unit,
c.print_context
FROM
pe_master_index a
JOIN pe_visit b ON b.pe_id = a.pe_id
JOIN pe_result_dict c ON c.pe_id = b.pe_id AND c.pe_visit_id = b.pe_visit_id
LEFT JOIN pe_unit_dict t ON t.unit_code = b.unit_code
WHERE
b.pe_id = #{peId}
) a
</select> </select>
<select id="getResultByIdNoOrPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo"> <select id="getResultByPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
select DISTINCT SELECT DISTINCT
ID_NO, ID_NO, NAME, PE_ID, PE_VISIT_ID, UNIT_CODE, UNIT_NAME,
NAME, to_char(PE_QUEUE_DATE, 'yyyy-mm-dd HH24:mi:ss') PE_QUEUE_DATE,
PE_ID, to_char(AUDIT_DATE, 'yyyy-mm-dd HH24:mi:ss') AUDIT_DATE
PE_VISIT_ID, FROM (
UNIT_CODE, SELECT b.pe_id, b.pe_visit_id, a.id_no, a.name, b.unit_code,
UNIT_NAME, (SELECT t.unit_name FROM pe_unit_dict t WHERE t.unit_code = b.unit_code) unit_name,
to_char( PE_QUEUE_DATE, 'yyyy-mm-dd HH24:mi:ss' ) PE_QUEUE_DATE, b.pe_queue_date, b.audit_date,
to_char( AUDIT_DATE, 'yyyy-mm-dd HH24:mi:ss' ) AUDIT_DATE c.pe_dept_code, c.pe_dept_name, c.item_assem_code, c.item_assem_name,
from V_PHYEXAM_RESULT c.pe_item_code, c.pe_item_name, c.pe_result, c.unit, c.print_context
<where> FROM pe_master_index a, pe_visit b, pe_result_dict c
1=1 WHERE a.pe_id = b.pe_id
<if test="idNo != null and idNo != '' "> AND b.pe_id = c.pe_id
and ID_NO = #{idNo} AND b.pe_visit_id = c.pe_visit_id
</if> )
<if test="peId != null and peId != '' "> WHERE PE_ID = #{peId}
and PE_ID = #{peId}
</if>
and AUDIT_DATE &gt;= TO_DATE(#{auditYear} || '-01-01', 'YYYY-MM-DD')
and AUDIT_DATE &lt; ADD_MONTHS(TO_DATE(#{auditYear} || '-01-01', 'YYYY-MM-DD'), 12)
</where>
</select> </select>
<!-- ==================== 按身份证查 peId/预约日期 / 人员列表 ==================== -->
<select id="getPeIdAndDateByIdcard" resultType="java.util.Map"> <select id="getPeIdAndDateByIdcard" resultType="java.util.Map">
select PE_ID,to_char( PE_PRE_DATE, 'yyyy-mm-dd' ) PE_PRE_DATE from V_PHYEXAM_PE_STATUS SELECT PE_ID, to_char(PE_PRE_DATE, 'yyyy-mm-dd') PE_PRE_DATE
FROM (
SELECT b.pe_id, b.name, b.id_no,
(SELECT unit_name FROM pe_unit_dict t WHERE t.unit_code = a.unit_code) unit_name,
a.department, a.pe_pre_date, a.pe_queue_date,
DECODE(a.queue_flag, '1',
DECODE(result_status, '7', '初审', '9', '终审', '1', '检中', '报到'),
DECODE(pe_transfer_hospital, '', '预约', '转向' || pe_transfer_hospital)) pe_status
FROM pe_visit a, pe_master_index b
WHERE a.pe_id = b.pe_id
)
<where> <where>
<if test="idNo != null and idNo != '' "> <if test="idNo != null and idNo != ''">
and ID_NO = #{idNo} AND ID_NO = #{idNo}
</if> </if>
and PE_PRE_DATE &gt;= TRUNC(SYSDATE, 'YYYY') AND PE_PRE_DATE &gt;= TRUNC(SYSDATE, 'YYYY')
and PE_PRE_DATE &lt; ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), 12) AND PE_PRE_DATE &lt; ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), 12)
</where> </where>
order by PE_PRE_DATE desc ORDER BY PE_PRE_DATE DESC
</select>
<select id="getResultByPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
select DISTINCT
ID_NO,
NAME,
PE_ID,
PE_VISIT_ID,
UNIT_CODE,
UNIT_NAME,
to_char( PE_QUEUE_DATE, 'yyyy-mm-dd HH24:mi:ss' ) PE_QUEUE_DATE,
to_char( AUDIT_DATE, 'yyyy-mm-dd HH24:mi:ss' ) AUDIT_DATE
from V_PHYEXAM_RESULT
where PE_ID = #{peId}
</select> </select>
<select id="getPeIdAndDateByIdcardList" resultType="java.util.Map"> <select id="getPeIdAndDateByIdcardList" resultType="java.util.Map">
select PE_ID,to_char( PE_PRE_DATE, 'yyyy-mm-dd' ) PE_PRE_DATE from V_PHYEXAM_PE_STATUS SELECT PE_ID, to_char(PE_PRE_DATE, 'yyyy-mm-dd') PE_PRE_DATE
FROM (
SELECT b.pe_id, b.name, b.id_no,
(SELECT unit_name FROM pe_unit_dict t WHERE t.unit_code = a.unit_code) unit_name,
a.department, a.pe_pre_date, a.pe_queue_date,
DECODE(a.queue_flag, '1',
DECODE(result_status, '7', '初审', '9', '终审', '1', '检中', '报到'),
DECODE(pe_transfer_hospital, '', '预约', '转向' || pe_transfer_hospital)) pe_status
FROM pe_visit a, pe_master_index b
WHERE a.pe_id = b.pe_id
)
<where> <where>
<if test="idNo != null and idNo != '' "> <if test="idNo != null and idNo != ''">
and ID_NO = #{idNo} AND ID_NO = #{idNo}
</if> </if>
and PE_PRE_DATE &gt;= TRUNC(SYSDATE, 'YYYY') AND PE_PRE_DATE &gt;= TRUNC(SYSDATE, 'YYYY')
and PE_PRE_DATE &lt; ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), 12) AND PE_PRE_DATE &lt; ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), 12)
</where> </where>
order by PE_PRE_DATE desc ORDER BY PE_PRE_DATE DESC
</select> </select>
<!-- 根据audit_date查询当天体检人员列表(去重) --> <!-- 根据audit_date查询当天体检人员列表(去重) -->
<select id="getExamListByDate" resultType="java.util.Map"> <select id="getExamListByDate" resultType="com.hospital.front.vo.ExamListVo">
SELECT DISTINCT PE_ID AS "peId", SELECT DISTINCT PE_ID AS "peId",
ID_NO AS "idNo", ID_NO AS "idNo",
NAME AS "name", NAME AS "name",
@@ -434,4 +352,5 @@
</choose> </choose>
</where> </where>
</select> </select>
</mapper> </mapper>
@@ -69,7 +69,7 @@
)} )}
</insert> </insert>
<select id="getPhyexamStatus" resultType="java.util.Map"> <select id="getPhyexamStatus" resultType="com.hospital.front.vo.PeStatusVo">
SELECT SELECT
PE_ID, PE_ID,
NAME, NAME,
@@ -123,6 +123,9 @@
DEPARTMENT, DEPARTMENT,
ITEM_ASSEM_CODE, ITEM_ASSEM_CODE,
ITEM_ASSEM_NAME, ITEM_ASSEM_NAME,
PE_DEPT_CODE,
PE_DEPT_NAME,
CONVERT ( VARCHAR ( 100 ), REGISTER_DATE, 20 ) REGISTER_DATE,
(case when Finished_sign = '1' then N'完成' when Finished_sign != '1' then N'未完成' end) FINISHED_SIGN, (case when Finished_sign = '1' then N'完成' when Finished_sign != '1' then N'未完成' end) FINISHED_SIGN,
CONVERT ( VARCHAR ( 100 ), PE_DATE, 20 ) PE_DATE CONVERT ( VARCHAR ( 100 ), PE_DATE, 20 ) PE_DATE
from V_PHYEXAM_PE_PERSONAL_ITEM from V_PHYEXAM_PE_PERSONAL_ITEM
@@ -144,35 +147,25 @@
<select id="getConclusion" resultType="java.util.Map"> <select id="getConclusionAll" resultType="com.hospital.front.vo.ConclusionVo">
select PE_ID, select PE_ID,
'' PE_VISIT_ID, '' PE_VISIT_ID,
PE_DEPT_CODE, PE_DEPT_CODE,
PE_DEPT_NAME, PE_DEPT_NAME,
REPLACE(REPLACE(REPLACE(REPLACE(CONCLUSION_TEXT,CHAR(10),''), ' ', ''),char(9),''),char(13),'') CONCLUSION_TEXT REPLACE(REPLACE(REPLACE(REPLACE(cast(CONCLUSION_TEXT as varchar(8000)),CHAR(10),''), ' ', ''),char(9),''),char(13),'') CONCLUSION_TEXT
from V_PHYEXAM_DEPT_RESULT
where PE_ID = #{peId}
and CONCLUSION_TEXT is not null
</select>
<select id="getConclusionAll" resultType="java.util.Map">
select PE_ID,
'' PE_VISIT_ID,
PE_DEPT_CODE,
PE_DEPT_NAME,
REPLACE(REPLACE(REPLACE(REPLACE(CONCLUSION_TEXT,CHAR(10),''), ' ', ''),char(9),''),char(13),'') CONCLUSION_TEXT
from V_PHYEXAM_DEPT_RESULT from V_PHYEXAM_DEPT_RESULT
where PE_ID = #{peId} where PE_ID = #{peId}
and CONCLUSION_TEXT is not null and CONCLUSION_TEXT is not null
</select> </select>
<select id="getSuggest" resultType="java.util.Map"> <select id="getSuggestAll" resultType="com.hospital.front.vo.SuggestVo">
SELECT top 1 SELECT top 1
PE_ID, PE_ID,
REPLACE( REPLACE(
REPLACE( REPLACE(
REPLACE( REPLACE(
REPLACE( REPLACE(
REPLACE(REPLACE(GUIDE_CONTENT, CHAR(10), '&lt;br/&gt;'), ' ', ''), REPLACE(REPLACE(CAST (GUIDE_CONTENT AS VARCHAR (8000)), CHAR(10), '&lt;br/&gt;'), ' ', ''),
CHAR(9),'' CHAR(9),''
), CHAR(13), '' ), CHAR(13), ''
), '&lt;br/&gt;&lt;br/&gt;','&lt;br/&gt;' ), '&lt;br/&gt;&lt;br/&gt;','&lt;br/&gt;'
@@ -183,25 +176,7 @@
where PE_ID = #{peId} where PE_ID = #{peId}
AND REC_CONTENT IS NOT NULL AND REC_CONTENT IS NOT NULL
</select> </select>
<select id="getSuggestAll" resultType="java.util.Map"> <select id="getPeStatusByIdnoAndYear" resultType="com.hospital.front.vo.PeStatusVo">
SELECT top 1
PE_ID,
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(REPLACE(GUIDE_CONTENT, CHAR(10), '&lt;br/&gt;'), ' ', ''),
CHAR(9),''
), CHAR(13), ''
), '&lt;br/&gt;&lt;br/&gt;','&lt;br/&gt;'
), '&lt;br/&gt;-谢谢合作祝您健康!&lt;br/&gt;',''
) GUIDE_CONTENT
FROM
V_PHYEXAM_DIAGNOSIS
where PE_ID = #{peId}
AND REC_CONTENT IS NOT NULL
</select>
<select id="getPeStatusByIdnoAndYear" resultType="java.util.Map">
SELECT SELECT
PE_ID, PE_ID,
NAME, NAME,
@@ -314,23 +289,6 @@
group by id_no,NAME,PE_ID,UNIT_NAME group by id_no,NAME,PE_ID,UNIT_NAME
</select> </select>
<select id="getDisResultByAuditDate" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
select
ID_NO,
NAME,
PE_ID,
'' PE_VISIT_ID,
'' UNIT_CODE,
UNIT_NAME,
CONVERT(varchar(100),max(PE_QUEUE_DATE),20) PE_QUEUE_DATE,
CONVERT(varchar(100),max(AUDIT_DATE),20) AUDIT_DATE
from V_PHYEXAM_RESULT
where AUDIT_DATE &gt;= CONVERT(DATETIME, #{auditDate})
AND AUDIT_DATE &lt; DATEADD(DAY, 1, CONVERT(DATETIME, #{auditDate}))
group by id_no,NAME,PE_ID,UNIT_NAME
</select>
<select id="getItemByPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo"> <select id="getItemByPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
SELECT SELECT
ITEM_ASSEM_CODE, ITEM_ASSEM_CODE,
@@ -362,31 +320,6 @@
and PE_RESULT is not null and PE_RESULT is not null
</select> </select>
<select id="getResultByIdNoOrPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
select
ID_NO,
NAME,
PE_ID,
'' PE_VISIT_ID,
'' UNIT_CODE,
UNIT_NAME,
CONVERT(varchar(100),max(PE_QUEUE_DATE),20) PE_QUEUE_DATE,
CONVERT(varchar(100),max(AUDIT_DATE),20) AUDIT_DATE
from V_PHYEXAM_RESULT
<where>
1=1
<if test="idNo != null and idNo != '' ">
and ID_NO = #{idNo}
</if>
<if test="peId != null and peId != '' ">
and PE_ID = #{peId}
</if>
and AUDIT_DATE &gt;= CONVERT(DATETIME, #{auditYear} + '-01-01')
and AUDIT_DATE &lt; DATEADD(YEAR, 1, CONVERT(DATETIME, #{auditYear} + '-01-01'))
</where>
group by id_no,NAME,PE_ID,UNIT_NAME
</select>
<select id="getResultByPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo"> <select id="getResultByPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
select select
ID_NO, ID_NO,
@@ -403,7 +336,7 @@
group by id_no,NAME,PE_ID,UNIT_NAME group by id_no,NAME,PE_ID,UNIT_NAME
</select> </select>
<!-- 根据audit_date查询当天体检人员列表(去重) --> <!-- 根据audit_date查询当天体检人员列表(去重) -->
<select id="getExamListByDate" resultType="java.util.Map"> <select id="getExamListByDate" resultType="com.hospital.front.vo.ExamListVo">
SELECT DISTINCT PE_ID AS "peId", SELECT DISTINCT PE_ID AS "peId",
ID_NO AS "idNo", ID_NO AS "idNo",
NAME AS "name", NAME AS "name",
@@ -1,414 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hospital.front.mapper.ya.YaOrderMapper">
<!-- 健康探测 -->
<select id="ping" resultType="int">
SELECT 1
</select>
<parameterMap id="pm" type="java.util.Map">
<parameter property="peInfoStr" jdbcType="VARCHAR" mode="IN"></parameter>
<parameter property="peItemStr" jdbcType="VARCHAR" mode="IN"></parameter>
<parameter property="resultCode" jdbcType="VARCHAR" mode="OUT"></parameter>
<parameter property="errorMsg" jdbcType="VARCHAR" mode="OUT"></parameter>
<parameter property="peId" jdbcType="VARCHAR" mode="OUT"></parameter>
</parameterMap>
<insert id="saveOrder" parameterMap="pm" statementType="CALLABLE">
{call P_OrderRegister_YARMYY(
#{peInfoStr,mode=IN,jdbcType=VARCHAR},
#{peItemStr,mode=IN,jdbcType=VARCHAR},
#{resultCode,mode=OUT,jdbcType=VARCHAR},
#{errorMsg,mode=OUT,jdbcType=VARCHAR},
#{peId,mode=OUT,jdbcType=VARCHAR}
)}
</insert>
<parameterMap id="cancenlPm" type="java.util.Map">
<parameter property="peId" jdbcType="VARCHAR" mode="IN"></parameter>
<parameter property="name" jdbcType="VARCHAR" mode="IN"></parameter>
<parameter property="idcard" jdbcType="VARCHAR" mode="IN"></parameter>
<parameter property="resultCode" jdbcType="VARCHAR" mode="OUT"></parameter>
<parameter property="errorMsg" jdbcType="VARCHAR" mode="OUT"></parameter>
</parameterMap>
<insert id="cancelOrder" parameterMap="cancenlPm" statementType="CALLABLE">
{call P_CANCELORDER(
#{peId,mode=IN,jdbcType=VARCHAR},
#{name,mode=IN,jdbcType=VARCHAR},
#{idcard,mode=IN,jdbcType=VARCHAR},
#{resultCode,mode=OUT,jdbcType=VARCHAR},
#{errorMsg,mode=OUT,jdbcType=VARCHAR}
)}
</insert>
<parameterMap id="unitPm" type="java.util.Map">
<parameter property="unitCode" jdbcType="VARCHAR" mode="IN"></parameter>
<parameter property="unitName" jdbcType="VARCHAR" mode="IN"></parameter>
<parameter property="resultCode" jdbcType="VARCHAR" mode="OUT"></parameter>
<parameter property="errorMsg" jdbcType="VARCHAR" mode="OUT"></parameter>
</parameterMap>
<insert id="addUnit" parameterMap="unitPm" statementType="CALLABLE">
{call P_ORGINFOUPDATE(
#{unitCode,mode=IN,jdbcType=VARCHAR},
#{unitName,mode=IN,jdbcType=VARCHAR},
#{resultCode,mode=OUT,jdbcType=VARCHAR},
#{errorMsg,mode=OUT,jdbcType=VARCHAR}
)}
</insert>
<select id="getPhyexamStatus" resultType="java.util.Map">
SELECT
t.tjh PE_ID,
t.xm NAME,
t.sfzh ID_NO,
t.dwmc UNIT_NAME,
t.dwbm DEPARTMENT,
(CONVERT(varchar(100), t.yyrq, 20)) PE_PRE_DATE,
(
CASE t.status_sum
WHEN 4 THEN N'终审'
WHEN 3 THEN N'初审'
WHEN 2 THEN N'检中'
WHEN 1 THEN N'报道'
WHEN 0 THEN N'预约'
ELSE N'预约'
END
) PE_STATUS
FROM
( SELECT tjh, xm, sfzh, dwmc, dwbm, yyrq,
( ISNULL(bdzt,0) + ISNULL(jczt,0) + ISNULL(cszt,0) + ISNULL(zszt,0) ) status_sum
FROM V_YGTJZT ) t
WHERE
CHARINDEX( ',' + t.tjh + ',', ',' + #{peIds} + ',' ) >0
</select>
<select id="getItemList" resultType="java.util.Map">
SELECT DISTINCT
tjzhxbm item_assem_code,
tjzhxmc item_assem_name
FROM
V_TJXMZH
</select>
<select id="getAllItemList" resultType="java.util.Map">
SELECT DISTINCT
tjzhxbm ITEM_ASSEM_CODE,
tjzhxmc ITEM_ASSEM_NAME,
tjzbxbm PE_ITEM_CODE,
tjzbxmc PE_ITEM_NAME
FROM
V_TJXMZH
</select>
<select id="getPeItemList" resultType="java.util.Map">
SELECT DISTINCT
tjzbxbm PE_ITEM_CODE,
tjzbxmc PE_ITEM_NAME
FROM
V_TJXMZH
</select>
<select id="getEmpItem" resultType="java.util.Map">
SELECT
TJH PE_ID,
XM NAME,
DWMC UNIT_NAME,
DWBM DEPARTMENT,
TJZHXBM ITEM_ASSEM_CODE,
TJZHXMC ITEM_ASSEM_NAME,
(case when WCZT = '1' then N'完成' when WCZT != '1' then N'未完成' end) FINISHED_SIGN,
CONVERT ( VARCHAR ( 100 ), JCWCSJ, 20 ) PE_DATE
from V_YGYYTJZHX
where TJH = #{peId}
</select>
<select id="getItemInfo" resultType="java.util.Map">
SELECT
tjzhxbm ITEM_ASSEM_CODE,
tjzhxmc ITEM_ASSEM_NAME,
TJZBXBM PE_ITEM_CODE,
TJZBXMC PE_ITEM_NAME
FROM
V_TJXMZH
where tjzhxbm = #{peItemCode}
</select>
<select id="getResult" resultType="java.util.Map">
SELECT
TJH PE_ID ,
XM NAME,
SFZH ID_NO,
DWMC UNIT_NAME,
DWBM PE_DEPT_NAME,
TJZHXBM ITEM_ASSEM_CODE,
TJZHXMC ITEM_ASSEM_NAME,
TJZBXBM PE_ITEM_CODE,
TJZBXMC PE_ITEM_NAME,
TJZBZ PE_RESULT,
TJZBDW UNIT,
CONVERT(varchar(100),shsj,20) PE_QUEUE_DATE,
CONVERT(varchar(100),shsj,20) AUDIT_DATE,
CKFW PRINT_CONTEXT
FROM
V_TJBGJG
WHERE
TJH = #{peId}
and TJZBXBM is not null
</select>
<select id="getConclusion" resultType="java.util.Map">
select TJH PE_ID,'' PE_VISIT_ID,
REPLACE(REPLACE(REPLACE(REPLACE(TJJL,CHAR(10),''), ' ', ''),char(9),''),char(13),'') CONCLUSION_TEXT
from V_TJJY
where TJH = #{peId}
and TJJL is not null
</select>
<select id="getConclusionAll" resultType="java.util.Map">
select TJH PE_ID,'' PE_VISIT_ID,
REPLACE(REPLACE(REPLACE(REPLACE(TJJL,CHAR(10),''), ' ', ''),char(9),''),char(13),'') CONCLUSION_TEXT
from V_TJJY
where TJH = #{peId}
and TJJL is not null
</select>
<!--<select id="getTotalConclusion" resultType="java.util.HashMap">
select wm_concat(PE_DEPT_NAME || ':' || REPLACE(REPLACE(REPLACE(to_lob(CONCLUSION_TEXT), chr(13) || chr(10), ''), chr(13), ''), chr(10), '') || '###') CONCLUSION_TEXT
from V_PHYEXAM_DEPT_RESULT
where PE_ID = #{peId}
and CONCLUSION_TEXT is not null
</select>-->
<select id="getSuggest" resultType="java.util.Map">
select TJH PE_ID,'' PE_VISIT_ID,
REPLACE(REPLACE(REPLACE(REPLACE(TJJL,CHAR(10),''), ' ', ''),char(9),''),char(13),'') REC_CONTENT,
REPLACE(REPLACE(REPLACE(REPLACE(TJJY,CHAR(10),''), ' ', ''),char(9),''),char(13),'') GUIDE_CONTENT
from V_TJJY
where TJH = #{peId}
and TJJL is not null
</select>
<select id="getSuggestAll" resultType="java.util.Map">
select TJH PE_ID,'' PE_VISIT_ID,
REPLACE(REPLACE(REPLACE(REPLACE(TJJL,CHAR(10),''), ' ', ''),char(9),''),char(13),'') REC_CONTENT,
REPLACE(REPLACE(REPLACE(REPLACE(TJJY,CHAR(10),''), ' ', ''),char(9),''),char(13),'') GUIDE_CONTENT
from V_TJJY
where TJH = #{peId}
and TJJL is not null
</select>
<!--<select id="getTotalSuggest" resultType="java.util.HashMap">
select wm_concat(REC_CONTENT || ':' || REPLACE(REPLACE(REPLACE(GUIDE_CONTENT, chr(13) || chr(10), ''), chr(13), ''), chr(10), '') || '###') GUIDE_CONTENT
from V_PHYEXAM_DIAGNOSIS
where PE_ID = #{peId}
and GUIDE_CONTENT is not null
</select>-->
<select id="getPeStatusByIdnoAndYear" resultType="java.util.Map">
SELECT
t.tjh PE_ID,
t.xm NAME,
t.sfzh ID_NO,
t.dwmc UNIT_NAME,
t.dwbm DEPARTMENT,
(CONVERT(varchar(100), t.yyrq, 20)) PE_PRE_DATE,
(
CASE t.status_sum
WHEN 4 THEN N'终审'
WHEN 3 THEN N'初审'
WHEN 2 THEN N'检中'
WHEN 1 THEN N'报道'
WHEN 0 THEN N'预约'
ELSE N'预约'
END
) PE_STATUS
FROM
( SELECT tjh, xm, sfzh, dwmc, dwbm, yyrq,
( ISNULL(bdzt,0) + ISNULL(jczt,0) + ISNULL(cszt,0) + ISNULL(zszt,0) ) status_sum
FROM V_YGTJZT ) t
WHERE t.SFZH = #{idNo}
AND t.yyrq &gt;= CONVERT(DATETIME, #{year} + '-01-01')
AND t.yyrq &lt; DATEADD(YEAR, 1, CONVERT(DATETIME, #{year} + '-01-01'))
</select>
<select id="getResultByIdcardAndYear" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
SELECT
SFZH ID_NO,
XM NAME,
TJH PE_ID,
'' PE_VISIT_ID,
'' UNIT_CODE,
DWMC UNIT_NAME,
CONVERT(varchar(100),max(shsj),20) PE_QUEUE_DATE,
CONVERT(varchar(100),max(shsj),20) AUDIT_DATE
FROM
V_TJBGJG
WHERE
SFZH = #{ idNo }
AND SHSJ &gt;= CONVERT(DATETIME, #{year} + '-01-01')
AND SHSJ &lt; DATEADD(YEAR, 1, CONVERT(DATETIME, #{year} + '-01-01'))
group by SFZH,XM,TJH,DWMC
</select>
<select id="getResultByIdcardAndYearAll" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
SELECT
SFZH ID_NO,
XM NAME,
TJH PE_ID,
'' PE_VISIT_ID,
'' UNIT_CODE,
DWMC UNIT_NAME,
CONVERT(varchar(100),max(shsj),20) PE_QUEUE_DATE,
CONVERT(varchar(100),max(shsj),20) AUDIT_DATE
FROM
V_TJBGJG
WHERE
SFZH = #{ idNo }
AND SHSJ &gt;= CONVERT(DATETIME, #{year} + '-01-01')
AND SHSJ &lt; DATEADD(YEAR, 1, CONVERT(DATETIME, #{year} + '-01-01'))
group by SFZH,XM,TJH,DWMC
</select>
<!--<select id="getResultByAuditDate" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
select *
from V_PHYEXAM_RESULT
where to_char(AUDIT_DATE,'yyyy-mm-dd')=#{auditDate}
</select>-->
<select id="getDisResultByAuditDate" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
SELECT
SFZH ID_NO,
XM NAME,
TJH PE_ID,
'' PE_VISIT_ID,
'' UNIT_CODE,
DWMC UNIT_NAME,
CONVERT(varchar(100),max(shsj),20) PE_QUEUE_DATE,
CONVERT(varchar(100),max(shsj),20) AUDIT_DATE
FROM
V_TJBGJG
WHERE
shsj &gt;= CONVERT(DATETIME, #{auditDate})
AND shsj &lt; DATEADD(DAY, 1, CONVERT(DATETIME, #{auditDate}))
GROUP BY SFZH,XM,TJH,DWMC
</select>
<select id="getItemByPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
SELECT DISTINCT
TJZHXBM ITEM_ASSEM_CODE,
TJZHXMC ITEM_ASSEM_NAME,
TJZBXBM PE_ITEM_CODE,
TJZBXMC PE_ITEM_NAME,
TJZBZ PE_RESULT,
TJZBDW UNIT,
CKFW PRINT_CONTEXT
FROM
V_TJBGJG
WHERE
TJH = #{peId}
and TJZBXBM is not null
</select>
<select id="getItemByPeIdAll" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
SELECT DISTINCT
TJZHXBM ITEM_ASSEM_CODE,
TJZHXMC ITEM_ASSEM_NAME,
TJZBXBM PE_ITEM_CODE,
TJZBXMC PE_ITEM_NAME,
TJZBZ PE_RESULT,
TJZBDW UNIT,
CKFW PRINT_CONTEXT
FROM
V_TJBGJG
WHERE
TJH = #{peId}
and TJZBXBM is not null
</select>
<select id="getResultByIdNoOrPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
SELECT
SFZH ID_NO,
XM NAME,
TJH PE_ID,
'' PE_VISIT_ID,
'' UNIT_CODE,
DWMC UNIT_NAME,
CONVERT(varchar(100),max(shsj),20) PE_QUEUE_DATE,
CONVERT(varchar(100),max(shsj),20) AUDIT_DATE
FROM
V_TJBGJG
<where>
1=1
<if test="idNo != null and idNo != '' ">
and SFZH = #{idNo}
</if>
<if test="peId != null and peId != '' ">
and TJH = #{peId}
</if>
and shsj &gt;= CONVERT(DATETIME, #{auditYear} + '-01-01')
and shsj &lt; DATEADD(YEAR, 1, CONVERT(DATETIME, #{auditYear} + '-01-01'))
</where>
GROUP BY SFZH,XM,TJH,DWMC
</select>
<select id="getPeIdAndDateByIdcard" resultType="java.util.Map">
SELECT
tjh pe_id,
CONVERT ( VARCHAR ( 100 ), JCWCSJ, 23 ) PE_PRE_DATE
FROM
V_YGYYTJZHX
<where>
<if test="idNo != null and idNo != '' ">
and SFZH = #{idNo}
</if>
and JCWCSJ &gt;= CONVERT(DATETIME, CONVERT(VARCHAR(4), GETDATE(), 23) + '-01-01')
and JCWCSJ &lt; DATEADD(YEAR, 1, CONVERT(DATETIME, CONVERT(VARCHAR(4), GETDATE(), 23) + '-01-01'))
</where>
order by JCWCSJ desc
</select>
<select id="getResultByPeId" resultType="com.hospital.front.vo.MedicalMiddleResultVo">
SELECT
SFZH ID_NO,
XM NAME,
TJH PE_ID,
'' PE_VISIT_ID,
'' UNIT_CODE,
DWMC UNIT_NAME,
CONVERT(varchar(100),max(shsj),20) PE_QUEUE_DATE,
CONVERT(varchar(100),max(shsj),20) AUDIT_DATE
FROM
V_TJBGJG
WHERE
TJH = #{peId}
GROUP BY SFZH,XM,TJH,DWMC
</select>
<select id="getPeIdAndDateByIdcardList" resultType="java.util.Map">
SELECT
distinct
tjh PE_ID,
CONVERT ( VARCHAR ( 100 ), JCWCSJ, 23 ) PE_PRE_DATE
FROM
V_YGYYTJZHX
<where>
<if test="idNo != null and idNo != '' ">
and SFZH = #{idNo}
</if>
and JCWCSJ &gt;= CONVERT(DATETIME, CONVERT(VARCHAR(4), GETDATE(), 23) + '-01-01')
and JCWCSJ &lt; DATEADD(YEAR, 1, CONVERT(DATETIME, CONVERT(VARCHAR(4), GETDATE(), 23) + '-01-01'))
</where>
</select>
<!-- 根据audit_date查询当天体检人员列表(去重) -->
<select id="getExamListByDate" resultType="java.util.Map">
SELECT DISTINCT TJH AS "peId",
SFZH AS "idNo",
XM AS "name",
CONVERT(VARCHAR(10), shsj, 23) AS "auditDate"
FROM V_TJBGJG
<where>
<choose>
<when test="auditDate != null and auditDate != ''">
CONVERT(VARCHAR(10), shsj, 23) = #{auditDate}
</when>
<otherwise>
CONVERT(VARCHAR(10), shsj, 23) = CONVERT(VARCHAR(10), GETDATE(), 23)
</otherwise>
</choose>
</where>
</select>
</mapper>
+2 -2
View File
@@ -30,7 +30,7 @@
<totalSizeCap>5GB</totalSizeCap> <totalSizeCap>5GB</totalSizeCap>
</rollingPolicy> </rollingPolicy>
<encoder> <encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern> <pattern>%date [%thread] %-5level [%logger{50}] - %msg%n</pattern>
</encoder> </encoder>
</appender> </appender>
@@ -44,7 +44,7 @@
<totalSizeCap>2GB</totalSizeCap> <totalSizeCap>2GB</totalSizeCap>
</rollingPolicy> </rollingPolicy>
<encoder> <encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern> <pattern>%date [%thread] %-5level [%logger{50}] - %msg%n</pattern>
</encoder> </encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level> <level>ERROR</level>
+3
View File
@@ -0,0 +1,3 @@
# 重新允许 TLSv1/TLSv1.1(仅本进程生效,为兼容老版 SQL Server 的登录加密)
jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, \
DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL