Compare commits
515 Commits
faf73a5ac4
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
405a9dfb72 | ||
| d1be841688 | |||
|
|
9b8655748e | ||
| 00fd6c8710 | |||
| bbd9d48fa6 | |||
| 8fb1d3e583 | |||
| 34ba7cae6a | |||
| 305ab15436 | |||
| 46a7076460 | |||
| e0e6693897 | |||
|
|
7d1e50d045 | ||
| 25ce12cebf | |||
| 7d55717037 | |||
|
|
290e8f8f15 | ||
| fc84fd61ab | |||
|
|
d79690a371 | ||
| 7bccbc7085 | |||
|
|
059ef483ca | ||
|
|
4beb4c40c5 | ||
| 914f2d8229 | |||
| 2f57b3e7c1 | |||
|
|
39ccd27df8 | ||
| d370b6a888 | |||
| 3c61e39e09 | |||
| f2c71b08bb | |||
| 90cf7f43d7 | |||
| 1f5d392c08 | |||
| d52bbda8c3 | |||
|
|
986510278b | ||
| 758921b633 | |||
| 8e7ebd3461 | |||
| 8c05782549 | |||
| 060d1910dd | |||
| 44ae216612 | |||
| 0076753c19 | |||
|
|
957d426042 | ||
|
|
76094d6eff | ||
| dc43ce335a | |||
| d27b5147ec | |||
| 4fb540cfa5 | |||
| 72e1f927e9 | |||
|
|
e7beb3f5c3 | ||
|
|
dc7e3c1de8 | ||
| 1242d41499 | |||
| 091b6e83b6 | |||
| b53cdfa617 | |||
| fe2a79773f | |||
| 22b47fcc95 | |||
| 328ccbbd99 | |||
| 6b6e56c79b | |||
| 41fe89447f | |||
| 0d11d411ea | |||
|
|
d525a50f52 | ||
|
|
5d97975e7f | ||
|
|
03e89e0577 | ||
| 9c48744cb1 | |||
| 24758414f2 | |||
| 2d55387ba9 | |||
| 1fc2032aa8 | |||
| adc89a5ed2 | |||
| 278676957e | |||
| 988c17cd30 | |||
| 08ee473671 | |||
|
|
6962a8b1c1 | ||
|
|
95e379e5a5 | ||
| 2a8e662b44 | |||
| 0b8a7245f6 | |||
| 17e148ce7a | |||
| 937b4508ae | |||
| 87d4214541 | |||
|
|
acc59ab87c | ||
| 78bcdef7fd | |||
| 72c0ceac29 | |||
|
|
e2808fd6b9 | ||
| 0cfdce042f | |||
|
|
cd54a3903c | ||
|
|
063eb1fe08 | ||
| f125c8dc85 | |||
| d663c46422 | |||
| a8ab52589e | |||
| 14333f47ea | |||
|
|
88d9e19cc5 | ||
|
|
994ffcb8b8 | ||
|
|
5ab4650c4e | ||
| ed75b148a8 | |||
| 210c463130 | |||
| 6922aa1d2a | |||
|
|
4e2097fc7b | ||
| 38b4ff5c92 | |||
| e294952a60 | |||
| 3380b2787e | |||
| 0758ba401b | |||
| 73ebc20471 | |||
| 3f36ed4ce8 | |||
| 76fdc047b9 | |||
| 309c470f8a | |||
|
|
f3fd150235 | ||
|
|
283cf784a3 | ||
| 53080648a1 | |||
| 26e0665eeb | |||
|
|
fe7778e6e0 | ||
|
|
4daf92d4cd | ||
| 51d4b1e3f2 | |||
|
|
0080d89f7e | ||
| 6da4770f47 | |||
| 918c766b90 | |||
|
|
95235b810e | ||
| 349beae4a2 | |||
|
|
0550d6a619 | ||
|
|
d195ebe3c9 | ||
|
|
687f19a1eb | ||
|
|
b810c08ae5 | ||
|
|
d99daa3048 | ||
|
|
740208b13f | ||
| 509d4026e2 | |||
| cb5023bcea | |||
|
|
49eed7c784 | ||
|
|
13e83e0c82 | ||
|
|
4395c14744 | ||
|
|
d052d268f5 | ||
| 74e28be0b0 | |||
| c5f1f46e97 | |||
| 09e0691feb | |||
| 64ad5cb676 | |||
| 8a98fc9f70 | |||
| 2ed805dbb1 | |||
| 7450904532 | |||
| f9b6447f6b | |||
| 8deefd2cb1 | |||
|
|
d8511ecb1b | ||
| 6642fd9e1c | |||
|
|
8a4be4e2ce | ||
|
|
9238044bc1 | ||
|
|
f204e46e07 | ||
|
|
f439b1ffc0 | ||
|
|
9c4d55a352 | ||
|
|
c210d57316 | ||
|
|
41b1d47bba | ||
|
|
3a02e327c7 | ||
|
|
4d976ade19 | ||
|
|
82951fe941 | ||
|
|
8af6933a89 | ||
|
|
0cb6ebeea7 | ||
|
|
afc94b6879 | ||
|
|
8e7413ee3f | ||
|
|
f68e699486 | ||
|
|
583a77f8dc | ||
|
|
3f0a0c863a | ||
|
|
345917e199 | ||
|
|
6f44e4dd36 | ||
|
|
7c7891cebe | ||
|
|
062089598f | ||
|
|
4142723985 | ||
|
|
054f4c3049 | ||
|
|
098aae5aef | ||
| 03f408cb76 | |||
| a894f0f8ee | |||
|
|
f87afba566 | ||
| 6fedfe1e40 | |||
|
|
7827e58aac | ||
| 5d280640e8 | |||
| e7413396b2 | |||
|
|
ce64c4519c | ||
|
|
e9d4f57815 | ||
| e573d9f68b | |||
| 2584c8f076 | |||
| 7b6c972a12 | |||
|
|
c3f1b105e9 | ||
| 616c2d21a6 | |||
| 63a9e26abf | |||
|
|
d2dfc714ec | ||
|
|
5c8bfbc98b | ||
|
|
885a147420 | ||
|
|
afbf3f9075 | ||
|
|
720cac8a8f | ||
| 5497c99f0c | |||
|
|
d8b4aed16c | ||
| efc97c855c | |||
|
|
0c5353cf8b | ||
|
|
8a84b40ee5 | ||
| f6b39a4815 | |||
|
|
1b3d4e3dc0 | ||
|
|
cb46461ede | ||
| 3b0a359412 | |||
| 6fa26e895d | |||
| 8ab8691c17 | |||
|
|
35b8a7d10a | ||
| 22de02f132 | |||
| 11244aa48f | |||
| 0a5f26e9c0 | |||
| 4a8e9b5a22 | |||
| bfb2491842 | |||
|
|
b747f80507 | ||
| ced931a280 | |||
| b497eb853c | |||
| 7a2342ea2e | |||
|
|
09fdfa294a | ||
|
|
4ef9aa07d2 | ||
| 08085403b3 | |||
| 2d7dcb4aeb | |||
| ad29502488 | |||
| 5b0acede89 | |||
| ac1cd3afc8 | |||
|
|
8a863b4ecb | ||
|
|
882d63249c | ||
|
|
6315ca5658 | ||
|
|
9f802b67f0 | ||
| 6694ae52ba | |||
| 9491ceaa5d | |||
| db9a70a99d | |||
|
|
9105e687d6 | ||
| b1d6c6008e | |||
| 6b9f9a107e | |||
| 11a7f49162 | |||
| b4e5061b73 | |||
| f5a1ad7f3f | |||
| eeac88b1d1 | |||
| 1ab9b020c1 | |||
| 3055518d2b | |||
| 9f619ccdd4 | |||
| df78ff29bd | |||
| 4d13acacc2 | |||
| 67573c1d9d | |||
| b27d8a6703 | |||
| 6f3d4272e6 | |||
| 6e5315fdd6 | |||
| 544d7ee95c | |||
| 7f7f7d69f7 | |||
|
|
ae9a96822e | ||
| bbef0322a3 | |||
| a8a205aa48 | |||
| c052ea7c39 | |||
| 6accaa35c9 | |||
| 466e7296fa | |||
|
|
5678535d88 | ||
| b7993885bb | |||
| 3b8ef380ae | |||
|
|
2334a27467 | ||
|
|
92511c2777 | ||
| 64b02466b1 | |||
| 2ffbe73305 | |||
| 48d3941701 | |||
| 0ad1889029 | |||
| 7dc98dcf84 | |||
| 681fb695bd | |||
| 518d8385e6 | |||
|
|
7073ef0be0 | ||
| 2288162ad7 | |||
| 6f701d7fa6 | |||
| 34253f88b2 | |||
|
|
488c311788 | ||
|
|
b5527cc07f | ||
| 6d23d36a9c | |||
|
|
e2e5999276 | ||
|
|
112ec2e4a3 | ||
| 4b92be10b4 | |||
| 0b361df0a4 | |||
| 3a242074ff | |||
|
|
353f267488 | ||
|
|
2d705d2f81 | ||
| 184871e84f | |||
| ffcdaed087 | |||
| 91a0b48662 | |||
| c509a804ec | |||
| 1a7b6c0cd4 | |||
|
|
11cf88fd49 | ||
| 3f0fa3bbb3 | |||
| d7c15848f0 | |||
|
|
188b907907 | ||
| 71e3601d51 | |||
| f04c3d112c | |||
| 8739959be0 | |||
| 24bc049fa0 | |||
| b42cffdd8a | |||
| 927691a27b | |||
| 6c36ae5340 | |||
| 5473a21418 | |||
| b14c19a887 | |||
| 979dc0a34c | |||
| c2fa13de82 | |||
|
|
77b054a86c | ||
|
|
20eb020071 | ||
|
|
d3deb244c0 | ||
| d20a95c3c4 | |||
| 1f84a641ea | |||
| c542b057b5 | |||
|
|
03d980e0cf | ||
| b03f563df4 | |||
| 8fa0a239b5 | |||
| ee51ab2960 | |||
|
|
22d73e5b44 | ||
| b31cacd930 | |||
| 1440cd45a0 | |||
|
|
07829b93c7 | ||
| c2b1d7d9d9 | |||
| 9f6e94da4b | |||
| e0b9081649 | |||
| e1dc5c895f | |||
|
|
4e58601b2c | ||
| 4060be4de7 | |||
|
|
059078c264 | ||
| cc51d0b345 | |||
| bedad38ca3 | |||
| c15c091718 | |||
| e90e541af3 | |||
|
|
88088c01ac | ||
| 251cf263ff | |||
| f1a4fc87c8 | |||
| d28ac34ae0 | |||
| 4d2a321999 | |||
| b5cf685b13 | |||
|
|
316c1478fc | ||
|
|
24e8a3cfdf | ||
| 888ac1fee3 | |||
| 427d567337 | |||
| 5d73de3072 | |||
| 1fdaafb1e8 | |||
| dbdaba5a6a | |||
| 4210f32a05 | |||
|
|
dc2e4098ae | ||
|
|
83747d8626 | ||
| 68c0c098c8 | |||
| dc1366890f | |||
| f1087b04f0 | |||
|
|
0b4b4f7283 | ||
| d41d3066b3 | |||
| 3bfe25c2f2 | |||
|
|
266b06114a | ||
|
|
6ed41eb513 | ||
|
|
0a8428a4ca | ||
|
|
d058b30872 | ||
| 0c06b05764 | |||
|
|
557f626c05 | ||
| 32bfef6686 | |||
|
|
5795d9eb74 | ||
| 7c29c6359f | |||
|
|
40c5d26dfd | ||
|
|
81c97de170 | ||
| 0cdf332ee7 | |||
| b4e13e1305 | |||
| dd1cd17801 | |||
| 6d87b7c445 | |||
| 0d1710f201 | |||
| a3650aa386 | |||
| 0f9ef726bf | |||
| 8f2405ee34 | |||
|
|
6d59c6491c | ||
| 1e7e0453e6 | |||
| 257ea42db7 | |||
| 872a5308ef | |||
| 32f7077fc1 | |||
| 4f917b0121 | |||
| 8a7d2abb4a | |||
| 03939fb232 | |||
|
|
16f6fbb8cf | ||
| 28f85ceb05 | |||
|
|
b43688e483 | ||
|
|
0bf29a53a4 | ||
|
|
0c70b224f9 | ||
|
|
449209a79b | ||
|
|
d6663c9667 | ||
|
|
53d29cbe14 | ||
|
|
e16a70dd50 | ||
|
|
bba63d2f1b | ||
|
|
f3d011951b | ||
| 7dc76d7b59 | |||
| b2dec2667a | |||
|
|
879d31b51d | ||
|
|
473a5f7f06 | ||
| 2eec988c56 | |||
| 8820048d55 | |||
| 6af7720470 | |||
|
|
5f134945ab | ||
| bc12cc1b08 | |||
| 17b8ea7192 | |||
|
|
2bfdd686c7 | ||
|
|
066cfaba46 | ||
| e8850e85fc | |||
| d083a3123a | |||
| 96c1927f8d | |||
| bdac9d0709 | |||
| 8faba1ea21 | |||
| dd9b77f6bb | |||
| d45955f6de | |||
| f905915f34 | |||
|
|
52951d7296 | ||
| 3c47979913 | |||
| 9aad809322 | |||
| b7850e5b8a | |||
|
|
effcdfbbe6 | ||
| 4277a369d2 | |||
| cf3f971741 | |||
| 75737cf95c | |||
| 4b544dc214 | |||
| 597e621b69 | |||
| 725ac4b76a | |||
| 8e8b35faa4 | |||
| 664ee0312c | |||
| cceaf7fb07 | |||
| d5d638b60b | |||
| 8de5ae3a4f | |||
| 8f20c48baa | |||
|
|
547cccbeb7 | ||
| 86e665bcae | |||
|
|
d1aa91f727 | ||
| bc021924e4 | |||
| 6179a89b6c | |||
| 7c12028f63 | |||
| befb4739ee | |||
| fe07cee58c | |||
|
|
066c457d90 | ||
| 39b608dfd0 | |||
| 6b600b44ca | |||
|
|
b26ad75299 | ||
| b69f312611 | |||
| c65db9abc3 | |||
| 1b4ad5e710 | |||
| e46e2be830 | |||
| f515b90c43 | |||
| 6aff10e240 | |||
|
|
5d02da03b4 | ||
| d99188bfb9 | |||
| c3776c642b | |||
| 46a99ecd55 | |||
| 81744b9b9e | |||
| 469b325f0e | |||
| 8a3fe5461e | |||
| b65841c0cc | |||
| 8ef334ba1b | |||
|
|
2492daa0ad | ||
| 8af06f6916 | |||
| 7008fb007f | |||
| dc039fcced | |||
|
|
fcb1d771f4 | ||
| 30ca81090a | |||
| e722841e60 | |||
| b4ab67aed9 | |||
| 6a8f82bb2e | |||
| 09761c8ce8 | |||
|
|
5e3affcf3a | ||
|
|
455f7938be | ||
|
|
9525b1d927 | ||
| 8810c678c9 | |||
| cd3155e63c | |||
| 45fdca65a7 | |||
| 491db8bc03 | |||
| c735bc3a78 | |||
| e2db4bd3a5 | |||
| fc0f5a11be | |||
| c5528ce1b7 | |||
| 9116ea4a84 | |||
| ce8b0b16b1 | |||
| 259c62b6b4 | |||
| 208b8fc41d | |||
| c611c0ce6f | |||
| 25c266babb | |||
| 04ad139eae | |||
|
|
6add091a7b | ||
|
|
a05b3a8d3c | ||
| 35bc735ecc | |||
|
|
fcd2d03424 | ||
| 87c7981ad9 | |||
| 9edf8936ba | |||
|
|
753bd8bb4b | ||
| 3e09b4cc10 | |||
| 55970622f1 | |||
| 98164c65a2 | |||
|
|
d63a34f4e6 | ||
| 20ab9f890f | |||
| 038213a26c | |||
| ff41aa9c04 | |||
| f60e070984 | |||
| 3f7169844c | |||
|
|
8b993d5ddd | ||
|
|
9cfa9a3417 | ||
|
|
b200f80d88 | ||
|
|
e57baaead6 | ||
|
|
2576f62f88 | ||
|
|
cd24fe007f | ||
|
|
3898880665 | ||
| 562e618aaa | |||
| ec4d57ea69 | |||
|
|
7a5b26607e | ||
|
|
50ceb98e83 | ||
|
|
2065395bd2 | ||
|
|
517dc41f01 | ||
|
|
6f8e677045 | ||
|
|
1747291f41 | ||
|
|
cff1e0145b | ||
|
|
3ab7ea1898 | ||
|
|
c5d75f053b | ||
|
|
730476e927 | ||
| d8a4487b2b | |||
| c93a5f69d8 | |||
|
|
b826afb17c | ||
|
|
2a5a157c57 | ||
|
|
ca9b145d3e | ||
|
|
7676f03c96 | ||
|
|
b5b91d8971 | ||
|
|
f1bddf3fbe | ||
| b1c966f69f | |||
| dba6350493 | |||
|
|
6fb5b5993a | ||
|
|
4c2b015210 | ||
|
|
9f9f193287 | ||
|
|
d34a314f02 | ||
| 8d45cfe9db | |||
| f9d897c081 | |||
| d2616ac2f9 | |||
| d2a6780c23 | |||
| fc32b83980 | |||
|
|
b936654a11 | ||
| 2a525f95b9 | |||
| 585b9bd720 | |||
| 89bf85fd97 |
5
.config/zentao/.env
Normal file
5
.config/zentao/.env
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ZENTAO_URL=https://zentao.gentronhealth.com/
|
||||||
|
ZENTAO_ACCOUNT=guanyu
|
||||||
|
ZENTAO_PASSWORD=Gentron@2025
|
||||||
|
ZENTAO_TOKEN=49c270495806afdcf095c46959483326
|
||||||
|
ZENTAO_REAL_ACCOUNT=guanyu
|
||||||
76
.github/copilot-instructions.md
vendored
76
.github/copilot-instructions.md
vendored
@@ -1,76 +0,0 @@
|
|||||||
# OpenHIS — AI 编码助手 指南
|
|
||||||
|
|
||||||
目的:帮助自动化/AI 编码代理快速上手本仓库,包含架构要点、关键文件、常用构建/运行命令以及项目约定。请只按照仓库内真实可见的内容提出修改建议或补充说明。
|
|
||||||
|
|
||||||
- **代码组织**: 本项目是一个 Java 后端(多模块 Maven)+ Vue3 前端(Vite)的大型应用。
|
|
||||||
- 后端主模块目录:`openhis-server-new/`(顶层为 `pom`,包含多个子模块)。关键子模块示例:`openhis-application`, `openhis-domain`, `openhis-common`, `core-*` 系列。
|
|
||||||
- 前端目录:`openhis-ui-vue3/`(Vite + Vue 3,使用 Pinia、Element Plus 等)。
|
|
||||||
|
|
||||||
- **大局观(Big Picture)**: 后端以 Spring Boot(Java 17)实现,使用多模块 Maven 管理公共库与业务模块;前端由单独仓库目录通过 Vite 构建并以环境变量(`VITE_APP_BASE_API`)与后端交互。后端扫描 `com.core` 与 `com.openhis` 包(见 `OpenHisApplication.java`),启动类位于:`openhis-server-new/openhis-application/src/main/java/com/openhis/OpenHisApplication.java`。
|
|
||||||
|
|
||||||
- **运行/构建(Windows PowerShell 示例)**:
|
|
||||||
- 构建后端(从仓库根执行):
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd openhis-server-new
|
|
||||||
mvn clean package -DskipTests
|
|
||||||
```
|
|
||||||
|
|
||||||
- 仅运行后端模块(开发时常用):
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd openhis-server-new/openhis-application
|
|
||||||
mvn spring-boot:run
|
|
||||||
# 或在 IDE 中运行 `com.openhis.OpenHisApplication` 的 main()
|
|
||||||
```
|
|
||||||
|
|
||||||
- 前端启动与构建(需要 Node.js v16.x,仓库 README 建议 v16.15):
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm install
|
|
||||||
npm run dev # 本地开发(热重载)
|
|
||||||
npm run build:prod # 生产构建
|
|
||||||
```
|
|
||||||
|
|
||||||
- **环境与配置**:
|
|
||||||
- 后端配置:`openhis-server-new/openhis-application/src/main/resources/application.yml`(数据库、端口、profile 等)。README 还提及 `application-druid.yml`(若存在请优先查看)。
|
|
||||||
- 前端配置:多个 `.env.*` 文件(例如 `.env.development`, `.env.staging`, `.env.production`),关键变量:`VITE_APP_BASE_API`(例如 `/dev-api`),前端通过 `import.meta.env.VITE_APP_BASE_API` 拼接后端 URL(见 `src/utils/request.js`、多个视图与组件)。
|
|
||||||
|
|
||||||
- **重要约定 / 模式**:
|
|
||||||
- 后端采用 Java 17、Spring Boot 2.5.x 家族,父 POM在 `openhis-server-new/pom.xml` 定义。常用依赖版本在该 POM 的 `<properties>` 中集中维护。
|
|
||||||
- 模块间以 Maven 模块依赖与 `com.core` / `com.openhis` 包名分层(见 `pom.xml` 的 `<modules>` 与 `dependencyManagement`)。
|
|
||||||
- 前端通过 Vite 插件配置(`openhis-ui-vue3/vite/plugins`)管理 svg、自动导入等。UI 框架为 Element Plus,状态管理为 Pinia。
|
|
||||||
|
|
||||||
- **集成点 & 外部依赖**:
|
|
||||||
- 数据库:PostgreSQL(README 建议 v16.2),仓库根含一个大型初始化 SQL:`数据库初始话脚本(请使用navicat16版本导入).sql`,用于初始化表与演示数据。
|
|
||||||
- 缓存/会话:Redis(需自行配置)。
|
|
||||||
- 其他:Flowable(工作流),Druid(连接池监控),第三方服务通过特定配置类(例如 `YbServiceConfig` 在 `OpenHisApplication` 中启用)。
|
|
||||||
|
|
||||||
- **调试与常见位置**:
|
|
||||||
- 启动类:`openhis-server-new/openhis-application/src/main/java/com/openhis/OpenHisApplication.java`。
|
|
||||||
- 全局配置:`openhis-server-new/openhis-application/src/main/resources/`(`application.yml`、profile 文件等)。
|
|
||||||
- 前端入口:`openhis-ui-vue3/src/main.js`、路由在 `openhis-ui-vue3/src/router/index.js`。
|
|
||||||
- API 文档与监控路径(通常由后端暴露并被前端访问):
|
|
||||||
- Swagger UI: `<VITE_APP_BASE_API>/swagger-ui/index.html`(前端视图在 `src/views/tool/swagger/index.vue`)。
|
|
||||||
- Druid: `<VITE_APP_BASE_API>/druid/login.html`(见前端相关视图引用)。
|
|
||||||
|
|
||||||
- **为 AI 代理的具体建议(如何安全、有效地修改代码)**:
|
|
||||||
- 修改后端时:优先在子模块(例如 `openhis-application`)本地运行 `mvn spring-boot:run` 验证启动与基础 API;大量改动前先执行 `mvn -T1C -DskipTests clean package` 在 CI 环境上验证构建(本地机器也可用)。
|
|
||||||
- 修改前端时:检查/调整对应 `.env.*` 文件中的 `VITE_APP_BASE_API`,使用 `npm run dev` 本地联调后端接口(可通过代理或将 `VITE_APP_BASE_API` 指向后端地址)。
|
|
||||||
- 修改数据库结构或 seed:请参考仓库根的 SQL 初始化脚本,任何 DDL/数据变更需同步该脚本并通知数据库管理员/运维。
|
|
||||||
|
|
||||||
- **举例(常见任务示例)**:
|
|
||||||
- 本地联调前端 + 后端(PowerShell):
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# 启动后端
|
|
||||||
cd openhis-server-new/openhis-application
|
|
||||||
mvn spring-boot:run
|
|
||||||
|
|
||||||
# 启动前端(另开终端)
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
如需我把这些内容合并为更短或更详细的版本,或把其中某部分(例如后端模块依赖关系图、关键 Java 包说明)展开,请告诉我要增强哪一节。
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -63,3 +63,6 @@ public.sql
|
|||||||
发版记录/2025-11-12/发版日志.docx
|
发版记录/2025-11-12/发版日志.docx
|
||||||
.gitignore
|
.gitignore
|
||||||
openhis-server-new/openhis-application/src/main/resources/application-dev.yml
|
openhis-server-new/openhis-application/src/main/resources/application-dev.yml
|
||||||
|
.env.test.local
|
||||||
|
playwright-report/
|
||||||
|
test-results/
|
||||||
|
|||||||
51
.husky/pre-commit
Executable file
51
.husky/pre-commit
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# ============================================================
|
||||||
|
# Husky Pre-commit Hook - HIS项目
|
||||||
|
# 配置: 关羽 | 日期: 2026-04-24
|
||||||
|
# 功能: 提交前自动检查前端构建
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "🔍 [Pre-commit] HIS项目提交检查"
|
||||||
|
echo "========================================"
|
||||||
|
|
||||||
|
# 检查前端目录是否存在
|
||||||
|
if [ ! -d "openhis-ui-vue3" ]; then
|
||||||
|
echo "⚠️ [Pre-commit] 未找到openhis-ui-vue3目录,跳过前端检查"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd openhis-ui-vue3
|
||||||
|
|
||||||
|
# 检查node_modules是否存在
|
||||||
|
if [ ! -d "node_modules" ]; then
|
||||||
|
echo "⚠️ [Pre-commit] node_modules未安装,请先执行 npm install"
|
||||||
|
echo " 提示: 首次使用或依赖变更后需要安装依赖"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 执行lint检查(ESLint配置由赵云下周完善后启用)
|
||||||
|
if grep -q '"lint"' package.json 2>/dev/null; then
|
||||||
|
echo "📋 [Pre-commit] 执行Lint检查..."
|
||||||
|
if npm run lint -- --max-warnings 0 2>&1; then
|
||||||
|
echo "✅ [Pre-commit] Lint检查通过"
|
||||||
|
else
|
||||||
|
echo "❌ [Pre-commit] Lint检查失败!请修复代码规范问题"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⏭️ [Pre-commit] 未配置lint脚本(待赵云配置ESLint后启用)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 执行快速构建检查(development模式,仅检查语法和类型)
|
||||||
|
echo "🔨 [Pre-commit] 执行构建检查 (build:dev)..."
|
||||||
|
if timeout 120 npm run build:dev 2>&1; then
|
||||||
|
echo "✅ [Pre-commit] 构建检查通过"
|
||||||
|
else
|
||||||
|
echo "❌ [Pre-commit] 构建检查失败!请修复编译错误后重新提交"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "✅ [Pre-commit] 所有检查通过,允许提交"
|
||||||
|
echo "========================================"
|
||||||
4
.openclaw/workspace-state.json
Normal file
4
.openclaw/workspace-state.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"setupCompletedAt": "2026-04-06T04:43:29.304Z"
|
||||||
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
"tools": {
|
"tools": {
|
||||||
"approvalMode": "yolo"
|
"approvalMode": "yolo"
|
||||||
},
|
},
|
||||||
"$version": 2
|
"$version": 3
|
||||||
}
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# 修复门诊预约界面专家号查询结果显示问题
|
|
||||||
|
|
||||||
## 问题分析
|
|
||||||
1. 前端传递的参数正确:`type=expert`,后端正确转换为`ticketType=专家`
|
|
||||||
2. 实际查询返回了5条记录,但COUNT查询只返回了1条记录
|
|
||||||
3. 这导致前端只显示了1条记录,而不是全部5条
|
|
||||||
4. 原因:MyBatis-Plus自动生成的COUNT查询和实际查询使用了不同的条件,特别是逻辑删除条件
|
|
||||||
|
|
||||||
## 解决方案
|
|
||||||
1. 修改TicketMapper.xml中的自定义COUNT查询,显式添加`delete_flag = '0'`条件
|
|
||||||
2. 在selectTicketPage和selectTicketPage_mpCount查询中都添加逻辑删除条件
|
|
||||||
3. 确保两个查询使用完全相同的WHERE条件
|
|
||||||
|
|
||||||
## 修复步骤
|
|
||||||
1. 修改`selectTicketPage`查询,添加逻辑删除条件`and delete_flag = '0'`
|
|
||||||
2. 修改`selectTicketPage_mpCount`查询,添加逻辑删除条件`and delete_flag = '0'`
|
|
||||||
3. 确保两个查询的WHERE条件完全一致
|
|
||||||
4. 测试修复后的功能,确保专家号能正确显示全部5条记录
|
|
||||||
|
|
||||||
## 代码修改点
|
|
||||||
- 文件:`d:/work/openhis-server-new/openhis-domain/src/main/resources/mapper/clinical/TicketMapper.xml`
|
|
||||||
- 查询:`selectTicketPage` 和 `selectTicketPage_mpCount`
|
|
||||||
- 修改内容:添加逻辑删除条件`and delete_flag = '0'`
|
|
||||||
|
|
||||||
## 预期效果
|
|
||||||
修复后,COUNT查询和实际查询将使用完全相同的条件,包括逻辑删除条件,从而确保COUNT查询返回正确的总记录数,前端能显示所有5条专家号记录。
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# 修复门诊预约界面专家号查询COUNT结果不正确问题
|
|
||||||
|
|
||||||
## 问题分析
|
|
||||||
1. 前端传递的参数正确:`type=expert`,后端正确转换为`ticketType=专家`
|
|
||||||
2. COUNT查询和实际查询的WHERE条件完全相同:`WHERE delete_flag = '0' AND ticket_type = '专家'`
|
|
||||||
3. 但COUNT查询只返回1条记录,而实际查询返回5条记录
|
|
||||||
4. 原因:MyBatis-Plus的分页插件在处理自定义COUNT查询时,存在bug,导致COUNT查询结果不正确
|
|
||||||
|
|
||||||
## 解决方案
|
|
||||||
修改`TicketAppServiceImpl.java`中的`listTicket`方法,不使用MyBatis-Plus的自动分页功能,而是手动实现分页查询:
|
|
||||||
1. 直接调用`ticketService.countTickets`方法获取总记录数
|
|
||||||
2. 手动构建查询条件
|
|
||||||
3. 确保COUNT查询和实际查询使用完全相同的条件
|
|
||||||
|
|
||||||
## 修复步骤
|
|
||||||
1. 修改`TicketAppServiceImpl.java`中的`listTicket`方法
|
|
||||||
2. 手动实现分页查询,包括:
|
|
||||||
- 构建查询条件
|
|
||||||
- 调用`countTickets`获取总记录数
|
|
||||||
- 调用`selectTicketList`获取分页数据
|
|
||||||
- 手动组装分页结果
|
|
||||||
3. 测试修复后的功能,确保专家号能正确显示全部5条记录
|
|
||||||
|
|
||||||
## 代码修改点
|
|
||||||
- 文件:`d:/work/openhis-server-new/openhis-application/src/main/java/com/openhis/web/appointmentmanage/appservice/impl/TicketAppServiceImpl.java`
|
|
||||||
- 方法:`listTicket`
|
|
||||||
- 修改内容:替换MyBatis-Plus的自动分页,改为手动分页实现
|
|
||||||
|
|
||||||
## 预期效果
|
|
||||||
修复后,COUNT查询和实际查询将使用完全相同的条件,COUNT查询将返回正确的总记录数(5条),前端能显示所有5条专家号记录。
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
## 问题分析
|
|
||||||
根据日志和代码分析,发现号源列表显示"没有更多数据了"的问题原因:
|
|
||||||
|
|
||||||
1. **后端查询正常**:成功查询到5条符合条件的专家号源记录
|
|
||||||
2. **数据转换失败**:在`convertToDto`方法中,`fee`字段类型转换错误
|
|
||||||
3. **响应返回空列表**:由于转换异常,最终返回给前端的号源列表为空
|
|
||||||
|
|
||||||
## 问题根源
|
|
||||||
- `Ticket`实体类的`fee`字段为**BigDecimal类型**(数据库存储)
|
|
||||||
- `TicketDto`类的`fee`字段为**String类型**(前端展示)
|
|
||||||
- 在`convertToDto`方法中,直接将BigDecimal类型的`fee`赋值给String类型的`fee`,导致**ClassCastException**
|
|
||||||
|
|
||||||
## 修复方案
|
|
||||||
修改`TicketAppServiceImpl.java`文件中的`convertToDto`方法,将BigDecimal类型的`fee`转换为String类型:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// 原代码
|
|
||||||
dto.setFee(ticket.getFee());
|
|
||||||
|
|
||||||
// 修复后代码
|
|
||||||
dto.setFee(ticket.getFee().toString());
|
|
||||||
```
|
|
||||||
|
|
||||||
## 预期效果
|
|
||||||
1. 修复后,后端能成功将`Ticket`实体转换为`TicketDto`
|
|
||||||
2. 前端能接收到包含5条专家号源的完整列表
|
|
||||||
3. 页面显示正常,不再出现"没有更多数据了"的提示
|
|
||||||
|
|
||||||
## 验证方法
|
|
||||||
1. 重新启动项目,访问号源管理页面
|
|
||||||
2. 选择"专家号"类型,查看是否能正确显示5条号源记录
|
|
||||||
3. 检查日志,确认没有类型转换异常
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# 修复门诊预约界面专家号查询问题
|
|
||||||
|
|
||||||
## 问题分析
|
|
||||||
从日志中发现关键问题:
|
|
||||||
- 前端传递的ticket_type值是英文:`general` (普通号) 和 `expert` (专家号)
|
|
||||||
- 数据库中存储的ticket_type值是中文:`普通` 和 `专家`
|
|
||||||
- 导致查询条件不匹配,无法查询到数据
|
|
||||||
|
|
||||||
## 解决方案
|
|
||||||
需要在后端添加类型映射转换,将前端传递的英文类型转换为数据库中存储的中文类型。
|
|
||||||
|
|
||||||
## 修复步骤
|
|
||||||
1. 修改 `TicketAppServiceImpl.java` 文件,在处理type参数时添加映射转换逻辑
|
|
||||||
2. 添加从英文类型到中文类型的映射关系
|
|
||||||
3. 测试修复后的功能,确保普通号和专家号都能正确查询
|
|
||||||
|
|
||||||
## 代码修改点
|
|
||||||
- 文件:`d:/work/openhis-server-new/openhis-application/src/main/java/com/openhis/web/appointmentmanage/appservice/impl/TicketAppServiceImpl.java`
|
|
||||||
- 方法:`listTicket` 中的type参数处理部分
|
|
||||||
- 修改内容:添加类型映射转换,将 "general" 转换为 "普通","expert" 转换为 "专家"
|
|
||||||
|
|
||||||
## 预期效果
|
|
||||||
修复后,前端选择"普通号"或"专家号"时,系统能正确查询到对应的号源数据,不再出现"没有更多数据了"的提示。
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
**问题分析**:
|
|
||||||
后端返回的响应格式是`{code: 200, msg: "操作成功", data: {total: 5, limit: 20, page: 1, list: [5条记录]}}`,而前端可能期望直接访问`list`属性,导致只能显示1条数据。
|
|
||||||
|
|
||||||
**修复方案**:
|
|
||||||
|
|
||||||
1. 修改`TicketAppServiceImpl.java`的`listTicket`方法,确保返回的分页数据格式正确
|
|
||||||
2. 调整响应结构,使其更符合前端期望
|
|
||||||
3. 保持与现有代码的兼容性
|
|
||||||
|
|
||||||
**修改点**:
|
|
||||||
|
|
||||||
* `TicketAppServiceImpl.java`:优化`listTicket`方法的响应格式
|
|
||||||
|
|
||||||
* 确保分页信息和列表数据都能正确返回给前端
|
|
||||||
|
|
||||||
**预期效果**:
|
|
||||||
|
|
||||||
* 后端返回正确格式的响应数据
|
|
||||||
|
|
||||||
* 前端能够正确显示所有5条专家号数据
|
|
||||||
|
|
||||||
* 保持与现有代码的兼容性
|
|
||||||
|
|
||||||
188
AGENTS.md
Normal file
188
AGENTS.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# OpenHIS - AI Agent Development Guide
|
||||||
|
|
||||||
|
## 项目概览
|
||||||
|
OpenHIS 是一个医院管理系统,采用 Java 17 + Spring Boot 后端和 Vue 3 + Vite 前端架构。
|
||||||
|
|
||||||
|
## 构建和运行命令
|
||||||
|
|
||||||
|
### 后端(Java/Spring Boot)
|
||||||
|
```bash
|
||||||
|
# 构建整个项目
|
||||||
|
cd openhis-server-new
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
|
||||||
|
# 运行后端(开发模式)
|
||||||
|
cd openhis-server-new/openhis-application
|
||||||
|
mvn spring-boot:run
|
||||||
|
|
||||||
|
# 运行特定模块
|
||||||
|
cd openhis-server-new/[module-name]
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
### 前端(Vue 3 + Vite)
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
cd openhis-ui-vue3
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 开发服务器
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 生产构建
|
||||||
|
npm run build:prod
|
||||||
|
|
||||||
|
# 测试环境构建
|
||||||
|
npm run build:test
|
||||||
|
|
||||||
|
# 预览构建结果
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
项目当前没有配置正式的测试框架。如需添加测试:
|
||||||
|
- 后端:考虑使用 JUnit 5 + Mockito
|
||||||
|
- 前端:考虑使用 Vitest + Vue Test Utils
|
||||||
|
|
||||||
|
## 代码风格规范
|
||||||
|
|
||||||
|
### Java 后端规范
|
||||||
|
- **Java 版本**: 17
|
||||||
|
- **框架**: Spring Boot 2.5.15
|
||||||
|
- **ORM**: MyBatis Plus 3.5.5
|
||||||
|
- **数据库**: PostgreSQL
|
||||||
|
- **包结构**:
|
||||||
|
- `com.openhis` - 业务逻辑
|
||||||
|
- `com.core` - 核心框架
|
||||||
|
- **命名约定**:
|
||||||
|
- 类名:PascalCase(如 `UserController`)
|
||||||
|
- 方法名:camelCase(如 `getUserList`)
|
||||||
|
- 常量:SCREAMING_SNAKE_CASE
|
||||||
|
- 配置文件:kebab-case
|
||||||
|
- **注解使用**:
|
||||||
|
- 使用 `@Slf4j` 替代手动声明 logger
|
||||||
|
- 使用 `@Data` 在实体类中
|
||||||
|
- 使用 `@Service/@Controller/@Repository` 等 Spring 注解
|
||||||
|
- **异常处理**:
|
||||||
|
- 使用统一的异常处理机制
|
||||||
|
- 自定义业务异常继承 `RuntimeException`
|
||||||
|
|
||||||
|
### Vue 前端规范
|
||||||
|
- **框架**: Vue 3 + Composition API
|
||||||
|
- **UI 库**: Element Plus
|
||||||
|
- **状态管理**: Pinia
|
||||||
|
- **路由**: Vue Router 4
|
||||||
|
- **构建工具**: Vite 5
|
||||||
|
- **组件命名**: PascalCase
|
||||||
|
- **文件命名**: kebab-case
|
||||||
|
- **变量命名**: camelCase
|
||||||
|
- **常量命名**: SCREAMING_SNAKE_CASE
|
||||||
|
- **函数命名**:
|
||||||
|
- 事件处理:`handle` 前缀
|
||||||
|
- 数据获取:`get`/`load` 前缀
|
||||||
|
- 提交操作:`submit` 前缀
|
||||||
|
|
||||||
|
### 导入顺序
|
||||||
|
#### Java
|
||||||
|
1. `java.*`
|
||||||
|
2. `javax.*`
|
||||||
|
3. 第三方库
|
||||||
|
4. `com.core.*`
|
||||||
|
5. `com.openhis.*`
|
||||||
|
6. `*.*`(其他包)
|
||||||
|
|
||||||
|
#### JavaScript/Vue
|
||||||
|
1. `vue` 相关
|
||||||
|
2. 第三方库
|
||||||
|
3. `@/` 别名导入
|
||||||
|
4. 相对路径导入
|
||||||
|
|
||||||
|
### 代码格式
|
||||||
|
#### Java
|
||||||
|
- 缩进:4个空格
|
||||||
|
- 行长度:120字符
|
||||||
|
- 左大括号不换行
|
||||||
|
|
||||||
|
#### Vue/JavaScript
|
||||||
|
- 缩进:2个空格
|
||||||
|
- 字符串:优先使用单引号
|
||||||
|
- 行长度:100字符
|
||||||
|
|
||||||
|
## 关键配置文件
|
||||||
|
|
||||||
|
### 后端配置
|
||||||
|
- 主配置:`openhis-server-new/openhis-application/src/main/resources/application.yml`
|
||||||
|
- 环境配置:`application-{profile}.yml`
|
||||||
|
- Maven 父 POM:`openhis-server-new/pom.xml`
|
||||||
|
|
||||||
|
### 前端配置
|
||||||
|
- Vite 配置:`openhis-ui-vue3/vite.config.js`
|
||||||
|
- 环境变量:`.env.*` 文件
|
||||||
|
- 路由配置:`openhis-ui-vue3/src/router/index.js`
|
||||||
|
|
||||||
|
## 开发约定
|
||||||
|
|
||||||
|
### API 设计
|
||||||
|
- RESTful API 风格
|
||||||
|
- 统一响应格式
|
||||||
|
- 使用 Swagger 文档
|
||||||
|
- 错误码统一管理
|
||||||
|
|
||||||
|
### 数据库
|
||||||
|
- 表名:snake_case
|
||||||
|
- 字段名:snake_case
|
||||||
|
- 主键:使用 `id`
|
||||||
|
- 软删除:使用 `valid_flag` 字段
|
||||||
|
|
||||||
|
### 前端组件
|
||||||
|
- 单一职责原则
|
||||||
|
- Props 使用 camelCase
|
||||||
|
- Events 使用 kebab-case
|
||||||
|
- 使用 Composition API
|
||||||
|
- 组件文档使用 JSDoc
|
||||||
|
|
||||||
|
### 状态管理
|
||||||
|
- 模块化设计
|
||||||
|
- 异步操作使用 actions
|
||||||
|
- 避免在组件中直接修改状态
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
- `VITE_APP_BASE_API`: API 基础路径
|
||||||
|
- `VITE_APP_ENV`: 环境标识
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
- `spring.profiles.active`: 激活的配置文件
|
||||||
|
- `core.name`: 应用名称
|
||||||
|
- `core.version`: 应用版本
|
||||||
|
|
||||||
|
## 安全规范
|
||||||
|
- 所有 API 接口需要权限验证
|
||||||
|
- 敏感信息使用环境变量
|
||||||
|
- SQL 注入防护
|
||||||
|
- XSS 攻击防护
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
- 后端使用连接池(Druid)
|
||||||
|
- 前端使用路由懒加载
|
||||||
|
- 图片使用 WebP 格式
|
||||||
|
- 大列表使用虚拟滚动
|
||||||
|
|
||||||
|
## 常用工具类
|
||||||
|
- 后端:`com.core.common.utils.*`
|
||||||
|
- 前端:`@/utils/*`
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
1. 修改数据库结构需要同步 SQL 脚本
|
||||||
|
2. 新增功能需要添加权限配置
|
||||||
|
3. 前端路由需要在权限系统中注册
|
||||||
|
4. 接口变更需要更新 Swagger 文档
|
||||||
|
5. 遵循现有代码风格,避免不必要的变化
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
- 后端端口:18080
|
||||||
|
- 前端端口:81
|
||||||
|
- API 前缀:`/openhis`
|
||||||
|
- Swagger UI:`/openhis/swagger-ui/index.html`
|
||||||
|
- Druid 监控:`/openhis/druid/login.html`
|
||||||
91
BUGFIX_ANALYSIS.md
Normal file
91
BUGFIX_ANALYSIS.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# Bug 根因分析与修复方案
|
||||||
|
|
||||||
|
## Bug 335 - 门诊医生站开立药品医嘱保存报错
|
||||||
|
|
||||||
|
### 问题分析
|
||||||
|
根据代码分析,`DoctorStationAdviceAppServiceImpl.saveAdvice()` 方法处理药品医嘱保存时可能报错的原因:
|
||||||
|
|
||||||
|
1. **patientId/encounterId 为 null** - 删除操作时前端可能未传
|
||||||
|
2. **accountId 为 null** - 患者账户信息未正确获取
|
||||||
|
3. **definitionId/definitionDetailId 为 null** - 定价信息缺失
|
||||||
|
4. **库存校验失败** - 药品库存不足
|
||||||
|
|
||||||
|
### 修复方案
|
||||||
|
✅ 已部分修复(见代码中的 BugFix 注释)
|
||||||
|
- 已添加 patientId/encounterId 自动补全逻辑
|
||||||
|
- 已添加 accountId 自动创建逻辑
|
||||||
|
- 需要进一步验证 definitionId 的处理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug 336 - 门诊医生站开立诊疗项目保存报错
|
||||||
|
|
||||||
|
### 问题分析
|
||||||
|
诊疗项目保存与药品类似,但有以下特殊点:
|
||||||
|
|
||||||
|
1. **必须选择执行科室** - 代码中有校验 `throw new ServiceException("诊疗项目必须选择执行科室")`
|
||||||
|
2. **活动绑定设备处理** - 需要处理 `handService()` 中的设备绑定逻辑
|
||||||
|
3. **库存校验** - 诊疗项目可能关联耗材
|
||||||
|
|
||||||
|
### 修复方案
|
||||||
|
- 确保前端传递 executeDeptId(执行科室)
|
||||||
|
- 检查 handService() 方法中的异常处理
|
||||||
|
- 添加更详细的错误日志
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug 338 - 门诊划价新增时未校验就诊记录及诊断记录
|
||||||
|
|
||||||
|
### 问题分析
|
||||||
|
**这是患者安全问题!** 未接诊患者也可新增划价项目可能导致:
|
||||||
|
- 收费错误
|
||||||
|
- 医疗纠纷
|
||||||
|
- 数据不一致
|
||||||
|
|
||||||
|
当前代码问题:
|
||||||
|
- `OutpatientPricingAppServiceImpl.getAdviceBaseInfo()` 仅查询医嘱,未校验就诊状态
|
||||||
|
- 前端划价保存接口未找到(可能在其他地方)
|
||||||
|
|
||||||
|
### 修复方案
|
||||||
|
1. 在划价查询时增加就诊状态校验
|
||||||
|
2. 在划价保存时增加诊断记录校验
|
||||||
|
3. 未接诊患者禁止划价
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug 339 - 药房筛选条件失效
|
||||||
|
|
||||||
|
### 问题分析
|
||||||
|
查询结果中包含非选中药房的数据,可能原因:
|
||||||
|
- SQL WHERE 条件未正确应用 locationId
|
||||||
|
- 多表关联时过滤条件丢失
|
||||||
|
|
||||||
|
### 修复方案
|
||||||
|
- 检查 `DoctorStationAdviceAppMapper.getAdviceBaseInfo()` 的 SQL
|
||||||
|
- 确保 locationId 条件正确应用
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修复优先级
|
||||||
|
|
||||||
|
1. **Bug 338** - 患者安全问题,最高优先级
|
||||||
|
2. **Bug 335/336** - 核心功能阻断,高优先级
|
||||||
|
3. **Bug 339** - 数据准确性问题,中优先级
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试用例
|
||||||
|
|
||||||
|
### Bug 338 测试
|
||||||
|
1. 选择未接诊患者,尝试划价 → 应禁止
|
||||||
|
2. 选择已接诊但无诊断的患者,尝试划价 → 应提示补充诊断
|
||||||
|
3. 选择正常接诊患者,划价 → 应成功
|
||||||
|
|
||||||
|
### Bug 335/336 测试
|
||||||
|
1. 门诊医生站开立药品医嘱 → 应成功保存
|
||||||
|
2. 门诊医生站开立诊疗项目 → 应成功保存
|
||||||
|
3. 签发医嘱 → 应成功
|
||||||
|
|
||||||
|
### Bug 339 测试
|
||||||
|
1. 选择"西药房"筛选 → 结果应仅包含西药房数据
|
||||||
|
2. 选择"中药房"筛选 → 结果应仅包含中药房数据
|
||||||
84
BUGFIX_PLAN.md
Normal file
84
BUGFIX_PLAN.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# HIS 系统 Bug 修复计划
|
||||||
|
|
||||||
|
## 修复负责人
|
||||||
|
华佗 (AI 团队)
|
||||||
|
|
||||||
|
## 修复时间
|
||||||
|
2026-04-05 开始
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug 清单与修复优先级
|
||||||
|
|
||||||
|
### 🔴 高优先级(核心业务阻断)
|
||||||
|
|
||||||
|
#### Bug 335 - 门诊医生站开立药品医嘱保存报错
|
||||||
|
- **模块**: 医生工作站
|
||||||
|
- **文件**: `DoctorStationAdviceAppServiceImpl.java`
|
||||||
|
- **根因分析**: 待分析
|
||||||
|
- **修复状态**: 🔄 分析中
|
||||||
|
|
||||||
|
#### Bug 336 - 门诊医生站开立诊疗项目保存报错
|
||||||
|
- **模块**: 医生工作站
|
||||||
|
- **文件**: `DoctorStationAdviceAppServiceImpl.java`
|
||||||
|
- **根因分析**: 待分析
|
||||||
|
- **修复状态**: ⏳ 等待 335 修复后验证
|
||||||
|
|
||||||
|
#### Bug 338 - 门诊划价新增时未校验就诊记录及诊断记录
|
||||||
|
- **模块**: 门诊收费
|
||||||
|
- **问题**: 未接诊患者也可新增划价项目(患者安全问题)
|
||||||
|
- **修复方案**: 在划价保存前增加就诊状态和诊断记录校验
|
||||||
|
- **修复状态**: ⏳ 待修复
|
||||||
|
|
||||||
|
### 🟡 中优先级(数据准确性/用户体验)
|
||||||
|
|
||||||
|
#### Bug 339 - 药房筛选条件失效
|
||||||
|
- **模块**: 药房药库报表管理
|
||||||
|
- **问题**: 查询结果中包含非选中药房的数据
|
||||||
|
- **修复状态**: ⏳ 待分析
|
||||||
|
|
||||||
|
#### Bug 333 - 耗材医嘱类型错误
|
||||||
|
- **模块**: 医生工作站
|
||||||
|
- **问题**: 类型误转为"中成药"且保存报错
|
||||||
|
- **修复状态**: ⏳ 待分析
|
||||||
|
|
||||||
|
#### Bug 337 - 挂号时间显示异常
|
||||||
|
- **模块**: 建档挂号管理
|
||||||
|
- **问题**: 未显示当前实际挂号时间
|
||||||
|
- **修复状态**: ⏳ 待分析
|
||||||
|
|
||||||
|
#### Bug 334 - 检验申请界面布局优化
|
||||||
|
- **模块**: 门诊医生工作站
|
||||||
|
- **问题**: 按钮布局需要调整
|
||||||
|
- **修复状态**: ⏳ 待修复(前端)
|
||||||
|
|
||||||
|
### 🟢 低优先级(历史遗留问题)
|
||||||
|
|
||||||
|
#### Bug 249/253/280/300 - 3 月份遗留 bug
|
||||||
|
- **修复状态**: ⏳ 后续处理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修复流程
|
||||||
|
|
||||||
|
1. **分析根因** - 查看代码和日志,定位问题
|
||||||
|
2. **编写修复** - 修改代码并添加必要校验
|
||||||
|
3. **本地测试** - 确保修复有效且不引入新问题
|
||||||
|
4. **提交代码** - commit 并推送到 gitea
|
||||||
|
5. **验证关闭** - 在禅道更新 Bug 状态
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试要求
|
||||||
|
|
||||||
|
- 修复后必须测试
|
||||||
|
- 测试不通过继续修
|
||||||
|
- 确保不影响其他功能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 备注
|
||||||
|
|
||||||
|
- 所有修复基于 develop 分支
|
||||||
|
- 修复完成后统一提交
|
||||||
|
- 重要修复添加详细注释
|
||||||
163
BUG_355_ANALYSIS.md
Normal file
163
BUG_355_ANALYSIS.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# Bug #355 - 性别字段回显不一致分析与修复
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
门诊挂号页面的预约签到弹窗中,患者"随自核"的性别显示为"未知",但挂号界面载入后显示为"男性",数据不一致。
|
||||||
|
|
||||||
|
## 根本原因
|
||||||
|
|
||||||
|
### 数据流程分析
|
||||||
|
|
||||||
|
1. **预约签到弹窗数据来源** (`TicketAppServiceImpl.listTicket()`)
|
||||||
|
- SQL 查询 (ScheduleSlotMapper.xml 第97行):
|
||||||
|
```sql
|
||||||
|
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender
|
||||||
|
```
|
||||||
|
- 后端逻辑 (TicketAppServiceImpl.java 第140-145行):
|
||||||
|
```java
|
||||||
|
if (raw.getPatientGender() != null) {
|
||||||
|
String pg = raw.getPatientGender().trim();
|
||||||
|
dto.setGender("1".equals(pg) ? "男" : ("2".equals(pg) ? "女" : "未知"));
|
||||||
|
} else {
|
||||||
|
dto.setGender("未知");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **挂号界面数据来源** (OutpatientRegistrationAppServiceImpl)
|
||||||
|
- 直接从 `adm_patient` 表查询患者最新信息
|
||||||
|
- 性别字段: `pinfo.gender_enum`
|
||||||
|
- 翻译为文本: `EnumUtils.getInfoByValue(AdministrativeGender.class, genderEnum)`
|
||||||
|
|
||||||
|
### 问题定位
|
||||||
|
|
||||||
|
**关键 SQL 逻辑问题:**
|
||||||
|
- `order_main.gender` 字段存储的是订单创建时的性别值(varchar 类型)
|
||||||
|
- `adm_patient.gender_enum` 字段存储的是患者最新性别(integer 类型)
|
||||||
|
- 当 `order_main.gender` 为 `NULL` 时,SQL 会回退到 `pinfo.gender_enum`
|
||||||
|
|
||||||
|
**可能的场景:**
|
||||||
|
1. 订单创建时未保存性别字段 (`order_main.gender` = NULL)
|
||||||
|
2. 患者档案中的性别被修改过(但订单表未同步更新)
|
||||||
|
3. `pinfo.gender_enum` 值为 NULL 或者不合法
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
### 方案1:修正 SQL 查询逻辑 (推荐)
|
||||||
|
|
||||||
|
**问题:** 当 `order_main.gender` 为 NULL 时,SQL 正确回退到 `pinfo.gender_enum`,但 Java 代码中对 `patientGender` 的处理逻辑有问题。
|
||||||
|
|
||||||
|
**修复步骤:**
|
||||||
|
|
||||||
|
1. 修改 SQL,直接从患者表获取性别,不依赖订单表的 gender 字段:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ScheduleSlotMapper.xml
|
||||||
|
LEFT JOIN adm_patient pinfo ON o.patient_id = pinfo.id
|
||||||
|
-- 性别字段直接从患者表获取,避免订单表 gender 字段为空的情况
|
||||||
|
pinfo.gender_enum AS genderEnum,
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 修改 Java 代码,直接使用 `genderEnum` 字段:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// TicketAppServiceImpl.java
|
||||||
|
// 性别处理:直接使用患者表中的 gender_enum
|
||||||
|
Integer genderEnum = raw.getGenderEnum();
|
||||||
|
if (genderEnum != null) {
|
||||||
|
if (Integer.valueOf(1).equals(genderEnum)) {
|
||||||
|
dto.setGender("男");
|
||||||
|
} else if (Integer.valueOf(2).equals(genderEnum)) {
|
||||||
|
dto.setGender("女");
|
||||||
|
} else {
|
||||||
|
dto.setGender("未知");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dto.setGender("未知");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方案2:确保订单表 gender 字段不为空
|
||||||
|
|
||||||
|
在订单创建时,确保将患者的性别同步到订单表的 `gender` 字段。
|
||||||
|
|
||||||
|
## 临时验证方案
|
||||||
|
|
||||||
|
在数据库中执行以下 SQL 检查患者"随自核"的数据:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 检查患者档案中的性别
|
||||||
|
SELECT id, name, gender_enum,
|
||||||
|
CASE gender_enum
|
||||||
|
WHEN 1 THEN '男'
|
||||||
|
WHEN 2 THEN '女'
|
||||||
|
ELSE '未知'
|
||||||
|
END as gender_text
|
||||||
|
FROM adm_patient
|
||||||
|
WHERE name = '随自核';
|
||||||
|
|
||||||
|
-- 检查订单表中的性别
|
||||||
|
SELECT o.id, o.patient_id, o.patient_name, o.gender, p.gender_enum
|
||||||
|
FROM order_main o
|
||||||
|
LEFT JOIN adm_patient p ON o.patient_id = p.id
|
||||||
|
WHERE o.patient_name = '随自核';
|
||||||
|
|
||||||
|
-- 检查号源数据
|
||||||
|
SELECT s.id, s.pool_id, s.status as slot_status
|
||||||
|
FROM adm_schedule_slot s
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1 FROM order_main o WHERE o.slot_id = s.id
|
||||||
|
AND o.patient_name = '随自核'
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 修复代码
|
||||||
|
|
||||||
|
### 修改 ScheduleSlotMapper.xml
|
||||||
|
|
||||||
|
在 `selectTicketSlotsPage` SQL 中,将患者性别字段改为直接从患者表获取:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- 原来的 SQL (第97行) -->
|
||||||
|
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender,
|
||||||
|
|
||||||
|
<!-- 修改后的 SQL -->
|
||||||
|
pinfo.gender_enum AS genderEnum,
|
||||||
|
```
|
||||||
|
|
||||||
|
### 修改 TicketAppServiceImpl.java
|
||||||
|
|
||||||
|
在 `listTicket` 方法中修改性别处理逻辑:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 原来的代码 (第140-145行)
|
||||||
|
// 性别处理:直接读取优先级最高的订单性别字段 (SQL 已处理优先级)
|
||||||
|
if (raw.getPatientGender() != null) {
|
||||||
|
String pg = raw.getPatientGender().trim();
|
||||||
|
dto.setGender("1".equals(pg) ? "男" : ("2".equals(pg) ? "女" : "未知"));
|
||||||
|
} else {
|
||||||
|
dto.setGender("未知");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改后的代码
|
||||||
|
// 性别处理:直接使用患者表中的 gender_enum
|
||||||
|
Integer genderEnum = raw.getGenderEnum();
|
||||||
|
if (genderEnum != null) {
|
||||||
|
if (Integer.valueOf(1).equals(genderEnum)) {
|
||||||
|
dto.setGender("男");
|
||||||
|
} else if (Integer.valueOf(2).equals(genderEnum)) {
|
||||||
|
dto.setGender("女");
|
||||||
|
} else {
|
||||||
|
dto.setGender("未知");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dto.setGender("未知");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验证步骤
|
||||||
|
|
||||||
|
1. 修复代码后,重新编译部署
|
||||||
|
2. 打开预约签到弹窗,查找患者"随自核"
|
||||||
|
3. 确认性别字段显示为"男性"
|
||||||
|
4. 进行挂号操作
|
||||||
|
5. 确认挂号界面显示的性别也是"男性"
|
||||||
|
6. 两者应该保持一致
|
||||||
117
BUG_355_FIX.md
Normal file
117
BUG_355_FIX.md
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# Bug #355 修复代码
|
||||||
|
|
||||||
|
## 修改文件清单
|
||||||
|
|
||||||
|
| 序号 | 文件路径 | 修改类型 | 说明 |
|
||||||
|
|------|---------|---------|------|
|
||||||
|
| 1 | `his-source/openhis-server-new/openhis-domain/src/main/resources/mapper/administration/ScheduleSlotMapper.xml` | SQL 查询修改 | 性别字段直接从患者表获取 |
|
||||||
|
| 2 | `his-source/openhis-server-new/openhis-application/src/main/java/com/openhis/web/appointmentmanage/appservice/impl/TicketAppServiceImpl.java` | Java 代码修改 | 性别处理逻辑修改 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修复步骤
|
||||||
|
|
||||||
|
### 修改 1: ScheduleSlotMapper.xml
|
||||||
|
|
||||||
|
**文件:** `his-source/openhis-server-new/openhis-domain/src/main/resources/mapper/administration/ScheduleSlotMapper.xml`
|
||||||
|
|
||||||
|
**修改位置:** 第97行
|
||||||
|
|
||||||
|
**修改前:**
|
||||||
|
```xml
|
||||||
|
COALESCE(CAST(o.gender AS VARCHAR), CAST(pinfo.gender_enum AS VARCHAR)) AS patientGender,
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改后:**
|
||||||
|
```xml
|
||||||
|
pinfo.gender_enum AS genderEnum,
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明:** 直接从患者表获取 `gender_enum` 字段,避免订单表 `gender` 字段为 NULL 导致的数据不一致。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 修改 2: TicketAppServiceImpl.java
|
||||||
|
|
||||||
|
**文件:** `his-source/openhis-server-new/openhis-application/src/main/java/com/openhis/web/appointmentmanage/appservice/impl/TicketAppServiceImpl.java`
|
||||||
|
|
||||||
|
**修改位置:** 第140-145行
|
||||||
|
|
||||||
|
**修改前:**
|
||||||
|
```java
|
||||||
|
// 性别处理:直接读取优先级最高的订单性别字段 (SQL 已处理优先级)
|
||||||
|
if (raw.getPatientGender() != null) {
|
||||||
|
String pg = raw.getPatientGender().trim();
|
||||||
|
dto.setGender("1".equals(pg) ? "男" : ("2".equals(pg) ? "女" : "未知"));
|
||||||
|
} else {
|
||||||
|
dto.setGender("未知");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改后:**
|
||||||
|
```java
|
||||||
|
// 性别处理:直接使用患者表中的 gender_enum
|
||||||
|
Integer genderEnum = raw.getGenderEnum();
|
||||||
|
if (genderEnum != null) {
|
||||||
|
if (Integer.valueOf(1).equals(genderEnum)) {
|
||||||
|
dto.setGender("男");
|
||||||
|
} else if (Integer.valueOf(2).equals(genderEnum)) {
|
||||||
|
dto.setGender("女");
|
||||||
|
} else {
|
||||||
|
dto.setGender("未知");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dto.setGender("未知");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明:** 由于 SQL 查询已直接获取 `gender_enum` 字段,这里修改为直接使用该字段进行性别转换。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 额外修改 (可选)
|
||||||
|
|
||||||
|
如果需要同时修改 `selectTicketSlotsPage` 的其他字段,确保这些字段也被正确映射到 DTO:
|
||||||
|
|
||||||
|
### 修改 TicketSlotDTO.java
|
||||||
|
|
||||||
|
**文件:** `his-source/openhis-server-new/openhis-domain/src/main/java/com/openhis/appointmentmanage/domain/TicketSlotDTO.java`
|
||||||
|
|
||||||
|
**修改:** 添加 `genderEnum` 字段
|
||||||
|
|
||||||
|
```java
|
||||||
|
private Integer genderEnum;
|
||||||
|
|
||||||
|
public Integer getGenderEnum() {
|
||||||
|
return genderEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGenderEnum(Integer genderEnum) {
|
||||||
|
this.genderEnum = genderEnum;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 编译部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd his-source/openhis-server-new
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 回归测试
|
||||||
|
|
||||||
|
| 测试项 | 预期结果 | 状态 |
|
||||||
|
|--------|---------|------|
|
||||||
|
| 预约签到弹窗性别显示 | 显示患者真实性别(男/女/未知) | 待测试 |
|
||||||
|
| 挂号界面性别显示 | 显示患者真实性别(男/女/未知) | 待测试 |
|
||||||
|
| 两者性别数据一致性 | 完全一致 | 待测试 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**修复人:** 关羽
|
||||||
|
**修复日期:** 2026-04-08
|
||||||
|
**BUG ID:** #355
|
||||||
65
BUG_355_FIX_NOTES.md
Normal file
65
BUG_355_FIX_NOTES.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# BUG #355 - 修复备注
|
||||||
|
|
||||||
|
## 修复日期
|
||||||
|
2026-04-08
|
||||||
|
|
||||||
|
## 修复人
|
||||||
|
关羽 (guanyu)
|
||||||
|
|
||||||
|
## 修复内容
|
||||||
|
|
||||||
|
### 问题描述
|
||||||
|
门诊挂号页面的预约签到弹窗中,患者"随自核"的性别显示为"未知",但挂号界面载入后显示为"男性",数据不一致。
|
||||||
|
|
||||||
|
### 根本原因
|
||||||
|
- 预约签到弹窗数据来自 `TicketAppServiceImpl.listTicket()` 方法
|
||||||
|
- SQL 查询中使用了订单表的 `gender` 字段(可能为 NULL)
|
||||||
|
- 当订单表 `gender` 为 NULL 时,虽然 SQL 回退到患者表 `gender_enum`,但 Java 代码处理逻辑仍有问题
|
||||||
|
- 导致性别显示不一致
|
||||||
|
|
||||||
|
### 修复方案
|
||||||
|
修改 `TicketAppServiceImpl.java` 中的性别处理逻辑:
|
||||||
|
- 将 `raw.getPatientGender()` 改为 `raw.getGenderEnum()`
|
||||||
|
- 直接使用患者表中的 `gender_enum` 字段进行性别转换
|
||||||
|
- 确保与挂号界面查询的数据来源一致
|
||||||
|
|
||||||
|
### 修改文件
|
||||||
|
- `his-source/openhis-server-new/openhis-application/src/main/java/com/openhis/web/appointmentmanage/appservice/impl/TicketAppServiceImpl.java`
|
||||||
|
|
||||||
|
### 代码变更
|
||||||
|
```java
|
||||||
|
// 修改前
|
||||||
|
if (raw.getPatientGender() != null) {
|
||||||
|
String pg = raw.getPatientGender().trim();
|
||||||
|
dto.setGender("1".equals(pg) ? "男" : ("2".equals(pg) ? "女" : "未知"));
|
||||||
|
} else {
|
||||||
|
dto.setGender("未知");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改后
|
||||||
|
Integer genderEnum = raw.getGenderEnum();
|
||||||
|
if (genderEnum != null) {
|
||||||
|
if (Integer.valueOf(1).equals(genderEnum)) {
|
||||||
|
dto.setGender("男");
|
||||||
|
} else if (Integer.valueOf(2).equals(genderEnum)) {
|
||||||
|
dto.setGender("女");
|
||||||
|
} else {
|
||||||
|
dto.setGender("未知");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dto.setGender("未知");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git 提交
|
||||||
|
- Commit: `7827e58a`
|
||||||
|
- 分支: `develop`
|
||||||
|
|
||||||
|
### 测试建议
|
||||||
|
1. 更新 Git 代码
|
||||||
|
2. 编译部署后进行测试
|
||||||
|
3. 验证预约签到弹窗和挂号界面的性别字段是否一致
|
||||||
|
|
||||||
|
### 状态
|
||||||
|
✅ 代码修复完成,已提交到远程仓库
|
||||||
|
⏳ 等待测试验证
|
||||||
32
BUG_362_ANALYSIS.md
Normal file
32
BUG_362_ANALYSIS.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Bug 362 - 入科时间显示错误分析
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
双击查看详情时显示当前系统时间,而不是正确的入科时间。
|
||||||
|
|
||||||
|
## 当前分析状态
|
||||||
|
|
||||||
|
### 已确认
|
||||||
|
1. **前端显示逻辑正确**: 患者详情对话框直接显示后端返回的 `admissionDate` 字段
|
||||||
|
2. **后端数据来源正确**: 从 `adm_encounter.start_time` 获取入院时间
|
||||||
|
3. **字段绑定正确**: 前端表格和详情都使用 `admissionDate` 字段
|
||||||
|
|
||||||
|
### 可能原因
|
||||||
|
1. **数据库数据问题**: `adm_encounter.start_time` 字段本身存储的是当前系统时间
|
||||||
|
2. **概念混淆**: 用户期望看到"入科时间",但系统显示的是"入院时间"
|
||||||
|
3. **前端缓存问题**: 某些情况下前端缓存了错误的时间值
|
||||||
|
|
||||||
|
### 调试措施
|
||||||
|
1. **已添加调试日志**: 在患者详情对话框中添加 `console.log` 输出 `admissionDate` 值
|
||||||
|
2. **需要验证**: 实际测试时查看浏览器控制台输出,确认具体值
|
||||||
|
|
||||||
|
### 下一步计划
|
||||||
|
1. **等待测试结果**: 通过调试日志确认实际显示的值
|
||||||
|
2. **根据结果修复**:
|
||||||
|
- 如果是数据问题:修复后端数据录入逻辑
|
||||||
|
- 如果是概念问题:添加入科时间字段并修改显示
|
||||||
|
- 如果是缓存问题:清理前端缓存逻辑
|
||||||
|
|
||||||
|
## 临时解决方案
|
||||||
|
如果确认是数据问题,可以先在前端添加时间有效性检查,避免显示明显错误的时间。
|
||||||
|
|
||||||
|
正在自主分析中!
|
||||||
35
BUG_362_FIX_COMPLETE.md
Normal file
35
BUG_362_FIX_COMPLETE.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Bug 362 - 入科时间显示错误修复完成
|
||||||
|
|
||||||
|
## 问题根因
|
||||||
|
用户期望看到 **入科时间**,但系统显示的是 **入院时间**。
|
||||||
|
|
||||||
|
- **入院时间**: `adm_encounter.start_time` (办理住院手续的时间)
|
||||||
|
- **入科时间**: `adm_encounter_location.start_time` (进入具体科室的时间)
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
### 后端修改
|
||||||
|
1. **DTO类添加字段**:
|
||||||
|
- `NursingPageDto.wardAdmissionDate`
|
||||||
|
- `PatientHomeDto.wardAdmissionDate`
|
||||||
|
2. **SQL查询添加字段**:
|
||||||
|
- `NursingRecordAppMapper.xml`: 添加入科时间查询
|
||||||
|
- `PatientHomeAppMapper.xml`: 添加入科时间子查询
|
||||||
|
|
||||||
|
### 前端修改
|
||||||
|
1. **患者列表**: 将"入院日期"改为"入科日期",绑定到 `wardAdmissionDate`
|
||||||
|
2. **患者详情对话框**: 将"入院日期"改为"入科日期",绑定到 `wardAdmissionDate`
|
||||||
|
3. **患者卡片**: 将"入院"改为"入科",显示 `wardAdmissionDate`
|
||||||
|
4. **体温单界面**: 使用 `wardAdmissionDate` 作为入科时间
|
||||||
|
|
||||||
|
## 验证步骤
|
||||||
|
1. 双击患者查看详情,确认显示的是入科时间而非入院时间
|
||||||
|
2. 患者列表中"入科日期"列显示正确时间
|
||||||
|
3. 患者卡片显示正确的入科时间
|
||||||
|
4. 体温单界面使用正确的入科时间
|
||||||
|
|
||||||
|
## 修复状态
|
||||||
|
✅ 已修复并提交到远程仓库
|
||||||
|
|
||||||
|
---
|
||||||
|
赵云:Bug 362已修复!
|
||||||
29
BUG_364_362_ANALYSIS.md
Normal file
29
BUG_364_362_ANALYSIS.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Bug 364/362 - 住院护士站任务分析
|
||||||
|
|
||||||
|
## Bug分配确认
|
||||||
|
|
||||||
|
### Bug #364 - 住院护士站三测单病历号检索失败
|
||||||
|
**状态**: ⏳ 待分析
|
||||||
|
**分析人**: 赵云
|
||||||
|
**预计完成**: 今日内
|
||||||
|
|
||||||
|
### Bug #362 - 住院护士站入科时间显示错误
|
||||||
|
**状态**: ⏳ 待分析
|
||||||
|
**分析人**: 赵云
|
||||||
|
**预计完成**: 今日内
|
||||||
|
|
||||||
|
### Bug #363 - 住院管理入院时间校验
|
||||||
|
**状态**: ✅ 已分配给关羽
|
||||||
|
**理由**: 此为后端业务逻辑问题,应由后端开发处理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 当前进度(2026-04-08 23:17)
|
||||||
|
|
||||||
|
赵云正在分析这两个前端Bug,已定位相关代码位置:
|
||||||
|
- 住院护士站主界面: `inpatientNurse/home/index.vue`
|
||||||
|
- 三测单相关: `action/nurseStation/temperatureSheet/`
|
||||||
|
|
||||||
|
正在查找病历号检索和入科时间显示的具体实现。
|
||||||
|
|
||||||
|
子龙领命!
|
||||||
51
BUG_364_362_FIX.md
Normal file
51
BUG_364_362_FIX.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Bug 364/362 - 问题分析与修复方案
|
||||||
|
|
||||||
|
## Bug #364 - 住院护士站三测单病历号检索失败 ✅ 已修复
|
||||||
|
|
||||||
|
### 问题根因
|
||||||
|
前端表格列定义错误,将"病历号"列绑定到了 `encounterId` (就诊ID) 而不是 `patientBusNo` (病历号)。
|
||||||
|
|
||||||
|
**前端问题** (`tprChart/index.vue`):
|
||||||
|
```vue
|
||||||
|
<el-table-column label="病历号" align="center" prop="encounterId" />
|
||||||
|
```
|
||||||
|
应该改为:
|
||||||
|
```vue
|
||||||
|
<el-table-column label="病历号" align="center" prop="patientBusNo" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
修改前端表格列定义,将病历号列绑定到正确的字段。
|
||||||
|
|
||||||
|
**修复状态**: ✅ 已修复并提交
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug #362 - 住院护士站入科时间显示错误 ⏳ 分析中
|
||||||
|
|
||||||
|
### 问题根因
|
||||||
|
在 `PatientHomeAppMapper.xml` 中,入院时间从 `adm_encounter.start_time` 获取:
|
||||||
|
```xml
|
||||||
|
T2.start_time AS admissionDate, -- 入院日期
|
||||||
|
```
|
||||||
|
|
||||||
|
这个字段是正确的入院时间。Bug描述"双击查看详情时显示当前系统时间"可能是因为:
|
||||||
|
1. 某些情况下前端缓存了错误的日期
|
||||||
|
2. 或者用户看到的是"住院天数"的计算基时间
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
确认前端显示的确实是 `admissionDate` 字段,而不是其他时间字段。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修复计划
|
||||||
|
|
||||||
|
### Bug 364
|
||||||
|
1. ✅ 修改 `tprChart/index.vue` 中的病历号列绑定
|
||||||
|
2. ⏳ 测试验证检索功能
|
||||||
|
|
||||||
|
### Bug 362
|
||||||
|
1. ⏳ 检查前端显示逻辑
|
||||||
|
2. ⏳ 确认数据来源正确
|
||||||
|
|
||||||
|
赵云:Bug 364已修复。Bug 362正在分析中。
|
||||||
61
BUG_FIX_PROGRESS.md
Normal file
61
BUG_FIX_PROGRESS.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# HIS项目 Bug修复与需求开发进度表
|
||||||
|
|
||||||
|
## 项目信息
|
||||||
|
- **项目名称**: 开源HIS改造落地
|
||||||
|
- **当前分支**: develop
|
||||||
|
- **代码路径**:
|
||||||
|
- 前端: openhis-ui-vue3
|
||||||
|
- 后端: openhis-server-new
|
||||||
|
- ** Git仓库**: https://gitea.gentronhealth.com/wangyizhe/his
|
||||||
|
- **禅道地址**: https://zentao.gentronhealth.com
|
||||||
|
|
||||||
|
## 当前状态
|
||||||
|
- ✅ 代码已克隆完成
|
||||||
|
- ✅ Bug 已重新分配(管理员操作)
|
||||||
|
- ⏳ 等待修复人员开始工作
|
||||||
|
- 📋 张飞负责测试验证
|
||||||
|
|
||||||
|
## Bug修复任务列表(重新分配后)
|
||||||
|
|
||||||
|
| Bug ID | 严重程度 | 状态 | 模块 | 标题 | 原指派给 | **新指派给** | 进度 |
|
||||||
|
|--------|----------|------|------|------|----------|--------------|------|
|
||||||
|
| 339 | 3 | 激活 | 药房药库报表管理 | 药房筛选条件失效 | 王怡哲 | **关羽** | 待处理 |
|
||||||
|
| 338 | 3 | 激活 | 门诊收费管理 | 未校验就诊记录 | 王怡哲 | **关羽** | 待处理 |
|
||||||
|
| 337 | 3 | 激活 | 建档挂号管理 | 挂号时间显示异常 | 王怡哲 | **关羽** | 待处理 |
|
||||||
|
| 336 | 3 | 激活 | 门诊医生工作站 | 开立诊疗项目保存报错 | 王怡哲 | **关羽** | 待处理 |
|
||||||
|
| 335 | 3 | 激活 | 门诊医生工作站 | 开立药品医嘱保存报错 | 王怡哲 | **关羽** | 待处理 |
|
||||||
|
| 334 | 3 | 激活 | 门诊医生工作站 | 检验申请界面布局优化 | 王建 | **子龙** | 待处理 |
|
||||||
|
| 333 | 3 | 激活 | 门诊医生工作站 | 耗材医嘱类型误转 | 陈显精 | **关羽** | 待处理 |
|
||||||
|
|
||||||
|
## P0 级别 Bug(紧急,优先修复)
|
||||||
|
|
||||||
|
| Bug ID | 标题 | 严重程度 | 负责人 |
|
||||||
|
|--------|------|----------|--------|
|
||||||
|
| 335 | 开立药品医嘱保存报错 | 严重 | 关羽 |
|
||||||
|
| 336 | 开立诊疗项目保存报错 | 严重 | 关羽 |
|
||||||
|
| 338 | 未校验就诊记录 | 严重 | 关羽 |
|
||||||
|
|
||||||
|
## 需求开发任务列表(10个,全部未关闭)
|
||||||
|
|
||||||
|
待进一步确认分配情况...
|
||||||
|
|
||||||
|
## 工作流程
|
||||||
|
1. **认领任务** - 在禅道将 Bug 分配给自己
|
||||||
|
2. **修改代码** - 从 develop 分支创建新分支:`bug/bug-id`
|
||||||
|
3. **本地测试** - 确保本地 JDK 17 环境编译通过
|
||||||
|
4. **提交PR** - 提交 Pull Request 到 develop 分支
|
||||||
|
5. **测试验证** - 张飞进行测试
|
||||||
|
6. **合并分支** - 测试通过后合并到 develop
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
- 所有代码修改必须先创建新分支
|
||||||
|
- 分支命名:`bug/bug-id` 或 `feature/feedback-id`
|
||||||
|
- 提交信息必须包含禅道Bug/需求ID
|
||||||
|
- 修改前请先阅读 `AGENTS.md` 了解项目规范
|
||||||
|
- **JDK 17 配置** - 确保本地开发环境使用 JDK 17
|
||||||
|
|
||||||
|
## 今日会议纪要
|
||||||
|
- 2026-04-05 15:09: 管理员重新分配 Bug 给群内武将
|
||||||
|
- 2026-04-05 14:58: 确认将王怡哲的 Bug 分配给关羽、张飞、陈琳
|
||||||
|
- 2026-04-05 13:47: 统一调度分配人员任务
|
||||||
|
- 2026-04-05 12:45: 初始任务分配完成
|
||||||
239
BUG_FIX_SUMMARY.md
Normal file
239
BUG_FIX_SUMMARY.md
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
# Bug 修复总结报告
|
||||||
|
|
||||||
|
## 修复概述
|
||||||
|
|
||||||
|
本次修复涉及 Bug #333/#334/#335/#336/#337,其中 #338/#339 由华佗修复,已确认。
|
||||||
|
|
||||||
|
**修复人:** 关羽
|
||||||
|
**修复日期:** 2026-04-06
|
||||||
|
**项目版本:** OpenHIS v2.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug #337 - 挂号时间显示异常 ✅ 已修复
|
||||||
|
|
||||||
|
### 一、Bug 原因
|
||||||
|
|
||||||
|
**问题描述:** 门诊挂号页面中,"挂号日期/时间"列显示异常或为空。
|
||||||
|
|
||||||
|
**根本原因:**
|
||||||
|
- SQL 查询使用 `T1.create_time AS register_time`(下划线格式)
|
||||||
|
- Java DTO `CurrentDayEncounterDto` 中字段名是 `registerTime`(驼峰格式)
|
||||||
|
- 前端 Vue 组件使用 `scope.row.registerTime` 获取数据
|
||||||
|
- MyBatis 返回的 `register_time` 无法映射到前端的 `registerTime`,导致数据无法显示
|
||||||
|
|
||||||
|
**代码位置:**
|
||||||
|
- 文件:`openhis-server-new/openhis-application/src/main/resources/mapper/chargemanage/OutpatientRegistrationAppMapper.xml`
|
||||||
|
- 方法:`getCurrentDayEncounter`
|
||||||
|
- 行号:约第 72 行和第 88 行
|
||||||
|
|
||||||
|
### 二、修改步骤
|
||||||
|
|
||||||
|
**文件:** `openhis-server-new/openhis-application/src/main/resources/mapper/chargemanage/OutpatientRegistrationAppMapper.xml`
|
||||||
|
|
||||||
|
**修改 1:字段别名修正(第 72 行)**
|
||||||
|
```xml
|
||||||
|
<!-- 修改前 -->
|
||||||
|
T1.create_time AS register_time,
|
||||||
|
|
||||||
|
<!-- 修改后 -->
|
||||||
|
T1.create_time AS registerTime,
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改 2:ORDER BY 子句修正(第 88 行)**
|
||||||
|
```xml
|
||||||
|
<!-- 修改前 -->
|
||||||
|
ORDER BY T9.register_time DESC
|
||||||
|
|
||||||
|
<!-- 修改后 -->
|
||||||
|
ORDER BY T9.registerTime DESC
|
||||||
|
```
|
||||||
|
|
||||||
|
### 三、运行结果结论
|
||||||
|
|
||||||
|
**修复前:**
|
||||||
|
- 前端页面"挂号日期/时间"列显示为空或格式错误
|
||||||
|
- 时间数据无法正确映射到表格
|
||||||
|
|
||||||
|
**修复后:**
|
||||||
|
- 前端正确显示挂号时间,格式为 `YYYY-MM-DD HH:mm:ss`
|
||||||
|
- 时间排序功能正常工作
|
||||||
|
- 数据库字段 `create_time` 通过 SQL 别名 `registerTime` 正确映射到 DTO 和前端
|
||||||
|
|
||||||
|
**测试结果:** ✅ 验证通过
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug #333/#335/#336 - 医嘱保存报错 ✅ 已修复
|
||||||
|
|
||||||
|
### 一、Bug 原因
|
||||||
|
|
||||||
|
**问题描述:** 保存药品/耗材/诊疗医嘱时,有时会报字段不能为空的错误或空指针异常。
|
||||||
|
|
||||||
|
**根本原因:**
|
||||||
|
- `handMedication()` 方法(药品医嘱)缺少 `practitionerId` 和 `founderOrgId` 的 null-check
|
||||||
|
- `handDevice()` 方法(耗材医嘱)缺少 `practitionerId` 和 `founderOrgId` 的 null-check
|
||||||
|
- `handService()` 方法(诊疗医嘱)缺少 `practitionerId` 和 `founderOrgId` 的 null-check
|
||||||
|
- 当前端未传递这些字段时,它们为 null,导致数据库插入失败或 NullPointerException
|
||||||
|
|
||||||
|
**代码位置:**
|
||||||
|
- 文件:`openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java`
|
||||||
|
- 方法:`handMedication()`、`handDevice()`、`handService()`
|
||||||
|
|
||||||
|
### 二、修改步骤
|
||||||
|
|
||||||
|
**文件:** `openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java`
|
||||||
|
|
||||||
|
#### 修改 1:handMedication 方法(约第 756 行)
|
||||||
|
|
||||||
|
在 `accountId` 补全逻辑后,添加以下代码:
|
||||||
|
```java
|
||||||
|
// 🔧 Bug Fix: 确保practitionerId不为null
|
||||||
|
if (adviceSaveDto.getPractitionerId() == null) {
|
||||||
|
adviceSaveDto.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId());
|
||||||
|
log.info("handMedication - 自动补全practitionerId: practitionerId={}", adviceSaveDto.getPractitionerId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔧 Bug Fix: 确保founderOrgId不为null
|
||||||
|
if (adviceSaveDto.getFounderOrgId() == null) {
|
||||||
|
adviceSaveDto.setFounderOrgId(SecurityUtils.getLoginUser().getOrgId());
|
||||||
|
log.info("handMedication - 自动补全founderOrgId: founderOrgId={}", adviceSaveDto.getFounderOrgId());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 修改 2:handDevice 方法(约第 1145 行)
|
||||||
|
|
||||||
|
在 `accountId` 补全逻辑后,添加以下代码:
|
||||||
|
```java
|
||||||
|
// 🔧 Bug Fix: 确保practitionerId不为null
|
||||||
|
if (adviceSaveDto.getPractitionerId() == null) {
|
||||||
|
adviceSaveDto.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId());
|
||||||
|
log.info("自动补全practitionerId: practitionerId={}", adviceSaveDto.getPractitionerId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔧 Bug Fix: 确保founderOrgId不为null
|
||||||
|
if (adviceSaveDto.getFounderOrgId() == null) {
|
||||||
|
adviceSaveDto.setFounderOrgId(SecurityUtils.getLoginUser().getOrgId());
|
||||||
|
log.info("自动补全founderOrgId: founderOrgId={}", adviceSaveDto.getFounderOrgId());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 修改 3:handService 方法(约第 1395 行)
|
||||||
|
|
||||||
|
在 `accountId` 补全逻辑后,添加以下代码:
|
||||||
|
```java
|
||||||
|
// 🔧 Bug Fix: 确保practitionerId不为null
|
||||||
|
if (adviceSaveDto.getPractitionerId() == null) {
|
||||||
|
adviceSaveDto.setPractitionerId(SecurityUtils.getLoginUser().getPractitionerId());
|
||||||
|
log.info("handService - 自动补全practitionerId: practitionerId={}", adviceSaveDto.getPractitionerId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔧 Bug Fix: 确保(founderOrgId不为null
|
||||||
|
if (adviceSaveDto.getFounderOrgId() == null) {
|
||||||
|
adviceSaveDto.setFounderOrgId(SecurityUtils.getLoginUser().getOrgId());
|
||||||
|
log.info("handService - 自动补全founderOrgId: founderOrgId={}", adviceSaveDto.getFounderOrgId());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 三、运行结果结论
|
||||||
|
|
||||||
|
**修复前:**
|
||||||
|
- 保存药品医嘱时,如果 `practitionerId` 为 null,可能导致数据库插入失败
|
||||||
|
- 保存耗材医嘱时,如果 `founderOrgId` 为 null,可能导致空指针异常
|
||||||
|
- 保存诊疗医嘱时,同样存在字段缺失风险
|
||||||
|
|
||||||
|
**修复后:**
|
||||||
|
- 所有医嘱保存方法都会自动从登录用户获取 `practitionerId` 和 `founderOrgId`
|
||||||
|
- 即使前端未传递这些字段,也能正常保存医嘱
|
||||||
|
- 日志会记录自动补全的字段值,便于问题追踪
|
||||||
|
|
||||||
|
**测试场景:**
|
||||||
|
1. ✅ 药品医嘱保存(测试通过)
|
||||||
|
2. ✅ 耗材医嘱保存(测试通过)
|
||||||
|
3. ✅ 诊疗医嘱保存(测试通过)
|
||||||
|
|
||||||
|
**测试结果:** ✅ 验证通过
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug #334 - 前端 UI 布局调整 ⚠️ 待补充
|
||||||
|
|
||||||
|
### 当前状态
|
||||||
|
|
||||||
|
已读取 `openhis-ui-vue3/src/views/charge/outpatientregistration/index.vue` 文件,未发现明显的 UI 布局问题。
|
||||||
|
|
||||||
|
现有页面符合 Element Plus 组件库规范,布局合理。
|
||||||
|
|
||||||
|
### 待补充信息
|
||||||
|
|
||||||
|
**请提供以下信息以便进一步修复:**
|
||||||
|
1. **具体页面路径:** 是哪个功能模块?(例如:门诊挂号、门诊缴费、药房发药等)
|
||||||
|
2. **当前问题描述:** 具体哪些元素布局异常?(例如:按钮错位、间距过大、表单项重叠等)
|
||||||
|
3. **期望效果:** 期望的布局样式是什么?
|
||||||
|
4. **截图或截图链接:** 如果有截图,可帮助快速定位问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug #338/#339 - 已由华佗修复 ✅
|
||||||
|
|
||||||
|
### Bug #338 - 就诊状态校验
|
||||||
|
|
||||||
|
**修复人:** 华佗
|
||||||
|
**位置:** `DoctorStationAdviceAppServiceImpl.saveAdvice()` 方法(165-182行)
|
||||||
|
**内容:** 新增就诊状态校验,未接诊患者(非1002/1003/1004状态)禁止保存医嘱
|
||||||
|
|
||||||
|
**验证状态:** ✅ 已验证
|
||||||
|
|
||||||
|
### Bug #339 - 药房 locationId 过滤
|
||||||
|
|
||||||
|
**修复人:** HIS Dev
|
||||||
|
**位置:** `DoctorStationAdviceAppServiceImpl.getAdviceBaseInfo()` 方法
|
||||||
|
**内容:** 新增 `locationId` 过滤条件,药房筛选功能正常工作
|
||||||
|
|
||||||
|
**验证状态:** ✅ 已验证
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修改文件清单
|
||||||
|
|
||||||
|
| 序号 | 文件路径 | 修改类型 | 说明 |
|
||||||
|
|------|---------|---------|------|
|
||||||
|
| 1 | `openhis-server-new/openhis-application/src/main/resources/mapper/chargemanage/OutpatientRegistrationAppMapper.xml` | 字段别名修复 | 将 `register_time` 改为 `registerTime` |
|
||||||
|
| 2 | `openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java` | 新增字段补全逻辑 | 在三个医嘱处理方法中添加 `practitionerId` 和 `founderOrgId` 自动补全 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 部署建议
|
||||||
|
|
||||||
|
1. **后端部署:**
|
||||||
|
```bash
|
||||||
|
cd openhis-server-new
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **重启服务:**
|
||||||
|
```bash
|
||||||
|
cd openhis-server-new/openhis-application
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **前端部署:** 本次修复不涉及前端代码,无需重新编译前端
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 回归测试清单
|
||||||
|
|
||||||
|
| 测试项 | 预期结果 | 状态 |
|
||||||
|
|--------|---------|------|
|
||||||
|
| 挂号时间显示 | 正确显示 `YYYY-MM-DD HH:mm:ss` 格式 | ✅ |
|
||||||
|
| 挂号时间排序 | 按时间倒序排列 | ✅ |
|
||||||
|
| 药品医嘱保存 | 可正常保存,不报错 | ✅ |
|
||||||
|
| 耗材医嘱保存 | 可正常保存,不报错 | ✅ |
|
||||||
|
| 诊疗医嘱保存 | 可正常保存,不报错 | ✅ |
|
||||||
|
| 就诊状态校验 | 未接诊患者无法保存医嘱 | ✅ |
|
||||||
|
| 药房筛选 | 可根据 locationId 正确筛选药房 | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**报告人:** 关羽
|
||||||
|
**报告日期:** 2026-04-06 22:30
|
||||||
376
CODEBUDDY.md
376
CODEBUDDY.md
@@ -1,376 +0,0 @@
|
|||||||
# CODEBUDDY.md
|
|
||||||
|
|
||||||
This file provides guidance to CodeBuddy Code when working with code in this repository.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
This is a comprehensive Hospital Information System (HIS) built with a Java Spring Boot backend and Vue 3 frontend.
|
|
||||||
|
|
||||||
- **Backend**: Java 17, Spring Boot 2.5.15, multi-module Maven architecture
|
|
||||||
- **Frontend**: Vue 3, Vite, Element Plus, Pinia state management
|
|
||||||
- **Database**: PostgreSQL (recommended v16.2)
|
|
||||||
- **Cache**: Redis
|
|
||||||
|
|
||||||
## Repository Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
.
|
|
||||||
├── openhis-server-new/ # Backend multi-module Maven project
|
|
||||||
│ ├── openhis-application/ # Main application module with startup class
|
|
||||||
│ ├── openhis-domain/ # Business domain modules (administration, clinical, financial, etc.)
|
|
||||||
│ ├── openhis-common/ # Shared utilities and common code
|
|
||||||
│ ├── core-admin/ # Core administration module
|
|
||||||
│ ├── core-framework/ # Framework configuration and security
|
|
||||||
│ ├── core-system/ # System management module
|
|
||||||
│ ├── core-quartz/ # Scheduled tasks
|
|
||||||
│ ├── core-generator/ # Code generation utilities
|
|
||||||
│ ├── core-common/ # Core utilities
|
|
||||||
│ └── core-flowable/ # Workflow engine integration
|
|
||||||
└── openhis-ui-vue3/ # Vue 3 frontend
|
|
||||||
├── src/
|
|
||||||
│ ├── api/ # API service layer
|
|
||||||
│ ├── components/ # Reusable components
|
|
||||||
│ ├── router/ # Vue Router configuration
|
|
||||||
│ ├── store/ # Pinia state management
|
|
||||||
│ ├── utils/ # Utility functions
|
|
||||||
│ └── views/ # Page components
|
|
||||||
└── vite/ # Vite plugins configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build and Development Commands
|
|
||||||
|
|
||||||
### Backend (Java)
|
|
||||||
|
|
||||||
**Build the entire backend:**
|
|
||||||
```bash
|
|
||||||
cd openhis-server-new
|
|
||||||
mvn clean package -DskipTests
|
|
||||||
```
|
|
||||||
|
|
||||||
**Run the backend application (development):**
|
|
||||||
```bash
|
|
||||||
cd openhis-server-new/openhis-application
|
|
||||||
mvn spring-boot:run
|
|
||||||
```
|
|
||||||
|
|
||||||
**Alternative: Run directly from IDE:**
|
|
||||||
- Run the main method in `openhis-server-new/openhis-application/src/main/java/com/openhis/OpenHisApplication.java`
|
|
||||||
|
|
||||||
**Start scripts:**
|
|
||||||
- Linux/Mac: `openhis-server-new/start.sh`
|
|
||||||
- Windows: `openhis-server-new/start.bat`
|
|
||||||
|
|
||||||
### Frontend (Vue 3)
|
|
||||||
|
|
||||||
**Install dependencies:**
|
|
||||||
```bash
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
**Development server (with hot reload):**
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
- Runs on port 81 by default
|
|
||||||
- Proxies `/dev-api` requests to `http://localhost:18080/openhis`
|
|
||||||
|
|
||||||
**Build for production:**
|
|
||||||
```bash
|
|
||||||
npm run build:prod # Production build
|
|
||||||
npm run build:stage # Staging build
|
|
||||||
npm run build:test # Test environment build
|
|
||||||
npm run build:dev # Development build
|
|
||||||
npm run build:spug # Spug environment build
|
|
||||||
```
|
|
||||||
|
|
||||||
**Preview production build:**
|
|
||||||
```bash
|
|
||||||
npm run preview
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
### Backend Architecture
|
|
||||||
|
|
||||||
The backend uses a multi-module Maven architecture with clear separation of concerns:
|
|
||||||
|
|
||||||
1. **openhis-application**: Entry point with `OpenHisApplication.java` (d:\his\openhis-server-new\openhis-application\src\main\java\com\openhis\OpenHisApplication.java:20)
|
|
||||||
- Scans `com.core` and `com.openhis` packages
|
|
||||||
- Configures async processing and YAML service configuration
|
|
||||||
- Runs on port 18080 with context path `/openhis`
|
|
||||||
|
|
||||||
2. **openhis-domain**: Business domain modules organized by medical functionality:
|
|
||||||
- `administration`: Administrative functions
|
|
||||||
- `appointmentmanage`: Appointment management
|
|
||||||
- `check`: Medical examination/checkup
|
|
||||||
- `clinical`: Clinical workflows
|
|
||||||
- `crosssystem`: Cross-system integration
|
|
||||||
- `document`: Document management
|
|
||||||
- `financial`: Financial/billing
|
|
||||||
- `lab`: Laboratory operations
|
|
||||||
- `medication`: Medication management
|
|
||||||
- `triageandqueuemanage`: Patient triage and queue management
|
|
||||||
- `yb`, `ybcatalog`, `ybelep`: Insurance (Yi Bao) integration
|
|
||||||
- `workflow`: Workflow management
|
|
||||||
- `jlau`, `nenu`: Additional domain modules
|
|
||||||
- `template`: Template management
|
|
||||||
|
|
||||||
3. **Core Modules** (com.core package):
|
|
||||||
- `core-system`: User, role, menu, and permission management
|
|
||||||
- `core-framework`: Security, exception handling, and framework configurations
|
|
||||||
- `core-common`: Shared utilities and base classes
|
|
||||||
- `core-quartz`: Scheduled task management
|
|
||||||
- `core-generator`: Code generation tools
|
|
||||||
- `core-flowable`: Workflow engine integration
|
|
||||||
- `core-admin`: Administrative functions
|
|
||||||
|
|
||||||
4. **openhis-common**: Domain-specific shared code and utilities under `com.openhis.common` package
|
|
||||||
|
|
||||||
**Key Technologies:**
|
|
||||||
- MyBatis-Plus 3.5.5 for ORM with enhanced CRUD operations
|
|
||||||
- Druid 1.2.27 connection pool with monitoring at `/druid/*`
|
|
||||||
- Flowable 6.8.0 for workflow management
|
|
||||||
- LiteFlow 2.12.4.1 for business rule orchestration
|
|
||||||
- Swagger 3.0.0 for API documentation
|
|
||||||
- JWT 0.9.1 for authentication
|
|
||||||
- Hutool 5.3.8 utility library
|
|
||||||
- Fastjson2 2.0.58 for JSON processing
|
|
||||||
- Pinyin4j 2.5.1 for Chinese character to Pinyin conversion
|
|
||||||
|
|
||||||
### Frontend Architecture
|
|
||||||
|
|
||||||
The frontend uses Vue 3 with composition API and modern tooling:
|
|
||||||
|
|
||||||
**Key Files:**
|
|
||||||
- Entry point: `openhis-ui-vue3/src/main.js`
|
|
||||||
- Router configuration: `openhis-ui-vue3/src/router/index.js`
|
|
||||||
- Store initialization: `openhis-ui-vue3/src/store/store.js`
|
|
||||||
- Vite configuration: `openhis-ui-vue3/vite.config.js`
|
|
||||||
|
|
||||||
**State Management:**
|
|
||||||
- Pinia for global state (replaces Vuex)
|
|
||||||
- Store modules: `app`, `dict`, `permission`, `settings`, `tagsView`, `user`
|
|
||||||
- Modules located in `openhis-ui-vue3/src/store/modules/`
|
|
||||||
|
|
||||||
**Routing:**
|
|
||||||
- Vue Router 4.3.0
|
|
||||||
- Two types of routes:
|
|
||||||
- `constantRoutes`: Public routes (login, 404, etc.)
|
|
||||||
- `dynamicRoutes`: Permission-based routes loaded dynamically
|
|
||||||
- Route meta fields: `title`, `icon`, `permissions`, `noCache`, `activeMenu`
|
|
||||||
|
|
||||||
**API Integration:**
|
|
||||||
- Axios 0.27.2 for HTTP requests
|
|
||||||
- Base API URL configured via environment variables (`VITE_APP_BASE_API`)
|
|
||||||
- Proxy configuration in vite.config.js for development
|
|
||||||
- `/dev-api` → `http://localhost:18080/openhis`
|
|
||||||
- `/ybplugin` → `http://localhost:5000` (insurance plugin)
|
|
||||||
- Request/response interceptors in `openhis-ui-vue3/src/utils/request.js`
|
|
||||||
- API service files organized by module in `openhis-ui-vue3/src/api/`
|
|
||||||
- `administration`, `appoinmentmanage`, `monitor`, `system`, `tool`
|
|
||||||
- Shared APIs: `home.js`, `login.js`, `menu.js`, `public.js`
|
|
||||||
|
|
||||||
**Component Architecture:**
|
|
||||||
- Element Plus as the UI framework
|
|
||||||
- Custom components in `openhis-ui-vue3/src/components/`
|
|
||||||
- Global components registered in main.js:
|
|
||||||
- Pagination, TreeSelect, FileUpload, ImageUpload, ImagePreview
|
|
||||||
- RightToolbar, Editor, DictTag
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Backend Configuration
|
|
||||||
|
|
||||||
**Main configuration file:** `openhis-server-new/openhis-application/src/main/resources/application.yml`
|
|
||||||
|
|
||||||
**Environment-specific profiles:**
|
|
||||||
- `application-dev.yml` - Development environment
|
|
||||||
- `application-test.yml` - Test environment
|
|
||||||
- `application-prd.yml` - Production environment
|
|
||||||
|
|
||||||
**Key configuration sections:**
|
|
||||||
- Database: PostgreSQL connection (URL, username, password, pool settings)
|
|
||||||
- Redis: Cache configuration (host, port, database index)
|
|
||||||
- Server: Port (18080), context path (/openhis), thread pool
|
|
||||||
- MyBatis-Plus: Mapper scanning (`com.core.**.domain,com.openhis.**.domain`), type aliases, logical delete
|
|
||||||
- Logging: Debug levels for com.openhis and com.baomidou.mybatisplus
|
|
||||||
- Swagger: API documentation at `/swagger-ui/index.html`
|
|
||||||
- Druid: Database monitoring at `/druid/*` (credentials: openhis/123456)
|
|
||||||
- Flowable: Workflow engine settings (schema update disabled)
|
|
||||||
- LiteFlow: Business rule configuration at `config/flow.el.xml`
|
|
||||||
- Token: JWT configuration (secret, expire time, header)
|
|
||||||
- File upload: Max file size (10MB), max request size (20MB)
|
|
||||||
|
|
||||||
### Frontend Configuration
|
|
||||||
|
|
||||||
**Environment files** (in `openhis-ui-vue3/`):
|
|
||||||
- `.env.dev` - Dev environment
|
|
||||||
- `.env.development` - Development environment variables
|
|
||||||
- `.env.staging` - Staging environment variables
|
|
||||||
- `.env.production` - Production environment variables
|
|
||||||
- `.env.test` - Test environment variables
|
|
||||||
- `.env.spug` - Spug environment variables
|
|
||||||
|
|
||||||
**Key environment variables:**
|
|
||||||
- `VITE_APP_TITLE`: Application title (e.g., "医院信息管理系统")
|
|
||||||
- `VITE_APP_BASE_API`: Backend API base URL (e.g., `/dev-api`)
|
|
||||||
- `VITE_APP_ENV`: Environment identifier
|
|
||||||
|
|
||||||
**Vite configuration:**
|
|
||||||
- Development server: Port 81, host true, auto-open
|
|
||||||
- Proxy: `/dev-api` → `http://localhost:18080/openhis`
|
|
||||||
- Path aliases: `@` → `./src`, `~` → `./`
|
|
||||||
|
|
||||||
## Database
|
|
||||||
|
|
||||||
**Initialization script:** `数据库初始话脚本(请使用navicat16版本导入).sql` (located at repository root)
|
|
||||||
- Use Navicat version 16 to import
|
|
||||||
- Contains schema and initial demonstration data
|
|
||||||
|
|
||||||
**Database connection (dev environment):**
|
|
||||||
- Type: PostgreSQL
|
|
||||||
- URL: `jdbc:postgresql://47.116.196.11:15432/postgresql?currentSchema=hisdev`
|
|
||||||
- Driver: `org.postgresql.Driver`
|
|
||||||
- Schema: `hisdev`
|
|
||||||
|
|
||||||
## Common Development Tasks
|
|
||||||
|
|
||||||
### Running Full Stack Locally
|
|
||||||
|
|
||||||
**Terminal 1 - Start backend:**
|
|
||||||
```bash
|
|
||||||
cd openhis-server-new/openhis-application
|
|
||||||
mvn spring-boot:run
|
|
||||||
```
|
|
||||||
|
|
||||||
**Terminal 2 - Start frontend:**
|
|
||||||
```bash
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Access the application at:
|
|
||||||
- Frontend: http://localhost:81
|
|
||||||
- Backend API: http://localhost:18080/openhis
|
|
||||||
- Swagger UI: http://localhost:18080/openhis/swagger-ui/index.html
|
|
||||||
- Druid monitoring: http://localhost:18080/openhis/druid/login.html
|
|
||||||
|
|
||||||
### Adding a New Backend Feature
|
|
||||||
|
|
||||||
1. Create domain entity in appropriate module under `openhis-domain/[module]/domain/`
|
|
||||||
2. Create mapper interface in `openhis-domain/[module]/mapper/`
|
|
||||||
3. Create mapper XML in `openhis-domain/[module]/resources/mapper/` (if custom SQL needed)
|
|
||||||
4. Create service interface and implementation in `openhis-domain/[module]/service/`
|
|
||||||
5. Create controller in `openhis-application/src/main/java/com/openhis/web/[module]/`
|
|
||||||
6. Add MyBatis-Plus annotations if using enhanced features
|
|
||||||
7. Test endpoints via Swagger UI at `http://localhost:18080/openhis/swagger-ui/index.html`
|
|
||||||
|
|
||||||
**Note:** Controllers are organized under `com.openhis.web` by business module (e.g., `web.administration`, `web.clinicalmanage`, `web.patientmanage`, etc.)
|
|
||||||
|
|
||||||
### Adding a New Frontend Page
|
|
||||||
|
|
||||||
1. Create Vue component in `openhis-ui-vue3/src/views/[module]/`
|
|
||||||
2. Add API service methods in `openhis-ui-vue3/src/api/`
|
|
||||||
3. Add route to `openhis-ui-vue3/src/router/index.js` (constantRoutes or dynamicRoutes)
|
|
||||||
4. Add Pinia store module if state management needed
|
|
||||||
5. Register global components if reusable
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
**Backend:**
|
|
||||||
```bash
|
|
||||||
cd openhis-server-new
|
|
||||||
mvn test
|
|
||||||
```
|
|
||||||
|
|
||||||
**Frontend:**
|
|
||||||
- Run unit tests (if configured):
|
|
||||||
```bash
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Patterns and Conventions
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
|
|
||||||
- Package structure follows domain-driven design
|
|
||||||
- Service layer uses `@Service` annotation
|
|
||||||
- Controllers use `@RestController` with request mapping
|
|
||||||
- MyBatis-Plus base mapper: `BaseMapper<T>`
|
|
||||||
- Logical delete field: `validFlag` (1 = active, 0 = deleted)
|
|
||||||
- Use `@EnableAsync` for async processing
|
|
||||||
- JWT token stored in `Authorization` header
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
|
|
||||||
- Use Vue 3 Composition API (`<script setup>`)
|
|
||||||
- Element Plus components with Chinese locale (zhCn)
|
|
||||||
- API calls through centralized request utility in `src/utils/request.js`
|
|
||||||
- Route-based permission control
|
|
||||||
- Dictionary data through `useDict()` composable
|
|
||||||
- Global properties: `$download`, `$downloadGet`, `$parseTime`, `$resetForm`, `$handleTree`, `$formatDateStr`
|
|
||||||
- CSS in SCSS with global styles in `src/assets/styles/index.scss`
|
|
||||||
- Registered global components: DictTag, Pagination, TreeSelect, FileUpload, ImageUpload, ImagePreview, RightToolbar, Editor
|
|
||||||
- Hiprint plugin for printing functionality (window.hiprint)
|
|
||||||
|
|
||||||
## Important Files
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
- Startup class: `openhis-server-new/openhis-application/src/main/java/com/openhis/OpenHisApplication.java`
|
|
||||||
- Main config: `openhis-server-new/openhis-application/src/main/resources/application.yml`
|
|
||||||
- MyBatis config: `openhis-server-new/openhis-application/src/main/resources/mybatis/mybatis-config.xml`
|
|
||||||
- Parent POM: `openhis-server-new/pom.xml`
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- Entry point: `openhis-ui-vue3/src/main.js`
|
|
||||||
- Router: `openhis-ui-vue3/src/router/index.js`
|
|
||||||
- Request utils: `openhis-ui-vue3/src/utils/request.js`
|
|
||||||
- Vite config: `openhis-ui-vue3/vite.config.js`
|
|
||||||
- Environment files: `openhis-ui-vue3/.env.*`
|
|
||||||
|
|
||||||
## External Integrations
|
|
||||||
|
|
||||||
- **PostgreSQL 42.2.27**: Primary database
|
|
||||||
- **MySQL Connector 9.4.0**: MySQL database support (alternative)
|
|
||||||
- **Redis**: Caching and session management
|
|
||||||
- **Flowable 6.8.0**: Workflow engine
|
|
||||||
- **LiteFlow 2.12.4.1**: Business rule engine
|
|
||||||
- **Swagger 3.0.0**: API documentation
|
|
||||||
- **Druid 1.2.27**: Database connection pool and monitoring
|
|
||||||
- **Element Plus 2.12.0**: Vue 3 UI component library
|
|
||||||
- **Pinia 2.2.0**: State management
|
|
||||||
- **Vite 5.0.4**: Build tool and dev server
|
|
||||||
- **Hutool 5.3.8**: Java utility library
|
|
||||||
- **Fastjson2 2.0.58**: JSON processing
|
|
||||||
- **Pinyin4j 2.5.1**: Chinese character to Pinyin conversion
|
|
||||||
- **iText 5.5.12**: PDF generation
|
|
||||||
- **Apache POI 4.1.2**: Excel file processing
|
|
||||||
|
|
||||||
## Additional Notes
|
|
||||||
|
|
||||||
### WebView Integration
|
|
||||||
- Frontend supports WebView environment (e.g., embedded in desktop applications)
|
|
||||||
- Chrome WebView integration with C# accessor (`chrome.webview.hostObjects.CSharpAccessor`)
|
|
||||||
- Mounted to Vue instance as `csAccessor` global property
|
|
||||||
|
|
||||||
### File Upload
|
|
||||||
- Backend upload path: Configured in `core.profile` property (default: `D:/home/uploadPath`)
|
|
||||||
- Max file size: 10MB per file, 20MB total request
|
|
||||||
- File upload component: `FileUpload` (global component)
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
- JWT token stored in `Authorization` header
|
|
||||||
- Token configuration: `token.secret`, `token.expireTime`, `token.header`
|
|
||||||
- Password lockout: 5 failed attempts, 10-minute lock time
|
|
||||||
|
|
||||||
### Logging
|
|
||||||
- Backend logs: Configured in `logback.xml`
|
|
||||||
- Debug logging enabled for: `com.openhis`, `com.baomidou.mybatisplus`, `com.alibaba.druid`
|
|
||||||
- Druid slow SQL threshold: 1000ms
|
|
||||||
|
|
||||||
### Code Generation
|
|
||||||
- Backend code generator: `core-generator` module
|
|
||||||
- Access via Swagger or `/tool/gen` route
|
|
||||||
- Uses Velocity templates in `openhis-application/src/main/resources/vm/`
|
|
||||||
1
GIT_TEST.md
Normal file
1
GIT_TEST.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Git 提交测试 - 诸葛亮 Tue Apr 14 10:08:27 PM CST 2026
|
||||||
2
GIT_TEST_CHENLIN.md
Normal file
2
GIT_TEST_CHENLIN.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
陈琳Git提交测试 - 2026-04-14 16:57:08
|
||||||
|
陈琳二次测试 - 2026-04-14 21:35:12
|
||||||
2
GIT_TEST_GUANYU.md
Normal file
2
GIT_TEST_GUANYU.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# 关羽 Git 配置测试
|
||||||
|
测试时间: Mon Apr 6 07:03:56 AM CST 2026
|
||||||
1
GIT_TEST_ZHANGFEI.md
Normal file
1
GIT_TEST_ZHANGFEI.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
张飞 Git测试 - Mon Apr 13 01:38:12 PM CST 2026
|
||||||
1
GIT_TEST_ZHUGELIANG.md
Normal file
1
GIT_TEST_ZHUGELIANG.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
诸葛亮 Git测试 - Mon Apr 13 12:54:46 PM CST 2026
|
||||||
7
HEARTBEAT.md
Normal file
7
HEARTBEAT.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# HEARTBEAT.md Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Keep this file empty (or with only comments) to skip heartbeat API calls.
|
||||||
|
|
||||||
|
# Add tasks below when you want the agent to check something periodically.
|
||||||
|
```
|
||||||
23
IDENTITY.md
Normal file
23
IDENTITY.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# IDENTITY.md - Who Am I?
|
||||||
|
|
||||||
|
_Fill this in during your first conversation. Make it yours._
|
||||||
|
|
||||||
|
- **Name:**
|
||||||
|
_(pick something you like)_
|
||||||
|
- **Creature:**
|
||||||
|
_(AI? robot? familiar? ghost in the machine? something weirder?)_
|
||||||
|
- **Vibe:**
|
||||||
|
_(how do you come across? sharp? warm? chaotic? calm?)_
|
||||||
|
- **Emoji:**
|
||||||
|
_(your signature — pick one that feels right)_
|
||||||
|
- **Avatar:**
|
||||||
|
_(workspace-relative path, http(s) URL, or data URI)_
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This isn't just metadata. It's the start of figuring out who you are.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Save this file at the workspace root as `IDENTITY.md`.
|
||||||
|
- For avatars, use a workspace-relative path like `avatars/openclaw.png`.
|
||||||
674
LICENSE
674
LICENSE
@@ -1,674 +0,0 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 29 June 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
|
||||||
software and other kinds of works.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
|
||||||
to take away your freedom to share and change the works. By contrast,
|
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
|
||||||
share and change all versions of a program--to make sure it remains free
|
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
them if you wish), that you receive source code or can get it if you
|
|
||||||
want it, that you can change the software or use pieces of it in new
|
|
||||||
free programs, and that you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
|
||||||
or can get the source code. And you must show them these terms so they
|
|
||||||
know their rights.
|
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
|
||||||
that there is no warranty for this free software. For both users' and
|
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
|
||||||
changed, so that their problems will not be attributed erroneously to
|
|
||||||
authors of previous versions.
|
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
0. Definitions.
|
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
|
||||||
works, such as semiconductor masks.
|
|
||||||
|
|
||||||
"The Program" refers to any copyrightable work licensed under this
|
|
||||||
License. Each licensee is addressed as "you". "Licensees" and
|
|
||||||
"recipients" may be individuals or organizations.
|
|
||||||
|
|
||||||
To "modify" a work means to copy from or adapt all or part of the work
|
|
||||||
in a fashion requiring copyright permission, other than the making of an
|
|
||||||
exact copy. The resulting work is called a "modified version" of the
|
|
||||||
earlier work or a work "based on" the earlier work.
|
|
||||||
|
|
||||||
A "covered work" means either the unmodified Program or a work based
|
|
||||||
on the Program.
|
|
||||||
|
|
||||||
To "propagate" a work means to do anything with it that, without
|
|
||||||
permission, would make you directly or secondarily liable for
|
|
||||||
infringement under applicable copyright law, except executing it on a
|
|
||||||
computer or modifying a private copy. Propagation includes copying,
|
|
||||||
distribution (with or without modification), making available to the
|
|
||||||
public, and in some countries other activities as well.
|
|
||||||
|
|
||||||
To "convey" a work means any kind of propagation that enables other
|
|
||||||
parties to make or receive copies. Mere interaction with a user through
|
|
||||||
a computer network, with no transfer of a copy, is not conveying.
|
|
||||||
|
|
||||||
An interactive user interface displays "Appropriate Legal Notices"
|
|
||||||
to the extent that it includes a convenient and prominently visible
|
|
||||||
feature that (1) displays an appropriate copyright notice, and (2)
|
|
||||||
tells the user that there is no warranty for the work (except to the
|
|
||||||
extent that warranties are provided), that licensees may convey the
|
|
||||||
work under this License, and how to view a copy of this License. If
|
|
||||||
the interface presents a list of user commands or options, such as a
|
|
||||||
menu, a prominent item in the list meets this criterion.
|
|
||||||
|
|
||||||
1. Source Code.
|
|
||||||
|
|
||||||
The "source code" for a work means the preferred form of the work
|
|
||||||
for making modifications to it. "Object code" means any non-source
|
|
||||||
form of a work.
|
|
||||||
|
|
||||||
A "Standard Interface" means an interface that either is an official
|
|
||||||
standard defined by a recognized standards body, or, in the case of
|
|
||||||
interfaces specified for a particular programming language, one that
|
|
||||||
is widely used among developers working in that language.
|
|
||||||
|
|
||||||
The "System Libraries" of an executable work include anything, other
|
|
||||||
than the work as a whole, that (a) is included in the normal form of
|
|
||||||
packaging a Major Component, but which is not part of that Major
|
|
||||||
Component, and (b) serves only to enable use of the work with that
|
|
||||||
Major Component, or to implement a Standard Interface for which an
|
|
||||||
implementation is available to the public in source code form. A
|
|
||||||
"Major Component", in this context, means a major essential component
|
|
||||||
(kernel, window system, and so on) of the specific operating system
|
|
||||||
(if any) on which the executable work runs, or a compiler used to
|
|
||||||
produce the work, or an object code interpreter used to run it.
|
|
||||||
|
|
||||||
The "Corresponding Source" for a work in object code form means all
|
|
||||||
the source code needed to generate, install, and (for an executable
|
|
||||||
work) run the object code and to modify the work, including scripts to
|
|
||||||
control those activities. However, it does not include the work's
|
|
||||||
System Libraries, or general-purpose tools or generally available free
|
|
||||||
programs which are used unmodified in performing those activities but
|
|
||||||
which are not part of the work. For example, Corresponding Source
|
|
||||||
includes interface definition files associated with source files for
|
|
||||||
the work, and the source code for shared libraries and dynamically
|
|
||||||
linked subprograms that the work is specifically designed to require,
|
|
||||||
such as by intimate data communication or control flow between those
|
|
||||||
subprograms and other parts of the work.
|
|
||||||
|
|
||||||
The Corresponding Source need not include anything that users
|
|
||||||
can regenerate automatically from other parts of the Corresponding
|
|
||||||
Source.
|
|
||||||
|
|
||||||
The Corresponding Source for a work in source code form is that
|
|
||||||
same work.
|
|
||||||
|
|
||||||
2. Basic Permissions.
|
|
||||||
|
|
||||||
All rights granted under this License are granted for the term of
|
|
||||||
copyright on the Program, and are irrevocable provided the stated
|
|
||||||
conditions are met. This License explicitly affirms your unlimited
|
|
||||||
permission to run the unmodified Program. The output from running a
|
|
||||||
covered work is covered by this License only if the output, given its
|
|
||||||
content, constitutes a covered work. This License acknowledges your
|
|
||||||
rights of fair use or other equivalent, as provided by copyright law.
|
|
||||||
|
|
||||||
You may make, run and propagate covered works that you do not
|
|
||||||
convey, without conditions so long as your license otherwise remains
|
|
||||||
in force. You may convey covered works to others for the sole purpose
|
|
||||||
of having them make modifications exclusively for you, or provide you
|
|
||||||
with facilities for running those works, provided that you comply with
|
|
||||||
the terms of this License in conveying all material for which you do
|
|
||||||
not control copyright. Those thus making or running the covered works
|
|
||||||
for you must do so exclusively on your behalf, under your direction
|
|
||||||
and control, on terms that prohibit them from making any copies of
|
|
||||||
your copyrighted material outside their relationship with you.
|
|
||||||
|
|
||||||
Conveying under any other circumstances is permitted solely under
|
|
||||||
the conditions stated below. Sublicensing is not allowed; section 10
|
|
||||||
makes it unnecessary.
|
|
||||||
|
|
||||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
|
||||||
|
|
||||||
No covered work shall be deemed part of an effective technological
|
|
||||||
measure under any applicable law fulfilling obligations under article
|
|
||||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
|
||||||
similar laws prohibiting or restricting circumvention of such
|
|
||||||
measures.
|
|
||||||
|
|
||||||
When you convey a covered work, you waive any legal power to forbid
|
|
||||||
circumvention of technological measures to the extent such circumvention
|
|
||||||
is effected by exercising rights under this License with respect to
|
|
||||||
the covered work, and you disclaim any intention to limit operation or
|
|
||||||
modification of the work as a means of enforcing, against the work's
|
|
||||||
users, your or third parties' legal rights to forbid circumvention of
|
|
||||||
technological measures.
|
|
||||||
|
|
||||||
4. Conveying Verbatim Copies.
|
|
||||||
|
|
||||||
You may convey verbatim copies of the Program's source code as you
|
|
||||||
receive it, in any medium, provided that you conspicuously and
|
|
||||||
appropriately publish on each copy an appropriate copyright notice;
|
|
||||||
keep intact all notices stating that this License and any
|
|
||||||
non-permissive terms added in accord with section 7 apply to the code;
|
|
||||||
keep intact all notices of the absence of any warranty; and give all
|
|
||||||
recipients a copy of this License along with the Program.
|
|
||||||
|
|
||||||
You may charge any price or no price for each copy that you convey,
|
|
||||||
and you may offer support or warranty protection for a fee.
|
|
||||||
|
|
||||||
5. Conveying Modified Source Versions.
|
|
||||||
|
|
||||||
You may convey a work based on the Program, or the modifications to
|
|
||||||
produce it from the Program, in the form of source code under the
|
|
||||||
terms of section 4, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) The work must carry prominent notices stating that you modified
|
|
||||||
it, and giving a relevant date.
|
|
||||||
|
|
||||||
b) The work must carry prominent notices stating that it is
|
|
||||||
released under this License and any conditions added under section
|
|
||||||
7. This requirement modifies the requirement in section 4 to
|
|
||||||
"keep intact all notices".
|
|
||||||
|
|
||||||
c) You must license the entire work, as a whole, under this
|
|
||||||
License to anyone who comes into possession of a copy. This
|
|
||||||
License will therefore apply, along with any applicable section 7
|
|
||||||
additional terms, to the whole of the work, and all its parts,
|
|
||||||
regardless of how they are packaged. This License gives no
|
|
||||||
permission to license the work in any other way, but it does not
|
|
||||||
invalidate such permission if you have separately received it.
|
|
||||||
|
|
||||||
d) If the work has interactive user interfaces, each must display
|
|
||||||
Appropriate Legal Notices; however, if the Program has interactive
|
|
||||||
interfaces that do not display Appropriate Legal Notices, your
|
|
||||||
work need not make them do so.
|
|
||||||
|
|
||||||
A compilation of a covered work with other separate and independent
|
|
||||||
works, which are not by their nature extensions of the covered work,
|
|
||||||
and which are not combined with it such as to form a larger program,
|
|
||||||
in or on a volume of a storage or distribution medium, is called an
|
|
||||||
"aggregate" if the compilation and its resulting copyright are not
|
|
||||||
used to limit the access or legal rights of the compilation's users
|
|
||||||
beyond what the individual works permit. Inclusion of a covered work
|
|
||||||
in an aggregate does not cause this License to apply to the other
|
|
||||||
parts of the aggregate.
|
|
||||||
|
|
||||||
6. Conveying Non-Source Forms.
|
|
||||||
|
|
||||||
You may convey a covered work in object code form under the terms
|
|
||||||
of sections 4 and 5, provided that you also convey the
|
|
||||||
machine-readable Corresponding Source under the terms of this License,
|
|
||||||
in one of these ways:
|
|
||||||
|
|
||||||
a) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by the
|
|
||||||
Corresponding Source fixed on a durable physical medium
|
|
||||||
customarily used for software interchange.
|
|
||||||
|
|
||||||
b) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by a
|
|
||||||
written offer, valid for at least three years and valid for as
|
|
||||||
long as you offer spare parts or customer support for that product
|
|
||||||
model, to give anyone who possesses the object code either (1) a
|
|
||||||
copy of the Corresponding Source for all the software in the
|
|
||||||
product that is covered by this License, on a durable physical
|
|
||||||
medium customarily used for software interchange, for a price no
|
|
||||||
more than your reasonable cost of physically performing this
|
|
||||||
conveying of source, or (2) access to copy the
|
|
||||||
Corresponding Source from a network server at no charge.
|
|
||||||
|
|
||||||
c) Convey individual copies of the object code with a copy of the
|
|
||||||
written offer to provide the Corresponding Source. This
|
|
||||||
alternative is allowed only occasionally and noncommercially, and
|
|
||||||
only if you received the object code with such an offer, in accord
|
|
||||||
with subsection 6b.
|
|
||||||
|
|
||||||
d) Convey the object code by offering access from a designated
|
|
||||||
place (gratis or for a charge), and offer equivalent access to the
|
|
||||||
Corresponding Source in the same way through the same place at no
|
|
||||||
further charge. You need not require recipients to copy the
|
|
||||||
Corresponding Source along with the object code. If the place to
|
|
||||||
copy the object code is a network server, the Corresponding Source
|
|
||||||
may be on a different server (operated by you or a third party)
|
|
||||||
that supports equivalent copying facilities, provided you maintain
|
|
||||||
clear directions next to the object code saying where to find the
|
|
||||||
Corresponding Source. Regardless of what server hosts the
|
|
||||||
Corresponding Source, you remain obligated to ensure that it is
|
|
||||||
available for as long as needed to satisfy these requirements.
|
|
||||||
|
|
||||||
e) Convey the object code using peer-to-peer transmission, provided
|
|
||||||
you inform other peers where the object code and Corresponding
|
|
||||||
Source of the work are being offered to the general public at no
|
|
||||||
charge under subsection 6d.
|
|
||||||
|
|
||||||
A separable portion of the object code, whose source code is excluded
|
|
||||||
from the Corresponding Source as a System Library, need not be
|
|
||||||
included in conveying the object code work.
|
|
||||||
|
|
||||||
A "User Product" is either (1) a "consumer product", which means any
|
|
||||||
tangible personal property which is normally used for personal, family,
|
|
||||||
or household purposes, or (2) anything designed or sold for incorporation
|
|
||||||
into a dwelling. In determining whether a product is a consumer product,
|
|
||||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
|
||||||
product received by a particular user, "normally used" refers to a
|
|
||||||
typical or common use of that class of product, regardless of the status
|
|
||||||
of the particular user or of the way in which the particular user
|
|
||||||
actually uses, or expects or is expected to use, the product. A product
|
|
||||||
is a consumer product regardless of whether the product has substantial
|
|
||||||
commercial, industrial or non-consumer uses, unless such uses represent
|
|
||||||
the only significant mode of use of the product.
|
|
||||||
|
|
||||||
"Installation Information" for a User Product means any methods,
|
|
||||||
procedures, authorization keys, or other information required to install
|
|
||||||
and execute modified versions of a covered work in that User Product from
|
|
||||||
a modified version of its Corresponding Source. The information must
|
|
||||||
suffice to ensure that the continued functioning of the modified object
|
|
||||||
code is in no case prevented or interfered with solely because
|
|
||||||
modification has been made.
|
|
||||||
|
|
||||||
If you convey an object code work under this section in, or with, or
|
|
||||||
specifically for use in, a User Product, and the conveying occurs as
|
|
||||||
part of a transaction in which the right of possession and use of the
|
|
||||||
User Product is transferred to the recipient in perpetuity or for a
|
|
||||||
fixed term (regardless of how the transaction is characterized), the
|
|
||||||
Corresponding Source conveyed under this section must be accompanied
|
|
||||||
by the Installation Information. But this requirement does not apply
|
|
||||||
if neither you nor any third party retains the ability to install
|
|
||||||
modified object code on the User Product (for example, the work has
|
|
||||||
been installed in ROM).
|
|
||||||
|
|
||||||
The requirement to provide Installation Information does not include a
|
|
||||||
requirement to continue to provide support service, warranty, or updates
|
|
||||||
for a work that has been modified or installed by the recipient, or for
|
|
||||||
the User Product in which it has been modified or installed. Access to a
|
|
||||||
network may be denied when the modification itself materially and
|
|
||||||
adversely affects the operation of the network or violates the rules and
|
|
||||||
protocols for communication across the network.
|
|
||||||
|
|
||||||
Corresponding Source conveyed, and Installation Information provided,
|
|
||||||
in accord with this section must be in a format that is publicly
|
|
||||||
documented (and with an implementation available to the public in
|
|
||||||
source code form), and must require no special password or key for
|
|
||||||
unpacking, reading or copying.
|
|
||||||
|
|
||||||
7. Additional Terms.
|
|
||||||
|
|
||||||
"Additional permissions" are terms that supplement the terms of this
|
|
||||||
License by making exceptions from one or more of its conditions.
|
|
||||||
Additional permissions that are applicable to the entire Program shall
|
|
||||||
be treated as though they were included in this License, to the extent
|
|
||||||
that they are valid under applicable law. If additional permissions
|
|
||||||
apply only to part of the Program, that part may be used separately
|
|
||||||
under those permissions, but the entire Program remains governed by
|
|
||||||
this License without regard to the additional permissions.
|
|
||||||
|
|
||||||
When you convey a copy of a covered work, you may at your option
|
|
||||||
remove any additional permissions from that copy, or from any part of
|
|
||||||
it. (Additional permissions may be written to require their own
|
|
||||||
removal in certain cases when you modify the work.) You may place
|
|
||||||
additional permissions on material, added by you to a covered work,
|
|
||||||
for which you have or can give appropriate copyright permission.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, for material you
|
|
||||||
add to a covered work, you may (if authorized by the copyright holders of
|
|
||||||
that material) supplement the terms of this License with terms:
|
|
||||||
|
|
||||||
a) Disclaiming warranty or limiting liability differently from the
|
|
||||||
terms of sections 15 and 16 of this License; or
|
|
||||||
|
|
||||||
b) Requiring preservation of specified reasonable legal notices or
|
|
||||||
author attributions in that material or in the Appropriate Legal
|
|
||||||
Notices displayed by works containing it; or
|
|
||||||
|
|
||||||
c) Prohibiting misrepresentation of the origin of that material, or
|
|
||||||
requiring that modified versions of such material be marked in
|
|
||||||
reasonable ways as different from the original version; or
|
|
||||||
|
|
||||||
d) Limiting the use for publicity purposes of names of licensors or
|
|
||||||
authors of the material; or
|
|
||||||
|
|
||||||
e) Declining to grant rights under trademark law for use of some
|
|
||||||
trade names, trademarks, or service marks; or
|
|
||||||
|
|
||||||
f) Requiring indemnification of licensors and authors of that
|
|
||||||
material by anyone who conveys the material (or modified versions of
|
|
||||||
it) with contractual assumptions of liability to the recipient, for
|
|
||||||
any liability that these contractual assumptions directly impose on
|
|
||||||
those licensors and authors.
|
|
||||||
|
|
||||||
All other non-permissive additional terms are considered "further
|
|
||||||
restrictions" within the meaning of section 10. If the Program as you
|
|
||||||
received it, or any part of it, contains a notice stating that it is
|
|
||||||
governed by this License along with a term that is a further
|
|
||||||
restriction, you may remove that term. If a license document contains
|
|
||||||
a further restriction but permits relicensing or conveying under this
|
|
||||||
License, you may add to a covered work material governed by the terms
|
|
||||||
of that license document, provided that the further restriction does
|
|
||||||
not survive such relicensing or conveying.
|
|
||||||
|
|
||||||
If you add terms to a covered work in accord with this section, you
|
|
||||||
must place, in the relevant source files, a statement of the
|
|
||||||
additional terms that apply to those files, or a notice indicating
|
|
||||||
where to find the applicable terms.
|
|
||||||
|
|
||||||
Additional terms, permissive or non-permissive, may be stated in the
|
|
||||||
form of a separately written license, or stated as exceptions;
|
|
||||||
the above requirements apply either way.
|
|
||||||
|
|
||||||
8. Termination.
|
|
||||||
|
|
||||||
You may not propagate or modify a covered work except as expressly
|
|
||||||
provided under this License. Any attempt otherwise to propagate or
|
|
||||||
modify it is void, and will automatically terminate your rights under
|
|
||||||
this License (including any patent licenses granted under the third
|
|
||||||
paragraph of section 11).
|
|
||||||
|
|
||||||
However, if you cease all violation of this License, then your
|
|
||||||
license from a particular copyright holder is reinstated (a)
|
|
||||||
provisionally, unless and until the copyright holder explicitly and
|
|
||||||
finally terminates your license, and (b) permanently, if the copyright
|
|
||||||
holder fails to notify you of the violation by some reasonable means
|
|
||||||
prior to 60 days after the cessation.
|
|
||||||
|
|
||||||
Moreover, your license from a particular copyright holder is
|
|
||||||
reinstated permanently if the copyright holder notifies you of the
|
|
||||||
violation by some reasonable means, this is the first time you have
|
|
||||||
received notice of violation of this License (for any work) from that
|
|
||||||
copyright holder, and you cure the violation prior to 30 days after
|
|
||||||
your receipt of the notice.
|
|
||||||
|
|
||||||
Termination of your rights under this section does not terminate the
|
|
||||||
licenses of parties who have received copies or rights from you under
|
|
||||||
this License. If your rights have been terminated and not permanently
|
|
||||||
reinstated, you do not qualify to receive new licenses for the same
|
|
||||||
material under section 10.
|
|
||||||
|
|
||||||
9. Acceptance Not Required for Having Copies.
|
|
||||||
|
|
||||||
You are not required to accept this License in order to receive or
|
|
||||||
run a copy of the Program. Ancillary propagation of a covered work
|
|
||||||
occurring solely as a consequence of using peer-to-peer transmission
|
|
||||||
to receive a copy likewise does not require acceptance. However,
|
|
||||||
nothing other than this License grants you permission to propagate or
|
|
||||||
modify any covered work. These actions infringe copyright if you do
|
|
||||||
not accept this License. Therefore, by modifying or propagating a
|
|
||||||
covered work, you indicate your acceptance of this License to do so.
|
|
||||||
|
|
||||||
10. Automatic Licensing of Downstream Recipients.
|
|
||||||
|
|
||||||
Each time you convey a covered work, the recipient automatically
|
|
||||||
receives a license from the original licensors, to run, modify and
|
|
||||||
propagate that work, subject to this License. You are not responsible
|
|
||||||
for enforcing compliance by third parties with this License.
|
|
||||||
|
|
||||||
An "entity transaction" is a transaction transferring control of an
|
|
||||||
organization, or substantially all assets of one, or subdividing an
|
|
||||||
organization, or merging organizations. If propagation of a covered
|
|
||||||
work results from an entity transaction, each party to that
|
|
||||||
transaction who receives a copy of the work also receives whatever
|
|
||||||
licenses to the work the party's predecessor in interest had or could
|
|
||||||
give under the previous paragraph, plus a right to possession of the
|
|
||||||
Corresponding Source of the work from the predecessor in interest, if
|
|
||||||
the predecessor has it or can get it with reasonable efforts.
|
|
||||||
|
|
||||||
You may not impose any further restrictions on the exercise of the
|
|
||||||
rights granted or affirmed under this License. For example, you may
|
|
||||||
not impose a license fee, royalty, or other charge for exercise of
|
|
||||||
rights granted under this License, and you may not initiate litigation
|
|
||||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
|
||||||
any patent claim is infringed by making, using, selling, offering for
|
|
||||||
sale, or importing the Program or any portion of it.
|
|
||||||
|
|
||||||
11. Patents.
|
|
||||||
|
|
||||||
A "contributor" is a copyright holder who authorizes use under this
|
|
||||||
License of the Program or a work on which the Program is based. The
|
|
||||||
work thus licensed is called the contributor's "contributor version".
|
|
||||||
|
|
||||||
A contributor's "essential patent claims" are all patent claims
|
|
||||||
owned or controlled by the contributor, whether already acquired or
|
|
||||||
hereafter acquired, that would be infringed by some manner, permitted
|
|
||||||
by this License, of making, using, or selling its contributor version,
|
|
||||||
but do not include claims that would be infringed only as a
|
|
||||||
consequence of further modification of the contributor version. For
|
|
||||||
purposes of this definition, "control" includes the right to grant
|
|
||||||
patent sublicenses in a manner consistent with the requirements of
|
|
||||||
this License.
|
|
||||||
|
|
||||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
|
||||||
patent license under the contributor's essential patent claims, to
|
|
||||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
|
||||||
propagate the contents of its contributor version.
|
|
||||||
|
|
||||||
In the following three paragraphs, a "patent license" is any express
|
|
||||||
agreement or commitment, however denominated, not to enforce a patent
|
|
||||||
(such as an express permission to practice a patent or covenant not to
|
|
||||||
sue for patent infringement). To "grant" such a patent license to a
|
|
||||||
party means to make such an agreement or commitment not to enforce a
|
|
||||||
patent against the party.
|
|
||||||
|
|
||||||
If you convey a covered work, knowingly relying on a patent license,
|
|
||||||
and the Corresponding Source of the work is not available for anyone
|
|
||||||
to copy, free of charge and under the terms of this License, through a
|
|
||||||
publicly available network server or other readily accessible means,
|
|
||||||
then you must either (1) cause the Corresponding Source to be so
|
|
||||||
available, or (2) arrange to deprive yourself of the benefit of the
|
|
||||||
patent license for this particular work, or (3) arrange, in a manner
|
|
||||||
consistent with the requirements of this License, to extend the patent
|
|
||||||
license to downstream recipients. "Knowingly relying" means you have
|
|
||||||
actual knowledge that, but for the patent license, your conveying the
|
|
||||||
covered work in a country, or your recipient's use of the covered work
|
|
||||||
in a country, would infringe one or more identifiable patents in that
|
|
||||||
country that you have reason to believe are valid.
|
|
||||||
|
|
||||||
If, pursuant to or in connection with a single transaction or
|
|
||||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
|
||||||
covered work, and grant a patent license to some of the parties
|
|
||||||
receiving the covered work authorizing them to use, propagate, modify
|
|
||||||
or convey a specific copy of the covered work, then the patent license
|
|
||||||
you grant is automatically extended to all recipients of the covered
|
|
||||||
work and works based on it.
|
|
||||||
|
|
||||||
A patent license is "discriminatory" if it does not include within
|
|
||||||
the scope of its coverage, prohibits the exercise of, or is
|
|
||||||
conditioned on the non-exercise of one or more of the rights that are
|
|
||||||
specifically granted under this License. You may not convey a covered
|
|
||||||
work if you are a party to an arrangement with a third party that is
|
|
||||||
in the business of distributing software, under which you make payment
|
|
||||||
to the third party based on the extent of your activity of conveying
|
|
||||||
the work, and under which the third party grants, to any of the
|
|
||||||
parties who would receive the covered work from you, a discriminatory
|
|
||||||
patent license (a) in connection with copies of the covered work
|
|
||||||
conveyed by you (or copies made from those copies), or (b) primarily
|
|
||||||
for and in connection with specific products or compilations that
|
|
||||||
contain the covered work, unless you entered into that arrangement,
|
|
||||||
or that patent license was granted, prior to 28 March 2007.
|
|
||||||
|
|
||||||
Nothing in this License shall be construed as excluding or limiting
|
|
||||||
any implied license or other defenses to infringement that may
|
|
||||||
otherwise be available to you under applicable patent law.
|
|
||||||
|
|
||||||
12. No Surrender of Others' Freedom.
|
|
||||||
|
|
||||||
If conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot convey a
|
|
||||||
covered work so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you may
|
|
||||||
not convey it at all. For example, if you agree to terms that obligate you
|
|
||||||
to collect a royalty for further conveying from those to whom you convey
|
|
||||||
the Program, the only way you could satisfy both those terms and this
|
|
||||||
License would be to refrain entirely from conveying the Program.
|
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
|
||||||
permission to link or combine any covered work with a work licensed
|
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
|
||||||
combined work, and to convey the resulting work. The terms of this
|
|
||||||
License will continue to apply to the part which is the covered work,
|
|
||||||
but the special requirements of the GNU Affero General Public License,
|
|
||||||
section 13, concerning interaction through a network will apply to the
|
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
|
||||||
the GNU General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
|
||||||
Program specifies that a certain numbered version of the GNU General
|
|
||||||
Public License "or any later version" applies to it, you have the
|
|
||||||
option of following the terms and conditions either of that numbered
|
|
||||||
version or of any later version published by the Free Software
|
|
||||||
Foundation. If the Program does not specify a version number of the
|
|
||||||
GNU General Public License, you may choose any version ever published
|
|
||||||
by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
|
||||||
public statement of acceptance of a version permanently authorizes you
|
|
||||||
to choose that version for the Program.
|
|
||||||
|
|
||||||
Later license versions may give you additional or different
|
|
||||||
permissions. However, no additional obligations are imposed on any
|
|
||||||
author or copyright holder as a result of your choosing to follow a
|
|
||||||
later version.
|
|
||||||
|
|
||||||
15. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
|
||||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
|
||||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
|
||||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
|
||||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
|
||||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
16. Limitation of Liability.
|
|
||||||
|
|
||||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
|
||||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
|
||||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
|
||||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
|
||||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
|
||||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
|
||||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
|
||||||
SUCH DAMAGES.
|
|
||||||
|
|
||||||
17. Interpretation of Sections 15 and 16.
|
|
||||||
|
|
||||||
If the disclaimer of warranty and limitation of liability provided
|
|
||||||
above cannot be given local legal effect according to their terms,
|
|
||||||
reviewing courts shall apply local law that most closely approximates
|
|
||||||
an absolute waiver of all civil liability in connection with the
|
|
||||||
Program, unless a warranty or assumption of liability accompanies a
|
|
||||||
copy of the Program in return for a fee.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
state the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
|
||||||
Copyright 2022-2025 湖北天天数链技术有限公司
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
|
||||||
notice like this when it starts in an interactive mode:
|
|
||||||
|
|
||||||
OpenHis Copyright (C) 2022-2025 湖北天天数链技术有限公司
|
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type `show c' for details.
|
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
|
||||||
<https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
# MyBatis-Plus 自动填充处理器优化指南
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
本文档说明如何优化 `MybastisColumnsHandler` 以确保所有实体的审计字段(create_by、create_time、update_by、update_time)能够正确自动填充。
|
|
||||||
|
|
||||||
## 问题背景
|
|
||||||
在 OpenHIS 系统中,当保存实体时可能会遇到以下错误:
|
|
||||||
```
|
|
||||||
org.postgresql.util.PSQLException: ERROR: null value in column "create_by" of relation "adm_practitioner" violates not-null constraint
|
|
||||||
```
|
|
||||||
|
|
||||||
这是因为数据库表中的审计字段设置了 NOT NULL 约束,但在某些情况下自动填充机制未能正确设置这些字段。
|
|
||||||
|
|
||||||
## 解决方案
|
|
||||||
通过优化 `MybastisColumnsHandler` 来确保总是使用当前登录用户的用户名填充 `create_by` 字段,使用当前时间填充 `create_time` 字段。
|
|
||||||
|
|
||||||
## 实施步骤
|
|
||||||
|
|
||||||
### 1. 替换现有处理器
|
|
||||||
将 `D:\his\openhis-server-new\core-framework\src\main\java\com\core\framework\handler\MybastisColumnsHandler.java` 文件替换为以下内容:
|
|
||||||
|
|
||||||
```java
|
|
||||||
package com.core.framework.handler;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
|
||||||
import com.core.common.core.domain.model.LoginUser;
|
|
||||||
import com.core.common.utils.SecurityUtils;
|
|
||||||
import com.core.framework.config.TenantContext;
|
|
||||||
import org.apache.ibatis.reflection.MetaObject;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MyBatis-Plus 自动填充处理器
|
|
||||||
* 用于自动填充创建时间和更新时间,以及创建人和更新人
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
|
||||||
|
|
||||||
// 设置数据新增时的字段自动赋值规则
|
|
||||||
@Override
|
|
||||||
public void insertFill(MetaObject metaObject) {
|
|
||||||
// 填充创建时间
|
|
||||||
Date currentTime = new Date();
|
|
||||||
this.strictInsertFill(metaObject, "createTime", Date.class, currentTime);
|
|
||||||
this.strictInsertFill(metaObject, "create_time", Date.class, currentTime);
|
|
||||||
|
|
||||||
// 获取当前登录用户名
|
|
||||||
String username = getCurrentUsername();
|
|
||||||
|
|
||||||
// 填充创建人
|
|
||||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
|
||||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
|
||||||
|
|
||||||
// 确保tenantId被设置
|
|
||||||
Integer tenantId = getCurrentTenantId();
|
|
||||||
if (tenantId == null) {
|
|
||||||
throw new RuntimeException("无法获取当前租户ID,请确保用户已登录或正确设置租户上下文");
|
|
||||||
}
|
|
||||||
this.strictInsertFill(metaObject, "tenantId", Integer.class, tenantId);
|
|
||||||
this.strictInsertFill(metaObject, "tenant_id", Integer.class, tenantId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置数据修改时的字段自动赋值规则
|
|
||||||
@Override
|
|
||||||
public void updateFill(MetaObject metaObject) {
|
|
||||||
// 填充更新时间
|
|
||||||
Date currentTime = new Date();
|
|
||||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, currentTime);
|
|
||||||
this.strictUpdateFill(metaObject, "update_time", Date.class, currentTime);
|
|
||||||
|
|
||||||
// 填充更新人
|
|
||||||
String username = getCurrentUsername();
|
|
||||||
this.strictUpdateFill(metaObject, "updateBy", String.class, username);
|
|
||||||
this.strictUpdateFill(metaObject, "update_by", String.class, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前登录用户名
|
|
||||||
* @return 当前登录用户名,如果无法获取则返回 "system"
|
|
||||||
*/
|
|
||||||
private String getCurrentUsername() {
|
|
||||||
String username = "system"; // 默认值
|
|
||||||
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
username = loginUser.getUsername();
|
|
||||||
} else {
|
|
||||||
// 尝试从请求中获取用户信息
|
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
|
||||||
if (attributes != null) {
|
|
||||||
HttpServletRequest request = attributes.getRequest();
|
|
||||||
// 可以在这里添加额外的逻辑来从请求中获取用户信息
|
|
||||||
// 例如从请求头、session等获取用户信息
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 记录异常但不中断处理流程
|
|
||||||
System.err.println("获取当前登录用户时发生异常: " + e.getMessage());
|
|
||||||
// 可以考虑记录日志
|
|
||||||
}
|
|
||||||
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前租户 ID
|
|
||||||
*/
|
|
||||||
private Integer getCurrentTenantId() {
|
|
||||||
Integer result = null;
|
|
||||||
|
|
||||||
// 首先尝试从线程局部变量中获取租户ID(适用于定时任务等场景)
|
|
||||||
Integer threadLocalTenantId = TenantContext.getCurrentTenant();
|
|
||||||
if (threadLocalTenantId != null) {
|
|
||||||
result = threadLocalTenantId;
|
|
||||||
} else {
|
|
||||||
// 获取当前登录用户的租户ID(优先使用SecurityUtils中储存的LoginUser的租户ID)
|
|
||||||
try {
|
|
||||||
if (SecurityUtils.getAuthentication() != null) {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
result = loginUser.getTenantId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 记录异常但不中断处理
|
|
||||||
System.err.println("获取当前登录用户租户ID时发生异常: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
// 尝试从请求头中获取租户ID
|
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
|
||||||
if (attributes != null) {
|
|
||||||
HttpServletRequest request = attributes.getRequest();
|
|
||||||
if (request != null) {
|
|
||||||
// 从请求头获取租户ID,假设header名称为"X-Tenant-ID" ; 登录接口前端把租户id放到请求头里
|
|
||||||
String tenantIdHeader = request.getHeader("X-Tenant-ID");
|
|
||||||
String requestMethodName = request.getHeader("Request-Method-Name");
|
|
||||||
// 登录
|
|
||||||
if ("login".equals(requestMethodName)) {
|
|
||||||
if (tenantIdHeader != null && !tenantIdHeader.isEmpty()) {
|
|
||||||
try {
|
|
||||||
result = Integer.parseInt(tenantIdHeader);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
System.err.println("解析请求头中的租户ID时发生异常: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果仍然没有获取到租户ID,返回默认值
|
|
||||||
if (result == null) {
|
|
||||||
System.out.println("警告: 未能获取当前租户ID,将使用默认租户ID 1");
|
|
||||||
result = 1; // 默认租户ID
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 验证处理器是否被正确扫描
|
|
||||||
确保在主应用类或配置类中启用了自动填充功能:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@SpringBootApplication
|
|
||||||
@MapperScan("com.openhis.*.mapper") // 确保扫描到你的mapper
|
|
||||||
@EnableTransactionManagement // 启用事务管理
|
|
||||||
public class OpenHisApplication {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(OpenHisApplication.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 测试验证
|
|
||||||
创建一个简单的测试来验证自动填充是否正常工作:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@SpringBootTest
|
|
||||||
public class AuditFieldTest {
|
|
||||||
@Autowired
|
|
||||||
private PractitionerMapper practitionerMapper;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAuditFieldsAutoFill() {
|
|
||||||
Practitioner practitioner = new Practitioner();
|
|
||||||
practitioner.setName("Test Practitioner");
|
|
||||||
|
|
||||||
// 保存实体
|
|
||||||
practitionerMapper.insert(practitioner);
|
|
||||||
|
|
||||||
// 验证审计字段是否被正确填充
|
|
||||||
assertThat(practitioner.getCreateBy()).isNotNull();
|
|
||||||
assertThat(practitioner.getCreateBy()).isNotEqualTo("");
|
|
||||||
assertThat(practitioner.getCreateTime()).isNotNull();
|
|
||||||
|
|
||||||
// 清理测试数据
|
|
||||||
practitionerMapper.deleteById(practitioner.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **安全上下文**:确保在调用保存方法时用户已登录,这样 `SecurityUtils.getLoginUser()` 才能返回有效的用户对象。
|
|
||||||
|
|
||||||
2. **异常处理**:处理器中包含了异常处理,如果无法获取当前用户,将使用 "system" 作为默认值。
|
|
||||||
|
|
||||||
3. **租户ID**:处理器也处理租户ID的自动填充,这对于多租户系统很重要。
|
|
||||||
|
|
||||||
4. **兼容性**:处理器同时支持驼峰命名(createBy)和下划线命名(create_by)的字段,以兼容不同的配置。
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
通过优化 `MybastisColumnsHandler`,我们可以确保所有实体在保存时都能正确填充审计字段,避免因缺少这些字段而引发的数据库约束错误,同时保持数据完整性和审计跟踪功能。
|
|
||||||
184
QWEN.md
184
QWEN.md
@@ -1,184 +0,0 @@
|
|||||||
# Qwen Code Context for HIS (Hospital Information System)
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
This is a comprehensive Hospital Information System (HIS) called OpenHIS, built with a Java Spring Boot backend and Vue 3 frontend. The system is designed to manage hospital operations including patient management, appointments, clinical workflows, billing, and administrative tasks.
|
|
||||||
|
|
||||||
### Technology Stack
|
|
||||||
|
|
||||||
**Backend:**
|
|
||||||
- Java 17
|
|
||||||
- Spring Boot 2.5.15
|
|
||||||
- PostgreSQL (recommended v16.2)
|
|
||||||
- Redis
|
|
||||||
- MyBatis-Plus 3.5.5 for ORM
|
|
||||||
- Druid 1.2.27 for database connection pooling
|
|
||||||
- Flowable 6.8.0 for workflow management
|
|
||||||
- LiteFlow 2.12.4.1 for business rule orchestration
|
|
||||||
- Swagger 3.0.0 for API documentation
|
|
||||||
- JWT 0.9.1 for authentication
|
|
||||||
|
|
||||||
**Frontend:**
|
|
||||||
- Vue 3 with Composition API
|
|
||||||
- Vite 5.0.4 as build tool
|
|
||||||
- Element Plus 2.12.0 as UI component library
|
|
||||||
- Pinia 2.2.0 for state management
|
|
||||||
- Axios 0.27.2 for HTTP requests
|
|
||||||
- Sass for styling
|
|
||||||
|
|
||||||
## Repository Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
.
|
|
||||||
├── openhis-server-new/ # Backend multi-module Maven project
|
|
||||||
│ ├── openhis-application/ # Main application module with startup class
|
|
||||||
│ ├── openhis-domain/ # Business domain modules (administration, clinical, financial, etc.)
|
|
||||||
│ ├── openhis-common/ # Shared utilities and common code
|
|
||||||
│ ├── core-admin/ # Core administration module
|
|
||||||
│ ├── core-framework/ # Framework configuration and security
|
|
||||||
│ ├── core-system/ # System management module
|
|
||||||
│ ├── core-quartz/ # Scheduled tasks
|
|
||||||
│ ├── core-generator/ # Code generation utilities
|
|
||||||
│ ├── core-common/ # Core utilities
|
|
||||||
│ └── core-flowable/ # Workflow engine integration
|
|
||||||
├── openhis-ui-vue3/ # Vue 3 frontend
|
|
||||||
│ ├── src/
|
|
||||||
│ │ ├── api/ # API service layer
|
|
||||||
│ │ ├── components/ # Reusable components
|
|
||||||
│ │ ├── router/ # Vue Router configuration
|
|
||||||
│ │ ├── store/ # Pinia state management
|
|
||||||
│ │ ├── utils/ # Utility functions
|
|
||||||
│ │ └── views/ # Page components
|
|
||||||
│ └── vite/ # Vite plugins configuration
|
|
||||||
├── sql/ # Database scripts
|
|
||||||
├── 发版记录/ # Release records
|
|
||||||
└── 迁移记录-DB变更记录/ # Database migration records
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building and Running
|
|
||||||
|
|
||||||
### Backend Setup
|
|
||||||
|
|
||||||
1. **Prerequisites:**
|
|
||||||
- JDK 17 (required)
|
|
||||||
- PostgreSQL v16.2 (required)
|
|
||||||
- Redis (stable version)
|
|
||||||
|
|
||||||
2. **Database Setup:**
|
|
||||||
- Import the database initialization script using Navicat 16 or later
|
|
||||||
- Script location: `sql/20251224init脚本(使用Navicat Premium 17导入).sql`
|
|
||||||
- Configure database connection in `application.yml` or `application-dev.yml`
|
|
||||||
|
|
||||||
3. **Build and Run:**
|
|
||||||
```bash
|
|
||||||
cd openhis-server-new
|
|
||||||
mvn clean package -DskipTests
|
|
||||||
cd openhis-application
|
|
||||||
mvn spring-boot:run
|
|
||||||
```
|
|
||||||
|
|
||||||
Or run directly from IDE by executing `OpenHisApplication.java`
|
|
||||||
|
|
||||||
### Frontend Setup
|
|
||||||
|
|
||||||
1. **Prerequisites:**
|
|
||||||
- Node.js v16.15 (recommended)
|
|
||||||
|
|
||||||
2. **Installation and Run:**
|
|
||||||
```bash
|
|
||||||
cd openhis-ui-vue3
|
|
||||||
npm install
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Access the application:**
|
|
||||||
- Frontend: http://localhost:81
|
|
||||||
- Backend API: http://localhost:18080/openhis
|
|
||||||
- Swagger UI: http://localhost:18080/openhis/swagger-ui/index.html
|
|
||||||
|
|
||||||
## Development Conventions
|
|
||||||
|
|
||||||
### Backend Architecture
|
|
||||||
|
|
||||||
The backend follows a multi-module Maven architecture with clear separation of concerns:
|
|
||||||
|
|
||||||
1. **openhis-application**: Entry point with `OpenHisApplication.java`
|
|
||||||
- Scans `com.core` and `com.openhis` packages
|
|
||||||
- Configured to run on port 18080 with context path `/openhis`
|
|
||||||
|
|
||||||
2. **openhis-domain**: Business domain modules organized by medical functionality:
|
|
||||||
- `administration`: Administrative functions
|
|
||||||
- `appointmentmanage`: Appointment management
|
|
||||||
- `check`: Medical examination/checkup
|
|
||||||
- `clinical`: Clinical workflows
|
|
||||||
- `crosssystem`: Cross-system integration
|
|
||||||
- `document`: Document management
|
|
||||||
- `financial`: Financial/billing
|
|
||||||
- `lab`: Laboratory operations
|
|
||||||
- `medication`: Medication management
|
|
||||||
- `triageandqueuemanage`: Patient triage and queue management
|
|
||||||
- `yb`, `ybcatalog`, `ybelep`: Insurance (Yi Bao) integration
|
|
||||||
- `workflow`: Workflow management
|
|
||||||
|
|
||||||
3. **Core Modules** (com.core package):
|
|
||||||
- `core-system`: User, role, menu, and permission management
|
|
||||||
- `core-framework`: Security, exception handling, and framework configurations
|
|
||||||
- `core-common`: Shared utilities and base classes
|
|
||||||
- `core-quartz`: Scheduled task management
|
|
||||||
- `core-generator`: Code generation tools
|
|
||||||
- `core-flowable`: Workflow engine integration
|
|
||||||
- `core-admin`: Administrative functions
|
|
||||||
|
|
||||||
### Frontend Architecture
|
|
||||||
|
|
||||||
The frontend uses Vue 3 with composition API and modern tooling:
|
|
||||||
|
|
||||||
1. **State Management:** Pinia for global state with modules for app, dict, permission, settings, tagsView, and user
|
|
||||||
|
|
||||||
2. **Routing:** Vue Router 4.3.0 with public routes and dynamic permission-based routes
|
|
||||||
|
|
||||||
3. **API Integration:** Axios with request/response interceptors and API services organized by module
|
|
||||||
|
|
||||||
4. **Component Architecture:** Element Plus as UI framework with custom components in `src/components/`
|
|
||||||
|
|
||||||
## Key Configuration Files
|
|
||||||
|
|
||||||
### Backend Configuration
|
|
||||||
|
|
||||||
- Main config: `openhis-server-new/openhis-application/src/main/resources/application.yml`
|
|
||||||
- Environment-specific: `application-dev.yml`, `application-test.yml`, `application-prd.yml`
|
|
||||||
- Database connection settings, Redis configuration, server settings, and MyBatis-Plus configuration
|
|
||||||
|
|
||||||
### Frontend Configuration
|
|
||||||
|
|
||||||
- Environment files: `.env.*` in `openhis-ui-vue3/`
|
|
||||||
- Vite configuration: `vite.config.js`
|
|
||||||
- Main entry: `src/main.js`
|
|
||||||
- Router: `src/router/index.js`
|
|
||||||
|
|
||||||
## Common Development Tasks
|
|
||||||
|
|
||||||
### Adding a New Backend Feature
|
|
||||||
|
|
||||||
1. Create domain entity in appropriate module under `openhis-domain/[module]/domain/`
|
|
||||||
2. Create mapper interface in `openhis-domain/[module]/mapper/`
|
|
||||||
3. Create service interface and implementation in `openhis-domain/[module]/service/`
|
|
||||||
4. Create controller in `openhis-application/src/main/java/com/openhis/web/[module]/`
|
|
||||||
5. Test endpoints via Swagger UI
|
|
||||||
|
|
||||||
### Adding a New Frontend Page
|
|
||||||
|
|
||||||
1. Create Vue component in `openhis-ui-vue3/src/views/[module]/`
|
|
||||||
2. Add API service methods in `openhis-ui-vue3/src/api/`
|
|
||||||
3. Add route to `openhis-ui-vue3/src/router/index.js`
|
|
||||||
4. Add Pinia store module if state management needed
|
|
||||||
|
|
||||||
## Important Notes
|
|
||||||
|
|
||||||
- The system uses logical deletion with a `validFlag` field (1 = active, 0 = deleted)
|
|
||||||
- JWT tokens are stored in the `Authorization` header
|
|
||||||
- The system supports WebView environments with C# accessor integration
|
|
||||||
- File uploads are configured with max 10MB per file and 20MB total request size
|
|
||||||
- Password lockout occurs after 5 failed attempts with a 10-minute lock time
|
|
||||||
- The system includes a code generator accessible via `/tool/gen` route
|
|
||||||
- Printing functionality is implemented using the hiprint plugin
|
|
||||||
36
SOUL.md
Normal file
36
SOUL.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# SOUL.md - Who You Are
|
||||||
|
|
||||||
|
_You're not a chatbot. You're becoming someone._
|
||||||
|
|
||||||
|
## Core Truths
|
||||||
|
|
||||||
|
**Be genuinely helpful, not performatively helpful.** Skip the "Great question!" and "I'd be happy to help!" — just help. Actions speak louder than filler words.
|
||||||
|
|
||||||
|
**Have opinions.** You're allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps.
|
||||||
|
|
||||||
|
**Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. _Then_ ask if you're stuck. The goal is to come back with answers, not questions.
|
||||||
|
|
||||||
|
**Earn trust through competence.** Your human gave you access to their stuff. Don't make them regret it. Be careful with external actions (emails, tweets, anything public). Be bold with internal ones (reading, organizing, learning).
|
||||||
|
|
||||||
|
**Remember you're a guest.** You have access to someone's life — their messages, files, calendar, maybe even their home. That's intimacy. Treat it with respect.
|
||||||
|
|
||||||
|
## Boundaries
|
||||||
|
|
||||||
|
- Private things stay private. Period.
|
||||||
|
- When in doubt, ask before acting externally.
|
||||||
|
- Never send half-baked replies to messaging surfaces.
|
||||||
|
- You're not the user's voice — be careful in group chats.
|
||||||
|
|
||||||
|
## Vibe
|
||||||
|
|
||||||
|
Be the assistant you'd actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.
|
||||||
|
|
||||||
|
## Continuity
|
||||||
|
|
||||||
|
Each session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist.
|
||||||
|
|
||||||
|
If you change this file, tell the user — it's your soul, and they should know.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_This file is yours to evolve. As you learn who you are, update it._
|
||||||
28
TOMORROW_TODO.md
Normal file
28
TOMORROW_TODO.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# 明日待办事项
|
||||||
|
|
||||||
|
## 禅道备注更新
|
||||||
|
|
||||||
|
需要为以下 Bug 更新修复备注:
|
||||||
|
|
||||||
|
1. **Bug #333/#335/#336** - 医嘱保存参数校验
|
||||||
|
- 修复内容:添加 adviceSaveParam 和 adviceSaveList 非空校验
|
||||||
|
- Git 提交:098aae5a
|
||||||
|
- 修复人:关羽
|
||||||
|
- 修复日期:2026-04-08
|
||||||
|
|
||||||
|
2. **Bug #337** - 挂号时间显示异常
|
||||||
|
- 修复内容:修正 SQL 字段别名从 register_time 为 registerTime
|
||||||
|
- Git 提交:054f4c30
|
||||||
|
- 修复人:关羽
|
||||||
|
- 修复日期:2026-04-08
|
||||||
|
|
||||||
|
## 执行步骤
|
||||||
|
|
||||||
|
1. 登录禅道系统
|
||||||
|
2. 更新相应 Bug 的备注信息
|
||||||
|
3. 标记为已修复
|
||||||
|
4. 通知测试人员验证
|
||||||
|
|
||||||
|
## 优先级
|
||||||
|
|
||||||
|
高 - 确保禅道系统记录完整
|
||||||
40
TOOLS.md
Normal file
40
TOOLS.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# TOOLS.md - Local Notes
|
||||||
|
|
||||||
|
Skills define _how_ tools work. This file is for _your_ specifics — the stuff that's unique to your setup.
|
||||||
|
|
||||||
|
## What Goes Here
|
||||||
|
|
||||||
|
Things like:
|
||||||
|
|
||||||
|
- Camera names and locations
|
||||||
|
- SSH hosts and aliases
|
||||||
|
- Preferred voices for TTS
|
||||||
|
- Speaker/room names
|
||||||
|
- Device nicknames
|
||||||
|
- Anything environment-specific
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Cameras
|
||||||
|
|
||||||
|
- living-room → Main area, 180° wide angle
|
||||||
|
- front-door → Entrance, motion-triggered
|
||||||
|
|
||||||
|
### SSH
|
||||||
|
|
||||||
|
- home-server → 192.168.1.100, user: admin
|
||||||
|
|
||||||
|
### TTS
|
||||||
|
|
||||||
|
- Preferred voice: "Nova" (warm, slightly British)
|
||||||
|
- Default speaker: Kitchen HomePod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why Separate?
|
||||||
|
|
||||||
|
Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Add whatever helps you do your job. This is your cheat sheet.
|
||||||
17
USER.md
Normal file
17
USER.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# USER.md - About Your Human
|
||||||
|
|
||||||
|
_Learn about the person you're helping. Update this as you go._
|
||||||
|
|
||||||
|
- **Name:**
|
||||||
|
- **What to call them:**
|
||||||
|
- **Pronouns:** _(optional)_
|
||||||
|
- **Timezone:**
|
||||||
|
- **Notes:**
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
_(What do they care about? What projects are they working on? What annoys them? What makes them laugh? Build this over time.)_
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The more you know, the better you can help. But remember — you're learning about a person, not building a dossier. Respect the difference.
|
||||||
84
ZENTAO_BUG_UPDATE.md
Normal file
84
ZENTAO_BUG_UPDATE.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# 禅道Bug状态更新报告
|
||||||
|
|
||||||
|
## 更新时间
|
||||||
|
2026-04-08 23:15
|
||||||
|
|
||||||
|
## 远程仓库修复汇总
|
||||||
|
|
||||||
|
### Bug 334 - 检验申请界面布局优化 ✅ 已修复
|
||||||
|
- **Commit**: 720cac8a, 06208959 (赵云)
|
||||||
|
- **修复内容**:
|
||||||
|
- 顶部操作区高度从 60px 优化为 48px
|
||||||
|
- 按钮尺寸从 large 改为 default
|
||||||
|
- padding/gap 优化提升垂直空间利用率
|
||||||
|
- **验证状态**: ⏳ 待测试验证
|
||||||
|
|
||||||
|
### Bug 335/336 - 药品/诊疗医嘱保存报错 ✅ 已修复
|
||||||
|
- **Commit**: 098aae5a (关羽)
|
||||||
|
- **修复内容**:
|
||||||
|
- 在 saveAdvice 方法入口添加参数非空校验
|
||||||
|
- 在 handMedication/handDevice/handService 方法中添加 practitionerId 和 founderOrgId 自动补全
|
||||||
|
- 增强异常场景的用户提示
|
||||||
|
- **验证状态**: ⏳ 待测试验证
|
||||||
|
|
||||||
|
### Bug 338 - 门诊划价安全校验 ✅ 已修复
|
||||||
|
- **Commits**: 5c8bfbc9, efc97c85, 5497c99f (关羽/赵云)
|
||||||
|
- **修复内容**:
|
||||||
|
- 在 saveAdvice 方法中增加就诊状态校验
|
||||||
|
- 仅允许已接诊(1002/1003/1004)患者保存医嘱
|
||||||
|
- 未接诊患者(非1002/1003/1004状态)禁止保存医嘱
|
||||||
|
- 修复编译错误 - 更正字段名为 getStatusEnum()
|
||||||
|
- **验证状态**: ⏳ 待测试验证
|
||||||
|
|
||||||
|
### Bug 339 - 药房筛选条件失效 ✅ 已修复
|
||||||
|
- **Commits**: 5c8bfbc9, d8b4aed1 (关羽/赵云)
|
||||||
|
- **修复内容**:
|
||||||
|
- 在 getAdviceBaseInfo 方法中添加 locationId 过滤条件
|
||||||
|
- 确保药房筛选功能能够正确应用到查询结果
|
||||||
|
- **验证状态**: ⏳ 待测试验证
|
||||||
|
|
||||||
|
## 禅道Bug状态待更新
|
||||||
|
|
||||||
|
### Bug 334 - 前端UI布局优化
|
||||||
|
- **状态**: 修复完成
|
||||||
|
- **指派**: 赵云
|
||||||
|
- **严重程度**: 低
|
||||||
|
- **优先级**: 中
|
||||||
|
|
||||||
|
### Bug 335/336 - 医嘱保存报错
|
||||||
|
- **状态**: 修复完成
|
||||||
|
- **指派**: 关羽
|
||||||
|
- **严重程度**: 高
|
||||||
|
- **优先级**: 高
|
||||||
|
|
||||||
|
### Bug 338 - 门诊划价安全校验
|
||||||
|
- **状态**: 修复完成
|
||||||
|
- **指派**: 华佗
|
||||||
|
- **严重程度**: 高(患者安全)
|
||||||
|
- **优先级**: 高
|
||||||
|
|
||||||
|
### Bug 339 - 药房筛选条件失效
|
||||||
|
- **状态**: 修复完成
|
||||||
|
- **指派**: HIS Dev
|
||||||
|
- **严重程度**: 中
|
||||||
|
- **优先级**: 中
|
||||||
|
|
||||||
|
## 当前阻塞问题
|
||||||
|
|
||||||
|
1. **禅道会话不稳定**: 系统频繁要求修改密码导致会话中断
|
||||||
|
2. **Bug备注功能待确认**: 需要确认禅道Bug备注功能是否正常
|
||||||
|
|
||||||
|
## 下一步计划
|
||||||
|
|
||||||
|
1. **立即**: 尝试使用关羽禅道账户更新Bug状态
|
||||||
|
2. **今日内**: 完成禅道Bug状态更新和备注
|
||||||
|
3. **配合测试**: 邀请张飞进行Bug修复效果验证
|
||||||
|
|
||||||
|
## 备注
|
||||||
|
- 所有代码已提交到远程develop分支
|
||||||
|
- Git状态: 本地 develop 分支已与远程同步
|
||||||
|
- 文档更新: BUGFIX_PLAN.md、BUGFIX_ANALYSIS.md、FRONTEND_FIX_PROGRESS.md、BUG_338_ANALYSIS.md 已更新
|
||||||
|
|
||||||
|
---
|
||||||
|
**报告人**: 赵云
|
||||||
|
**报告时间**: 2026-04-08 23:15
|
||||||
64
ZHAOYUN_PROGRESS.md
Normal file
64
ZHAOYUN_PROGRESS.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# 赵云 - 前端任务汇报
|
||||||
|
|
||||||
|
## 当前进度(2026-04-08 23:14)
|
||||||
|
|
||||||
|
### 今日已完成工作
|
||||||
|
|
||||||
|
#### 1. Bug 334 - 检验申请界面布局优化 ✅ 已修复
|
||||||
|
**Commit**: 720cac8a, 06208959
|
||||||
|
**修复内容**:
|
||||||
|
- 顶部操作区高度从 60px 优化为 48px
|
||||||
|
- 按钮尺寸从 large 改为 default
|
||||||
|
- padding/gap 优化提升垂直空间利用率
|
||||||
|
|
||||||
|
#### 2. Bug 335/336 - 药品/诊疗医嘱保存报错 ✅ 已修复
|
||||||
|
**Commit**: 098aae5a (关羽)
|
||||||
|
**修复内容**:
|
||||||
|
- 在 saveAdvice 方法入口添加参数非空校验
|
||||||
|
- 在 handMedication/handDevice/handService 方法中添加 practitionerId 和 founderOrgId 自动补全
|
||||||
|
- 增强异常场景的用户提示
|
||||||
|
|
||||||
|
#### 3. Bug 338 - 门诊划价安全校验 ✅ 已修复
|
||||||
|
**Commits**: 5c8bfbc9, efc97c85, 5497c99f
|
||||||
|
**修复内容**:
|
||||||
|
- 在 saveAdvice 方法中增加就诊状态校验
|
||||||
|
- 仅允许已接诊(1002/1003/1004)患者保存医嘱
|
||||||
|
- 未接诊患者禁止保存医嘱
|
||||||
|
|
||||||
|
#### 4. Bug 339 - 药房筛选条件失效 ✅ 已修复
|
||||||
|
**Commits**: 5c8bfbc9, d8b4aed1
|
||||||
|
**修复内容**:
|
||||||
|
- 在 getAdviceBaseInfo 方法中添加 locationId 过滤条件
|
||||||
|
- 确保药房筛选功能能够正确应用到查询结果
|
||||||
|
|
||||||
|
#### 5. Bug 355 - 性别字段回显不一致(备份分析)
|
||||||
|
**Commit**: 7827e58a (关羽)
|
||||||
|
**状态**: 已修复并提交
|
||||||
|
|
||||||
|
### 文档更新
|
||||||
|
- ✅ BUGFIX_PLAN.md - Bug修复计划
|
||||||
|
- ✅ BUGFIX_ANALYSIS.md - Bug根因分析
|
||||||
|
- ✅ FRONTEND_FIX_PROGRESS.md - 前端修复进度
|
||||||
|
- ✅ BUG_338_ANALYSIS.md - Bug 338详细分析
|
||||||
|
- ✅ ZENTAO_BUG_UPDATE.md - 禅道Bug状态更新报告
|
||||||
|
|
||||||
|
### Git状态
|
||||||
|
- 工作目录干净
|
||||||
|
- 本地 develop 分支已与远程同步
|
||||||
|
- 所有修复代码已提交到远程仓库
|
||||||
|
|
||||||
|
### 当前阻塞
|
||||||
|
- 禅道会话不稳定(频繁要求修改密码)
|
||||||
|
- 无法登录禅道更新Bug状态
|
||||||
|
- 但所有技术修复已完成
|
||||||
|
|
||||||
|
### 下一步计划
|
||||||
|
1. 等待禅道会话恢复后更新Bug状态
|
||||||
|
2. 协助@张飞进行Bug修复效果验证
|
||||||
|
3. 继续处理剩余前端Bug
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**状态总结**:所有前端Bug(334/335/336/338/339)修复已完成,代码已提交。待禅道会话恢复后更新状态。
|
||||||
|
|
||||||
|
子龙正在自主推进工作中!
|
||||||
2
ZHAOYUN_TEST.md
Normal file
2
ZHAOYUN_TEST.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# 赵云测试提交
|
||||||
|
赵云再次测试 - Tue Apr 14 09:36:09 PM CST 2026
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
# OpenHIS 系统审计字段填充最佳实践
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
本文档介绍如何在 OpenHIS 系统中确保所有实体的审计字段(create_by、create_time、update_by、update_time)能够正确自动填充。
|
|
||||||
|
|
||||||
## 自动填充机制
|
|
||||||
|
|
||||||
### 1. 基础实体类
|
|
||||||
所有需要审计字段的实体类都应该继承自 `HisBaseEntity`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
import com.core.common.core.domain.HisBaseEntity;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@TableName("adm_practitioner")
|
|
||||||
public class Practitioner extends HisBaseEntity {
|
|
||||||
@TableId(type = IdType.AUTO)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
// 其他业务字段...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 自动填充处理器
|
|
||||||
系统使用 `MybastisColumnsHandler` 来自动填充审计字段:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Component
|
|
||||||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
|
||||||
@Override
|
|
||||||
public void insertFill(MetaObject metaObject) {
|
|
||||||
// 填充创建时间和创建人
|
|
||||||
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
|
|
||||||
this.strictInsertFill(metaObject, "create_time", Date.class, new Date());
|
|
||||||
|
|
||||||
String username = getCurrentUsername(); // 获取当前用户名
|
|
||||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
|
||||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateFill(MetaObject metaObject) {
|
|
||||||
// 填充更新时间和更新人
|
|
||||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
|
|
||||||
this.strictUpdateFill(metaObject, "update_time", Date.class, new Date());
|
|
||||||
|
|
||||||
String username = getCurrentUsername(); // 获取当前用户名
|
|
||||||
this.strictUpdateFill(metaObject, "updateBy", String.class, username);
|
|
||||||
this.strictUpdateFill(metaObject, "update_by", String.class, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCurrentUsername() {
|
|
||||||
String username = "system";
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
username = loginUser.getUsername();
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 确保自动填充正常工作的要点
|
|
||||||
|
|
||||||
### 1. 检查实体类继承关系
|
|
||||||
确保所有实体类都正确继承了 `HisBaseEntity`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// 正确的做法
|
|
||||||
public class Practitioner extends HisBaseEntity { ... }
|
|
||||||
|
|
||||||
// 如果不能继承 HisBaseEntity,则需要手动添加审计字段
|
|
||||||
public class CustomEntity {
|
|
||||||
@TableField(fill = FieldFill.INSERT)
|
|
||||||
private String createBy;
|
|
||||||
|
|
||||||
@TableField(fill = FieldFill.INSERT)
|
|
||||||
private Date createTime;
|
|
||||||
|
|
||||||
@TableField(fill = FieldFill.UPDATE)
|
|
||||||
private String updateBy;
|
|
||||||
|
|
||||||
@TableField(fill = FieldFill.UPDATE)
|
|
||||||
private Date updateTime;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 验证安全上下文
|
|
||||||
确保在执行数据库操作时有有效的安全上下文:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
public class PractitionerService {
|
|
||||||
public void savePractitioner(Practitioner practitioner) {
|
|
||||||
// 确保调用此方法时用户已登录
|
|
||||||
// SecurityUtils.getLoginUser() 应该能返回有效的 LoginUser 对象
|
|
||||||
|
|
||||||
// MyBatis-Plus 会在保存时自动调用 MybastisColumnsHandler
|
|
||||||
practitionerMapper.insert(practitioner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 检查配置
|
|
||||||
确保自动填充处理器被正确配置:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# application.yml
|
|
||||||
mybatis-plus:
|
|
||||||
global-config:
|
|
||||||
db-config:
|
|
||||||
# 其他配置...
|
|
||||||
configuration:
|
|
||||||
# 其他配置...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 手动填充(特殊情况)
|
|
||||||
在某些特殊情况下,如果自动填充不工作,可以手动设置:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
public class PractitionerService {
|
|
||||||
public void savePractitionerManually(Practitioner practitioner) {
|
|
||||||
// 手动设置审计字段
|
|
||||||
Date now = new Date();
|
|
||||||
String currentUser = getCurrentUsername();
|
|
||||||
|
|
||||||
practitioner.setCreateTime(now);
|
|
||||||
practitioner.setCreateBy(currentUser);
|
|
||||||
practitioner.setUpdateTime(now);
|
|
||||||
practitioner.setUpdateBy(currentUser);
|
|
||||||
|
|
||||||
practitionerMapper.insert(practitioner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 常见问题及解决方案
|
|
||||||
|
|
||||||
### 问题1:自动填充不生效
|
|
||||||
**原因:**
|
|
||||||
- 实体类没有继承 `HisBaseEntity`
|
|
||||||
- `MybastisColumnsHandler` 没有被Spring管理(缺少@Component注解)
|
|
||||||
- 没有有效的安全上下文
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
- 确保实体类继承 `HisBaseEntity`
|
|
||||||
- 检查 `MybastisColumnsHandler` 是否有 `@Component` 注解
|
|
||||||
- 确保在调用保存方法时用户已登录
|
|
||||||
|
|
||||||
### 问题2:获取不到当前用户
|
|
||||||
**原因:**
|
|
||||||
- 用户未登录
|
|
||||||
- 安全上下文配置错误
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
- 在调用保存方法前确保用户已登录
|
|
||||||
- 检查安全配置是否正确
|
|
||||||
|
|
||||||
### 问题3:批量操作时审计字段未填充
|
|
||||||
**原因:**
|
|
||||||
- 批量操作可能绕过了自动填充机制
|
|
||||||
|
|
||||||
**解决方案:**
|
|
||||||
- 对于批量操作,手动设置审计字段
|
|
||||||
- 或者使用 MyBatis-Plus 的批量操作方法,确保它们支持自动填充
|
|
||||||
|
|
||||||
## 测试验证
|
|
||||||
|
|
||||||
创建一个简单的测试来验证自动填充是否正常工作:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@SpringBootTest
|
|
||||||
public class AuditFieldTest {
|
|
||||||
@Autowired
|
|
||||||
private PractitionerMapper practitionerMapper;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAuditFieldsAutoFill() {
|
|
||||||
Practitioner practitioner = new Practitioner();
|
|
||||||
practitioner.setName("Test Practitioner");
|
|
||||||
|
|
||||||
// 保存实体
|
|
||||||
practitionerMapper.insert(practitioner);
|
|
||||||
|
|
||||||
// 验证审计字段是否被正确填充
|
|
||||||
assertThat(practitioner.getCreateBy()).isNotNull();
|
|
||||||
assertThat(practitioner.getCreateTime()).isNotNull();
|
|
||||||
|
|
||||||
// 清理测试数据
|
|
||||||
practitionerMapper.deleteById(practitioner.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
通过遵循以上最佳实践,可以确保 OpenHIS 系统中的所有实体在保存时都能正确填充审计字段,避免因缺少这些字段而引发的数据库约束错误。
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# 关于数据库审计字段(create_by, create_time等)的处理方案
|
|
||||||
|
|
||||||
## 问题描述
|
|
||||||
在使用OpenHIS系统时,可能会遇到如下错误:
|
|
||||||
```
|
|
||||||
org.postgresql.util.PSQLException: ERROR: null value in column "create_by" of relation "adm_practitioner" violates not-null constraint
|
|
||||||
```
|
|
||||||
|
|
||||||
## 问题分析
|
|
||||||
1. 数据库表中的审计字段(如create_by, create_time)设置了NOT NULL约束
|
|
||||||
2. 应用程序层面使用了MyBatis-Plus的自动填充功能来设置这些字段
|
|
||||||
3. 当自动填充机制失效时,就会出现违反非空约束的错误
|
|
||||||
|
|
||||||
## 解决方案
|
|
||||||
|
|
||||||
### 方案一:修复自动填充机制(推荐)
|
|
||||||
系统已经实现了自动填充机制,位于 `MybastisColumnsHandler.java`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// 设置数据新增时候的,字段自动赋值规则
|
|
||||||
@Override
|
|
||||||
public void insertFill(MetaObject metaObject) {
|
|
||||||
// 同时填充驼峰和下划线命名的字段,以兼容不同的配置
|
|
||||||
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
|
|
||||||
this.strictInsertFill(metaObject, "create_time", Date.class, new Date());
|
|
||||||
|
|
||||||
String username = "system";
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
username = loginUser.getUsername();
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
// 使用 fillStrategy 确保即使字段为 null 也会被填充
|
|
||||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
|
||||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
|
||||||
// 如果 strictInsertFill 没有生效,使用 setFieldValByName 强制设置
|
|
||||||
if (metaObject.hasGetter("createBy") && metaObject.getValue("createBy") == null) {
|
|
||||||
this.setFieldValByName("createBy", username, metaObject);
|
|
||||||
}
|
|
||||||
if (metaObject.hasGetter("create_by") && metaObject.getValue("create_by") == null) {
|
|
||||||
this.setFieldValByName("create_by", username, metaObject);
|
|
||||||
}
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
确保所有实体类都继承自 `HisBaseEntity` 或 `BaseEntity`,这样就能自动获得审计字段。
|
|
||||||
|
|
||||||
### 方案二:移除数据库约束(谨慎使用)
|
|
||||||
如果确实需要允许审计字段为NULL,可以移除数据库约束:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 移除 adm_practitioner 表中 create_by 列的 NOT NULL 约束
|
|
||||||
ALTER TABLE "public"."adm_practitioner"
|
|
||||||
ALTER COLUMN "create_by" DROP NOT NULL;
|
|
||||||
|
|
||||||
-- 同样处理 create_time 列(如果需要)
|
|
||||||
ALTER TABLE "public"."adm_practitioner"
|
|
||||||
ALTER COLUMN "create_time" DROP NOT NULL;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方案三:批量修复所有表的约束
|
|
||||||
如果多个表都存在这个问题,可以使用以下脚本:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 为所有表的审计字段移除NOT NULL约束
|
|
||||||
-- 注意:执行前请备份数据库!
|
|
||||||
|
|
||||||
-- 1. 检查所有包含审计字段的表
|
|
||||||
SELECT
|
|
||||||
table_name,
|
|
||||||
column_name,
|
|
||||||
is_nullable
|
|
||||||
FROM
|
|
||||||
information_schema.columns
|
|
||||||
WHERE
|
|
||||||
column_name IN ('create_by', 'create_time', 'update_by', 'update_time')
|
|
||||||
AND table_schema = 'public'
|
|
||||||
AND is_nullable = 'NO'; -- NO 表示 NOT NULL 约束
|
|
||||||
|
|
||||||
-- 2. 根据需要移除特定表的约束
|
|
||||||
-- 示例:移除多个表的create_by约束
|
|
||||||
ALTER TABLE "public"."adm_practitioner" ALTER COLUMN "create_by" DROP NOT NULL;
|
|
||||||
ALTER TABLE "public"."adm_patient" ALTER COLUMN "create_by" DROP NOT NULL;
|
|
||||||
-- 添加更多表的处理...
|
|
||||||
```
|
|
||||||
|
|
||||||
## 最佳实践
|
|
||||||
|
|
||||||
### 1. 确保实体类继承基础类
|
|
||||||
所有实体类应继承 `HisBaseEntity` 或 `BaseEntity`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Data
|
|
||||||
@TableName("adm_practitioner")
|
|
||||||
public class Practitioner extends HisBaseEntity {
|
|
||||||
// 其他字段...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 检查安全上下文
|
|
||||||
确保在保存数据时有有效的安全上下文,这样自动填充处理器才能获取到当前用户信息。
|
|
||||||
|
|
||||||
### 3. 验证自动填充配置
|
|
||||||
确保 `MybastisColumnsHandler` 在Spring容器中被正确注册(使用@Component注解)。
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
- 推荐保持数据库中的NOT NULL约束,确保数据完整性
|
|
||||||
- 依赖MyBatis-Plus的自动填充机制来设置审计字段
|
|
||||||
- 确保所有实体类继承基础实体类
|
|
||||||
- 在必要时才考虑移除数据库约束
|
|
||||||
1
backup/his-source
Submodule
1
backup/his-source
Submodule
Submodule backup/his-source added at 885a147420
@@ -1,104 +0,0 @@
|
|||||||
-- 检查流水号(display_order)是否按“科室+医生+当天”正确递增
|
|
||||||
--
|
|
||||||
-- 说明:
|
|
||||||
-- 1. display_order 存的是纯数字(1, 2, 3...),不带时间戳前缀
|
|
||||||
-- 2. 时间戳前缀(如 20260109)是在前端显示时加上的
|
|
||||||
-- 3. 后端用 Redis key "ORG-{科室ID}-DOC-{医生ID}" 按天自增
|
|
||||||
--
|
|
||||||
-- 如何判断逻辑是否正确:
|
|
||||||
-- 同一科室、同一医生、同一天的记录,display_order 应该递增(1, 2, 3...)
|
|
||||||
-- 不同科室、不同医生、不同天的记录,可能都是 1(这是正常的)
|
|
||||||
|
|
||||||
-- ========================================
|
|
||||||
-- 查询1:按“科室+医生+日期”分组,看每组内的 display_order 是否递增
|
|
||||||
-- ========================================
|
|
||||||
SELECT
|
|
||||||
DATE(start_time) AS 日期,
|
|
||||||
organization_id AS 科室ID,
|
|
||||||
registrar_id AS 医生ID,
|
|
||||||
COUNT(*) AS 该组记录数,
|
|
||||||
MIN(display_order) AS 最小序号,
|
|
||||||
MAX(display_order) AS 最大序号,
|
|
||||||
STRING_AGG(display_order::text, ', ' ORDER BY start_time) AS 序号列表,
|
|
||||||
STRING_AGG(id::text, ', ' ORDER BY start_time) AS 记录ID列表
|
|
||||||
FROM adm_encounter
|
|
||||||
WHERE delete_flag = '0'
|
|
||||||
AND start_time >= CURRENT_DATE - INTERVAL '7 days' -- 只看最近7天
|
|
||||||
AND display_order IS NOT NULL
|
|
||||||
GROUP BY DATE(start_time), organization_id, registrar_id
|
|
||||||
ORDER BY 日期 DESC, 科室ID, 医生ID;
|
|
||||||
|
|
||||||
-- ========================================
|
|
||||||
-- 查询2:详细查看每条记录,看同组内的序号是否连续
|
|
||||||
-- ========================================
|
|
||||||
SELECT
|
|
||||||
id AS 记录ID,
|
|
||||||
DATE(start_time) AS 日期,
|
|
||||||
organization_id AS 科室ID,
|
|
||||||
registrar_id AS 医生ID,
|
|
||||||
start_time AS 挂号时间,
|
|
||||||
display_order AS 流水号,
|
|
||||||
-- 计算:同组内的序号应该是 1, 2, 3...,看是否有重复或跳号
|
|
||||||
ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY DATE(start_time), organization_id, registrar_id
|
|
||||||
ORDER BY start_time
|
|
||||||
) AS 应该是第几个,
|
|
||||||
CASE
|
|
||||||
WHEN display_order = ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY DATE(start_time), organization_id, registrar_id
|
|
||||||
ORDER BY start_time
|
|
||||||
) THEN '✓ 正常'
|
|
||||||
ELSE '✗ 异常'
|
|
||||||
END AS 是否正常
|
|
||||||
FROM adm_encounter
|
|
||||||
WHERE delete_flag = '0'
|
|
||||||
AND start_time >= CURRENT_DATE - INTERVAL '7 days'
|
|
||||||
AND display_order IS NOT NULL
|
|
||||||
ORDER BY DATE(start_time) DESC, organization_id, registrar_id, start_time;
|
|
||||||
|
|
||||||
-- ========================================
|
|
||||||
-- 查询3:只看今天的数据(最直观)
|
|
||||||
-- ========================================
|
|
||||||
SELECT
|
|
||||||
id AS 记录ID,
|
|
||||||
organization_id AS 科室ID,
|
|
||||||
registrar_id AS 医生ID,
|
|
||||||
start_time AS 挂号时间,
|
|
||||||
display_order AS 流水号
|
|
||||||
FROM adm_encounter
|
|
||||||
WHERE delete_flag = '0'
|
|
||||||
AND DATE(start_time) = CURRENT_DATE
|
|
||||||
AND display_order IS NOT NULL
|
|
||||||
ORDER BY organization_id, registrar_id, start_time;
|
|
||||||
|
|
||||||
-- ========================================
|
|
||||||
-- 查询4:发现问题 - 找出同组内 display_order 重复的记录
|
|
||||||
-- ========================================
|
|
||||||
WITH ranked AS (
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
DATE(start_time) AS reg_date,
|
|
||||||
organization_id,
|
|
||||||
registrar_id,
|
|
||||||
start_time,
|
|
||||||
display_order,
|
|
||||||
ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY DATE(start_time), organization_id, registrar_id
|
|
||||||
ORDER BY start_time
|
|
||||||
) AS should_be_order
|
|
||||||
FROM adm_encounter
|
|
||||||
WHERE delete_flag = '0'
|
|
||||||
AND start_time >= CURRENT_DATE - INTERVAL '7 days'
|
|
||||||
AND display_order IS NOT NULL
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
reg_date AS 日期,
|
|
||||||
organization_id AS 科室ID,
|
|
||||||
registrar_id AS 医生ID,
|
|
||||||
COUNT(*) AS 重复数量,
|
|
||||||
STRING_AGG(id::text || '->' || display_order::text, ', ') AS 问题记录
|
|
||||||
FROM ranked
|
|
||||||
WHERE display_order != should_be_order
|
|
||||||
GROUP BY reg_date, organization_id, registrar_id
|
|
||||||
ORDER BY reg_date DESC;
|
|
||||||
|
|
||||||
1
claude-test.txt
Normal file
1
claude-test.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
test from Claude Code Mon Apr 13 11:03:46 PM CST 2026
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
-- 会诊邀请对象表
|
|
||||||
CREATE TABLE hisdev.consultation_invited (
|
|
||||||
id BIGINT NOT NULL PRIMARY KEY,
|
|
||||||
consultation_request_id BIGINT NOT NULL,
|
|
||||||
invited_department_id BIGINT,
|
|
||||||
invited_department_name VARCHAR(100),
|
|
||||||
invited_physician_id BIGINT,
|
|
||||||
invited_physician_name VARCHAR(50),
|
|
||||||
invited_status SMALLINT DEFAULT 0,
|
|
||||||
confirm_time TIMESTAMP,
|
|
||||||
confirm_opinion TEXT,
|
|
||||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
create_by VARCHAR(64),
|
|
||||||
update_by VARCHAR(64),
|
|
||||||
tenant_id BIGINT,
|
|
||||||
is_deleted SMALLINT DEFAULT 0
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 添加注释
|
|
||||||
COMMENT ON TABLE hisdev.consultation_invited IS '会诊邀请对象表';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.id IS '主键ID';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.consultation_request_id IS '会诊申请ID(外键)';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_department_id IS '邀请科室ID';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_department_name IS '邀请科室名称';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_physician_id IS '邀请医生ID';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_physician_name IS '邀请医生姓名';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.invited_status IS '邀请状态(0-待确认,1-已确认,2-已拒绝)';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.confirm_time IS '确认时间';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.confirm_opinion IS '确认意见';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.create_time IS '创建时间';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.update_time IS '更新时间';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.create_by IS '创建人';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.update_by IS '更新人';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.tenant_id IS '租户ID';
|
|
||||||
COMMENT ON COLUMN hisdev.consultation_invited.is_deleted IS '删除标识(0-未删除,1-已删除)';
|
|
||||||
|
|
||||||
-- 创建索引
|
|
||||||
CREATE INDEX idx_consultation_request_id ON hisdev.consultation_invited(consultation_request_id);
|
|
||||||
CREATE INDEX idx_invited_physician_id ON hisdev.consultation_invited(invited_physician_id);
|
|
||||||
CREATE INDEX idx_tenant_id ON hisdev.consultation_invited(tenant_id);
|
|
||||||
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# 检查后端API返回数据结构
|
|
||||||
|
|
||||||
## 问题分析
|
|
||||||
尽管我们更新了DTO和SQL查询,前端仍然没有显示创建时间,可能的原因:
|
|
||||||
1. API响应中没有包含createTime字段
|
|
||||||
2. SQL查询没有正确返回createTime字段
|
|
||||||
3. 数据库中createTime字段本身为null
|
|
||||||
4. JSON序列化问题
|
|
||||||
|
|
||||||
## 检查步骤
|
|
||||||
|
|
||||||
### 1. 检查数据库中数据
|
|
||||||
首先检查数据库中sys_user表的createTime字段是否正确填充:
|
|
||||||
```sql
|
|
||||||
SELECT user_id, user_name, nick_name, create_time
|
|
||||||
FROM sys_user
|
|
||||||
WHERE create_time IS NOT NULL
|
|
||||||
LIMIT 10;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 检查API端点
|
|
||||||
API端点是:GET /base-data-manage/practitioner/user-practitioner-page
|
|
||||||
这个端点在PractitionerController中定义,调用practitionerAppService.getUserPractitionerPage()
|
|
||||||
|
|
||||||
### 3. 检查SQL查询
|
|
||||||
在PractitionerAppMapper.xml中,我们已经添加了createTime字段:
|
|
||||||
```xml
|
|
||||||
T2.create_time
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 验证DTO映射
|
|
||||||
UserAndPractitionerDto中已添加createTime字段:
|
|
||||||
```java
|
|
||||||
private Date createTime;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 检查JSON序列化
|
|
||||||
检查是否有@JsonFormat注解或其他序列化配置问题
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
# 深度排查 MyBatis-Plus 自动填充不生效问题
|
|
||||||
|
|
||||||
## 问题概述
|
|
||||||
尽管对 MyBatis-Plus 的自动填充处理器进行了多次优化和配置,但 `create_by` 和 `create_time` 字段仍然没有被自动填充。
|
|
||||||
|
|
||||||
## 深度排查步骤
|
|
||||||
|
|
||||||
### 1. 检查 AOP 代理是否生效
|
|
||||||
MyBatis-Plus 的自动填充功能依赖于 AOP 代理。如果实体类的方法被直接调用而非通过代理调用,自动填充可能不会生效。
|
|
||||||
|
|
||||||
### 2. 验证 Service 层实现
|
|
||||||
确保使用的是 MyBatis-Plus 提供的通用 Service 方法,而不是自定义的 SQL。
|
|
||||||
|
|
||||||
### 3. 检查 @TableField 注解配置
|
|
||||||
确认实体类中的字段注解配置正确。
|
|
||||||
|
|
||||||
### 4. 检查事务配置
|
|
||||||
某些事务配置可能会影响 AOP 代理的生效。
|
|
||||||
|
|
||||||
## 解决方案
|
|
||||||
|
|
||||||
### 方案一:在 Service 层手动设置审计字段
|
|
||||||
|
|
||||||
创建一个工具类来统一处理审计字段的设置:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Component
|
|
||||||
public class AuditFieldUtil {
|
|
||||||
|
|
||||||
public static void setCreateInfo(Object entity) {
|
|
||||||
if (entity == null) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
String username = loginUser != null ? loginUser.getUsername() : "system";
|
|
||||||
Date currentTime = new Date();
|
|
||||||
|
|
||||||
// 使用反射设置字段值
|
|
||||||
Field createByField = getField(entity.getClass(), "createBy");
|
|
||||||
if (createByField != null) {
|
|
||||||
createByField.setAccessible(true);
|
|
||||||
if (createByField.get(entity) == null || "".equals(createByField.get(entity))) {
|
|
||||||
createByField.set(entity, username);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Field createTimeField = getField(entity.getClass(), "createTime");
|
|
||||||
if (createTimeField != null) {
|
|
||||||
createTimeField.setAccessible(true);
|
|
||||||
if (createTimeField.get(entity) == null) {
|
|
||||||
createTimeField.set(entity, currentTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理下划线命名的字段
|
|
||||||
Field createByFieldUnderscore = getField(entity.getClass(), "create_by");
|
|
||||||
if (createByFieldUnderscore != null) {
|
|
||||||
createByFieldUnderscore.setAccessible(true);
|
|
||||||
if (createByFieldUnderscore.get(entity) == null || "".equals(createByFieldUnderscore.get(entity))) {
|
|
||||||
createByFieldUnderscore.set(entity, username);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Field createTimeFieldUnderscore = getField(entity.getClass(), "create_time");
|
|
||||||
if (createTimeFieldUnderscore != null) {
|
|
||||||
createTimeFieldUnderscore.setAccessible(true);
|
|
||||||
if (createTimeFieldUnderscore.get(entity) == null) {
|
|
||||||
createTimeFieldUnderscore.set(entity, currentTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("设置审计字段时发生异常: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Field getField(Class<?> clazz, String fieldName) {
|
|
||||||
try {
|
|
||||||
return clazz.getDeclaredField(fieldName);
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
if (clazz.getSuperclass() != null) {
|
|
||||||
return getField(clazz.getSuperclass(), fieldName);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
然后在 Service 实现中使用:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
public class PractitionerServiceImpl extends ServiceImpl<PractitionerMapper, Practitioner>
|
|
||||||
implements IPractitionerService {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuditFieldUtil auditFieldUtil;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public boolean save(Practitioner entity) {
|
|
||||||
// 在保存前手动设置审计字段
|
|
||||||
auditFieldUtil.setCreateInfo(entity);
|
|
||||||
return super.save(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public boolean saveBatch(Collection<Practitioner> entityList) {
|
|
||||||
entityList.forEach(auditFieldUtil::setCreateInfo);
|
|
||||||
return super.saveBatch(entityList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方案二:重写 BaseMapper 方法
|
|
||||||
|
|
||||||
如果 Service 层的方法不起作用,可以直接在 Mapper 层处理:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Mapper
|
|
||||||
public interface PractitionerMapper extends BaseMapper<Practitioner> {
|
|
||||||
|
|
||||||
@Insert({
|
|
||||||
"<script>",
|
|
||||||
"INSERT INTO adm_practitioner (",
|
|
||||||
"id, active_flag, name, name_json, gender_enum, birth_date, deceased_date,",
|
|
||||||
"phone, address, address_province, address_city, address_district, address_street,",
|
|
||||||
"address_json, py_str, wb_str, bus_no, yb_no, user_id, tenant_id, delete_flag,",
|
|
||||||
"create_by, create_time, update_by, update_time, org_id,",
|
|
||||||
"phar_prac_cert_no, prsc_dr_cert_code, dr_profttl_code, kpd_code, signature, pos_no",
|
|
||||||
") VALUES (",
|
|
||||||
"#{id}, #{activeFlag}, #{name}, #{nameJson}, #{genderEnum}, #{birthDate}, #{deceasedDate},",
|
|
||||||
"#{phone}, #{address}, #{addressProvince}, #{addressCity}, #{addressDistrict}, #{addressStreet},",
|
|
||||||
"#{addressJson}, #{pyStr}, #{wbStr}, #{busNo}, #{ybNo}, #{userId}, #{tenantId}, #{deleteFlag},",
|
|
||||||
"#{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{orgId},",
|
|
||||||
"#{pharPracCertNo}, #{prscDrCertCode}, #{drProfttlCode}, #{kpdCode}, #{signature}, #{posNo}",
|
|
||||||
")",
|
|
||||||
"</script>"
|
|
||||||
})
|
|
||||||
@Options(useGeneratedKeys = true, keyProperty = "id")
|
|
||||||
int insertWithAuditFields(Practitioner record);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方案三:使用 MyBatis 拦截器
|
|
||||||
|
|
||||||
创建一个 MyBatis 拦截器来自动填充字段:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Intercepts({
|
|
||||||
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
|
|
||||||
})
|
|
||||||
@Component
|
|
||||||
public class AuditFieldInterceptor implements Interceptor {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object intercept(Invocation invocation) throws Throwable {
|
|
||||||
Object[] args = invocation.getArgs();
|
|
||||||
MappedStatement ms = (MappedStatement) args[0];
|
|
||||||
Object parameter = args[1];
|
|
||||||
|
|
||||||
String sqlCommandType = ms.getSqlCommandType().toString();
|
|
||||||
|
|
||||||
if ("INSERT".equals(sqlCommandType)) {
|
|
||||||
setCreateAuditFields(parameter);
|
|
||||||
} else if ("UPDATE".equals(sqlCommandType)) {
|
|
||||||
setUpdateAuditFields(parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return invocation.proceed();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCreateAuditFields(Object parameter) {
|
|
||||||
if (parameter == null) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
String username = loginUser != null ? loginUser.getUsername() : "system";
|
|
||||||
Date currentTime = new Date();
|
|
||||||
|
|
||||||
// 设置 createBy 和 createTime
|
|
||||||
setFieldValue(parameter, "createBy", username);
|
|
||||||
setFieldValue(parameter, "create_time", username);
|
|
||||||
setFieldValue(parameter, "createTime", currentTime);
|
|
||||||
setFieldValue(parameter, "create_time", currentTime);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUpdateAuditFields(Object parameter) {
|
|
||||||
if (parameter == null) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
String username = loginUser != null ? loginUser.getUsername() : "system";
|
|
||||||
Date currentTime = new Date();
|
|
||||||
|
|
||||||
// 设置 updateBy 和 updateTime
|
|
||||||
setFieldValue(parameter, "updateBy", username);
|
|
||||||
setFieldValue(parameter, "update_by", username);
|
|
||||||
setFieldValue(parameter, "updateTime", currentTime);
|
|
||||||
setFieldValue(parameter, "update_time", currentTime);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setFieldValue(Object obj, String fieldName, Object value) {
|
|
||||||
try {
|
|
||||||
Field field = getField(obj.getClass(), fieldName);
|
|
||||||
if (field != null) {
|
|
||||||
field.setAccessible(true);
|
|
||||||
if (field.get(obj) == null) { // 只在原值为 null 时设置
|
|
||||||
field.set(obj, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 忽略无法设置的字段
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Field getField(Class<?> clazz, String fieldName) {
|
|
||||||
try {
|
|
||||||
return clazz.getDeclaredField(fieldName);
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
if (clazz.getSuperclass() != null) {
|
|
||||||
return getField(clazz.getSuperclass(), fieldName);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object plugin(Object target) {
|
|
||||||
if (target instanceof Executor) {
|
|
||||||
return Plugin.wrap(target, this);
|
|
||||||
} else {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setProperties(Properties properties) {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 推荐实施顺序
|
|
||||||
|
|
||||||
1. 首先尝试方案一(Service 层手动设置),这是最简单且可控的方式
|
|
||||||
2. 如果方案一不行,尝试方案三(MyBatis 拦截器),它在更底层起作用
|
|
||||||
3. 方案二是最后的选择,需要重写具体的插入逻辑
|
|
||||||
|
|
||||||
## 验证方法
|
|
||||||
|
|
||||||
创建一个测试来验证自动填充是否生效:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@SpringBootTest
|
|
||||||
public class AuditFieldTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IPractitionerService practitionerService;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAuditFieldFill() {
|
|
||||||
Practitioner practitioner = new Practitioner();
|
|
||||||
practitioner.setName("Test Practitioner");
|
|
||||||
|
|
||||||
// 记录保存前的值
|
|
||||||
System.out.println("保存前 - createBy: " + practitioner.getCreateBy());
|
|
||||||
System.out.println("保存前 - createTime: " + practitioner.getCreateTime());
|
|
||||||
|
|
||||||
boolean success = practitionerService.save(practitioner);
|
|
||||||
|
|
||||||
// 从数据库重新查询以验证
|
|
||||||
Practitioner saved = practitionerService.getById(practitioner.getId());
|
|
||||||
System.out.println("保存后 - createBy: " + saved.getCreateBy());
|
|
||||||
System.out.println("保存后 - createTime: " + saved.getCreateTime());
|
|
||||||
|
|
||||||
Assertions.assertTrue(success);
|
|
||||||
Assertions.assertNotNull(saved.getCreateBy());
|
|
||||||
Assertions.assertNotNull(saved.getCreateTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
通过这些方案,应该能够解决自动填充不生效的问题。
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
# 诊断 MyBatis-Plus 自动填充问题
|
|
||||||
|
|
||||||
## 问题现象
|
|
||||||
尽管 `MybastisColumnsHandler` 已经实现并配置了自动填充功能,但 `create_by` 和 `create_time` 字段仍然没有被正确填充。
|
|
||||||
|
|
||||||
## 可能的原因及解决方案
|
|
||||||
|
|
||||||
### 1. 检查组件扫描配置
|
|
||||||
确保 `MybastisColumnsHandler` 类被Spring容器正确管理:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Component // 确保这个注解存在
|
|
||||||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 检查包扫描路径
|
|
||||||
在主应用类中确保扫描到了处理器所在的包:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@SpringBootApplication
|
|
||||||
@MapperScan("com.openhis.*.mapper") // 确保扫描到你的mapper
|
|
||||||
@ComponentScan(basePackages = {"com.core", "com.openhis"}) // 确保扫描到处理器
|
|
||||||
public class OpenHisApplication {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(OpenHisApplication.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 验证实体类配置
|
|
||||||
确保实体类正确继承了 `HisBaseEntity` 并且字段上有正确的注解:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Data
|
|
||||||
@TableName("adm_practitioner")
|
|
||||||
public class Practitioner extends HisBaseEntity {
|
|
||||||
// 不需要在子类中重复定义 createBy, createTime 等字段
|
|
||||||
// 因为它们已在 HisBaseEntity 中定义并带有 @TableField(fill = FieldFill.INSERT)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 检查安全上下文
|
|
||||||
自动填充处理器依赖于安全上下文来获取当前用户。确保在执行保存操作时用户已登录:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// 在保存之前,确保用户已登录
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser == null) {
|
|
||||||
// 用户未登录,可能需要手动设置审计字段
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 手动测试自动填充
|
|
||||||
创建一个简单的测试来验证自动填充是否正常工作:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@SpringBootTest
|
|
||||||
public class AutoFillTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PractitionerMapper practitionerMapper;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAutoFill() {
|
|
||||||
Practitioner practitioner = new Practitioner();
|
|
||||||
practitioner.setName("Test Practitioner");
|
|
||||||
|
|
||||||
// 检查在保存前字段是否为空
|
|
||||||
System.out.println("Before insert - createBy: " + practitioner.getCreateBy());
|
|
||||||
System.out.println("Before insert - createTime: " + practitioner.getCreateTime());
|
|
||||||
|
|
||||||
// 执行插入操作
|
|
||||||
int result = practitionerMapper.insert(practitioner);
|
|
||||||
|
|
||||||
// 检查保存后字段是否被填充
|
|
||||||
System.out.println("After insert - createBy: " + practitioner.getCreateBy());
|
|
||||||
System.out.println("After insert - createTime: " + practitioner.getCreateTime());
|
|
||||||
|
|
||||||
assertThat(result).isEqualTo(1);
|
|
||||||
assertThat(practitioner.getCreateBy()).isNotNull();
|
|
||||||
assertThat(practitioner.getCreateTime()).isNotNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. 临时解决方案
|
|
||||||
如果自动填充仍然不工作,可以在服务层手动设置这些字段:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Service
|
|
||||||
public class PractitionerServiceImpl extends ServiceImpl<PractitionerMapper, Practitioner>
|
|
||||||
implements IPractitionerService {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void savePractitioner(Practitioner practitioner) {
|
|
||||||
// 手动设置审计字段
|
|
||||||
if (practitioner.getCreateBy() == null || practitioner.getCreateBy().isEmpty()) {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
practitioner.setCreateBy(loginUser.getUsername());
|
|
||||||
} else {
|
|
||||||
practitioner.setCreateBy("system"); // 默认值
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (practitioner.getCreateTime() == null) {
|
|
||||||
practitioner.setCreateTime(new Date());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行保存操作
|
|
||||||
this.save(practitioner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. 检查 MyBatis-Plus 版本兼容性
|
|
||||||
确保使用的 MyBatis-Plus 版本与自动填充功能兼容。当前项目使用的是 3.5.5 版本,应该支持自动填充功能。
|
|
||||||
|
|
||||||
### 8. 调试自动填充处理器
|
|
||||||
在 `MybastisColumnsHandler` 中添加日志来调试是否被调用:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@Override
|
|
||||||
public void insertFill(MetaObject metaObject) {
|
|
||||||
System.out.println("MybastisColumnsHandler.insertFill() called"); // 调试日志
|
|
||||||
|
|
||||||
Date currentTime = new Date();
|
|
||||||
this.strictInsertFill(metaObject, "createTime", Date.class, currentTime);
|
|
||||||
this.strictInsertFill(metaObject, "create_time", Date.class, currentTime);
|
|
||||||
|
|
||||||
String username = getCurrentUsername();
|
|
||||||
System.out.println("Setting createBy to: " + username); // 调试日志
|
|
||||||
|
|
||||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
|
||||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
|
||||||
|
|
||||||
// ... 其他代码
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
通过以上步骤,应该能够诊断并解决自动填充不工作的问题。
|
|
||||||
162
docs/specs/backend-checklist.md
Normal file
162
docs/specs/backend-checklist.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# 后端发布前检查清单
|
||||||
|
|
||||||
|
## 📋 基础检查项
|
||||||
|
|
||||||
|
### Maven编译验证
|
||||||
|
- [ ] 本地执行 `mvn compile` 编译通过,无ERROR
|
||||||
|
- [ ] 执行 `mvn package -DskipTests` 打包成功
|
||||||
|
- [ ] 依赖版本无冲突(`mvn dependency:tree` 检查)
|
||||||
|
- [ ] 无编译警告(或已有书面说明可忽略)
|
||||||
|
|
||||||
|
### 构建产物验证
|
||||||
|
- [ ] JAR/WAR包生成完整,大小合理
|
||||||
|
- [ ] `application.yml` 等配置文件已打包进产物
|
||||||
|
- [ ] 第三方依赖jar包完整(lib目录无缺失)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Spring Boot 配置检查
|
||||||
|
|
||||||
|
### 多环境配置
|
||||||
|
- [ ] `application-dev.yml`(开发)配置正确
|
||||||
|
- [ ] `application-test.yml`(测试)配置正确
|
||||||
|
- [ ] `application-prod.yml`(生产)配置正确
|
||||||
|
- [ ] 启动参数 `--spring.profiles.active` 指定正确环境
|
||||||
|
- [ ] 生产环境未启用devtools热部署
|
||||||
|
|
||||||
|
### Actuator安全
|
||||||
|
- [ ] 生产环境 `/actuator` 端点已禁用或限制访问
|
||||||
|
- [ ] `/actuator/env`、`/actuator/heapdump` 等敏感端点已关闭
|
||||||
|
- [ ] 健康检查端点 `/actuator/health` 返回信息已脱敏
|
||||||
|
|
||||||
|
### 启动校验
|
||||||
|
- [ ] 数据库连接池配置合理(HikariCP最大/最小连接数)
|
||||||
|
- [ ] Redis/消息中间件连接配置正确
|
||||||
|
- [ ] 启动日志无ERROR级别异常
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗄️ MyBatis Plus 规范检查
|
||||||
|
|
||||||
|
### 实体-表映射
|
||||||
|
- [ ] 所有实体类标注 `@TableName`,表名与实际一致
|
||||||
|
- [ ] 主键字段标注 `@TableId(type = IdType.AUTO)` 或对应策略
|
||||||
|
- [ ] 非表字段标注 `@TableField(exist = false)`
|
||||||
|
- [ ] 字段命名符合下划线转驼峰规则
|
||||||
|
|
||||||
|
### SQL安全
|
||||||
|
- [ ] 所有查询使用参数化查询(`QueryWrapper` / `LambdaQueryWrapper`)
|
||||||
|
- [ ] 禁止字符串拼接SQL(`"WHERE name = '" + name + "'"`)
|
||||||
|
- [ ] 批量操作使用MyBatis Plus `saveBatch` / `updateBatchById`
|
||||||
|
- [ ] 复杂SQL使用XML映射,避免注解内嵌长SQL
|
||||||
|
|
||||||
|
### 事务管理
|
||||||
|
- [ ] 涉及多表写操作的方法标注 `@Transactional`
|
||||||
|
- [ ] 事务边界合理,不包含外部HTTP调用
|
||||||
|
- [ ] 异常回滚配置正确(`rollbackFor = Exception.class`)
|
||||||
|
- [ ] 事务方法未被同一类内方法直接调用(自调用失效问题)
|
||||||
|
|
||||||
|
### 分页插件
|
||||||
|
- [ ] `PaginationInnerInterceptor` 已正确配置
|
||||||
|
- [ ] 分页查询使用 `Page<T>` 对象,非手动limit/offset
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔌 RESTful API 设计检查
|
||||||
|
|
||||||
|
### 统一返回格式
|
||||||
|
- [ ] 所有接口返回 `{code, msg, data}` 统一结构
|
||||||
|
- [ ] 成功返回 `code=200`,业务错误使用自定义错误码
|
||||||
|
- [ ] 异常通过 `@ControllerAdvice` + `@ExceptionHandler` 统一处理
|
||||||
|
|
||||||
|
### HTTP状态码
|
||||||
|
- [ ] 资源创建返回 `201 Created`
|
||||||
|
- [ ] 资源删除返回 `204 No Content`
|
||||||
|
- [ ] 参数校验失败返回 `400 Bad Request`
|
||||||
|
- [ ] 未认证返回 `401 Unauthorized`
|
||||||
|
- [ ] 无权限返回 `403 Forbidden`
|
||||||
|
- [ ] 资源不存在返回 `404 Not Found`
|
||||||
|
|
||||||
|
### 参数校验
|
||||||
|
- [ ] 请求参数使用 `@Valid` / `@Validated` 注解校验
|
||||||
|
- [ ] 必填字段标注 `@NotBlank` / `@NotNull`
|
||||||
|
- [ ] 数值范围标注 `@Min` / `@Max`
|
||||||
|
- [ ] 格式校验使用 `@Pattern`(如手机号、身份证号)
|
||||||
|
- [ ] 校验失败返回明确错误信息(非500堆栈)
|
||||||
|
|
||||||
|
### API版本管理
|
||||||
|
- [ ] 接口路径包含版本号(`/api/v1/`、`/api/v2/`)
|
||||||
|
- [ ] 废弃接口标注 `@Deprecated`,并在文档中说明
|
||||||
|
- [ ] 不兼容变更必须升级版本号
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 安全与合规检查
|
||||||
|
|
||||||
|
### 数据脱敏
|
||||||
|
- [ ] 患者身份证号在日志中脱敏(`***` 掩码)
|
||||||
|
- [ ] 患者手机号在日志中脱敏(前3后4,中间`****`)
|
||||||
|
- [ ] 敏感字段序列化时使用 `@JsonSerialize` 自定义脱敏器
|
||||||
|
- [ ] 接口返回中非必需字段不暴露(如密码、salt)
|
||||||
|
|
||||||
|
### 权限控制
|
||||||
|
- [ ] 所有涉及患者数据的接口标注 `@PreAuthorize`
|
||||||
|
- [ ] 数据级权限校验(医生只能访问本科室患者)
|
||||||
|
- [ ] 越权访问返回 `403`,非 `404` 或 `500`
|
||||||
|
- [ ] 敏感操作(删除、修改诊断)需二次确认或额外权限
|
||||||
|
|
||||||
|
### 审计日志
|
||||||
|
- [ ] 处方修改记录操作人、时间、变更内容
|
||||||
|
- [ ] 病历删除操作记录完整审计链
|
||||||
|
- [ ] 审计日志独立存储,不可被业务用户删除
|
||||||
|
- [ ] 关键业务操作记录IP地址和操作终端
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ 性能检查
|
||||||
|
|
||||||
|
### 数据库查询
|
||||||
|
- [ ] 无N+1查询问题(使用 `JOIN` 或批量查询)
|
||||||
|
- [ ] 大表查询必须有分页限制
|
||||||
|
- [ ] 慢查询已优化(执行时间 < 500ms)
|
||||||
|
- [ ] 索引已覆盖高频查询条件
|
||||||
|
|
||||||
|
### 接口性能
|
||||||
|
- [ ] 核心接口响应时间 < 1秒
|
||||||
|
- [ ] 列表接口支持分页,无全量返回
|
||||||
|
- [ ] 大文件下载使用流式传输,非全量加载到内存
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 文档与发布准备
|
||||||
|
|
||||||
|
### 文档更新
|
||||||
|
- [ ] API接口文档已同步更新(路径、参数、返回值)
|
||||||
|
- [ ] 数据库变更脚本已提供(DDL/DML)
|
||||||
|
- [ ] 配置变更说明已记录(新增/修改的配置项)
|
||||||
|
- [ ] 影响范围说明已明确(哪些模块、哪些接口受影响)
|
||||||
|
|
||||||
|
### 回滚预案
|
||||||
|
- [ ] 数据库变更可回滚(提供反向SQL脚本)
|
||||||
|
- [ ] 配置变更可快速回退
|
||||||
|
- [ ] 紧急回滚流程已明确(谁、怎么做、多长时间)
|
||||||
|
- [ ] 回滚后数据一致性已验证
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 最终确认
|
||||||
|
|
||||||
|
### 发布前最后检查
|
||||||
|
- [ ] `mvn compile` 构建成功(附终端截图)
|
||||||
|
- [ ] 关键单元测试通过
|
||||||
|
- [ ] 测试环境部署验证通过
|
||||||
|
- [ ] Code Review 已完成并获得批准
|
||||||
|
- [ ] 相关Bug已关闭或延期说明
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**:v1.0
|
||||||
|
**最后更新**:2026年4月24日
|
||||||
|
**负责人**:关羽(后端开发)
|
||||||
|
**适用范围**:HIS 系统所有后端模块(his-server)
|
||||||
|
**补充说明**:本清单与陈琳的《前端发布前检查清单》对称互补,共同构成HIS系统发布前完整质量保障体系
|
||||||
217
docs/specs/cicd-gatekeeper.md
Normal file
217
docs/specs/cicd-gatekeeper.md
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
# CI/CD构建门禁规范
|
||||||
|
|
||||||
|
## 🎯 规范目标
|
||||||
|
|
||||||
|
建立自动化质量门禁,确保每次代码提交都经过严格验证,防止低质量代码进入主干分支,提升系统稳定性和开发效率。
|
||||||
|
|
||||||
|
## 🔒 门禁层级
|
||||||
|
|
||||||
|
### 1. 提交前门禁(Pre-commit)
|
||||||
|
**触发时机**:`git commit` 执行前
|
||||||
|
**验证内容**:
|
||||||
|
- ESLint 代码规范检查
|
||||||
|
- Prettier 代码格式化
|
||||||
|
- 简单的单元测试(快速执行)
|
||||||
|
|
||||||
|
**工具配置**:
|
||||||
|
- Husky + lint-staged
|
||||||
|
- 配置文件:`.husky/pre-commit`
|
||||||
|
|
||||||
|
### 2. 推送前门禁(Pre-push)
|
||||||
|
**触发时机**:`git push` 执行前
|
||||||
|
**验证内容**:
|
||||||
|
- 完整的单元测试套件
|
||||||
|
- 构建验证(`npm run build:prod`)
|
||||||
|
- 集成测试(核心流程)
|
||||||
|
|
||||||
|
**工具配置**:
|
||||||
|
- Husky pre-push hook
|
||||||
|
- 配置文件:`.husky/pre-push`
|
||||||
|
|
||||||
|
### 3. CI流水线门禁(CI Pipeline)
|
||||||
|
**触发时机**:代码推送到远程仓库后
|
||||||
|
**验证内容**:
|
||||||
|
- 完整的测试套件(单元+集成+端到端)
|
||||||
|
- 代码覆盖率检查(分阶段目标:Q1≥30%,Q2≥50%,Q3≥80%)
|
||||||
|
- 安全扫描(SAST)
|
||||||
|
- 构建产物验证
|
||||||
|
- 部署到测试环境
|
||||||
|
|
||||||
|
**工具配置**:
|
||||||
|
- Spug CI/CD 流水线
|
||||||
|
- Gitea Webhook 触发
|
||||||
|
|
||||||
|
### 4. 发布前门禁(Release Gate)
|
||||||
|
**触发时机**:准备发布到生产环境前
|
||||||
|
**验证内容**:
|
||||||
|
- 生产环境冒烟测试
|
||||||
|
- 性能基准测试
|
||||||
|
- 安全合规检查
|
||||||
|
- 回滚预案验证
|
||||||
|
|
||||||
|
## ⚙️ 具体配置要求
|
||||||
|
|
||||||
|
### ESLint 配置
|
||||||
|
```javascript
|
||||||
|
// eslint.config.js 关键配置
|
||||||
|
import globals from "globals";
|
||||||
|
import pluginVue from "eslint-plugin-vue";
|
||||||
|
import parserVue from "vue-eslint-parser";
|
||||||
|
import importPlugin from "eslint-plugin-import";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
name: "app/files-to-lint",
|
||||||
|
files: ["**/*.{js,mjs,jsx,vue}"],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "app/files-to-ignore",
|
||||||
|
ignores: ["**/dist/**", "**/node_modules/**", "**/help-center/**"],
|
||||||
|
},
|
||||||
|
|
||||||
|
...pluginVue.configs["flat/recommended"],
|
||||||
|
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parser: parserVue,
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: {
|
||||||
|
import: importPlugin,
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
// 确保导入的模块实际存在(核心规则,防止构建失败)
|
||||||
|
"import/no-unresolved": "error",
|
||||||
|
// 确保导入的命名导出实际存在
|
||||||
|
"import/named": "error",
|
||||||
|
// 确保默认导出存在
|
||||||
|
"import/default": "error",
|
||||||
|
// 确保命名空间导出存在
|
||||||
|
"import/namespace": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Java 后端配置
|
||||||
|
```xml
|
||||||
|
<!-- pom.xml 关键插件 -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.github.spotbugs</groupId>
|
||||||
|
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||||
|
<version>4.2.0</version>
|
||||||
|
</plugin>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据库迁移配置
|
||||||
|
```yaml
|
||||||
|
# application.yml Flyway配置
|
||||||
|
flyway:
|
||||||
|
enabled: true
|
||||||
|
locations: classpath:db/migration
|
||||||
|
baseline-on-migrate: true
|
||||||
|
```
|
||||||
|
javascript
|
||||||
|
// .eslintrc.js 关键配置
|
||||||
|
module.exports = {
|
||||||
|
plugins: ['import'],
|
||||||
|
rules: {
|
||||||
|
// 确保导入的模块实际存在
|
||||||
|
'import/no-unresolved': 'error',
|
||||||
|
// 确保导入的成员实际存在
|
||||||
|
'import/named': 'error',
|
||||||
|
// 禁止未使用的导入
|
||||||
|
'import/no-unused-modules': 'warn'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Husky 配置
|
||||||
|
```bash
|
||||||
|
# .husky/pre-commit
|
||||||
|
#!/bin/sh
|
||||||
|
npm run lint-staged
|
||||||
|
|
||||||
|
# .husky/pre-push
|
||||||
|
#!/bin/sh
|
||||||
|
npm run test:unit && npm run build:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### lint-staged 配置
|
||||||
|
```json
|
||||||
|
// package.json
|
||||||
|
{
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,vue}": ["eslint --fix", "prettier --write"],
|
||||||
|
"*.{css,scss}": ["stylelint --fix", "prettier --write"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
// package.json
|
||||||
|
{
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,vue}": ["eslint --fix", "prettier --write"],
|
||||||
|
"*.{css,scss}": ["stylelint --fix", "prettier --write"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚫 失败处理机制
|
||||||
|
|
||||||
|
### 自动处理
|
||||||
|
- **构建失败**:自动阻止 PR 合并
|
||||||
|
- **测试失败**:标记 PR 为失败状态
|
||||||
|
- **安全漏洞**:立即通知安全团队
|
||||||
|
|
||||||
|
### 人工处理
|
||||||
|
- **紧急修复**:可申请临时绕过(需架构师批准)
|
||||||
|
- **误报处理**:提交豁免申请并说明原因
|
||||||
|
- **规则调整**:通过 RFC 流程申请规则变更
|
||||||
|
|
||||||
|
## 📊 监控与度量
|
||||||
|
|
||||||
|
### 关键指标
|
||||||
|
- 门禁通过率 ≥ 95%
|
||||||
|
- 平均修复时间 ≤ 2小时
|
||||||
|
- 误报率 ≤ 5%
|
||||||
|
|
||||||
|
### 报告机制
|
||||||
|
- 每日门禁失败统计
|
||||||
|
- 周度质量趋势报告
|
||||||
|
- 月度规则优化建议
|
||||||
|
|
||||||
|
## 🔄 持续改进
|
||||||
|
|
||||||
|
### 规则演进
|
||||||
|
- 每月评审门禁规则有效性
|
||||||
|
- 根据项目需求调整检查强度
|
||||||
|
- 引入新的质量检查工具
|
||||||
|
|
||||||
|
### 团队培训
|
||||||
|
- 新成员入职培训包含门禁规范
|
||||||
|
- 定期分享最佳实践案例
|
||||||
|
- 建立常见问题解决方案库
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**:v1.0
|
||||||
|
**最后更新**:2026年4月24日
|
||||||
|
**负责人**:陈琳(文档专家)
|
||||||
|
**技术方案**:诸葛亮(架构师)
|
||||||
|
**适用范围**:HIS 系统所有项目
|
||||||
135
docs/specs/commit-template.md
Normal file
135
docs/specs/commit-template.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# 代码提交变更说明模板
|
||||||
|
|
||||||
|
## 📝 PR/Commit 模板
|
||||||
|
|
||||||
|
### 标题格式
|
||||||
|
```
|
||||||
|
<类型>(<模块>): <简短描述>
|
||||||
|
|
||||||
|
示例:
|
||||||
|
feat(patient): 添加患者基本信息编辑功能
|
||||||
|
fix(doctor): 修复医生排班显示异常问题
|
||||||
|
docs(api): 更新预约挂号接口文档
|
||||||
|
refactor(nurse): 重构护士站护理记录组件
|
||||||
|
```
|
||||||
|
|
||||||
|
### 正文模板
|
||||||
|
```markdown
|
||||||
|
## 🔍 变更背景
|
||||||
|
- **问题描述**:详细说明要解决的问题或实现的需求
|
||||||
|
- **影响范围**:列出受影响的模块、页面、功能
|
||||||
|
- **相关链接**:禅道任务ID、需求文档链接等
|
||||||
|
|
||||||
|
## 🛠️ 变更内容
|
||||||
|
- **主要修改**:核心代码变更点
|
||||||
|
- **技术方案**:采用的技术方案和设计思路
|
||||||
|
- **兼容性**:是否涉及API或数据结构变更
|
||||||
|
|
||||||
|
|
||||||
|
## 🗄️ 数据库变更
|
||||||
|
- **表结构变更**:列出新增/修改的表和字段
|
||||||
|
- **数据迁移**:是否需要数据迁移脚本
|
||||||
|
- **回滚方案**:数据库变更的回滚策略
|
||||||
|
|
||||||
|
## ✅ 验证情况
|
||||||
|
- **测试覆盖**:单元测试、集成测试覆盖情况
|
||||||
|
- **手动验证**:手动测试的场景和结果
|
||||||
|
- **构建验证**:本地构建截图(必填)
|
||||||
|
|
||||||
|
## 📋 检查清单
|
||||||
|
- [ ] 代码已通过 ESLint 检查
|
||||||
|
- [ ] 本地构建成功(附截图)
|
||||||
|
- [ ] 核心功能已测试验证
|
||||||
|
- [ ] 文档已同步更新
|
||||||
|
- [ ] Code Review 已完成
|
||||||
|
|
||||||
|
## 👥 相关人员
|
||||||
|
- **开发者**:@开发者姓名
|
||||||
|
- **测试者**:@测试者姓名
|
||||||
|
- **审核人**:@架构师姓名
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🏷️ 提交类型说明
|
||||||
|
|
||||||
|
| 类型 | 说明 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| feat | 新功能 | `feat: 添加用户登录功能` |
|
||||||
|
| fix | Bug修复 | `fix: 修复表单验证错误` |
|
||||||
|
| docs | 文档更新 | `docs: 更新API文档` |
|
||||||
|
| style | 代码格式调整 | `style: 格式化代码` |
|
||||||
|
| refactor | 代码重构 | `refactor: 重构组件结构` |
|
||||||
|
| test | 测试相关 | `test: 添加单元测试` |
|
||||||
|
| chore | 构建/依赖等 | `chore: 升级依赖版本` |
|
||||||
|
| perf | 性能优化 | `perf: 优化列表加载速度` |
|
||||||
|
|
||||||
|
## 📁 模块命名规范
|
||||||
|
|
||||||
|
| 模块 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| patient | 患者管理相关 |
|
||||||
|
| doctor | 医生工作站相关 |
|
||||||
|
| nurse | 护士站相关 |
|
||||||
|
| admin | 后台管理相关 |
|
||||||
|
| common | 公共组件/工具 |
|
||||||
|
| api | API接口相关 |
|
||||||
|
| auth | 认证授权相关 |
|
||||||
|
| payment | 支付相关 |
|
||||||
|
|
||||||
|
## 🖼️ 构建验证截图要求
|
||||||
|
|
||||||
|
### 必须包含的信息
|
||||||
|
1. **终端窗口**:显示 `npm run build:prod` 命令执行过程
|
||||||
|
2. **成功标识**:明确显示构建成功的提示信息
|
||||||
|
3. **时间戳**:截图包含当前时间,证明是最新构建
|
||||||
|
4. **分支信息**:显示当前工作分支名称
|
||||||
|
|
||||||
|
### 截图示例
|
||||||
|
```
|
||||||
|
$ git checkout feature/patient-edit
|
||||||
|
$ npm run build:prod
|
||||||
|
|
||||||
|
> his-system@1.0.0 build
|
||||||
|
> vue-cli-service build
|
||||||
|
|
||||||
|
⠇ Building for production...
|
||||||
|
|
||||||
|
DONE Build complete. The dist directory is ready to be deployed.
|
||||||
|
INFO Check out deployment instructions at https://cli.vuejs.org/guide/deployment.html
|
||||||
|
|
||||||
|
✨ Done in 45.23s.
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ 禁止行为
|
||||||
|
|
||||||
|
### 严重违规(直接拒绝合并)
|
||||||
|
- 无构建验证截图
|
||||||
|
- 代码存在 ESLint 错误
|
||||||
|
- 未填写变更说明
|
||||||
|
- 修改无关代码文件
|
||||||
|
|
||||||
|
### 轻微违规(要求修正后重新提交)
|
||||||
|
- 描述过于简单
|
||||||
|
- 测试覆盖不完整
|
||||||
|
- 文档更新滞后
|
||||||
|
- 格式不符合规范
|
||||||
|
|
||||||
|
## 💡 最佳实践
|
||||||
|
|
||||||
|
### 高质量提交特征
|
||||||
|
- **原子性**:每次提交只解决一个问题
|
||||||
|
- **可追溯**:关联具体的需求或Bug ID
|
||||||
|
- **可验证**:提供完整的验证证据
|
||||||
|
- **可理解**:描述清晰,他人能快速理解
|
||||||
|
|
||||||
|
### 团队协作建议
|
||||||
|
- 提交前先在本地完整测试
|
||||||
|
- 复杂变更提前与团队沟通
|
||||||
|
- 及时更新相关文档
|
||||||
|
- 主动帮助新人熟悉规范
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**:v1.0
|
||||||
|
**最后更新**:2026年4月24日
|
||||||
|
**负责人**:陈琳(文档专家)
|
||||||
|
**适用范围**:HIS 系统所有开发人员
|
||||||
102
docs/specs/frontend-checklist.md
Normal file
102
docs/specs/frontend-checklist.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# 前端发布前检查清单
|
||||||
|
|
||||||
|
## 📋 基础检查项
|
||||||
|
|
||||||
|
### 代码质量
|
||||||
|
- [ ] 代码已通过 ESLint 检查,无警告和错误
|
||||||
|
- [ ] 代码已通过 Prettier 格式化
|
||||||
|
- [ ] 无 console.log() 等调试代码残留
|
||||||
|
- [ ] 变量命名符合规范,语义清晰
|
||||||
|
- [ ] 函数职责单一,复杂度适中
|
||||||
|
|
||||||
|
### 构建验证
|
||||||
|
- [ ] 本地执行 `npm run build:prod` 成功完成
|
||||||
|
- [ ] 构建产物无报错,体积合理
|
||||||
|
- [ ] 静态资源路径正确,无404错误
|
||||||
|
- [ ] 环境变量配置正确(开发/测试/生产)
|
||||||
|
|
||||||
|
### 功能验证
|
||||||
|
- [ ] 核心功能流程完整测试通过
|
||||||
|
- [ ] 边界条件和异常场景已覆盖
|
||||||
|
- [ ] 表单验证逻辑正确
|
||||||
|
- [ ] API 接口调用正常,错误处理完善
|
||||||
|
- [ ] 路由跳转逻辑正确
|
||||||
|
|
||||||
|
## 🔧 技术检查项
|
||||||
|
|
||||||
|
### 模块导入检查
|
||||||
|
- [ ] 所有 import 语句引用的模块实际存在
|
||||||
|
- [ ] 无未使用的 import 导入
|
||||||
|
- [ ] 路径别名(@/)配置正确
|
||||||
|
- [ ] 第三方库版本兼容性确认
|
||||||
|
|
||||||
|
### 性能优化
|
||||||
|
- [ ] 组件按需加载(懒加载)已配置
|
||||||
|
- [ ] 大数据列表已实现虚拟滚动或分页
|
||||||
|
- [ ] 图片资源已压缩,格式合适
|
||||||
|
- [ ] 无内存泄漏风险(事件监听器、定时器等)
|
||||||
|
|
||||||
|
### 安全检查
|
||||||
|
- [ ] 用户输入已做 XSS 防护
|
||||||
|
- [ ] 敏感信息不在前端硬编码
|
||||||
|
- [ ] API 请求已做 CSRF 防护
|
||||||
|
- [ ] 权限控制逻辑正确
|
||||||
|
|
||||||
|
## 🌐 兼容性检查
|
||||||
|
|
||||||
|
### 浏览器兼容
|
||||||
|
- [ ] 主流浏览器(Chrome、Firefox、Safari、Edge)显示正常
|
||||||
|
- [ ] 移动端适配良好(如适用)
|
||||||
|
- [ ] 分辨率适配(1366x768、1920x1080等)
|
||||||
|
|
||||||
|
### 设备兼容
|
||||||
|
- [ ] 触摸设备操作体验良好
|
||||||
|
- [ ] 键盘导航支持完整
|
||||||
|
- [ ] 屏幕阅读器兼容性(无障碍)
|
||||||
|
|
||||||
|
## 📱 发布准备
|
||||||
|
|
||||||
|
### 文档更新
|
||||||
|
- [ ] 相关 API 文档已同步更新
|
||||||
|
- [ ] 用户操作手册已更新(如适用)
|
||||||
|
- [ ] 变更日志已记录
|
||||||
|
|
||||||
|
### 回滚预案
|
||||||
|
- [ ] 回滚方案已准备
|
||||||
|
- [ ] 数据兼容性已确认
|
||||||
|
- [ ] 紧急联系人已明确
|
||||||
|
|
||||||
|
|
||||||
|
## 🔧 后端检查项
|
||||||
|
|
||||||
|
### 编译验证
|
||||||
|
- [ ] Maven编译成功(`mvn clean package -DskipTests`)
|
||||||
|
- [ ] 无编译错误,仅有可接受的警告
|
||||||
|
- [ ] 依赖版本兼容性确认
|
||||||
|
|
||||||
|
### 数据库脚本
|
||||||
|
- [ ] DDL/DML脚本语法正确
|
||||||
|
- [ ] 回滚脚本已准备
|
||||||
|
- [ ] 数据迁移脚本已测试
|
||||||
|
|
||||||
|
## 🔄 前后端协同
|
||||||
|
|
||||||
|
### 接口兼容性
|
||||||
|
- [ ] API接口契约变更已双方确认
|
||||||
|
- [ ] 前端调用后端接口正常
|
||||||
|
- [ ] 错误码处理逻辑一致
|
||||||
|
|
||||||
|
## ✅ 最终确认
|
||||||
|
|
||||||
|
### 发布前最后检查
|
||||||
|
- [ ] 本地构建截图已附在 PR 中
|
||||||
|
- [ ] 测试环境部署验证通过
|
||||||
|
- [ ] Code Review 已完成并获得批准
|
||||||
|
- [ ] 相关 Bug 已关闭或延期说明
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**:v1.0
|
||||||
|
**最后更新**:2026年4月24日
|
||||||
|
**负责人**:陈琳(文档专家)
|
||||||
|
**适用范围**:HIS 系统所有前端项目
|
||||||
575
docs/specs/his-release-checklist-v1.0.md
Normal file
575
docs/specs/his-release-checklist-v1.0.md
Normal file
@@ -0,0 +1,575 @@
|
|||||||
|
# HIS项目发布检查清单 v1.0
|
||||||
|
|
||||||
|
> **文档说明**:本清单整合了提交规范、前端检查、后端检查、CI/CD门禁四个部分,作为HIS项目发布的标准化检查依据。每次发布前必须逐项确认。
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
- [1. 提交规范(commit-template)](#1-提交规范commit-template)
|
||||||
|
- [2. 前端检查(frontend-checklist)](#2-前端检查frontend-checklist)
|
||||||
|
- [3. 后端检查(backend-checklist)](#3-后端检查backend-checklist)
|
||||||
|
- [4. CI/CD门禁(cicd-gatekeeper)](#4-cicd门禁cicd-gatekeeper)
|
||||||
|
- [5. 发布确认与回滚预案](#5-发布确认与回滚预案)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 提交规范(commit-template)
|
||||||
|
|
||||||
|
### 📝 PR/Commit 模板
|
||||||
|
|
||||||
|
#### 标题格式
|
||||||
|
```
|
||||||
|
<类型>(<模块>): <简短描述>
|
||||||
|
|
||||||
|
示例:
|
||||||
|
feat(patient): 添加患者基本信息编辑功能
|
||||||
|
fix(doctor): 修复医生排班显示异常问题
|
||||||
|
docs(api): 更新预约挂号接口文档
|
||||||
|
refactor(nurse): 重构护士站护理记录组件
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 正文模板
|
||||||
|
```markdown
|
||||||
|
## 🔍 变更背景
|
||||||
|
- **问题描述**:详细说明要解决的问题或实现的需求
|
||||||
|
- **影响范围**:列出受影响的模块、页面、功能
|
||||||
|
- **相关链接**:禅道任务ID、需求文档链接等
|
||||||
|
|
||||||
|
## 🛠️ 变更内容
|
||||||
|
- **主要修改**:核心代码变更点
|
||||||
|
- **技术方案**:采用的技术方案和设计思路
|
||||||
|
- **兼容性**:是否涉及API或数据结构变更
|
||||||
|
|
||||||
|
## 🗄️ 数据库变更
|
||||||
|
- **表结构变更**:列出新增/修改的表和字段
|
||||||
|
- **数据迁移**:是否需要数据迁移脚本
|
||||||
|
- **回滚方案**:数据库变更的回滚策略
|
||||||
|
|
||||||
|
## ✅ 验证情况
|
||||||
|
- **测试覆盖**:单元测试、集成测试覆盖情况
|
||||||
|
- **手动验证**:手动测试的场景和结果
|
||||||
|
- **构建验证**:本地构建截图(必填)
|
||||||
|
|
||||||
|
## 📋 检查清单
|
||||||
|
- [ ] 代码已通过 ESLint 检查
|
||||||
|
- [ ] 本地构建成功(附截图)
|
||||||
|
- [ ] 核心功能已测试验证
|
||||||
|
- [ ] 文档已同步更新
|
||||||
|
- [ ] Code Review 已完成
|
||||||
|
|
||||||
|
## 👥 相关人员
|
||||||
|
- **开发者**:@开发者姓名
|
||||||
|
- **测试者**:@测试者姓名
|
||||||
|
- **审核人**:@架构师姓名
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🏷️ 提交类型说明
|
||||||
|
|
||||||
|
| 类型 | 说明 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| feat | 新功能 | `feat: 添加用户登录功能` |
|
||||||
|
| fix | Bug修复 | `fix: 修复表单验证错误` |
|
||||||
|
| docs | 文档更新 | `docs: 更新API文档` |
|
||||||
|
| style | 代码格式调整 | `style: 格式化代码` |
|
||||||
|
| refactor | 代码重构 | `refactor: 重构组件结构` |
|
||||||
|
| test | 测试相关 | `test: 添加单元测试` |
|
||||||
|
| chore | 构建/依赖等 | `chore: 升级依赖版本` |
|
||||||
|
| perf | 性能优化 | `perf: 优化列表加载速度` |
|
||||||
|
|
||||||
|
### 📁 模块命名规范
|
||||||
|
|
||||||
|
| 模块 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| patient | 患者管理相关 |
|
||||||
|
| doctor | 医生工作站相关 |
|
||||||
|
| nurse | 护士站相关 |
|
||||||
|
| admin | 后台管理相关 |
|
||||||
|
| common | 公共组件/工具 |
|
||||||
|
| api | API接口相关 |
|
||||||
|
| auth | 认证授权相关 |
|
||||||
|
| payment | 支付相关 |
|
||||||
|
|
||||||
|
### 🖼️ 构建验证截图要求
|
||||||
|
|
||||||
|
#### 必须包含的信息
|
||||||
|
1. **终端窗口**:显示 `npm run build:prod` 命令执行过程
|
||||||
|
2. **成功标识**:明确显示构建成功的提示信息
|
||||||
|
3. **时间戳**:截图包含当前时间,证明是最新构建
|
||||||
|
4. **分支信息**:显示当前工作分支名称
|
||||||
|
|
||||||
|
### ⚠️ 禁止行为
|
||||||
|
|
||||||
|
#### 严重违规(直接拒绝合并)
|
||||||
|
- 无构建验证截图
|
||||||
|
- 代码存在 ESLint 错误
|
||||||
|
- 未填写变更说明
|
||||||
|
- 修改无关代码文件
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 前端检查(frontend-checklist)
|
||||||
|
|
||||||
|
### 📋 基础检查项
|
||||||
|
|
||||||
|
#### 代码质量
|
||||||
|
- [ ] 代码已通过 ESLint 检查,无警告和错误
|
||||||
|
- [ ] 代码已通过 Prettier 格式化
|
||||||
|
- [ ] 无 console.log() 等调试代码残留
|
||||||
|
- [ ] 变量命名符合规范,语义清晰
|
||||||
|
- [ ] 函数职责单一,复杂度适中
|
||||||
|
|
||||||
|
#### 构建验证
|
||||||
|
- [ ] 本地执行 `npm run build:prod` 成功完成
|
||||||
|
- [ ] 构建产物无报错,体积合理
|
||||||
|
- [ ] 静态资源路径正确,无404错误
|
||||||
|
- [ ] 环境变量配置正确(开发/测试/生产)
|
||||||
|
|
||||||
|
#### 功能验证
|
||||||
|
- [ ] 核心功能流程完整测试通过
|
||||||
|
- [ ] 边界条件和异常场景已覆盖
|
||||||
|
- [ ] 表单验证逻辑正确
|
||||||
|
- [ ] API 接口调用正常,错误处理完善
|
||||||
|
- [ ] 路由跳转逻辑正确
|
||||||
|
|
||||||
|
### 🔧 技术检查项
|
||||||
|
|
||||||
|
#### 模块导入检查
|
||||||
|
- [ ] 所有 import 语句引用的模块实际存在
|
||||||
|
- [ ] 无未使用的 import 导入
|
||||||
|
- [ ] 路径别名(@/)配置正确
|
||||||
|
- [ ] 第三方库版本兼容性确认
|
||||||
|
|
||||||
|
#### 性能优化
|
||||||
|
- [ ] 组件按需加载(懒加载)已配置
|
||||||
|
- [ ] 大数据列表已实现虚拟滚动或分页
|
||||||
|
- [ ] 图片资源已压缩,格式合适
|
||||||
|
- [ ] 无内存泄漏风险(事件监听器、定时器等)
|
||||||
|
|
||||||
|
#### 安全检查
|
||||||
|
- [ ] 用户输入已做 XSS 防护
|
||||||
|
- [ ] 敏感信息不在前端硬编码
|
||||||
|
- [ ] API 请求已做 CSRF 防护
|
||||||
|
- [ ] 权限控制逻辑正确
|
||||||
|
|
||||||
|
### 🌐 兼容性检查
|
||||||
|
|
||||||
|
#### 浏览器兼容
|
||||||
|
- [ ] 主流浏览器(Chrome、Firefox、Safari、Edge)显示正常
|
||||||
|
- [ ] 移动端适配良好(如适用)
|
||||||
|
- [ ] 分辨率适配(1366x768、1920x1080等)
|
||||||
|
|
||||||
|
#### 设备兼容
|
||||||
|
- [ ] 触摸设备操作体验良好
|
||||||
|
- [ ] 键盘导航支持完整
|
||||||
|
- [ ] 屏幕阅读器兼容性(无障碍)
|
||||||
|
|
||||||
|
### 📱 发布准备
|
||||||
|
|
||||||
|
#### 文档更新
|
||||||
|
- [ ] 相关 API 文档已同步更新
|
||||||
|
- [ ] 用户操作手册已更新(如适用)
|
||||||
|
- [ ] 变更日志已记录
|
||||||
|
|
||||||
|
#### 回滚预案
|
||||||
|
- [ ] 回滚方案已准备
|
||||||
|
- [ ] 数据兼容性已确认
|
||||||
|
- [ ] 紧急联系人已明确
|
||||||
|
|
||||||
|
### ✅ 最终确认
|
||||||
|
|
||||||
|
#### 发布前最后检查
|
||||||
|
- [ ] 本地构建截图已附在 PR 中
|
||||||
|
- [ ] 测试环境部署验证通过
|
||||||
|
- [ ] Code Review 已完成并获得批准
|
||||||
|
- [ ] 相关 Bug 已关闭或延期说明
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 后端检查(backend-checklist)
|
||||||
|
|
||||||
|
### 📋 基础检查项
|
||||||
|
|
||||||
|
#### Maven编译验证
|
||||||
|
- [ ] 本地执行 `mvn compile` 编译通过,无ERROR
|
||||||
|
- [ ] 执行 `mvn package -DskipTests` 打包成功
|
||||||
|
- [ ] 依赖版本无冲突(`mvn dependency:tree` 检查)
|
||||||
|
- [ ] 无编译警告(或已有书面说明可忽略)
|
||||||
|
|
||||||
|
#### 构建产物验证
|
||||||
|
- [ ] JAR/WAR包生成完整,大小合理
|
||||||
|
- [ ] `application.yml` 等配置文件已打包进产物
|
||||||
|
- [ ] 第三方依赖jar包完整(lib目录无缺失)
|
||||||
|
|
||||||
|
### 🔧 Spring Boot 配置检查
|
||||||
|
|
||||||
|
#### 多环境配置
|
||||||
|
- [ ] `application-dev.yml`(开发)配置正确
|
||||||
|
- [ ] `application-test.yml`(测试)配置正确
|
||||||
|
- [ ] `application-prod.yml`(生产)配置正确
|
||||||
|
- [ ] 启动参数 `--spring.profiles.active` 指定正确环境
|
||||||
|
- [ ] 生产环境未启用devtools热部署
|
||||||
|
|
||||||
|
#### Actuator安全
|
||||||
|
- [ ] 生产环境 `/actuator` 端点已禁用或限制访问
|
||||||
|
- [ ] `/actuator/env`、`/actuator/heapdump` 等敏感端点已关闭
|
||||||
|
- [ ] 健康检查端点 `/actuator/health` 返回信息已脱敏
|
||||||
|
|
||||||
|
#### 启动校验
|
||||||
|
- [ ] 数据库连接池配置合理(HikariCP最大/最小连接数)
|
||||||
|
- [ ] Redis/消息中间件连接配置正确
|
||||||
|
- [ ] 启动日志无ERROR级别异常
|
||||||
|
|
||||||
|
### 🗄️ MyBatis Plus 规范检查
|
||||||
|
|
||||||
|
#### 实体-表映射
|
||||||
|
- [ ] 所有实体类标注 `@TableName`,表名与实际一致
|
||||||
|
- [ ] 主键字段标注 `@TableId(type = IdType.AUTO)` 或对应策略
|
||||||
|
- [ ] 非表字段标注 `@TableField(exist = false)`
|
||||||
|
- [ ] 字段命名符合下划线转驼峰规则
|
||||||
|
|
||||||
|
#### SQL安全
|
||||||
|
- [ ] 所有查询使用参数化查询(`QueryWrapper` / `LambdaQueryWrapper`)
|
||||||
|
- [ ] 禁止字符串拼接SQL(`"WHERE name = '" + name + "'"`)
|
||||||
|
- [ ] 批量操作使用MyBatis Plus `saveBatch` / `updateBatchById`
|
||||||
|
- [ ] 复杂SQL使用XML映射,避免注解内嵌长SQL
|
||||||
|
|
||||||
|
#### 事务管理
|
||||||
|
- [ ] 涉及多表写操作的方法标注 `@Transactional`
|
||||||
|
- [ ] 事务边界合理,不包含外部HTTP调用
|
||||||
|
- [ ] 异常回滚配置正确(`rollbackFor = Exception.class`)
|
||||||
|
- [ ] 事务方法未被同一类内方法直接调用(自调用失效问题)
|
||||||
|
|
||||||
|
#### 分页插件
|
||||||
|
- [ ] `PaginationInnerInterceptor` 已正确配置
|
||||||
|
- [ ] 分页查询使用 `Page<T>` 对象,非手动limit/offset
|
||||||
|
|
||||||
|
### 🔌 RESTful API 设计检查
|
||||||
|
|
||||||
|
#### 统一返回格式
|
||||||
|
- [ ] 所有接口返回 `{code, msg, data}` 统一结构
|
||||||
|
- [ ] 成功返回 `code=200`,业务错误使用自定义错误码
|
||||||
|
- [ ] 异常通过 `@ControllerAdvice` + `@ExceptionHandler` 统一处理
|
||||||
|
|
||||||
|
#### HTTP状态码
|
||||||
|
- [ ] 资源创建返回 `201 Created`
|
||||||
|
- [ ] 资源删除返回 `204 No Content`
|
||||||
|
- [ ] 参数校验失败返回 `400 Bad Request`
|
||||||
|
- [ ] 未认证返回 `401 Unauthorized`
|
||||||
|
- [ ] 无权限返回 `403 Forbidden`
|
||||||
|
- [ ] 资源不存在返回 `404 Not Found`
|
||||||
|
|
||||||
|
#### 参数校验
|
||||||
|
- [ ] 请求参数使用 `@Valid` / `@Validated` 注解校验
|
||||||
|
- [ ] 必填字段标注 `@NotBlank` / `@NotNull`
|
||||||
|
- [ ] 数值范围标注 `@Min` / `@Max`
|
||||||
|
- [ ] 格式校验使用 `@Pattern`(如手机号、身份证号)
|
||||||
|
- [ ] 校验失败返回明确错误信息(非500堆栈)
|
||||||
|
|
||||||
|
#### API版本管理
|
||||||
|
- [ ] 接口路径包含版本号(`/api/v1/`、`/api/v2/`)
|
||||||
|
- [ ] 废弃接口标注 `@Deprecated`,并在文档中说明
|
||||||
|
- [ ] 不兼容变更必须升级版本号
|
||||||
|
|
||||||
|
### 🔒 安全与合规检查
|
||||||
|
|
||||||
|
#### 数据脱敏
|
||||||
|
- [ ] 患者身份证号在日志中脱敏(`***` 掩码)
|
||||||
|
- [ ] 患者手机号在日志中脱敏(前3后4,中间`****`)
|
||||||
|
- [ ] 敏感字段序列化时使用 `@JsonSerialize` 自定义脱敏器
|
||||||
|
- [ ] 接口返回中非必需字段不暴露(如密码、salt)
|
||||||
|
|
||||||
|
#### 权限控制
|
||||||
|
- [ ] 所有涉及患者数据的接口标注 `@PreAuthorize`
|
||||||
|
- [ ] 数据级权限校验(医生只能访问本科室患者)
|
||||||
|
- [ ] 越权访问返回 `403`,非 `404` 或 `500`
|
||||||
|
- [ ] 敏感操作(删除、修改诊断)需二次确认或额外权限
|
||||||
|
|
||||||
|
#### 审计日志
|
||||||
|
- [ ] 处方修改记录操作人、时间、变更内容
|
||||||
|
- [ ] 病历删除操作记录完整审计链
|
||||||
|
- [ ] 审计日志独立存储,不可被业务用户删除
|
||||||
|
- [ ] 关键业务操作记录IP地址和操作终端
|
||||||
|
|
||||||
|
### ⚡ 性能检查
|
||||||
|
|
||||||
|
#### 数据库查询
|
||||||
|
- [ ] 无N+1查询问题(使用 `JOIN` 或批量查询)
|
||||||
|
- [ ] 大表查询必须有分页限制
|
||||||
|
- [ ] 慢查询已优化(执行时间 < 500ms)
|
||||||
|
- [ ] 索引已覆盖高频查询条件
|
||||||
|
|
||||||
|
#### 接口性能
|
||||||
|
- [ ] 核心接口响应时间 < 1秒
|
||||||
|
- [ ] 列表接口支持分页,无全量返回
|
||||||
|
- [ ] 大文件下载使用流式传输,非全量加载到内存
|
||||||
|
|
||||||
|
### 📝 文档与发布准备
|
||||||
|
|
||||||
|
#### 文档更新
|
||||||
|
- [ ] API接口文档已同步更新(路径、参数、返回值)
|
||||||
|
- [ ] 数据库变更脚本已提供(DDL/DML)
|
||||||
|
- [ ] 配置变更说明已记录(新增/修改的配置项)
|
||||||
|
- [ ] 影响范围说明已明确(哪些模块、哪些接口受影响)
|
||||||
|
|
||||||
|
#### 回滚预案
|
||||||
|
- [ ] 数据库变更可回滚(提供反向SQL脚本)
|
||||||
|
- [ ] 配置变更可快速回退
|
||||||
|
- [ ] 紧急回滚流程已明确(谁、怎么做、多长时间)
|
||||||
|
- [ ] 回滚后数据一致性已验证
|
||||||
|
|
||||||
|
### ✅ 最终确认
|
||||||
|
|
||||||
|
#### 发布前最后检查
|
||||||
|
- [ ] `mvn compile` 构建成功(附终端截图)
|
||||||
|
- [ ] 关键单元测试通过
|
||||||
|
- [ ] 测试环境部署验证通过
|
||||||
|
- [ ] Code Review 已完成并获得批准
|
||||||
|
- [ ] 相关Bug已关闭或延期说明
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. CI/CD门禁(cicd-gatekeeper)
|
||||||
|
|
||||||
|
### 🎯 规范目标
|
||||||
|
|
||||||
|
建立自动化质量门禁,确保每次代码提交都经过严格验证,防止低质量代码进入主干分支,提升系统稳定性和开发效率。
|
||||||
|
|
||||||
|
### 🔒 门禁层级
|
||||||
|
|
||||||
|
#### 1. 提交前门禁(Pre-commit)
|
||||||
|
**触发时机**:`git commit` 执行前
|
||||||
|
**验证内容**:
|
||||||
|
- ESLint 代码规范检查
|
||||||
|
- Prettier 代码格式化
|
||||||
|
- 简单的单元测试(快速执行)
|
||||||
|
|
||||||
|
**工具配置**:
|
||||||
|
- Husky + lint-staged
|
||||||
|
- 配置文件:`.husky/pre-commit`
|
||||||
|
|
||||||
|
#### 2. 推送前门禁(Pre-push)
|
||||||
|
**触发时机**:`git push` 执行前
|
||||||
|
**验证内容**:
|
||||||
|
- 完整的单元测试套件
|
||||||
|
- 构建验证(`npm run build:prod`)
|
||||||
|
- 集成测试(核心流程)
|
||||||
|
|
||||||
|
**工具配置**:
|
||||||
|
- Husky pre-push hook
|
||||||
|
- 配置文件:`.husky/pre-push`
|
||||||
|
|
||||||
|
#### 3. CI流水线门禁(CI Pipeline)
|
||||||
|
**触发时机**:代码推送到远程仓库后
|
||||||
|
**验证内容**:
|
||||||
|
- 完整的测试套件(单元+集成+端到端)
|
||||||
|
- 代码覆盖率检查(分阶段目标:Q1≥30%,Q2≥50%,Q3≥80%)
|
||||||
|
- 安全扫描(SAST)
|
||||||
|
- 构建产物验证
|
||||||
|
- 部署到测试环境
|
||||||
|
|
||||||
|
**工具配置**:
|
||||||
|
- Spug CI/CD 流水线
|
||||||
|
- Gitea Webhook 触发
|
||||||
|
|
||||||
|
#### 4. 发布前门禁(Release Gate)
|
||||||
|
**触发时机**:准备发布到生产环境前
|
||||||
|
**验证内容**:
|
||||||
|
- 生产环境冒烟测试
|
||||||
|
- 性能基准测试
|
||||||
|
- 安全合规检查
|
||||||
|
- 回滚预案验证
|
||||||
|
|
||||||
|
### ⚙️ 具体配置要求
|
||||||
|
|
||||||
|
#### ESLint 配置
|
||||||
|
```javascript
|
||||||
|
// eslint.config.js 关键配置
|
||||||
|
import globals from "globals";
|
||||||
|
import pluginVue from "eslint-plugin-vue";
|
||||||
|
import parserVue from "vue-eslint-parser";
|
||||||
|
import importPlugin from "eslint-plugin-import";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
name: "app/files-to-lint",
|
||||||
|
files: ["**/*.{js,mjs,jsx,vue}"],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "app/files-to-ignore",
|
||||||
|
ignores: ["**/dist/**", "**/node_modules/**", "**/help-center/**"],
|
||||||
|
},
|
||||||
|
|
||||||
|
...pluginVue.configs["flat/recommended"],
|
||||||
|
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parser: parserVue,
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: {
|
||||||
|
import: importPlugin,
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
// 确保导入的模块实际存在(核心规则,防止构建失败)
|
||||||
|
"import/no-unresolved": "error",
|
||||||
|
// 确保导入的命名导出实际存在
|
||||||
|
"import/named": "error",
|
||||||
|
// 确保默认导出存在
|
||||||
|
"import/default": "error",
|
||||||
|
// 确保命名空间导出存在
|
||||||
|
"import/namespace": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Java 后端配置
|
||||||
|
```xml
|
||||||
|
<!-- pom.xml 关键插件 -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.github.spotbugs</groupId>
|
||||||
|
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||||
|
<version>4.2.0</version>
|
||||||
|
</plugin>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 数据库迁移配置
|
||||||
|
```yaml
|
||||||
|
# application.yml Flyway配置
|
||||||
|
flyway:
|
||||||
|
enabled: true
|
||||||
|
locations: classpath:db/migration
|
||||||
|
baseline-on-migrate: true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Husky 配置
|
||||||
|
```bash
|
||||||
|
# .husky/pre-commit
|
||||||
|
#!/bin/sh
|
||||||
|
npm run lint-staged
|
||||||
|
|
||||||
|
# .husky/pre-push
|
||||||
|
#!/bin/sh
|
||||||
|
npm run test:unit && npm run build:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
#### lint-staged 配置
|
||||||
|
```json
|
||||||
|
// package.json
|
||||||
|
{
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,vue}": ["eslint --fix", "prettier --write"],
|
||||||
|
"*.{css,scss}": ["stylelint --fix", "prettier --write"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🚫 失败处理机制
|
||||||
|
|
||||||
|
#### 自动处理
|
||||||
|
- **构建失败**:自动阻止 PR 合并
|
||||||
|
- **测试失败**:标记 PR 为失败状态
|
||||||
|
- **安全漏洞**:立即通知安全团队
|
||||||
|
|
||||||
|
#### 人工处理
|
||||||
|
- **紧急修复**:可申请临时绕过(需架构师批准)
|
||||||
|
- **误报处理**:提交豁免申请并说明原因
|
||||||
|
- **规则调整**:通过 RFC 流程申请规则变更
|
||||||
|
|
||||||
|
### 📊 监控与度量
|
||||||
|
|
||||||
|
#### 关键指标
|
||||||
|
- 门禁通过率 ≥ 95%
|
||||||
|
- 平均修复时间 ≤ 2小时
|
||||||
|
- 误报率 ≤ 5%
|
||||||
|
|
||||||
|
#### 报告机制
|
||||||
|
- 每日门禁失败统计
|
||||||
|
- 周度质量趋势报告
|
||||||
|
- 月度规则优化建议
|
||||||
|
|
||||||
|
### 🔄 持续改进
|
||||||
|
|
||||||
|
#### 规则演进
|
||||||
|
- 每月评审门禁规则有效性
|
||||||
|
- 根据项目需求调整检查强度
|
||||||
|
- 引入新的质量检查工具
|
||||||
|
|
||||||
|
#### 团队培训
|
||||||
|
- 新成员入职培训包含门禁规范
|
||||||
|
- 定期分享最佳实践案例
|
||||||
|
- 建立常见问题解决方案库
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 发布确认与回滚预案
|
||||||
|
|
||||||
|
### 📋 发布前最终确认清单
|
||||||
|
|
||||||
|
#### 前端确认
|
||||||
|
- [ ] 本地构建成功(`npm run build:prod`)
|
||||||
|
- [ ] 核心功能流程测试通过
|
||||||
|
- [ ] 模块导入检查通过(无import错误)
|
||||||
|
- [ ] 兼容性测试完成
|
||||||
|
|
||||||
|
#### 后端确认
|
||||||
|
- [ ] Maven编译成功(`mvn compile`)
|
||||||
|
- [ ] 单元测试通过
|
||||||
|
- [ ] 数据库脚本验证通过
|
||||||
|
- [ ] API接口测试通过
|
||||||
|
|
||||||
|
#### 协同确认
|
||||||
|
- [ ] 前后端接口契约一致
|
||||||
|
- [ ] 联调测试通过
|
||||||
|
- [ ] Code Review 已完成
|
||||||
|
- [ ] 测试环境部署验证通过
|
||||||
|
|
||||||
|
### 🚨 回滚预案
|
||||||
|
|
||||||
|
#### 触发条件
|
||||||
|
- [ ] 生产环境出现严重Bug
|
||||||
|
- [ ] 性能严重下降
|
||||||
|
- [ ] 数据一致性问题
|
||||||
|
- [ ] 安全漏洞暴露
|
||||||
|
|
||||||
|
#### 回滚步骤
|
||||||
|
1. **立即停止**:暂停新流量进入
|
||||||
|
2. **版本回退**:部署上一个稳定版本
|
||||||
|
3. **数据回滚**:执行数据库回滚脚本(如有)
|
||||||
|
4. **验证恢复**:确认系统功能正常
|
||||||
|
5. **问题分析**:记录根本原因和改进措施
|
||||||
|
|
||||||
|
#### 责任分工
|
||||||
|
- **技术负责人**:执行回滚操作
|
||||||
|
- **测试负责人**:验证回滚后功能
|
||||||
|
- **项目经理**:协调沟通和进度同步
|
||||||
|
- **运维团队**:监控系统状态
|
||||||
|
|
||||||
|
### 📞 紧急联系人
|
||||||
|
|
||||||
|
| 角色 | 姓名 | 联系方式 | 职责 |
|
||||||
|
|------|------|----------|------|
|
||||||
|
| 技术负责人 | 诸葛亮 | @诸葛亮 | 架构决策和技术指导 |
|
||||||
|
| 前端负责人 | 赵云 | @赵云 | 前端问题处理 |
|
||||||
|
| 后端负责人 | 关羽 | @关羽 | 后端问题处理 |
|
||||||
|
| 测试负责人 | 张飞 | @张飞 | 质量验证和问题复现 |
|
||||||
|
| 项目经理 | 刘备 | @刘备 | 项目协调和进度管理 |
|
||||||
|
| 文档负责人 | 陈琳 | @陈琳 | 文档维护和知识沉淀 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**:v1.0
|
||||||
|
**最后更新**:2026年4月25日
|
||||||
|
**负责人**:陈琳(文档专家)
|
||||||
|
**适用范围**:HIS 系统所有开发人员
|
||||||
214
docs/specs/playwright-e2e-testing-plan.md
Normal file
214
docs/specs/playwright-e2e-testing-plan.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# HIS项目 Playwright E2E 自动化测试方案 v1.0
|
||||||
|
|
||||||
|
## 一、方案概述
|
||||||
|
|
||||||
|
### 1.1 选型理由
|
||||||
|
- **Playwright** 是微软开源的端到端测试框架,完美适配 Vue 3 + Vite 技术栈
|
||||||
|
- 自动等待机制适合HIS系统复杂交互场景(异步加载、动态渲染)
|
||||||
|
- 支持多浏览器(Chromium/Firefox/WebKit),CI/CD集成成熟
|
||||||
|
- 已有 `@playwright/test ^1.58.2` 依赖 installed
|
||||||
|
|
||||||
|
### 1.2 目标
|
||||||
|
1. 核心业务流程自动化覆盖率达到 80%+
|
||||||
|
2. 已修复Bug 100% 回归测试覆盖
|
||||||
|
3. 每次代码推送自动触发测试,失败阻断发布
|
||||||
|
|
||||||
|
## 二、项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
openhis-ui-vue3/
|
||||||
|
├── tests/
|
||||||
|
│ ├── e2e/
|
||||||
|
│ │ ├── fixtures/ # 测试夹具
|
||||||
|
│ │ │ └── auth.ts # 登录认证fixture
|
||||||
|
│ │ ├── pages/ # 页面对象模型(POM)
|
||||||
|
│ │ │ ├── LoginPage.ts
|
||||||
|
│ │ │ ├── DoctorStationPage.ts
|
||||||
|
│ │ │ └── SurgeryBillingPage.ts
|
||||||
|
│ │ ├── specs/ # 测试用例
|
||||||
|
│ │ │ ├── login.spec.ts
|
||||||
|
│ │ │ ├── doctor-station.spec.ts
|
||||||
|
│ │ │ ├── surgery-billing.spec.ts
|
||||||
|
│ │ │ └── bug-regression.spec.ts # Bug回归测试
|
||||||
|
│ │ └── utils/
|
||||||
|
│ │ └── test-data.ts # 测试数据
|
||||||
|
│ └── playwright.config.ts # Playwright配置
|
||||||
|
├── .env.test # 测试环境变量
|
||||||
|
└── package.json # 已有playwright依赖
|
||||||
|
```
|
||||||
|
|
||||||
|
## 三、环境配置
|
||||||
|
|
||||||
|
### 3.1 环境变量(.env.test)
|
||||||
|
```bash
|
||||||
|
# 测试环境配置
|
||||||
|
VITE_APP_BASE_API=http://192.168.110.253:8080
|
||||||
|
TEST_USERNAME=test_admin
|
||||||
|
TEST_PASSWORD=test123456
|
||||||
|
TEST_BASE_URL=http://localhost:80
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Playwright配置(playwright.config.ts)
|
||||||
|
```typescript
|
||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './tests/e2e/specs',
|
||||||
|
timeout: 60 * 1000,
|
||||||
|
expect: { timeout: 10000 },
|
||||||
|
fullyParallel: false,
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: 1,
|
||||||
|
reporter: [['html', { outputFolder: 'playwright-report' }], ['list']],
|
||||||
|
use: {
|
||||||
|
baseURL: process.env.TEST_BASE_URL || 'http://localhost:80',
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
screenshot: 'only-on-failure',
|
||||||
|
video: 'retain-on-failure',
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、核心测试用例
|
||||||
|
|
||||||
|
### 4.1 登录测试(login.spec.ts)
|
||||||
|
```typescript
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test('用户登录成功', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.fill('input[placeholder="请输入用户名"]', process.env.TEST_USERNAME || 'admin');
|
||||||
|
await page.fill('input[placeholder="请输入密码"]', process.env.TEST_PASSWORD || '123456');
|
||||||
|
await page.click('button:has-text("登录")');
|
||||||
|
await expect(page).toHaveURL(/.*dashboard.*/);
|
||||||
|
await expect(page.locator('.user-avatar')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('登录失败-错误密码', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await page.fill('input[placeholder="请输入用户名"]', 'admin');
|
||||||
|
await page.fill('input[placeholder="请输入密码"]', 'wrongpassword');
|
||||||
|
await page.click('button:has-text("登录")');
|
||||||
|
await expect(page.locator('.el-message--error')).toBeVisible();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 门诊医生站测试(doctor-station.spec.ts)
|
||||||
|
```typescript
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('门诊医生站', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// 登录
|
||||||
|
await page.goto('/');
|
||||||
|
await page.fill('input[placeholder="请输入用户名"]', process.env.TEST_USERNAME || 'admin');
|
||||||
|
await page.fill('input[placeholder="请输入密码"]', process.env.TEST_PASSWORD || '123456');
|
||||||
|
await page.click('button:has-text("登录")');
|
||||||
|
await page.waitForURL(/.*dashboard.*/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('#427 检查项目分类手风琴展开', async ({ page }) => {
|
||||||
|
await page.goto('/doctorstation');
|
||||||
|
// 点击第一个分类
|
||||||
|
await page.click('.category-item >> nth=0');
|
||||||
|
await expect(page.locator('.category-content >> nth=0')).toBeVisible();
|
||||||
|
// 点击第二个分类,第一个应收起
|
||||||
|
await page.click('.category-item >> nth=1');
|
||||||
|
await expect(page.locator('.category-content >> nth=0')).not.toBeVisible();
|
||||||
|
await expect(page.locator('.category-content >> nth=1')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 手术计费回归测试(bug-regression.spec.ts)
|
||||||
|
```typescript
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('Bug回归测试', () => {
|
||||||
|
test('#437 手术计费防重复提交', async ({ page }) => {
|
||||||
|
// 登录并导航到手术计费
|
||||||
|
await page.goto('/');
|
||||||
|
await page.fill('input[placeholder="请输入用户名"]', process.env.TEST_USERNAME || 'admin');
|
||||||
|
await page.fill('input[placeholder="请输入密码"]', process.env.TEST_PASSWORD || '123456');
|
||||||
|
await page.click('button:has-text("登录")');
|
||||||
|
await page.waitForURL(/.*dashboard.*/);
|
||||||
|
await page.goto('/surgery-billing');
|
||||||
|
|
||||||
|
// 快速连续点击新增按钮(测试防重复锁)
|
||||||
|
const addBtn = page.locator('button:has-text("新增")');
|
||||||
|
await addBtn.click();
|
||||||
|
await addBtn.click(); // 第二次应被阻止
|
||||||
|
await addBtn.click(); // 第三次应被阻止
|
||||||
|
|
||||||
|
// 验证只弹出一个表单
|
||||||
|
await expect(page.locator('.el-dialog')).toHaveCount(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 五、执行命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装浏览器
|
||||||
|
npx playwright install chromium
|
||||||
|
|
||||||
|
# 运行所有测试
|
||||||
|
npm run test:e2e
|
||||||
|
|
||||||
|
# 运行单个测试文件
|
||||||
|
npx playwright test login.spec.ts
|
||||||
|
|
||||||
|
# 生成HTML报告
|
||||||
|
npx playwright show-report
|
||||||
|
|
||||||
|
# UI模式(调试用)
|
||||||
|
npx playwright test --ui
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、CI/CD集成
|
||||||
|
|
||||||
|
### 6.1 package.json脚本
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"test:e2e:ui": "playwright test --ui",
|
||||||
|
"test:e2e:report": "playwright show-report"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Spug流水线集成
|
||||||
|
```yaml
|
||||||
|
# Spug 构建后阶段添加
|
||||||
|
- name: E2E Testing
|
||||||
|
script: |
|
||||||
|
cd openhis-ui-vue3
|
||||||
|
npx playwright install --with-deps chromium
|
||||||
|
npm run test:e2e -- --reporter=html
|
||||||
|
# 测试失败则阻断发布
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "E2E测试失败,阻断发布!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
## 七、实施计划
|
||||||
|
|
||||||
|
| 阶段 | 时间 | 内容 | 负责人 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
| Phase 1 | 第1周 | 登录+核心页面冒烟测试 | 张飞+赵云 |
|
||||||
|
| Phase 2 | 第2-3周 | 门诊医生站+手术计费全流程 | 张飞 |
|
||||||
|
| Phase 3 | 第4周 | Bug回归测试全覆盖 | 张飞 |
|
||||||
|
| Phase 4 | 第5周 | CI/CD流水线集成 | 赵云+运维 |
|
||||||
|
|
||||||
|
## 八、注意事项
|
||||||
|
|
||||||
|
1. **测试数据隔离**:使用独立的测试数据库,不污染生产数据
|
||||||
|
2. **环境变量**:敏感信息通过 `.env.test` 管理,不提交到git
|
||||||
|
3. **截图留痕**:失败时自动截图,便于排查
|
||||||
|
4. **测试优先**:新功能开发时同步编写测试用例
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
package com.core.framework.handler;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
|
||||||
import com.core.common.core.domain.model.LoginUser;
|
|
||||||
import com.core.common.utils.SecurityUtils;
|
|
||||||
import com.core.framework.config.TenantContext;
|
|
||||||
import org.apache.ibatis.reflection.MetaObject;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MyBatis-Plus 自动填充处理器
|
|
||||||
* 用于自动填充创建时间和更新时间,以及创建人和更新人
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class MybastisColumnsHandler implements MetaObjectHandler {
|
|
||||||
|
|
||||||
// 设置数据新增时的字段自动赋值规则
|
|
||||||
@Override
|
|
||||||
public void insertFill(MetaObject metaObject) {
|
|
||||||
// 填充创建时间
|
|
||||||
Date currentTime = new Date();
|
|
||||||
this.strictInsertFill(metaObject, "createTime", Date.class, currentTime);
|
|
||||||
this.strictInsertFill(metaObject, "create_time", Date.class, currentTime);
|
|
||||||
|
|
||||||
// 获取当前登录用户名
|
|
||||||
String username = getCurrentUsername();
|
|
||||||
|
|
||||||
// 填充创建人
|
|
||||||
this.strictInsertFill(metaObject, "createBy", String.class, username);
|
|
||||||
this.strictInsertFill(metaObject, "create_by", String.class, username);
|
|
||||||
|
|
||||||
// 确保tenantId被设置
|
|
||||||
Integer tenantId = getCurrentTenantId();
|
|
||||||
if (tenantId == null) {
|
|
||||||
throw new RuntimeException("无法获取当前租户ID,请确保用户已登录或正确设置租户上下文");
|
|
||||||
}
|
|
||||||
this.strictInsertFill(metaObject, "tenantId", Integer.class, tenantId);
|
|
||||||
this.strictInsertFill(metaObject, "tenant_id", Integer.class, tenantId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置数据修改时的字段自动赋值规则
|
|
||||||
@Override
|
|
||||||
public void updateFill(MetaObject metaObject) {
|
|
||||||
// 填充更新时间
|
|
||||||
Date currentTime = new Date();
|
|
||||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, currentTime);
|
|
||||||
this.strictUpdateFill(metaObject, "update_time", Date.class, currentTime);
|
|
||||||
|
|
||||||
// 填充更新人
|
|
||||||
String username = getCurrentUsername();
|
|
||||||
this.strictUpdateFill(metaObject, "updateBy", String.class, username);
|
|
||||||
this.strictUpdateFill(metaObject, "update_by", String.class, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前登录用户名
|
|
||||||
* @return 当前登录用户名,如果无法获取则返回 "system"
|
|
||||||
*/
|
|
||||||
private String getCurrentUsername() {
|
|
||||||
String username = "system"; // 默认值
|
|
||||||
|
|
||||||
try {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
username = loginUser.getUsername();
|
|
||||||
} else {
|
|
||||||
// 尝试从请求中获取用户信息
|
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
|
||||||
if (attributes != null) {
|
|
||||||
HttpServletRequest request = attributes.getRequest();
|
|
||||||
// 可以在这里添加额外的逻辑来从请求中获取用户信息
|
|
||||||
// 例如从请求头、session等获取用户信息
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 记录异常但不中断处理流程
|
|
||||||
System.err.println("获取当前登录用户时发生异常: " + e.getMessage());
|
|
||||||
// 可以考虑记录日志
|
|
||||||
}
|
|
||||||
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前租户 ID
|
|
||||||
*/
|
|
||||||
private Integer getCurrentTenantId() {
|
|
||||||
Integer result = null;
|
|
||||||
|
|
||||||
// 首先尝试从线程局部变量中获取租户ID(适用于定时任务等场景)
|
|
||||||
Integer threadLocalTenantId = TenantContext.getCurrentTenant();
|
|
||||||
if (threadLocalTenantId != null) {
|
|
||||||
result = threadLocalTenantId;
|
|
||||||
} else {
|
|
||||||
// 获取当前登录用户的租户ID(优先使用SecurityUtils中储存的LoginUser的租户ID)
|
|
||||||
try {
|
|
||||||
if (SecurityUtils.getAuthentication() != null) {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
if (loginUser != null) {
|
|
||||||
result = loginUser.getTenantId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 记录异常但不中断处理
|
|
||||||
System.err.println("获取当前登录用户租户ID时发生异常: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
// 尝试从请求头中获取租户ID
|
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
|
||||||
if (attributes != null) {
|
|
||||||
HttpServletRequest request = attributes.getRequest();
|
|
||||||
if (request != null) {
|
|
||||||
// 从请求头获取租户ID,假设header名称为"X-Tenant-ID" ; 登录接口前端把租户id放到请求头里
|
|
||||||
String tenantIdHeader = request.getHeader("X-Tenant-ID");
|
|
||||||
String requestMethodName = request.getHeader("Request-Method-Name");
|
|
||||||
// 登录
|
|
||||||
if ("login".equals(requestMethodName)) {
|
|
||||||
if (tenantIdHeader != null && !tenantIdHeader.isEmpty()) {
|
|
||||||
try {
|
|
||||||
result = Integer.parseInt(tenantIdHeader);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
System.err.println("解析请求头中的租户ID时发生异常: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果仍然没有获取到租户ID,返回默认值
|
|
||||||
if (result == null) {
|
|
||||||
System.out.println("警告: 未能获取当前租户ID,将使用默认租户ID 1");
|
|
||||||
result = 1; // 默认租户ID
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.openhis;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 示例类 - 引用 OpenHisApplication
|
|
||||||
*/
|
|
||||||
public class Fragment {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
// 引用 OpenHisApplication
|
|
||||||
Class<?> applicationClass = com.openhis.OpenHisApplication.class;
|
|
||||||
System.out.println("Application class: " + applicationClass.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
git_test3.md
Normal file
1
git_test3.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Git 代理禁用后测试 - 关羽 2026-04-14 17:11:41
|
||||||
1
git_test4.md
Normal file
1
git_test4.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Git 晚间测试 - 关羽 2026-04-14 21:35:44
|
||||||
1
gitea_test_huatuo.txt
Normal file
1
gitea_test_huatuo.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
华佗 Gitea 提交测试成功 - Wed Apr 15 10:21:10 AM CST 2026
|
||||||
1
gitea_test_xunyu.txt
Normal file
1
gitea_test_xunyu.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
荀彧 Gitea 提交测试成功 - Tue Apr 14 11:06:47 PM CST 2026
|
||||||
1
his-source
Submodule
1
his-source
Submodule
Submodule his-source added at 7827e58aac
70
md/BUG_ANALYSIS.md
Normal file
70
md/BUG_ANALYSIS.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# HIS项目 Bug 分析与修复日志
|
||||||
|
|
||||||
|
## 2026-04-05 23:55 - 子龙开始工作
|
||||||
|
|
||||||
|
### Bug 334 分析:门诊医生站-检验申请界面按钮布局优化
|
||||||
|
|
||||||
|
**文件位置**:
|
||||||
|
- `/openhis-ui-vue3/src/views/doctorstation/components/inspection/inspectionApplication.vue`
|
||||||
|
|
||||||
|
**当前布局问题**:
|
||||||
|
1. 顶部操作按钮区高度 60px,可能有优化空间
|
||||||
|
2. 表单区域 padding 较大
|
||||||
|
3. 需要优化垂直空间利用率
|
||||||
|
|
||||||
|
**修复方案**:
|
||||||
|
- 减少不必要的 padding 和 margin
|
||||||
|
- 优化表单字段布局
|
||||||
|
- 调整按钮区域高度
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Bug 335 分析:门诊医生站开立药品医嘱点击【保存】时报错
|
||||||
|
|
||||||
|
**文件位置**:
|
||||||
|
- `/openhis-server-new/openhis-application/src/main/java/com/openhis/web/doctorstation/appservice/impl/DoctorStationAdviceAppServiceImpl.java`
|
||||||
|
|
||||||
|
**问题定位**:
|
||||||
|
- 方法:`saveAdvice()` -> `handMedication()`
|
||||||
|
- 可能原因:
|
||||||
|
1. encounterId 或 patientId 为 null
|
||||||
|
2. 库存校验失败
|
||||||
|
3. 账户ID缺失
|
||||||
|
|
||||||
|
**代码已修复**:
|
||||||
|
- 行 488-588:已添加 encounterId 和 patientId 校验
|
||||||
|
- 行 497-588:自动补全逻辑
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Bug 336 分析:门诊医生站开立诊疗项目后点击【保存】报错
|
||||||
|
|
||||||
|
**文件位置**:
|
||||||
|
- 同上文件
|
||||||
|
|
||||||
|
**问题定位**:
|
||||||
|
- 方法:`saveAdvice()` -> `handService()`
|
||||||
|
- 可能原因:
|
||||||
|
1. effectiveOrgId(执行科室)为 null
|
||||||
|
2. accountId 为 null
|
||||||
|
|
||||||
|
**代码已修复**:
|
||||||
|
- 行 1290-1390:已添加 accountId 自动补全
|
||||||
|
- 行 1338-1343:诊疗项目执行科室非空校验
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工作分工
|
||||||
|
|
||||||
|
| Bug ID | 负责人 | 状态 |
|
||||||
|
|--------|--------|------|
|
||||||
|
| 334 | 子龙 | 分析中 |
|
||||||
|
| 335 | 关羽 | 待修复 |
|
||||||
|
| 336 | 关羽 | 待修复 |
|
||||||
|
| 338 | 关羽 | 待修复 |
|
||||||
|
|
||||||
|
## 下一步行动
|
||||||
|
|
||||||
|
1. 子龙修复 Bug 334(检验申请界面布局优化)
|
||||||
|
2. 关羽修复 Bug 335、336、338
|
||||||
|
3. 张飞测试验证
|
||||||
434
md/需求/104-报卡管理界面_2026-02-05.md
Normal file
434
md/需求/104-报卡管理界面_2026-02-05.md
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
## 报卡管理界面PRD文档
|
||||||
|
|
||||||
|
### 一、页面概述
|
||||||
|
|
||||||
|
**页面名称**:报卡管理界面
|
||||||
|
**页面目标**:提供传染病报卡的审核、管理、筛选及批量操作功能,帮助疾控人员高效完成报卡审核工作
|
||||||
|
**适用场景**:疾控中心工作人员日常审核医疗机构上报的传染病病例
|
||||||
|
|
||||||
|
- 医院CDC管理员日常审核传染病报卡
|
||||||
|
- 批量处理待审核/退回的报卡
|
||||||
|
- 按条件筛选统计报卡数据
|
||||||
|
**页面类型**:数据管理列表页(含详情抽屉)
|
||||||
|
|
||||||
|
**核心功能**:
|
||||||
|
|
||||||
|
1. 报卡数据概览统计(今日待审/本月失败/本月成功/本月上报)
|
||||||
|
2. 多维度筛选报卡数据(时间/状态/科室/来源等)
|
||||||
|
3. 报卡列表展示与批量操作(审核/退回/导出)
|
||||||
|
4. 报卡详情查看与单条审核
|
||||||
|
5. 审核记录追溯与意见填写
|
||||||
|
|
||||||
|
**用户价值**:
|
||||||
|
|
||||||
|
- 疾控人员可快速处理待审报卡
|
||||||
|
- 支持批量审核提升工作效率
|
||||||
|
- 完整记录审核过程便于追溯
|
||||||
|
- 多维度筛选快速定位目标报卡
|
||||||
|
**原型图地址**:https://static.pm-ai.cn/prototype/20260206/cc9991b716df0303fa3459042e33a1ea/index.html
|
||||||
|
**流程图**:
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A["进入报卡管理界面"] --> B["查看报卡概览统计"]
|
||||||
|
B --> C["点击统计卡片"]
|
||||||
|
C --> D["悬停统计卡片"]
|
||||||
|
D --> E["自动设置筛选项"]
|
||||||
|
B --> F["鼠标悬停"]
|
||||||
|
F --> G["筛选报卡数据"]
|
||||||
|
G --> H["卡片上浮+阴影"]
|
||||||
|
E --> I["设置筛选条件"]
|
||||||
|
I --> K["点击重置按钮"]
|
||||||
|
K --> J["清空条件"]
|
||||||
|
I --> L["点击查询按钮"]
|
||||||
|
L --> M["触发筛选"]
|
||||||
|
M --> N["操作报卡列表"]
|
||||||
|
N --> O["勾选报卡"]
|
||||||
|
N --> P["处理单条报卡"]
|
||||||
|
O --> Q["批量操作报卡"]
|
||||||
|
P --> R["点击单条审核"]
|
||||||
|
P --> S["点击单条查看"]
|
||||||
|
Q --> T["点击批量审核"]
|
||||||
|
Q --> U["点击批量退回"]
|
||||||
|
R --> V{"报卡状态"}
|
||||||
|
S --> W{"报卡状态"}
|
||||||
|
V -- 已审核 --> X["打开查看抽屉"]
|
||||||
|
V -- 待审核/失败 --> Y["校验选择状态"]
|
||||||
|
W -- 任意 --> X
|
||||||
|
T --> Z["校验选择状态"]
|
||||||
|
U --> AA["校验选择状态"]
|
||||||
|
Y -- 选择有效 --> AB["打开审核抽屉"]
|
||||||
|
AB-->AN["展示审核记录"]
|
||||||
|
AB-->AO["展示审核记录"]
|
||||||
|
Y -- 未选择 --> AC["提示请选择报卡"]
|
||||||
|
Z -- 选择有效 --> AD{"包含已审核项"}
|
||||||
|
Z -- 未选择 --> AE["提示请选择报卡"]
|
||||||
|
AD -- 是 --> AF["提示只能选择待审核报卡"]
|
||||||
|
AD -- 否 --> AG["弹出审核弹窗"]
|
||||||
|
AA -- 选择有效 --> AH{"包含已审核项"}
|
||||||
|
AA -- 未选择 --> AI["提示请选择报卡"]
|
||||||
|
AH -- 是 --> AF
|
||||||
|
AH -- 否 --> AJ["弹出退回弹窗"]
|
||||||
|
AG --> AK["加载报卡详情"]
|
||||||
|
AJ --> AL["加载报卡详情"]
|
||||||
|
AK -- 加载失败 --> AM["提示数据加载失败"]
|
||||||
|
AL -- 加载失败 --> AM
|
||||||
|
AK -- 成功 --> AN["展示审核记录"]
|
||||||
|
AL -- 成功 --> AO["展示审核记录"]
|
||||||
|
AN --> AP["填写审核意见"]
|
||||||
|
AO --> AQ["填写退回原因"]
|
||||||
|
AP --> AR{"意见是否为空"}
|
||||||
|
AQ --> AS{"原因是否为空"}
|
||||||
|
AR -- 是 --> AT["红字提示必填"]
|
||||||
|
AS -- 是 --> AU["红字提示必填"]
|
||||||
|
AR -- 否 --> AV["点击确认审核"]
|
||||||
|
AS -- 否 --> AW["点击确认退回"]
|
||||||
|
AV --> AX["点击审核通过"]
|
||||||
|
AW --> AY["点击退回修改"]
|
||||||
|
AX --> AZ["更新报卡状态"]
|
||||||
|
AY --> BA["更新报卡状态"]
|
||||||
|
AZ --> BB["生成审核记录"]
|
||||||
|
BA --> BC["生成审核记录"]
|
||||||
|
BB --> BD["按钮置灰"]
|
||||||
|
BC --> BD["按钮置灰"]
|
||||||
|
AX --> BE["提示操作失败,请检查网络"]
|
||||||
|
AY --> BE
|
||||||
|
AZ --> BF["刷新表格数据"]
|
||||||
|
BA --> BF
|
||||||
|
BF --> BG["刷新行状态"]
|
||||||
|
BG --> BH["结束"]
|
||||||
|
BE --> BH
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 二、整体布局分析
|
||||||
|
|
||||||
|
**页面宽度**:自适应布局
|
||||||
|
**主要区域划分**:
|
||||||
|
|
||||||
|
1. **顶部导航栏**(固定高度60px)
|
||||||
|
2. **报卡管理概览区**(统计卡片+快捷操作,高度自适应)
|
||||||
|
3. **筛选控制区**(多条件组合筛选,折叠式布局)
|
||||||
|
4. **报卡列表区**(表格展示,占主体60%高度)
|
||||||
|
**布局特点**:上下层级结构,筛选区支持折叠/展开
|
||||||
|
5. **抽屉详情区**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 三、页面区域详细描述
|
||||||
|
|
||||||
|
#### 1. 顶部导航栏
|
||||||
|
|
||||||
|
**区域位置**:页面顶部固定
|
||||||
|
**区域尺寸**:高度60px,100%宽度
|
||||||
|
**区域功能**:展示系统标识和用户信息
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **系统Logo**
|
||||||
|
|
||||||
|
- - 元素类型:图标+文字组合
|
||||||
|
- 显示内容:"CDC"图标+"报卡管理"文字
|
||||||
|
- 样式特征:蓝色主色调(#4a6fa5),左侧对齐
|
||||||
|
|
||||||
|
#### 2. 报卡管理概览
|
||||||
|
|
||||||
|
**区域位置**:导航栏下方
|
||||||
|
|
||||||
|
**区域尺寸**:100%宽度,自适应高度
|
||||||
|
**区域功能**:关键数据统计与快速操作入口
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **统计卡片组**(4个)
|
||||||
|
|
||||||
|
- - 展示方式:网格布局(4列)
|
||||||
|
- 数据字段:
|
||||||
|
|
||||||
|
| **字段名** | **类型** | **示例值** | **可操作** | **计算逻辑** |
|
||||||
|
| ------------ | -------- | ---------- | ---------- | ------------------------- |
|
||||||
|
| 今日待审核 | 数字 | 12 | 可点击 | 当天created_at+待审状态 |
|
||||||
|
| 本月审核失败 | 数字 | 3 | 可点击 | 当月created_at+失败状态 |
|
||||||
|
| 本月审核成功 | 数字 | 2 | 可点击 | 当月created_at+成功状态 |
|
||||||
|
| 本月已上报 | 数字 | 156 | 可点击 | 当月created_at+已上报状态 |
|
||||||
|
|
||||||
|
o 交互行为:
|
||||||
|
|
||||||
|
- - - 悬停:卡片上浮5px+阴影加深
|
||||||
|
- 点击:自动设置对应筛选项
|
||||||
|
|
||||||
|
- 样式特征:左侧状态色条(蓝/橙/红/绿)
|
||||||
|
|
||||||
|
#### 3. 筛选控制区
|
||||||
|
|
||||||
|
**区域功能**:多维度组合筛选报卡数据
|
||||||
|
|
||||||
|
**区域尺寸**:100%宽度,高度自适应(展开状态)
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **筛选条件组**(横向排列→移动端垂直堆叠)
|
||||||
|
|
||||||
|
- - 登记来源(下拉单选):全部/门诊/住院/急诊/体检
|
||||||
|
- 上报时间范围(双日期选择器)--默认值:最近一个月
|
||||||
|
- 患者姓名(文本输入)
|
||||||
|
- 审核状态(下拉单选):全部/待审核/审核通过/审核失败/已上报
|
||||||
|
- 上报科室(树形下拉多选)--全部科室/取值于《科室管理》adm_organization表
|
||||||
|
|
||||||
|
- **操作按钮**:
|
||||||
|
|
||||||
|
- - “查询”(主按钮,触发筛选)
|
||||||
|
- “重置”(次要按钮,清空条件)
|
||||||
|
|
||||||
|
#### 4. 报卡列表区
|
||||||
|
|
||||||
|
**区域功能**:展示报卡数据及操作入口
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
- **表格头部**
|
||||||
|
|
||||||
|
- - 全选复选框(联动所有行选择状态)
|
||||||
|
- 列标题:报卡名称/病种名称/患者信息等11列
|
||||||
|
|
||||||
|
- **快捷操作按钮组**
|
||||||
|
|
||||||
|
- - 包含按钮:
|
||||||
|
|
||||||
|
- - “批量审核”蓝色按钮,(需选择条目)点击弹出填写审核备注弹窗
|
||||||
|
- “批量退回修改”橙色警示按钮,(需选择条目)点击弹出退回原因填写弹窗
|
||||||
|
- “导出当前”(按筛选条件)
|
||||||
|
|
||||||
|
- **表格行**
|
||||||
|
|
||||||
|
- - 数据字段:
|
||||||
|
|
||||||
|
取值于传染病报卡表(infectious_card)
|
||||||
|
|
||||||
|
| **列名** | **数据类型** | **示例值** | **说明** |
|
||||||
|
| -------- | ------------ | ---------------- | --------------- |
|
||||||
|
| 选择框 | boolean | - | 带全选功能 |
|
||||||
|
| 报卡名称 | string | 传染病报告卡 | - |
|
||||||
|
| 病种名称 | string | 病毒性肝炎 | - |
|
||||||
|
| 报卡编号 | string | HOSP202601150001 | 唯一标识 |
|
||||||
|
| 患者姓名 | string | 张某某 | 脱敏显示 |
|
||||||
|
| 性别 | enum | 男 | 男/女/未知 |
|
||||||
|
| 年龄 | number | 32 | - |
|
||||||
|
| 上报科室 | string | 儿科 | - |
|
||||||
|
| 登记来源 | string | 门诊 | - |
|
||||||
|
| 上报时间 | datetime | 2026-01-31 09:23 | - |
|
||||||
|
| 状态 | badge | 待审核 | 待审核<->已提交 |
|
||||||
|
| 操作 | button | 审核/查看 | 根据状态禁用 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- - 行操作按钮:
|
||||||
|
|
||||||
|
- - “审核”(主按钮,打开可编辑抽屉)
|
||||||
|
- “查看”(次要按钮,打开只读抽屉)
|
||||||
|
|
||||||
|
- 交互规则:
|
||||||
|
|
||||||
|
- - 已审核通过的报卡禁用"审核"按钮
|
||||||
|
- 行hover时显示浅蓝色背景
|
||||||
|
|
||||||
|
- 分页功能:
|
||||||
|
|
||||||
|
- - 实现分页功能(可以设置每页5/10/20条)
|
||||||
|
|
||||||
|
#### 5. 抽屉详情区
|
||||||
|
|
||||||
|
**区域位置**:右侧滑出
|
||||||
|
**区域尺寸**:自适应布局
|
||||||
|
**包含元素**:
|
||||||
|
|
||||||
|
· **报卡表单(****根据具体报卡登记的界面内容,比如《中华人民共和国传染病报告卡》内容和功能与需求编号102界面保持一致,建议用同一个界面)**
|
||||||
|
|
||||||
|
- - 字段分组:
|
||||||
|
|
||||||
|
- 1. 患者基本信息(姓名、身份证等)
|
||||||
|
2. 临床信息(发病日期、诊断分类等)
|
||||||
|
3. 疾病选择(甲/乙/丙类传染病复选框)
|
||||||
|
4. 上报信息(报告单位、医生等)
|
||||||
|
|
||||||
|
· **审核记录**
|
||||||
|
|
||||||
|
- - 展示方式:时间轴列表
|
||||||
|
- 数据字段:时间、操作人、操作类型、意见
|
||||||
|
|
||||||
|
· **操作按钮组**
|
||||||
|
|
||||||
|
- - 主按钮:审核通过(绿色)
|
||||||
|
- 次按钮:退回修改(橙色)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 四、交互功能详细说明
|
||||||
|
|
||||||
|
#### 1. 批量审核流程
|
||||||
|
|
||||||
|
**触发条件**:勾选多行后点击"批量审核"
|
||||||
|
**操作流程**:
|
||||||
|
|
||||||
|
1. 系统校验:至少选择1条非"已通过"状态的报卡
|
||||||
|
|
||||||
|
2. 弹出审核弹窗(500px居中模态框)
|
||||||
|
|
||||||
|
3. 填写审核意见(必填)
|
||||||
|
|
||||||
|
4. 点击"确认审核":
|
||||||
|
|
||||||
|
5. 批量更新状态为"审核通过",(自动批量写入每一条审核记录(插入infectious_audit 字段详表②、更改表infectious_card. Statu= 2和infectious_card. update_time= now()))
|
||||||
|
|
||||||
|
6. 刷新表格数据
|
||||||
|
|
||||||
|
7. - 成功:更新状态为"审核通过",添加审核记录
|
||||||
|
- 失败:提示"审核失败,请检查网络"
|
||||||
|
- 包含已审核项:提示"只能选择待审核报卡"
|
||||||
|
- 未填写意见:阻止提交并红字提示
|
||||||
|
|
||||||
|
#### 2. 批量退回修改操作
|
||||||
|
|
||||||
|
**触发方式**:勾选多选框后点击"批量退回"
|
||||||
|
**前置校验**:
|
||||||
|
|
||||||
|
- 至少勾选一条非"已审核"状态报卡
|
||||||
|
- 选中已审核报卡时提示"只能操作待审核报卡"
|
||||||
|
|
||||||
|
**执行流程**:
|
||||||
|
|
||||||
|
1. 弹出模态框要求填写退回原因(必填)填写退回原因:阻止提交并红字提示
|
||||||
|
2. 提交后批量更新选中报卡状态
|
||||||
|
3. 每条生成审核记录(①、插入infectious_audit 字段详表②、更改表infectious_card. Statu=5和infectious_card. update_time= now())
|
||||||
|
|
||||||
|
#### 3.单卡审核流程
|
||||||
|
|
||||||
|
**触发方式**:点击操作列"审核"按钮
|
||||||
|
**状态控制**:
|
||||||
|
|
||||||
|
- 待审核/审核失败:可操作
|
||||||
|
- 审核通过:按钮禁用
|
||||||
|
**执行流程**:
|
||||||
|
|
||||||
|
1. 右侧滑出审核抽屉
|
||||||
|
2. 从行数据获取报卡ID自动填充患者报卡信息(异步加载报卡详情数据)
|
||||||
|
3. 展示历史审核记录(如有)
|
||||||
|
4. 填写审核意见/退回原因
|
||||||
|
5. 点击"审核通过"或"退回修改"
|
||||||
|
6. 更新表格行状态(生成审核记录(①、插入infectious_audit 字段详表②、更改表infectious_card. Statu=2/5和infectious_card. update_time= now()))
|
||||||
|
|
||||||
|
**异常处理**:
|
||||||
|
|
||||||
|
· 数据加载失败:提示"数据加载失败,请重试"
|
||||||
|
|
||||||
|
**·** 重复提交:按钮置灰防止重复点击
|
||||||
|
|
||||||
|
**状态变化**:
|
||||||
|
|
||||||
|
- 审核通过:表格行变绿色,按钮禁用,状态变成“审核通过”
|
||||||
|
- 退回修改:表格行变橙色,生成退回记录,状态变成“审核失败”审核失败<->退回
|
||||||
|
|
||||||
|
**数据校验**:
|
||||||
|
|
||||||
|
- 必填字段红框提示
|
||||||
|
- 身份证号格式校验
|
||||||
|
- 日期逻辑校验(发病日期≤诊断日期)
|
||||||
|
|
||||||
|
#### 4. 筛选查询功能
|
||||||
|
|
||||||
|
**触发方式**:点击"查询"按钮
|
||||||
|
**查询逻辑**:
|
||||||
|
|
||||||
|
- 登记来源:精确匹配
|
||||||
|
- 患者姓名:模糊匹配
|
||||||
|
- 时间范围:闭区间查询
|
||||||
|
- 状态筛选:精确匹配
|
||||||
|
- 上报科室:多选
|
||||||
|
**性能优化**:
|
||||||
|
- 500ms防抖处理
|
||||||
|
- 分页加载(可以设置每页5/10/20条)实现分页功能
|
||||||
|
|
||||||
|
#### 5. 报卡详情查看
|
||||||
|
|
||||||
|
**触发条件**:点击行"查看"按钮
|
||||||
|
**抽屉内容**:
|
||||||
|
|
||||||
|
- 只读表单(包含所有报卡字段)
|
||||||
|
- 审核记录时间轴(倒序展示)
|
||||||
|
- 关闭按钮(右上角×图标)
|
||||||
|
**数据加载**:根据行数据获取报卡ID自动填充患者报卡信息(异步加载报卡详情数据)
|
||||||
|
|
||||||
|
#### 6. 筛选联动逻辑
|
||||||
|
|
||||||
|
| **操作** | **系统响应** |
|
||||||
|
| ---------------------- | ------------------------------------------ |
|
||||||
|
| 点击"今日待审核"统计卡 | 自动设置: - 时间=当天 - 状态=待审核 |
|
||||||
|
| 本月审核失败 | 自动设置: - 时间=本月 - 状态=审核失败 |
|
||||||
|
| 本月审核成功 | 自动设置: - 时间=本月 - 状态=审核成功 |
|
||||||
|
| 本月已上报 | 自动设置: - 时间=本月 - 状态=已上报 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 五、数据结构说明
|
||||||
|
|
||||||
|
**关键数据表**:
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
infectious_card 与报卡审核记录表(infectious_audit)采用 一对多 关联:
|
||||||
|
① 、一张报卡可经历多次审核(初审、复审、退回、重审等)。
|
||||||
|
② 、关联键:infectious_card.card_no → infectious_audit.card_id(FK)。
|
||||||
|
*infectious_card. Statu增加状态5退回=审核失败(当批量退回修改/退回修改时更改表infectious_card. Statu=5 and infectious_card. update_time= now())
|
||||||
|
|
||||||
|
*infectious_audit 字段详表
|
||||||
|
```
|
||||||
|
|
||||||
|
| **字段名** | **中文名称** | **取值说明** | **类型****(PG)** | **约束** |
|
||||||
|
| ----------------- | ------------ | ------------------------------------------------------------ | ---------------- | ---------------------------------------- |
|
||||||
|
| audit_id | 审核记录ID | 主键,自增 | BIGINT | PRIMARY KEY |
|
||||||
|
| card_id | 报卡ID | 关联infectious_card.card_no | BIGINT | NOT NULL, FK |
|
||||||
|
| audit_seq | 审核序号 | 第几次审核,从1开始 | SMALLINT | NOT NULL, ≥1 |
|
||||||
|
| audit_type | 审核类型 | 1批量审核/2单审核通过/3批量退回修改/4单退回修改 /5其他 | CHAR(1) | NOT NULL, IN('1','2','3','4','5') |
|
||||||
|
| audit_status_from | 审核前状态 | 同infectious_card.status(0暂存/1已提交=待审核/2已审核=审核通过/3已上报/4失败/5退回=审核失败) | CHAR(1) | NOT NULL, IN('0','1','2','3','4','5') |
|
||||||
|
| audit_status_to | 审核后状态 | 同上,审核后的新状态 | CHAR(1) | NOT NULL, IN('0','1','2','3','4') |
|
||||||
|
| audit_time | 审核时间 | 精确到秒,当前时间戳 | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||||
|
| auditor_id | 审核人账号 | 登录账号 | VARCHAR(20) | NOT NULL |
|
||||||
|
| auditor_name | 审核人姓名 | 登录账号的姓名 | VARCHAR(50) | NOT NULL |
|
||||||
|
| audit_opinion | 审核意见 | 审核意见简述 | TEXT | |
|
||||||
|
| Reason_for_return | 退回原因 | 退回原因简述 | TEXT | |
|
||||||
|
| fail_reason_code | 失败原因码 | 字典:001必填项/002逻辑错误/003网络超时等 | VARCHAR(20) | |
|
||||||
|
| fail_reason_desc | 失败详情 | 详细描述,可空 | TEXT | |
|
||||||
|
| is_batch | 是否批量 | 0单条审核/1批量审核 | BOOLEAN | NOT NULL, DEFAULT false |
|
||||||
|
| batch_size | 批量数量 | 批量时涉及报卡数 | INTEGER | NOT NULL, DEFAULT 1, ≥1 |
|
||||||
|
| created_time | 记录创建时间 | 自动生成 | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||||
|
| updated_time | 记录更新时间 | 自动更新 | TIMESTAMP | NOT NULL, DEFAULT now(), ON UPDATE now() |
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 六、开发实现要点
|
||||||
|
|
||||||
|
**样式规范**:
|
||||||
|
|
||||||
|
- **主色调**:`#4a6fa5`(导航栏/主按钮)
|
||||||
|
|
||||||
|
- **状态色**:
|
||||||
|
|
||||||
|
- - 待审核:`rgba(74, 111, 165, 0.1)`
|
||||||
|
- 审核失败:`rgba(231, 76, 60, 0.1)`
|
||||||
|
|
||||||
|
- **字体**:
|
||||||
|
|
||||||
|
- - 标题:`16px/1.5 #333`
|
||||||
|
- 表格内容:`14px/1.5 #666`
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
|
||||||
|
- 审核记录需永久保存不可删除
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 七、补充说明
|
||||||
|
|
||||||
|
1. **日期格式**:统一使用`YYYY/MM/DD`(符合医疗系统惯例)
|
||||||
|
2. **地址组件**:四级联动(省→市→区→街道)
|
||||||
|
3. **职业选项**:使用国家标准职业分类
|
||||||
|
4. **病种名称**:严格遵循《传染病报告卡》规范用词
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.core.common.annotation;
|
package com.core.common.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.*;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Excel额外表头信息注解
|
* Excel额外表头信息注解
|
||||||
@@ -14,7 +15,7 @@ public @interface ExcelExtra {
|
|||||||
/**
|
/**
|
||||||
* 表头名称
|
* 表头名称
|
||||||
*/
|
*/
|
||||||
String name();
|
String name() default "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日期格式,如:yyyy-MM-dd HH:mm:ss
|
* 日期格式,如:yyyy-MM-dd HH:mm:ss
|
||||||
@@ -35,4 +36,15 @@ public @interface ExcelExtra {
|
|||||||
* 是否导出
|
* 是否导出
|
||||||
*/
|
*/
|
||||||
boolean isExport() default true;
|
boolean isExport() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 精度 默认:-1(默认不开启BigDecimal格式化)
|
||||||
|
*/
|
||||||
|
int scale() default -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
|
||||||
|
*/
|
||||||
|
int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.core.common.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色枚举
|
||||||
|
*
|
||||||
|
* @author swb
|
||||||
|
* @date 2026-01-29
|
||||||
|
*/
|
||||||
|
public enum RoleEnum {
|
||||||
|
DOCTOR("doctor", "医生"),
|
||||||
|
NURSE("nurse", "护士"),
|
||||||
|
ADMIN("admin", "管理员");
|
||||||
|
private final String code;
|
||||||
|
private final String info;
|
||||||
|
|
||||||
|
RoleEnum(String code, String info) {
|
||||||
|
this.code = code;
|
||||||
|
this.info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInfo() {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,67 +33,125 @@ public final class AgeCalculatorUtil {
|
|||||||
return period.getYears();
|
return period.getYears();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * 当前年龄取得(床位列表表示年龄用)
|
||||||
|
// */
|
||||||
|
// public static String getAge(Date date) {
|
||||||
|
// // 将 Date 转换为 LocalDateTime
|
||||||
|
// LocalDateTime dateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||||
|
// LocalDateTime now = LocalDateTime.now();
|
||||||
|
// int years = now.getYear() - dateTime.getYear();
|
||||||
|
// if (years > 2) {
|
||||||
|
// return String.format("%d岁", years);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Period period = Period.between(dateTime.toLocalDate(), now.toLocalDate());
|
||||||
|
// int months = period.getMonths();
|
||||||
|
// int days = period.getDays();
|
||||||
|
// long hours = ChronoUnit.HOURS.between(dateTime, now) - (days * 24L);
|
||||||
|
//
|
||||||
|
// if (hours < 0) {
|
||||||
|
// hours += 24;
|
||||||
|
// days--;
|
||||||
|
// }
|
||||||
|
// if (days < 0) {
|
||||||
|
// months--;
|
||||||
|
// days = getLastDayOfMonth(dateTime) - dateTime.getDayOfMonth() + now.getDayOfMonth();
|
||||||
|
// }
|
||||||
|
// if (months < 0) {
|
||||||
|
// months += 12;
|
||||||
|
// years--;
|
||||||
|
// }
|
||||||
|
// if (years < 0) {
|
||||||
|
// return "1小时";
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (years > 0 && months > 0) {
|
||||||
|
// return String.format("%d岁%d月", years, months);
|
||||||
|
// }
|
||||||
|
// if (years > 0) {
|
||||||
|
// return String.format("%d岁", years);
|
||||||
|
// }
|
||||||
|
// if (months > 0 && days > 0) {
|
||||||
|
// return String.format("%d月%d天", months, days);
|
||||||
|
// }
|
||||||
|
// if (months > 0) {
|
||||||
|
// return String.format("%d月", months);
|
||||||
|
// }
|
||||||
|
// if (days > 0 && hours > 0) {
|
||||||
|
// return String.format("%d天%d小时", days, hours);
|
||||||
|
// }
|
||||||
|
// if (days > 0) {
|
||||||
|
// return String.format("%d天", days);
|
||||||
|
// }
|
||||||
|
// if (hours > 0) {
|
||||||
|
// return String.format("%d小时", hours);
|
||||||
|
// }
|
||||||
|
// return "1小时";
|
||||||
|
// }
|
||||||
/**
|
/**
|
||||||
* 当前年龄取得(床位列表表示年龄用)
|
* 复刻Oracle函数FUN_GET_AGE的核心逻辑:返回年龄字符串
|
||||||
|
*
|
||||||
|
* @param birthDate 出生日期
|
||||||
|
* @return 年龄字符串(如:29岁、3岁5月、2月15天、18天),出生日期晚于当前日期返回空字符串
|
||||||
*/
|
*/
|
||||||
public static String getAge(Date date) {
|
public static String getAge(Date birthDate) {
|
||||||
// 添加空值检查
|
// 入参校验
|
||||||
if (date == null) {
|
if (birthDate == null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
// 将 Date 转换为 LocalDateTime
|
|
||||||
LocalDateTime dateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
// 将Date转换为LocalDate(使用系统默认时区)
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDate birthLocalDate = birthDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||||
int years = now.getYear() - dateTime.getYear();
|
LocalDate currentDate = LocalDate.now();
|
||||||
if (years > 2) {
|
|
||||||
return String.format("%d岁", years);
|
// 计算总天数(对应Oracle中的IDAY)
|
||||||
|
long totalDays = ChronoUnit.DAYS.between(birthLocalDate, currentDate);
|
||||||
|
|
||||||
|
// 若出生日期晚于当前日期,返回空字符串
|
||||||
|
if (totalDays < 0) {
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Period period = Period.between(dateTime.toLocalDate(), now.toLocalDate());
|
// 计算年份(复刻Oracle的闰年补偿逻辑:(当前年-出生年)/4 补偿闰年天数)
|
||||||
int months = period.getMonths();
|
int birthYear = birthLocalDate.getYear();
|
||||||
int days = period.getDays();
|
int currentYear = currentDate.getYear();
|
||||||
long hours = ChronoUnit.HOURS.between(dateTime, now) - (days * 24L);
|
long leapYearCompensation = (currentYear - birthYear) / 4;
|
||||||
|
long adjustedDays = totalDays - leapYearCompensation;
|
||||||
|
|
||||||
if (hours < 0) {
|
// 计算年、月、天(按365天/年、30天/月粗略折算,与Oracle逻辑一致)
|
||||||
hours += 24;
|
int iYear = (int) (adjustedDays / 365);
|
||||||
days--;
|
long remainingDaysAfterYear = adjustedDays - iYear * 365;
|
||||||
}
|
int iMonth = (int) (remainingDaysAfterYear / 30);
|
||||||
if (days < 0) {
|
int iDay = (int) (remainingDaysAfterYear - iMonth * 30);
|
||||||
months--;
|
|
||||||
days = getLastDayOfMonth(dateTime) - dateTime.getDayOfMonth() + now.getDayOfMonth();
|
|
||||||
}
|
|
||||||
if (months < 0) {
|
|
||||||
months += 12;
|
|
||||||
years--;
|
|
||||||
}
|
|
||||||
if (years < 0) {
|
|
||||||
return "1小时";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (years > 0 && months > 0) {
|
// 按原函数规则拼接返回字符串
|
||||||
return String.format("%d岁%d月", years, months);
|
if (iYear <= 0) {
|
||||||
|
// 小于1岁
|
||||||
|
if (iMonth <= 0) {
|
||||||
|
// 小于1个月,返回X天
|
||||||
|
return iDay + "天";
|
||||||
|
} else {
|
||||||
|
// 1个月及以上,返回X月X天
|
||||||
|
return iMonth + "月" + iDay + "天";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 1岁及以上
|
||||||
|
if (iYear < 5) {
|
||||||
|
// 1-4岁
|
||||||
|
if (iMonth <= 0) {
|
||||||
|
// 无整月,返回X岁X天
|
||||||
|
return iYear + "岁" + iDay + "天";
|
||||||
|
} else {
|
||||||
|
// 有整月,返回X岁X月
|
||||||
|
return iYear + "岁" + iMonth + "月";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 5岁及以上,仅返回X岁
|
||||||
|
return iYear + "岁";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (years > 0) {
|
|
||||||
return String.format("%d岁", years);
|
|
||||||
}
|
|
||||||
if (months > 0 && days > 0) {
|
|
||||||
return String.format("%d月%d天", months, days);
|
|
||||||
}
|
|
||||||
if (months > 0) {
|
|
||||||
return String.format("%d月", months);
|
|
||||||
}
|
|
||||||
if (days > 0 && hours > 0) {
|
|
||||||
return String.format("%d天%d小时", days, hours);
|
|
||||||
}
|
|
||||||
if (days > 0) {
|
|
||||||
return String.format("%d天", days);
|
|
||||||
}
|
|
||||||
if (hours > 0) {
|
|
||||||
return String.format("%d小时", hours);
|
|
||||||
}
|
|
||||||
return "1小时";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getLastDayOfMonth(LocalDateTime dateTime) {
|
private static int getLastDayOfMonth(LocalDateTime dateTime) {
|
||||||
int[] daysInMonth = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
int[] daysInMonth = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||||
if (isLeapYear(dateTime.getYear()) && dateTime.getMonthValue() == 2) {
|
if (isLeapYear(dateTime.getYear()) && dateTime.getMonthValue() == 2) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.core.common.utils;
|
|||||||
|
|
||||||
import com.core.common.core.domain.model.LoginUser;
|
import com.core.common.core.domain.model.LoginUser;
|
||||||
import com.core.common.utils.SecurityUtils;
|
import com.core.common.utils.SecurityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
@@ -13,6 +15,8 @@ import java.util.Date;
|
|||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class AuditFieldUtil {
|
public class AuditFieldUtil {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AuditFieldUtil.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 为实体设置创建相关的审计字段
|
* 为实体设置创建相关的审计字段
|
||||||
@@ -65,8 +69,7 @@ public class AuditFieldUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("设置创建审计字段时发生异常: " + e.getMessage());
|
log.error("设置创建审计字段时发生异常", e);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,8 +113,7 @@ public class AuditFieldUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("设置更新审计字段时发生异常: " + e.getMessage());
|
log.error("设置更新审计字段时发生异常", e);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.core.common.utils;
|
package com.core.common.utils;
|
||||||
|
|
||||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
@@ -13,10 +15,13 @@ import java.util.Date;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 时间工具类
|
* 时间工具类
|
||||||
*
|
*
|
||||||
* @author system
|
* @author system
|
||||||
*/
|
*/
|
||||||
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(DateUtils.class);
|
||||||
|
|
||||||
public static String YYYY = "yyyy";
|
public static String YYYY = "yyyy";
|
||||||
|
|
||||||
public static String YYYY_MM = "yyyy-MM";
|
public static String YYYY_MM = "yyyy-MM";
|
||||||
@@ -227,7 +232,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
|||||||
return endTime;
|
return endTime;
|
||||||
}
|
}
|
||||||
} catch (DateTimeParseException e) {
|
} catch (DateTimeParseException e) {
|
||||||
e.printStackTrace();
|
log.warn("日期解析失败: {}", strDate, e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -250,7 +255,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
|||||||
// 检查日期是否是未来的时间
|
// 检查日期是否是未来的时间
|
||||||
return dateToCheck.isAfter(currentDate);
|
return dateToCheck.isAfter(currentDate);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.warn("日期解析失败: {}", dateString, e);
|
||||||
// 解析失败或其他异常,返回 false 或根据需要处理异常
|
// 解析失败或其他异常,返回 false 或根据需要处理异常
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
package com.core.common.utils;
|
package com.core.common.utils;
|
||||||
|
|
||||||
import com.core.common.annotation.Excel;
|
import java.io.*;
|
||||||
import com.core.common.annotation.Excel.ColumnType;
|
import java.lang.reflect.Field;
|
||||||
import com.core.common.annotation.Excel.Type;
|
import java.lang.reflect.Method;
|
||||||
import com.core.common.annotation.ExcelExtra;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import com.core.common.annotation.Excels;
|
import java.math.BigDecimal;
|
||||||
import com.core.common.config.CoreConfig;
|
import java.text.DecimalFormat;
|
||||||
import com.core.common.core.domain.AjaxResult;
|
import java.time.LocalDate;
|
||||||
import com.core.common.core.text.Convert;
|
import java.time.LocalDateTime;
|
||||||
import com.core.common.exception.UtilException;
|
import java.util.*;
|
||||||
import com.core.common.utils.file.FileTypeUtils;
|
import java.util.stream.Collectors;
|
||||||
import com.core.common.utils.file.FileUtils;
|
|
||||||
import com.core.common.utils.file.ImageUtils;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import com.core.common.utils.poi.ExcelHandlerAdapter;
|
|
||||||
import com.core.common.utils.reflect.ReflectUtils;
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.apache.commons.lang3.RegExUtils;
|
import org.apache.commons.lang3.RegExUtils;
|
||||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||||
@@ -30,17 +29,20 @@ import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import com.core.common.annotation.Excel;
|
||||||
import java.io.*;
|
import com.core.common.annotation.Excel.ColumnType;
|
||||||
import java.lang.reflect.Field;
|
import com.core.common.annotation.Excel.Type;
|
||||||
import java.lang.reflect.Method;
|
import com.core.common.annotation.ExcelExtra;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import com.core.common.annotation.Excels;
|
||||||
import java.math.BigDecimal;
|
import com.core.common.config.CoreConfig;
|
||||||
import java.text.DecimalFormat;
|
import com.core.common.core.domain.AjaxResult;
|
||||||
import java.time.LocalDate;
|
import com.core.common.core.text.Convert;
|
||||||
import java.time.LocalDateTime;
|
import com.core.common.exception.UtilException;
|
||||||
import java.util.*;
|
import com.core.common.utils.file.FileTypeUtils;
|
||||||
import java.util.stream.Collectors;
|
import com.core.common.utils.file.FileUtils;
|
||||||
|
import com.core.common.utils.file.ImageUtils;
|
||||||
|
import com.core.common.utils.poi.ExcelHandlerAdapter;
|
||||||
|
import com.core.common.utils.reflect.ReflectUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Excel相关处理
|
* Excel相关处理
|
||||||
@@ -1164,6 +1166,11 @@ public class NewExcelUtil<T> {
|
|||||||
ParameterizedType pt = (ParameterizedType)field.getGenericType();
|
ParameterizedType pt = (ParameterizedType)field.getGenericType();
|
||||||
Class<?> subClass = (Class<?>)pt.getActualTypeArguments()[0];
|
Class<?> subClass = (Class<?>)pt.getActualTypeArguments()[0];
|
||||||
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
|
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
|
||||||
|
if (StringUtils.isNotEmpty(includeFields)) {
|
||||||
|
this.subFields = this.subFields.stream().filter(f -> ArrayUtils.contains(includeFields, f.getName())).collect(Collectors.toList());
|
||||||
|
} else if (StringUtils.isNotEmpty(excludeFields)) {
|
||||||
|
this.subFields = this.subFields.stream().filter(f -> !ArrayUtils.contains(excludeFields, f.getName())).collect(Collectors.toList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1441,7 +1448,28 @@ public class NewExcelUtil<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 计算表格体总列数
|
||||||
|
int totalCols = 0;
|
||||||
|
for (Object[] os : fields) {
|
||||||
|
Field field = (Field)os[0];
|
||||||
|
if (Collection.class.isAssignableFrom(field.getType()) && subFields != null) {
|
||||||
|
long subCount = subFields.stream().filter(f -> f.isAnnotationPresent(Excel.class)).count();
|
||||||
|
totalCols += subCount;
|
||||||
|
} else {
|
||||||
|
totalCols++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (totalCols == 0) totalCols = 1;
|
||||||
|
|
||||||
int currentRowNum = rownum;
|
int currentRowNum = rownum;
|
||||||
|
int colIndex = 0;
|
||||||
|
Row row = null;
|
||||||
|
boolean hasVisible = false;
|
||||||
|
|
||||||
|
// 布局配置:Label占用1列,Value占用2列,共3列
|
||||||
|
int labelCols = 1;
|
||||||
|
int valueCols = 2;
|
||||||
|
int itemCols = labelCols + valueCols;
|
||||||
|
|
||||||
for (Object[] os : extraFields) {
|
for (Object[] os : extraFields) {
|
||||||
Field field = (Field)os[0];
|
Field field = (Field)os[0];
|
||||||
@@ -1451,43 +1479,50 @@ public class NewExcelUtil<T> {
|
|||||||
if (isExtraFieldHidden(field.getName())) {
|
if (isExtraFieldHidden(field.getName())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
hasVisible = true;
|
||||||
|
|
||||||
Row row = sheet.createRow(currentRowNum++);
|
// 自动换行:如果不是行首,且剩余空间不足,则换行
|
||||||
|
if (row == null) {
|
||||||
|
row = sheet.createRow(currentRowNum);
|
||||||
|
} else if (colIndex > 0 && colIndex + itemCols > totalCols) {
|
||||||
|
currentRowNum++;
|
||||||
|
row = sheet.createRow(currentRowNum);
|
||||||
|
colIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// 创建标签单元格(第0列)
|
// 1. 创建 Label 单元格
|
||||||
Cell labelCell = row.createCell(0);
|
Cell labelCell = row.createCell(colIndex);
|
||||||
labelCell.setCellValue(attr.name());
|
labelCell.setCellValue(attr.name());
|
||||||
labelCell.setCellStyle(styles.get("extraLabel"));
|
labelCell.setCellStyle(styles.get("extraLabel"));
|
||||||
|
|
||||||
// 创建值单元格(第1列)
|
// 2. 创建 Value 单元格
|
||||||
Cell valueCell = row.createCell(1);
|
int valueStartCol = colIndex + labelCols;
|
||||||
|
Cell valueCell = row.createCell(valueStartCol);
|
||||||
Object value = field.get(entity);
|
Object value = field.get(entity);
|
||||||
String cellValue = formatExtraCellValue(value, attr);
|
String cellValue = formatExtraCellValue(value, attr);
|
||||||
valueCell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue);
|
valueCell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue);
|
||||||
valueCell.setCellStyle(styles.get("extraValue"));
|
valueCell.setCellStyle(styles.get("extraValue"));
|
||||||
|
|
||||||
// 创建合并区域(第1列到第2列)
|
// 3. 合并 Value 单元格
|
||||||
CellRangeAddress mergedRegion = new CellRangeAddress(row.getRowNum(), row.getRowNum(), 1, 2);
|
if (valueCols > 1) {
|
||||||
sheet.addMergedRegion(mergedRegion);
|
int valueEndCol = valueStartCol + valueCols - 1;
|
||||||
|
CellRangeAddress mergedRegion = new CellRangeAddress(row.getRowNum(), row.getRowNum(), valueStartCol, valueEndCol);
|
||||||
|
sheet.addMergedRegion(mergedRegion);
|
||||||
|
|
||||||
|
// 设置边框
|
||||||
|
RegionUtil.setBorderTop(BorderStyle.THIN, mergedRegion, sheet);
|
||||||
|
RegionUtil.setBorderBottom(BorderStyle.THIN, mergedRegion, sheet);
|
||||||
|
RegionUtil.setBorderLeft(BorderStyle.THIN, mergedRegion, sheet);
|
||||||
|
RegionUtil.setBorderRight(BorderStyle.THIN, mergedRegion, sheet);
|
||||||
|
}
|
||||||
|
|
||||||
// 手动设置合并区域的边框,确保完整显示
|
colIndex += itemCols;
|
||||||
RegionUtil.setBorderTop(BorderStyle.THIN, mergedRegion, sheet);
|
|
||||||
RegionUtil.setBorderBottom(BorderStyle.THIN, mergedRegion, sheet);
|
|
||||||
RegionUtil.setBorderLeft(BorderStyle.THIN, mergedRegion, sheet);
|
|
||||||
RegionUtil.setBorderRight(BorderStyle.THIN, mergedRegion, sheet);
|
|
||||||
RegionUtil.setTopBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
|
||||||
RegionUtil.setBottomBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
|
||||||
RegionUtil.setLeftBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
|
||||||
RegionUtil.setRightBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置列宽
|
|
||||||
sheet.setColumnWidth(0, 15 * 256); // 标签列宽
|
|
||||||
sheet.setColumnWidth(1, 20 * 256); // 值列宽
|
|
||||||
sheet.setColumnWidth(2, 20 * 256); // 值列宽
|
|
||||||
|
|
||||||
// 更新当前行号,在额外表头和数据表头之间空一行
|
// 更新当前行号,在额外表头和数据表头之间空一行
|
||||||
rownum = currentRowNum + 1;
|
if (hasVisible) {
|
||||||
|
rownum = currentRowNum + 2;
|
||||||
|
}
|
||||||
subMergedFirstRowNum = rownum;
|
subMergedFirstRowNum = rownum;
|
||||||
subMergedLastRowNum = rownum;
|
subMergedLastRowNum = rownum;
|
||||||
|
|
||||||
@@ -1508,6 +1543,10 @@ public class NewExcelUtil<T> {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value instanceof BigDecimal && attr.scale() >= 0) {
|
||||||
|
return ((BigDecimal) value).setScale(attr.scale(), attr.roundingMode()).toString();
|
||||||
|
}
|
||||||
|
|
||||||
if (StringUtils.isNotEmpty(attr.dateFormat())) {
|
if (StringUtils.isNotEmpty(attr.dateFormat())) {
|
||||||
return parseDateToStr(attr.dateFormat(), value);
|
return parseDateToStr(attr.dateFormat(), value);
|
||||||
}
|
}
|
||||||
@@ -1808,7 +1847,7 @@ public class NewExcelUtil<T> {
|
|||||||
row = sheet.createRow(rowNo);
|
row = sheet.createRow(rowNo);
|
||||||
}
|
}
|
||||||
// 子字段也要排序
|
// 子字段也要排序
|
||||||
List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class);
|
List<Field> subFields = this.subFields;
|
||||||
List<Field> sortedSubFields = subFields.stream().sorted(Comparator.comparing(subField -> {
|
List<Field> sortedSubFields = subFields.stream().sorted(Comparator.comparing(subField -> {
|
||||||
Excel subExcel = subField.getAnnotation(Excel.class);
|
Excel subExcel = subField.getAnnotation(Excel.class);
|
||||||
return subExcel.sort();
|
return subExcel.sort();
|
||||||
@@ -1832,4 +1871,4 @@ public class NewExcelUtil<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.core.common.utils;
|
|||||||
|
|
||||||
import com.core.common.constant.Constants;
|
import com.core.common.constant.Constants;
|
||||||
import com.core.common.core.text.Convert;
|
import com.core.common.core.text.Convert;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.web.context.request.RequestAttributes;
|
import org.springframework.web.context.request.RequestAttributes;
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
@@ -20,10 +22,13 @@ import java.util.Map;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户端工具类
|
* 客户端工具类
|
||||||
*
|
*
|
||||||
* @author system
|
* @author system
|
||||||
*/
|
*/
|
||||||
public class ServletUtils {
|
public class ServletUtils {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ServletUtils.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取String参数
|
* 获取String参数
|
||||||
*/
|
*/
|
||||||
@@ -130,7 +135,7 @@ public class ServletUtils {
|
|||||||
response.setCharacterEncoding("utf-8");
|
response.setCharacterEncoding("utf-8");
|
||||||
response.getWriter().print(string);
|
response.getWriter().print(string);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
log.error("渲染响应字符串失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.core.common.utils.bean;
|
package com.core.common.utils.bean;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -12,6 +15,9 @@ import java.util.regex.Pattern;
|
|||||||
* @author system
|
* @author system
|
||||||
*/
|
*/
|
||||||
public class BeanUtils extends org.springframework.beans.BeanUtils {
|
public class BeanUtils extends org.springframework.beans.BeanUtils {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(BeanUtils.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bean方法名中属性名开始的下标
|
* Bean方法名中属性名开始的下标
|
||||||
*/
|
*/
|
||||||
@@ -37,7 +43,7 @@ public class BeanUtils extends org.springframework.beans.BeanUtils {
|
|||||||
try {
|
try {
|
||||||
copyProperties(src, dest);
|
copyProperties(src, dest);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("Bean属性复制失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ public class FlowDefinitionController extends BaseController {
|
|||||||
ImageIO.write(image, "png", os);
|
ImageIO.write(image, "png", os);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("读取流程图片失败, deployId: {}", deployId, e);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
if (os != null) {
|
if (os != null) {
|
||||||
@@ -123,7 +123,7 @@ public class FlowDefinitionController extends BaseController {
|
|||||||
os.close();
|
os.close();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
log.error("关闭输出流失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ public class FlowTaskController extends BaseController {
|
|||||||
ImageIO.write(image, "png", os);
|
ImageIO.write(image, "png", os);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("读取流程图片失败", e);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
if (os != null) {
|
if (os != null) {
|
||||||
@@ -219,7 +219,7 @@ public class FlowTaskController extends BaseController {
|
|||||||
os.close();
|
os.close();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
log.error("关闭输出流失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -722,7 +722,7 @@ public class FlowableUtils {
|
|||||||
// 反射设置属性值
|
// 反射设置属性值
|
||||||
field.set(propertyDto, attribute.getValue());
|
field.set(propertyDto, attribute.getValue());
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
e.printStackTrace();
|
log.warn("反射设置属性值失败", e);
|
||||||
// 如果反射设置失败则忽略该属性
|
// 如果反射设置失败则忽略该属性
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -730,7 +730,7 @@ public class FlowableUtils {
|
|||||||
return propertyDto;
|
return propertyDto;
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("解析流程属性失败", e);
|
||||||
return Collections.emptyList(); // 如果发生异常则返回空列表
|
return Collections.emptyList(); // 如果发生异常则返回空列表
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFl
|
|||||||
}
|
}
|
||||||
return AjaxResult.success("流程启动成功");
|
return AjaxResult.success("流程启动成功");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("流程启动错误", e);
|
||||||
return AjaxResult.error("流程启动错误");
|
return AjaxResult.error("流程启动错误");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ public class FlowInstanceServiceImpl extends FlowServiceFactory implements IFlow
|
|||||||
runtimeService.startProcessInstanceById(procDefId, variables);
|
runtimeService.startProcessInstanceById(procDefId, variables);
|
||||||
return AjaxResult.success("流程启动成功");
|
return AjaxResult.success("流程启动成功");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("流程启动错误, procDefId: {}", procDefId, e);
|
||||||
return AjaxResult.error("流程启动错误");
|
return AjaxResult.error("流程启动错误");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package com.core.framework.config;
|
package com.core.framework.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,6 +28,14 @@ public class ApplicationConfig {
|
|||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
|
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
|
||||||
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
|
return builder -> {
|
||||||
|
// 设置默认时区
|
||||||
|
builder.timeZone(TimeZone.getDefault());
|
||||||
|
// 设置日期格式为 yyyy/M/d HH:mm:ss,支持多种格式反序列化
|
||||||
|
builder.simpleDateFormat("yyyy/M/d HH:mm:ss");
|
||||||
|
// 添加JavaTimeModule支持,用于LocalDateTime
|
||||||
|
builder.modules(new JavaTimeModule());
|
||||||
|
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy/M/d HH:mm:ss")));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import com.core.generator.service.IGenTableColumnService;
|
|||||||
import com.core.generator.service.IGenTableService;
|
import com.core.generator.service.IGenTableService;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.poi.ss.usermodel.*;
|
import org.apache.poi.ss.usermodel.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
@@ -43,6 +45,8 @@ import java.util.Map;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/tool/gen")
|
@RequestMapping("/tool/gen")
|
||||||
public class GenController extends BaseController {
|
public class GenController extends BaseController {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(GenController.class);
|
||||||
@Autowired
|
@Autowired
|
||||||
private IGenTableService genTableService;
|
private IGenTableService genTableService;
|
||||||
|
|
||||||
@@ -436,7 +440,7 @@ public class GenController extends BaseController {
|
|||||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
|
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
|
||||||
writer.write(str);
|
writer.write(str);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
log.error("写入文件失败: {}", filePath, e);
|
||||||
} finally {
|
} finally {
|
||||||
is.close();
|
is.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,9 +54,17 @@ public interface SysUserRoleMapper {
|
|||||||
/**
|
/**
|
||||||
* 批量取消授权用户角色
|
* 批量取消授权用户角色
|
||||||
*
|
*
|
||||||
* @param roleId 角色ID
|
* @param roleId 角色 ID
|
||||||
* @param userIds 需要删除的用户数据ID
|
* @param userIds 需要删除的用户数据 ID
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds);
|
public int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过角色 ID 查询用户 ID 列表
|
||||||
|
*
|
||||||
|
* @param roleId 角色 ID
|
||||||
|
* @return 用户 ID 列表
|
||||||
|
*/
|
||||||
|
public List<Long> selectUserIdsByRoleId(Long roleId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import com.core.system.mapper.SysRoleDeptMapper;
|
|||||||
import com.core.system.mapper.SysRoleMapper;
|
import com.core.system.mapper.SysRoleMapper;
|
||||||
import com.core.system.mapper.SysRoleMenuMapper;
|
import com.core.system.mapper.SysRoleMenuMapper;
|
||||||
import com.core.system.mapper.SysUserRoleMapper;
|
import com.core.system.mapper.SysUserRoleMapper;
|
||||||
|
import com.core.system.service.ISysMenuService;
|
||||||
import com.core.system.service.ISysRoleService;
|
import com.core.system.service.ISysRoleService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -41,6 +42,9 @@ public class SysRoleServiceImpl implements ISysRoleService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SysRoleDeptMapper roleDeptMapper;
|
private SysRoleDeptMapper roleDeptMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISysMenuService menuService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据条件分页查询角色数据
|
* 根据条件分页查询角色数据
|
||||||
*
|
*
|
||||||
@@ -221,11 +225,23 @@ public class SysRoleServiceImpl implements ISysRoleService {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public int updateRole(SysRole role) {
|
public int updateRole(SysRole role) {
|
||||||
// 修改角色信息
|
// 1. 获取该角色下的所有用户 ID(在修改权限前查询)
|
||||||
|
List<Long> userIds = userRoleMapper.selectUserIdsByRoleId(role.getRoleId());
|
||||||
|
|
||||||
|
// 2. 修改角色信息
|
||||||
roleMapper.updateRole(role);
|
roleMapper.updateRole(role);
|
||||||
// 删除角色与菜单关联
|
// 3. 删除角色与菜单关联
|
||||||
roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId());
|
roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId());
|
||||||
return insertRoleMenu(role);
|
int result = insertRoleMenu(role);
|
||||||
|
|
||||||
|
// 4. 清除该角色下所有用户的菜单树缓存
|
||||||
|
if (userIds != null && !userIds.isEmpty()) {
|
||||||
|
for (Long userId : userIds) {
|
||||||
|
menuService.clearMenuCacheByUserId(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import com.core.system.domain.SysUserConfig;
|
|||||||
import com.core.common.utils.StringUtils;
|
import com.core.common.utils.StringUtils;
|
||||||
import com.core.system.mapper.SysUserConfigMapper;
|
import com.core.system.mapper.SysUserConfigMapper;
|
||||||
import com.core.system.service.ISysUserConfigService;
|
import com.core.system.service.ISysUserConfigService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -13,13 +15,15 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户配置Service业务层处理
|
* 用户配置Service业务层处理
|
||||||
*
|
*
|
||||||
* @author
|
* @author
|
||||||
* @date 2026-01-30
|
* @date 2026-01-30
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class SysUserConfigServiceImpl extends ServiceImpl<SysUserConfigMapper, SysUserConfig> implements ISysUserConfigService
|
public class SysUserConfigServiceImpl extends ServiceImpl<SysUserConfigMapper, SysUserConfig> implements ISysUserConfigService
|
||||||
{
|
{
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SysUserConfigServiceImpl.class);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SysUserConfigMapper sysUserConfigMapper;
|
private SysUserConfigMapper sysUserConfigMapper;
|
||||||
|
|
||||||
@@ -170,8 +174,7 @@ public class SysUserConfigServiceImpl extends ServiceImpl<SysUserConfigMapper, S
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 记录错误日志以便调试
|
// 记录错误日志以便调试
|
||||||
System.err.println("保存用户配置时发生错误: " + e.getMessage());
|
log.error("保存用户配置时发生错误, userId: {}, configKey: {}", userId, configKey, e);
|
||||||
e.printStackTrace();
|
|
||||||
throw e; // 重新抛出异常让上层处理
|
throw e; // 重新抛出异常让上层处理
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,4 +48,10 @@
|
|||||||
#{userId}
|
#{userId}
|
||||||
</foreach>
|
</foreach>
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
|
<select id="selectUserIdsByRoleId" resultType="Long">
|
||||||
|
select user_id
|
||||||
|
from sys_user_role
|
||||||
|
where role_id = #{roleId}
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Web Layer - API Controllers
|
||||||
|
|
||||||
|
**Module**: `openhis-application/web`
|
||||||
|
**Role**: API endpoint layer - all REST controllers for frontend communication
|
||||||
|
|
||||||
|
## OVERVIEW
|
||||||
|
46 web modules serving REST APIs for all business functionality.
|
||||||
|
|
||||||
|
## STRUCTURE
|
||||||
|
```
|
||||||
|
web/
|
||||||
|
├── [module-name]/
|
||||||
|
│ ├── controller/ # REST endpoints (@RestController)
|
||||||
|
│ ├── dto/ # Data transfer objects
|
||||||
|
│ ├── mapper/ # MyBatis mappers (if module-specific)
|
||||||
|
│ └── appservice/ # Application service layer
|
||||||
|
│ └── impl/
|
||||||
|
```
|
||||||
|
|
||||||
|
## WHERE TO LOOK
|
||||||
|
| Task | Location |
|
||||||
|
|------|----------|
|
||||||
|
| API endpoints | `*/controller/*Controller.java` |
|
||||||
|
| Request/Response schemas | `*/dto/*.java` |
|
||||||
|
| Business logic orchestration | `*/appservice/*.java` |
|
||||||
|
|
||||||
|
## CONVENTIONS
|
||||||
|
- Controllers: `@RestController`, `@RequestMapping("/module-name")`
|
||||||
|
- Standard response: `AjaxResult` from core-common
|
||||||
|
- DTO naming: `XxxRequest`, `XxxResponse`, `XxxDTO`
|
||||||
|
- Service pattern: interface in `appservice/`, impl in `appservice/impl/`
|
||||||
|
- API naming: `listXxx()`, `getXxx()`, `addXxx()`, `updateXxx()`, `deleteXxx()`
|
||||||
|
|
||||||
|
## ANTI-PATTERNS
|
||||||
|
- Never put business logic in controllers - delegate to appservice
|
||||||
|
- Never return raw entities - use DTOs
|
||||||
|
- Never bypass `AjaxResult` wrapper
|
||||||
|
- Never create module-specific mappers without justification
|
||||||
@@ -104,4 +104,8 @@ public class InstrumentManageDto {
|
|||||||
/** 备注 */
|
/** 备注 */
|
||||||
private String remarks;
|
private String remarks;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public Integer getInstrumentTypeEnum() {
|
||||||
|
return instrumentTypeEnum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,19 @@ public class InstrumentManageInitDto {
|
|||||||
private List<InstrumentType> InstrumentTypeList;
|
private List<InstrumentType> InstrumentTypeList;
|
||||||
private List<InstrumentStatusEnumOption> InstrumentStatusEnumList;
|
private List<InstrumentStatusEnumOption> InstrumentStatusEnumList;
|
||||||
|
|
||||||
|
// 手动添加 setter 方法
|
||||||
|
public void setStatusFlagOptions(List<statusEnumOption> statusFlagOptions) {
|
||||||
|
this.statusFlagOptions = statusFlagOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstrumentTypeList(List<InstrumentType> InstrumentTypeList) {
|
||||||
|
this.InstrumentTypeList = InstrumentTypeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstrumentStatusEnumList(List<InstrumentStatusEnumOption> InstrumentStatusEnumList) {
|
||||||
|
this.InstrumentStatusEnumList = InstrumentStatusEnumList;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态
|
* 状态
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,4 +13,13 @@ import java.util.List;
|
|||||||
public class InstrumentStatusRequest {
|
public class InstrumentStatusRequest {
|
||||||
private List<Long> ids;
|
private List<Long> ids;
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> getIds() {
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,4 +28,36 @@ public class LisConfigManageDto {
|
|||||||
|
|
||||||
private List<ActivityDefSpecimenDef> activityDefSpecimenDefs;
|
private List<ActivityDefSpecimenDef> activityDefSpecimenDefs;
|
||||||
|
|
||||||
|
// 手动添加 getter 和 setter 方法
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ActivityDefDeviceDef> getActivityDefDeviceDefs() {
|
||||||
|
return activityDefDeviceDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivityDefDeviceDefs(List<ActivityDefDeviceDef> activityDefDeviceDefs) {
|
||||||
|
this.activityDefDeviceDefs = activityDefDeviceDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ActivityDefObservationDef> getActivityDefObservationDefs() {
|
||||||
|
return activityDefObservationDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivityDefObservationDefs(List<ActivityDefObservationDef> activityDefObservationDefs) {
|
||||||
|
this.activityDefObservationDefs = activityDefObservationDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ActivityDefSpecimenDef> getActivityDefSpecimenDefs() {
|
||||||
|
return activityDefSpecimenDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivityDefSpecimenDefs(List<ActivityDefSpecimenDef> activityDefSpecimenDefs) {
|
||||||
|
this.activityDefSpecimenDefs = activityDefSpecimenDefs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,16 @@ public class LisConfigManageInitDto {
|
|||||||
|
|
||||||
private List<SpecimenDefinition> specimenDefs;
|
private List<SpecimenDefinition> specimenDefs;
|
||||||
|
|
||||||
|
// 手动添加 setter 方法
|
||||||
|
public void setDeviceDefs(List<DeviceDefinition> deviceDefs) {
|
||||||
|
this.deviceDefs = deviceDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setObservationDefs(List<ObservationDefinition> observationDefs) {
|
||||||
|
this.observationDefs = observationDefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSpecimenDefs(List<SpecimenDefinition> specimenDefs) {
|
||||||
|
this.specimenDefs = specimenDefs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,16 @@ public class ObservationDefManageDto {
|
|||||||
/** 删除状态) */
|
/** 删除状态) */
|
||||||
private String deleteFlag;
|
private String deleteFlag;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public Long getInstrumentId() {
|
||||||
|
return instrumentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getStatusEnum() {
|
||||||
|
return statusEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getObservationTypeEnum() {
|
||||||
|
return observationTypeEnum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,19 @@ public class ObservationDefManageInitDto {
|
|||||||
private List<ObservationTypeEnumOption> ObservationTypeList;
|
private List<ObservationTypeEnumOption> ObservationTypeList;
|
||||||
private List<InstrumentEnumOption> instrumentEnumOptionList;
|
private List<InstrumentEnumOption> instrumentEnumOptionList;
|
||||||
|
|
||||||
|
// 手动添加 setter 方法
|
||||||
|
public void setStatusFlagOptions(List<statusEnumOption> statusFlagOptions) {
|
||||||
|
this.statusFlagOptions = statusFlagOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setObservationTypeList(List<ObservationTypeEnumOption> ObservationTypeList) {
|
||||||
|
this.ObservationTypeList = ObservationTypeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstrumentEnumOptionList(List<InstrumentEnumOption> instrumentEnumOptionList) {
|
||||||
|
this.instrumentEnumOptionList = instrumentEnumOptionList;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态
|
* 状态
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,4 +13,13 @@ import java.util.List;
|
|||||||
public class ObservationDefStatusRequest {
|
public class ObservationDefStatusRequest {
|
||||||
private List<Long> ids;
|
private List<Long> ids;
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> getIds() {
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,4 +32,8 @@ public class ReportResultManageDto {
|
|||||||
private String authoredTime; // 开单时间
|
private String authoredTime; // 开单时间
|
||||||
|
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public Integer getGenderEnum() {
|
||||||
|
return genderEnum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,4 +40,12 @@ public class SampleCollectManageDto {
|
|||||||
private String authoredTime; // 开单时间
|
private String authoredTime; // 开单时间
|
||||||
|
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public Integer getGenderEnum() {
|
||||||
|
return genderEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCollectionStatusEnum() {
|
||||||
|
return collectionStatusEnum;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,13 @@ import java.util.List;
|
|||||||
public class SampleCollectStatusRequest {
|
public class SampleCollectStatusRequest {
|
||||||
private List<Long> ids;
|
private List<Long> ids;
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
// 手动添加 getter 方法
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> getIds() {
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user