Skip to content

Commit 7e0f1f6

Browse files
committed
feat: SMART App Launch; SMART on FHIR proxy; client assertion request client_credentials token; H2 Config synchronization function; mask for confidential data fields; support for queryString for HTTP methods other than GET; fixed IP address error in homepage dashboard; fixed API update tag search clearing error; fixed gRPC call error; fixed error when uploading multiple files via POST form-data and error caused by null keys; Angular upgraded to v20.
1 parent 3bee539 commit 7e0f1f6

827 files changed

Lines changed: 50433 additions & 11546 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build.gradle

Lines changed: 68 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
plugins {
2-
id 'org.springframework.boot' version '3.5.7'
2+
id 'org.springframework.boot' version '3.5.14'
33
id 'io.spring.dependency-management' version '1.1.7'
44
id 'com.github.jk1.dependency-license-report' version '2.9'
55

@@ -22,7 +22,7 @@ licenseReport {
2222

2323
allprojects {
2424
group = 'tpi.dgrv4'
25-
version = 'release-rc-v4.7.10.0-14-g81f406576'
25+
version = 'release-rc-v4.7.24.1-2-g2be5dee15'
2626
apply plugin: 'io.spring.dependency-management'
2727

2828
repositories {
@@ -59,35 +59,16 @@ subprojects {
5959
}
6060

6161
dependencies {
62-
implementation files("${rootDir}/libsext/dgrv4_CodecUtil-v4.7.10.0-14-g81f406576-lib.jar")
63-
implementation files("${rootDir}/libsext/dgrv4_HttpUtil-v4.7.10.0-14-g81f406576-lib.jar")
64-
65-
implementation ('org.springframework.boot:spring-boot-starter-data-jpa'){
66-
//CVE-2025-41249
67-
exclude group:'org.springframework', module:'spring-core'
68-
//CVE-2026-1225
69-
exclude group:'ch.qos.logback', module:'logback-core'
70-
//CVE-2026-1225
71-
exclude group:'ch.qos.logback', module:'logback-classic'
72-
}
73-
74-
// Correct the above exclusions, CVE-2025-41249
75-
implementation 'org.springframework:spring-core:6.2.11'
76-
// Correct the above exclusions, CVE-2026-1225
77-
implementation 'ch.qos.logback:logback-core:1.5.25'
78-
// Correct the above exclusions, CVE-2026-1225
79-
implementation 'ch.qos.logback:logback-classic:1.5.25'
62+
implementation files("${rootDir}/libsext/dgrv4_CodecUtil-v4.7.24.1-2-g2be5dee15-lib.jar")
63+
implementation files("${rootDir}/libsext/dgrv4_HttpUtil-v4.7.24.1-2-g2be5dee15-lib.jar")
8064

65+
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
8166
implementation ('org.springframework.boot:spring-boot-starter-web'){
82-
//CVE-2025-66614, CVE-2026-24733
8367
exclude group:'org.apache.tomcat.embed', module:'tomcat-embed-core'
8468
}
85-
86-
// Correct the above exclusions, CVE-2025-66614, CVE-2026-24733
87-
implementation 'org.apache.tomcat.embed:tomcat-embed-core:10.1.49'
8869

89-
implementation 'org.jboss.xnio:xnio-nio:3.8.16.Final'
90-
implementation 'jakarta.servlet:jakarta.servlet-api:6.1.0'
70+
// Correct the above exclusions
71+
implementation 'org.apache.tomcat.embed:tomcat-embed-core:10.1.55'
9172

9273
compileOnly 'com.h2database:h2'
9374
compileOnly 'org.projectlombok:lombok:1.18.42'
@@ -97,6 +78,8 @@ subprojects {
9778
implementation 'org.bouncycastle:bc-fips:2.1.2'
9879

9980
implementation 'org.bouncycastle:bcpkix-fips:2.1.9'
81+
82+
annotationProcessor 'org.hibernate.orm:hibernate-jpamodelgen'
10083
}
10184

10285
// clear lib
@@ -149,6 +132,36 @@ static def getDate() {
149132
// Gateway jar runnable
150133
project(':dgrv4_Gateway_serv'){
151134

135+
// [EN]grpc-netty-shaded and grpc-netty cannot coexist — both register the same gRPC transport provider
136+
// but with different Netty classpaths. Having both causes AbstractMethodError at startup.
137+
// [ZH]grpc-netty-shaded 和 grpc-netty 不能同時存在,兩者都會註冊相同的 gRPC transport provider,
138+
// 但使用不同的 Netty classpath,同時存在會在啟動時造成 AbstractMethodError。
139+
// [EN]grpc-netty-shaded is pulled in by google-cloud-logging-logback → gax-grpc → grpc-netty-shaded:1.55.1,
140+
// so per-dependency exclude is insufficient; a global exclude is required.
141+
// [ZH]grpc-netty-shaded 會透過 google-cloud-logging-logback → gax-grpc 傳遞引入 1.55.1,
142+
// 單一依賴的 exclude 不夠,必須全域排除。
143+
configurations.all {
144+
exclude group: 'io.grpc', module: 'grpc-netty-shaded'
145+
resolutionStrategy {
146+
// [EN]Without force, grpc-inprocess:1.58.0 and grpc-services:1.58.0 (from the starter) are NOT
147+
// upgraded by Gradle's conflict resolution, so grpc-core:1.58.0 can still appear in
148+
// the effective classpath alongside grpc-core:1.61.0 → wrong interface version is loaded.
149+
// [ZH]若不強制,grpc-inprocess:1.58.0 和 grpc-services:1.58.0(來自 starter)不會被 Gradle
150+
// 衝突解析升版,grpc-core:1.58.0 可能仍與 grpc-core:1.61.0 同時存在於 classpath,
151+
// 導致載入錯誤的介面版本。
152+
force 'io.grpc:grpc-core:1.61.0'
153+
force 'io.grpc:grpc-api:1.61.0'
154+
force 'io.grpc:grpc-context:1.61.0'
155+
force 'io.grpc:grpc-stub:1.61.0'
156+
force 'io.grpc:grpc-protobuf:1.61.0'
157+
force 'io.grpc:grpc-protobuf-lite:1.61.0'
158+
force 'io.grpc:grpc-netty:1.61.0'
159+
force 'io.grpc:grpc-inprocess:1.61.0'
160+
force 'io.grpc:grpc-services:1.61.0'
161+
force 'io.grpc:grpc-util:1.61.0'
162+
}
163+
}
164+
152165
// 引用其它 project
153166
dependencies{
154167

@@ -195,12 +208,7 @@ project(':dgrv4_Gateway_serv'){
195208
implementation 'com.esotericsoftware:kryo:5.4.0'
196209

197210
// websocket
198-
implementation ('org.springframework.boot:spring-boot-starter-websocket'){
199-
//CVE-2025-41254
200-
exclude group:'org.springframework', module:'spring-websocket'
201-
}
202-
//修正上方排除的CVE-2025-41254
203-
implementation 'org.springframework:spring-websocket:6.2.12'
211+
implementation 'org.springframework.boot:spring-boot-starter-websocket'
204212

205213
implementation 'com.github.oshi:oshi-core:6.4.5'
206214
implementation 'net.java.dev.jna:jna:5.13.0'
@@ -211,7 +219,7 @@ project(':dgrv4_Gateway_serv'){
211219
implementation project(':dgrv4_H2Config_lib')
212220
implementation 'com.microsoft.sqlserver:mssql-jdbc:13.2.1.jre11'
213221
implementation 'com.oracle.database.jdbc:ojdbc11'
214-
implementation 'org.postgresql:postgresql:42.7.7'
222+
implementation 'org.postgresql:postgresql:42.7.11'
215223
implementation('org.mariadb.jdbc:mariadb-java-client:3.1.2', {
216224
exclude group: 'net.java.dev.jna', module: 'jna-platform' //使用其它版本的相依
217225
exclude group: 'com.google.protobuf', module: 'protobuf-java'
@@ -221,8 +229,17 @@ project(':dgrv4_Gateway_serv'){
221229
compileOnly 'com.google.protobuf:protobuf-java:3.25.5'
222230

223231
// gRPC Proxy
224-
implementation 'net.devh:grpc-server-spring-boot-starter:2.15.0.RELEASE'
225-
implementation 'io.grpc:grpc-netty-shaded:1.75.0'
232+
// [EN]Exclude grpc-netty-shaded from the starter; use non-shaded grpc-netty instead.
233+
// [ZH]從 starter 排除 grpc-netty-shaded,改用 non-shaded grpc-netty。
234+
// [EN]grpc-netty-shaded and grpc-netty cannot coexist — both register the same transport provider
235+
// but with different Netty classpaths (shaded vs. real). Having both causes TLS handshake to hang.
236+
// [ZH]grpc-netty-shaded 和 grpc-netty 不能同時存在 — 兩者都會註冊相同的 transport provider
237+
// 但使用不同的 Netty classpath(shaded vs. 真實),同時存在會導致 TLS handshake hang。
238+
// [EN]Switching to non-shaded also allows Netty CVEs to be patched via exclude + override (impossible with shaded).
239+
// [ZH]改用 non-shaded 後可透過 exclude + 版本覆蓋修補 Netty CVE(shaded 版無法做到)。
240+
implementation ('net.devh:grpc-server-spring-boot-starter:2.15.0.RELEASE') {
241+
exclude group:'io.grpc', module:'grpc-netty-shaded'
242+
}
226243
implementation ('io.grpc:grpc-protobuf:1.61.0'){
227244
exclude group:'com.google.protobuf', module:'protobuf-java'
228245
}
@@ -234,26 +251,33 @@ project(':dgrv4_Gateway_serv'){
234251

235252
// 添加對 Netty 的非遮蔽(non-shaded)依賴,以便 TLS 相關功能
236253
implementation ('io.grpc:grpc-netty:1.61.0'){
254+
// [EN]grpc-netty:1.61.0 has a transitive dep on grpc-netty-shaded:1.55.1,
255+
// which puts a second grpc-core (v1.55.1) on the classpath and causes AbstractMethodError.
256+
// [ZH]grpc-netty:1.61.0 傳遞依賴了 grpc-netty-shaded:1.55.1,
257+
// 會把第二個 grpc-core(v1.55.1)放上 classpath 導致 AbstractMethodError,必須排除。
258+
exclude group:'io.grpc', module:'grpc-netty-shaded'
237259
// CVE-2025-55163
238260
exclude group:'io.netty', module:'netty-codec-http2'
239261
//CVE-2025-58056
240262
exclude group:'io.netty', module:'netty-codec-http'
241263
//CVE-2025-58057
242264
exclude group:'io.netty', module:'netty-codec'
243265
}
244-
// Correct the above exclusions, CVE-2025-55163
245-
implementation ('io.netty:netty-codec-http2:4.1.125.Final') {
246-
//CVE-2025-58057
247-
exclude group:'io.netty', module:'netty-codec'
248-
}
249-
// Correct the above exclusions, CVE-2025-58056, CVE-2025-67735
250-
implementation 'io.netty:netty-codec-http:4.1.129.Final'
251-
// Correct the above exclusions, CVE-2025-58057
252-
implementation 'io.netty:netty-codec:4.1.125.Final'
266+
// Correct the above exclusions, CVE-2025-55163, CVE-2026-33871, CVE-2026-42587
267+
implementation 'io.netty:netty-codec-http2:4.1.133.Final'
268+
// Correct the above exclusions, CVE-2025-58056, CVE-2025-67735, CVE-2026-33870, CVE-2026-41417, CVE-2026-42587
269+
implementation 'io.netty:netty-codec-http:4.1.133.Final'
270+
// Correct the above exclusions, CVE-2025-58057, CVE-2026-42583
271+
implementation 'io.netty:netty-codec:4.1.133.Final'
272+
//CVE-2026-42578
273+
implementation 'io.netty:netty-handler-proxy:4.1.133.Final'
253274

254275
implementation 'io.netty:netty-tcnative-boringssl-static:2.0.61.Final'
255276

256277
implementation 'com.google.code.gson:gson:2.10.1'
278+
279+
// JsonNullable for partial updates (used in batch update APIs)
280+
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
257281
}
258282

259283
protobuf {

dgrv4_Common_lib/.settings/org.eclipse.buildship.core.prefs

Lines changed: 0 additions & 2 deletions
This file was deleted.

dgrv4_Common_lib/.settings/org.eclipse.jdt.core.prefs

Lines changed: 0 additions & 4 deletions
This file was deleted.

dgrv4_Common_lib/src/main/java/tpi/dgrv4/common/component/cache/core/DaoGenericCache.java

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,17 @@
88
import java.util.stream.Collectors;
99

1010
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.beans.factory.annotation.Value;
1112
import org.springframework.stereotype.Component;
1213

1314
import tpi.dgrv4.common.utils.StackTraceUtil;
1415

1516
@Component
1617
public class DaoGenericCache implements IDaoGenericCache<String, Object> {
1718

18-
private static final Long DEFAULT_CACHE_TIMEOUT = 120000L; // 預設快取存活時間 120sec
19-
20-
public static final long BUFFER_INTERVAL = 6000L; // 預設快取緩衝時間 6sec
2119

2220
protected Long cacheTimeout;
21+
protected long bufferInterval;
2322

2423
protected Map<String, CacheValue> cacheMap;
2524

@@ -66,30 +65,35 @@ public void setUpdateTime(long updateTime) {
6665
};
6766
}
6867

68+
private Object daoGenericCacheLock = new Object();
69+
6970
@Autowired
70-
public DaoGenericCache() {
71-
this(DEFAULT_CACHE_TIMEOUT, null);
71+
public DaoGenericCache(
72+
@Value("${digi.cache.default-timeout.ms:120000}") Long cacheTimeout,
73+
@Value("${digi.cache.buffer-interval.ms:6000}") Long bufferInterval) {
74+
this(cacheTimeout,bufferInterval,null);
7275
}
7376

74-
private Object daoGenericCacheLock = new Object();
75-
public DaoGenericCache(Long cacheTimeout, Consumer<String> traceLogger) {
76-
77+
public DaoGenericCache(Long cacheTimeout, Long bufferInterval, Consumer<String> traceLogger) {
7778
this.cacheTimeout = cacheTimeout;
79+
this.bufferInterval = bufferInterval;
7880
if (traceLogger != null) {
79-
// junit用到
81+
//testcase用
8082
this.traceLogger = traceLogger;
8183
}
8284
this.clear();
85+
this.startCleanThread();
86+
}
8387

84-
// 處理超過 DEFAULT_CACHE_TIMEOUT(預設120sec) 的舊快取資料
88+
// [EN]Starts the background thread that periodically cleans expired cache entries
89+
// [ZH]啟動背景執行緒,定期清除過期的快取項目
90+
private void startCleanThread() {
8591
new Thread() {
8692
public void run() {
8793
while (true) {
8894
try {
89-
// 休息1秒,避免CPU飆高
90-
// Thread.sleep(1000);
9195
synchronized (daoGenericCacheLock) {
92-
daoGenericCacheLock.wait(1000);
96+
daoGenericCacheLock.wait(1000);
9397
}
9498
clean();
9599
} catch (InterruptedException e) {
@@ -140,7 +144,7 @@ public Optional<Object> get(String key) {
140144

141145
CacheValue cv = opt.get();
142146

143-
long expirationTimeMillis = cv.getUpdateTime() + BUFFER_INTERVAL;
147+
long expirationTimeMillis = cv.getUpdateTime() + this.bufferInterval;
144148
boolean isExpired = System.currentTimeMillis() > expirationTimeMillis;
145149

146150
// 檢查有沒有超過 預設快取緩衝時間,若超過則從快取移除,沒超過就更新快取時間。

dgrv4_Common_lib/src/main/java/tpi/dgrv4/common/component/cache/proxy/DaoCacheProxy.java

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.Collection;
66
import java.util.List;
77
import java.util.Optional;
8+
import java.util.concurrent.ConcurrentHashMap;
89
import java.util.function.Consumer;
910
import java.util.function.Function;
1011
import java.util.function.Supplier;
@@ -33,6 +34,14 @@ public abstract class DaoCacheProxy {
3334
protected final DaoGenericCache cache;
3435

3536
private final CacheValueAdapter adapter;
37+
38+
// [EN]Per-key lock map to prevent cache stampede (thundering herd problem):
39+
// when multiple threads encounter a cache miss for the same key simultaneously,
40+
// only ONE thread queries the DB while the others wait, then all read from cache.
41+
// [ZH]每個 key 對應的鎖,防止 cache stampede(雷擊群問題):
42+
// 當多個執行緒同時遇到相同 key 的 cache miss 時,只有一個執行緒查詢 DB,
43+
// 其餘執行緒等待後直接從快取讀取,避免重複打穿 DB。
44+
private final ConcurrentHashMap<String, Object> keyLocks = new ConcurrentHashMap<>();
3645

3746
@Autowired
3847
protected DaoCacheProxy(ObjectMapper objectMapper, DaoGenericCache cache) {
@@ -59,21 +68,52 @@ protected <R> List<R> getList(String methodName, Supplier<List<R>> supplier, Obj
5968
private <R> R get(String methodName, Supplier<R> supplier, Function<Object, R> caster, Object... params) {
6069
String cacheKey = genCacheKey(getDaoClass(), methodName, params);
6170

62-
// 如果有存入過 cache
63-
boolean hasCacheEntry = getCache().containsKey(cacheKey);
64-
if (hasCacheEntry) {
65-
Optional<Object> opt = getCache().get(cacheKey);
66-
// 且 cache 中有值
67-
if (opt.isPresent()) {
68-
// 就直接從 cache 拿值
69-
return caster.apply(opt.get());
71+
// [EN]Fast path: return immediately if cache has a valid (non-expired) value, no locking overhead.
72+
// [ZH]快速路徑:若快取有有效值(未過期)則直接回傳,避免取鎖的開銷(絕大多數請求走此路徑)。
73+
Optional<Object> fastResult = tryGetFromCache(cacheKey);
74+
if (fastResult.isPresent()) {
75+
return caster.apply(fastResult.get());
76+
}
77+
78+
// [EN]Slow path: use a per-key lock to prevent cache stampede.
79+
// computeIfAbsent is atomic — only one lock object is created per key.
80+
// Threads that arrive simultaneously for the same key will queue up on that lock
81+
// instead of all hitting the DB.
82+
// [ZH]慢速路徑:使用 per-key 鎖防止 cache stampede。
83+
// computeIfAbsent 是原子操作,每個 key 只會建立一個鎖物件。
84+
// 同時到達的執行緒會在同一把鎖上排隊,而不是同時打穿 DB。
85+
Object lock = keyLocks.computeIfAbsent(cacheKey, k -> new Object());
86+
synchronized (lock) {
87+
try {
88+
// [EN]Double-check inside the lock: a thread that was waiting may now find the cache already populated.
89+
// [ZH]在鎖內雙重確認:前一個執行緒已查 DB 並寫入快取,後續執行緒應直接從快取讀取。
90+
Optional<Object> rechecked = tryGetFromCache(cacheKey);
91+
if (rechecked.isPresent()) {
92+
return caster.apply(rechecked.get());
93+
}
94+
95+
// [EN]Confirmed cache miss: only this thread queries the DB and populates the cache.
96+
// [ZH]確認 cache miss:僅此執行緒向 DB 查詢並寫入快取(同一時間只有一個執行緒執行此操作)。
97+
R r = supplier.get();
98+
getCache().put(cacheKey, r, getCacheValueAdapter());
99+
return r;
100+
} finally {
101+
// [EN]Remove the lock entry after the value is populated.
102+
// Threads already waiting hold a reference to the lock object, so removal is safe.
103+
// [ZH]寫入快取後移除鎖物件,防止 keyLocks 無限累積。
104+
// 等待中的執行緒已持有鎖物件引用,移除 map 中的 entry 不影響其正確性。
105+
keyLocks.remove(cacheKey);
70106
}
71107
}
108+
}
72109

73-
// 某一種情況是:從 cache 取出的值為空,可能是過期被清掉了,應該要重查並放回 cache
74-
R r = supplier.get();
75-
getCache().put(cacheKey, r, getCacheValueAdapter());
76-
return r;
110+
// [EN]Attempt to retrieve a value from cache; returns empty if the key is absent or the entry is expired.
111+
// [ZH]嘗試從快取取值;若 key 不存在或快取項目已過期則回傳 Optional.empty()。
112+
private Optional<Object> tryGetFromCache(String cacheKey) {
113+
if (!getCache().containsKey(cacheKey)) {
114+
return Optional.empty();
115+
}
116+
return getCache().get(cacheKey);
77117
}
78118

79119
/**

0 commit comments

Comments
 (0)