From da93fafc417b4f590b6ab639ccbbe67c14c8753c Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期五, 16 十月 2026 17:58:21 +0800
Subject: [PATCH] feat(mes): 种子标签追溯配置改为字典驱动
---
docs/seed_qr_label_gb_compliance_integration.md | 36 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesProperties.java | 17
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/trace/MesTraceConfigController.java | 11
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesQrCodeConfig.java | 99 +---
yudao-server/src/main/resources/application.yaml | 17
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceConfigResolverTest.java | 169 ++++++++
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilderTest.java | 117 +++++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceConfigSnapshot.java | 25 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesTraceConfigProvider.java | 24 +
docs/mes_trace_config_dict_integration.md | 94 ++++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceUrlValidator.java | 97 ++++
yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceUrlValidatorTest.java | 81 ++++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/trace/vo/MesTraceQrConfigRespVO.java | 15
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java | 90 +++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceUrlSource.java | 25 +
sql/mysql/mes_trace_config_init.sql | 55 ++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceConfigResolver.java | 192 +++++++++
17 files changed, 1,064 insertions(+), 100 deletions(-)
diff --git a/docs/mes_trace_config_dict_integration.md b/docs/mes_trace_config_dict_integration.md
new file mode 100644
index 0000000..2af7067
--- /dev/null
+++ b/docs/mes_trace_config_dict_integration.md
@@ -0,0 +1,94 @@
+# 婧簮閰嶇疆鏀逛负瀛楀吀椹卞姩 - 鍓嶇鑱旇皟鏂规
+
+## 鑳屾櫙
+
+绉嶅瓙浜岀淮鐮佹爣绛剧殑**杩芥函缃戝潃**銆�**鐢熶骇缁忚惀鑰呭悕绉�**姝ゅ墠鍙兘鏀归厤缃枃浠跺啀閲嶅惎鏈嶅姟銆傛湰娆℃敼涓哄彲鍦ㄣ�岀郴缁熺鐞� 鈫� 瀛楀吀绠$悊銆嶇殑瀛楀吀 `mes_trace_config` 涓洿鎺ヤ慨鏀癸紝鏀瑰畬绾� 1 鍒嗛挓鍐呯敓鏁堛��
+
+**鎺ュ彛璺緞涓庤姹傚弬鏁板潎鏃犲彉鍖�**锛屼粎 `/mes/trace-config/qr-config` 鐨勫搷搴旀柊澧炰竴涓瓧娈� `traceUrlSource`銆傚墠绔�**涓嶆敼閫犱篃鑳芥甯歌窇**锛堟柊瀛楁鍙拷鐣ワ級锛屾寜闇�鎺ュ叆鍗冲彲銆�
+
+## 娑夊強椤甸潰
+
+- 鐢熶骇鎵规绠$悊 - 鎵规銆屾煡鐪嬩簩缁寸爜銆嶅脊绐�
+- 琚嬬爜绠$悊锛堣祴鐮佺鐞嗭級 - 琚嬬爜銆屾煡鐪嬩簩缁寸爜銆嶅脊绐�
+- 鎵樼洏绠$悊 - 鎵樼洏銆屾煡鐪嬩簩缁寸爜銆嶅脊绐�
+- H5 鎵爜婧簮椤碉紙娑堣垂鑰呮壂鐮佸悗鎵撳紑鐨勯〉闈級
+- 绯荤粺绠$悊 鈫� 瀛楀吀绠$悊锛堣繍缁�/涓氬姟淇敼閰嶇疆鐨勫叆鍙o級
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /admin-api/mes/trace-config/qr-config | 鑾峰緱婧簮 H5 椤甸潰鍩虹鍦板潃锛堜簩缁寸爜鍐呭鍓嶇紑锛� |
+
+**璇锋眰鍙傛暟锛�** 鏃狅紙鐧诲綍鎬佸嵆鍙闂級
+
+**鍝嶅簲瀛楁鏂板锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| traceUrlSource | String | 杩芥函缃戝潃鐨勫疄闄呯敓鏁堟潵婧愶紝鍙栧�艰涓嬭〃 |
+
+**`traceUrlSource` 鍙栧�艰鏄庯細**
+
+| 鍙栧�� | 鍚箟 | 鍓嶇寤鸿鎻愮ず |
+|------|------|--------------|
+| DICT | 鍙栬嚜瀛楀吀锛岀鐞嗗憳鍦ㄥ瓧鍏哥鐞嗛噷閰嶇疆鐨勫�煎凡鐢熸晥 | 鏃犻渶鎻愮ず |
+| YAML | 瀛楀吀鏈厤缃鍊硷紝浣跨敤閰嶇疆鏂囦欢涓殑鍦板潃锛堟甯稿洖閫�锛� | 鏃犻渶鎻愮ず |
+| YAML_INVALID_DICT | 瀛楀吀閲屽~浜嗗湴鍧�锛屼絾鏈�氳繃鍚堣鏍¢獙锛堝崰浣嶇 / 鏈満 / 鍐呯綉鍦板潃锛夛紝宸插洖閫�閰嶇疆鏂囦欢 | 鎻愮ず绠$悊鍛樺幓瀛楀吀绠$悊淇 |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "traceBaseUrl": "http://1.95.78.124:9008/prod/trace.html",
+ "traceUrlSource": "DICT"
+ },
+ "msg": ""
+}
+```
+
+**鍏嶇櫥褰曟帴鍙o紙H5 婧簮椤典娇鐢級鍝嶅簲鏃犲彉鍖栵細**
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /public/mes/trace/config | 婧簮椤甸厤缃紙鐢熶骇缁忚惀鑰呭悕绉般�佽拷婧綉鍧�锛� |
+| GET | /public/mes/trace/query | 鏍规嵁杩芥函鐮佹煡璇㈠叏绋嬫函婧愪俊鎭� |
+
+> 杩欎袱涓帴鍙h繑鍥炵殑 `traceUrl` / `operatorName` 宸茶嚜鍔ㄨ窡闅忓瓧鍏革紝**鍓嶇鏃犻渶浠讳綍鏀瑰姩**銆�
+
+## 鏁版嵁甯﹀叆璇存槑
+
+1. 瀛楀吀绠$悊锛堣繍缁翠慨鏀广�岃拷婧綉鍧�銆嶏級鈫� 鍚庣缂撳瓨 鈫� 绠$悊绔�屾煡鐪嬩簩缁寸爜銆嶆帴鍙o細淇敼鍚庣害 1 鍒嗛挓鍐咃紝`traceBaseUrl` 鑷姩鍙樹负鏂板�硷紝`traceUrlSource` 鍙樹负 `DICT`銆傚墠绔棤闇�鎰熺煡锛屾瘡娆℃墦寮�寮圭獥鎷変竴娆″嵆鍙��
+2. 銆屾煡鐪嬩簩缁寸爜銆嶆帴鍙� 鈫� 浜岀淮鐮佸脊绐楋細`traceBaseUrl` 鎷兼帴杩芥函鐮佺敓鎴愪簩缁寸爜鍐呭锛屼笌鍚庣瀵煎嚭鎵撳嵃鐨勬爣绛�**瀹屽叏涓�鑷�**锛堝悓婧愬悓鍊硷級銆�
+3. H5 婧簮椤甸厤缃帴鍙� 鈫� 婧簮椤点�岃拷婧綉鍧�銆嶅睍绀猴細涓庢爣绛惧嵃鍒峰唴瀹瑰悓婧愶紝鎵爜鍚庨〉闈㈠睍绀虹殑杩芥函缃戝潃涓庢爣绛句笂鍗扮殑涓�鑷淬��
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| traceBaseUrl | 浜岀淮鐮佸脊绐楋紙浜岀淮鐮佸唴瀹瑰墠缂�锛� | 鍊间负绌烘椂浜岀淮鐮佸洖閫�涓虹函鐮佸�硷紝涓嶆姤閿� |
+| traceUrlSource | 浜岀淮鐮佸脊绐楋紙鍙�夛紝寤鸿浠呭紓甯告椂鎻愮ず锛� | 浠呭綋鍊间负 `YAML_INVALID_DICT` 鏃跺缓璁粰鍑哄憡璀︽枃妗� |
+| traceUrl | H5 婧簮椤点�岃拷婧綉鍧�銆� | 浣滀负閾炬帴灞曠ず锛屽彲鐐瑰嚮璺宠浆 |
+| operatorName | H5 婧簮椤点�岀敓浜х粡钀ヨ�呭悕绉般�� | 杩涘彛绉嶅瓙鍦烘櫙灞曠ず鐨勬槸杩涘彛鍟嗗悕绉� |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 瀛楀吀閲岄厤缃簡鍚堟硶鐨勮拷婧綉鍧� | 鏍囩銆佷簩缁寸爜銆丠5 椤甸潰銆佺鐞嗙棰勮鍏ㄩ儴浣跨敤瀛楀吀鍊� |
+| 瀛楀吀椤逛负绌� / 琚鐢� / 瀛楀吀绫诲瀷鏈垱寤� | 鍥為��浣跨敤閰嶇疆鏂囦欢 `mes.trace-base-url` 鐨勫�硷紝`traceUrlSource` 涓� `YAML` |
+| 瀛楀吀閲岀殑鍦板潃闈炴硶锛堝崰浣嶇銆乴ocalhost銆佸唴缃� IP锛� | 鍚庣璁� ERROR 鏃ュ織骞跺洖閫�閰嶇疆鏂囦欢鍊硷紝`traceUrlSource` 涓� `YAML_INVALID_DICT`锛�**鏍囩瀵煎嚭涓嶄腑鏂�** |
+| 瀛楀吀涓庨厤缃枃浠堕兘鎷夸笉鍒拌拷婧綉鍧� | 瀵煎嚭鏍囩鏃舵姤閿欍�屾湭閰嶇疆杩芥函缃戝潃锛屾棤娉曠敓鎴愪簩缁寸爜鏍囩銆嶏紝鎷掔粷瀵煎嚭 |
+| 瀛楀吀銆屽瓧鍏告爣绛俱�嶆敼鍚� | 浼氬鑷磋椤归厤缃け鏁堬紙浠g爜鎸夋爣绛惧悕鍖归厤锛夈�傝鎻愰啋杩愮淮鍙敼銆屽瓧鍏搁敭鍊笺�嶏紝涓嶈鏀广�屽瓧鍏告爣绛俱�� |
+| 淇敼瀛楀吀鍚庣殑鐢熸晥鏃堕棿 | 绾� 1 鍒嗛挓锛堝悗绔� 60 绉掓湰鍦扮紦瀛橈級銆傝嫢涓虹█鐤忚皟鐢ㄥ満鏅紝鍙兘闇�鍐嶈Е鍙戜竴娆℃煡璇㈡垨瀵煎嚭鎵嶅垏鎹� |
+
+## 娉ㄦ剰浜嬮」
+
+1. **鏈鍓嶇闆跺繀鏀归」**锛歚traceUrlSource` 鏄彲閫夌殑澧炲己瀛楁锛屼笉鎺ュ叆涓嶅奖鍝嶄换浣曞姛鑳姐��
+2. 杩芥函缃戝潃鏄瀛愭爣绛惧浗鏍囧洓瑕佺礌涔嬩竴锛屼竴鏃﹀嵃鍑烘棤娉曞彫鍥炪�傚墠绔�**涓嶈**鍦ㄤ换浣曞湴鏂瑰杩芥函缃戝潃鍋氬厹搴曠‖缂栫爜锛屼竴寰嬩互鎺ュ彛杩斿洖鍊间负鍑嗐��
+3. 浜岀淮鐮佸唴瀹瑰墠缂�蹇呴』涓庡悗绔竴鑷达紝鍓嶇鎷兼帴鏂瑰紡淇濇寔 `traceBaseUrl + "?code=" + 缂栫爜鍚庣殑鐮佸�糮 涓嶅彉锛沗traceBaseUrl` 涓虹┖鏃跺洖閫�涓虹函鐮佸�笺��
+4. 鎵爜鏋壂鍒版墦鍗版爣绛剧殑瀹屾暣 H5 閾炬帴鏃讹紝鍚庣浼氳嚜鍔ㄤ粠閾炬帴涓彁鍙� `code` 鍙傛暟锛屽墠绔洿鎺ュ師鏍锋彁浜ゅ嵆鍙��
+5. `allow-plain-qr-code`锛堝厑璁镐簩缁寸爜缂栫爜绾爜鍊硷級**鏈夋剰鏈撼鍏ュ瓧鍏�**锛屼粛鐢遍厤缃枃浠舵帶鍒讹紝涓嶅湪鍓嶇鏆撮湶鍏ュ彛銆�
+6. 瀛楀吀绠$悊涓�屽瓧鍏告爣绛俱�嶏紙濡傘�岃拷婧綉鍧�銆嶏級鏄唬鐮佸浐瀹氭爣璇嗭紝涓嶅彲鏀瑰悕锛涘彲缂栬緫鐨勬槸銆屽瓧鍏搁敭鍊笺�嶃��
diff --git a/docs/seed_qr_label_gb_compliance_integration.md b/docs/seed_qr_label_gb_compliance_integration.md
index 044f3a6..1c6e6b3 100644
--- a/docs/seed_qr_label_gb_compliance_integration.md
+++ b/docs/seed_qr_label_gb_compliance_integration.md
@@ -139,20 +139,42 @@
## 閰嶇疆椤�
-鍚庣閰嶇疆椤癸紙`application-*.yaml` 鐨� `mes` 鍓嶇紑锛夛細
+### 瀛楀吀閰嶇疆锛堜紭鍏堬紝鍙湪绠$悊鍚庡彴淇敼锛�
+
+杩芥函缃戝潃绛夊洓椤归厤缃殑**杩愯鏃跺彇鍊间紭鍏堟潵鑷瓧鍏�** `mes_trace_config`锛屽湪銆岀郴缁熺鐞� 鈫� 瀛楀吀绠$悊銆嶄腑淇敼锛屾敼瀹岀害 1 鍒嗛挓鐢熸晥锛屾棤闇�閲嶅惎鏈嶅姟銆傚瓧鍏搁」銆屽瓧鍏告爣绛俱�嶄负浠g爜鍥哄畾鏍囪瘑涓嶅彲鏀瑰悕锛屽彲缂栬緫鐨勬槸銆屽瓧鍏搁敭鍊笺�嶏紱閿�肩暀绌哄嵆鍥為��涓嬮潰鐨勯厤缃枃浠躲��
+
+| 瀛楀吀鏍囩 | 璇存槑 |
+|----------|------|
+| 杩芥函缃戝潃 | 浜岀淮鐮佸唴瀹瑰墠缂� + 鏍囩/H5 灞曠ず鐨勮拷婧綉鍧�銆傚~浜嗗�间絾闈炴硶锛堝崰浣嶇/鏈満/鍐呯綉锛変細璁� ERROR 骞跺洖閫�閰嶇疆鏂囦欢 |
+| 鐢熶骇缁忚惀鑰呭悕绉� | 绉嶅瓙鏍囩鍥涜绱犱箣涓� |
+| 杩涘彛鍟嗗悕绉� | 杩涘彛绉嶅瓙鍦烘櫙濉啓 |
+| 鏄惁杩涘彛绉嶅瓙 | 濉� true / false |
+
+鍒濆鍖栬剼鏈細`sql/mysql/mes_trace_config_init.sql`锛堥敭鍊肩暀绌猴紝涓婄嚎闆惰涓哄彉鍖栵紝鐢辫繍缁存寜鐜濉啓锛夈��
+
+### 閰嶇疆鏂囦欢锛堝厹搴曢粯璁ゅ�硷級
+
+鍚庣閰嶇疆椤癸紙`application-*.yaml` 鐨� `mes` 鍓嶇紑锛夈�傚墠涓夐」鐜颁负**瀛楀吀鏈厤缃椂鐨勫厹搴曞��**锛�
| 閰嶇疆椤� | 蹇呭~ | 璇存槑 |
|--------|------|------|
-| `mes.trace-base-url` | 鏄� | 杩芥函缃戝潃锛屽繀椤讳负鍏紬鍙闂湴鍧�銆傞潪 local 鐜鑻ヤ负鍐呯綉 IP / localhost / 鍗犱綅绗﹀皢**鎷掔粷鍚姩** |
-| `mes.operator-name` | 鏄� | 鐢熶骇缁忚惀鑰呭悕绉般�傛湭閰嶇疆鍒欐棤娉曞鍑烘爣绛撅紙鍚姩鏃跺憡璀︼級 |
-| `mes.importer-name` | 鍚� | 杩涘彛鍟嗗悕绉帮紝杩涘彛绉嶅瓙鍦烘櫙濉啓 |
-| `mes.imported` | 鍚� | 鏄惁杩涘彛绉嶅瓙鍦烘櫙锛岄粯璁� false |
-| `mes.allow-plain-qr-code` | 鍚� | 鏄惁鍏佽浜岀淮鐮佺紪鐮佺函鐮佸�硷紝浠呬緵鏈湴鍏煎鍘嗗彶鐮侊紝榛樿 false |
+| `mes.trace-base-url` | 鏄� | 杩芥函缃戝潃鍏滃簳鍊硷紝蹇呴』涓哄叕浼楀彲璁块棶鍦板潃銆傞潪 local 鐜鑻ヤ负鍐呯綉 IP / localhost / 鍗犱綅绗﹀皢**鎷掔粷鍚姩**锛堣繖閬撻槻绾垮繀椤诲湪鏁版嵁搴撳彲鐢ㄥ墠鐢熸晥锛屾晠涓嶈兘鍙緷璧栧瓧鍏革級 |
+| `mes.operator-name` | 鏄� | 鐢熶骇缁忚惀鑰呭悕绉板厹搴曞�笺�傛湭閰嶇疆鍒欐棤娉曞鍑烘爣绛撅紙鍚姩鏃跺憡璀︼級 |
+| `mes.importer-name` | 鍚� | 杩涘彛鍟嗗悕绉板厹搴曞�硷紝杩涘彛绉嶅瓙鍦烘櫙濉啓 |
+| `mes.imported` | 鍚� | 鏄惁杩涘彛绉嶅瓙鍦烘櫙鍏滃簳鍊硷紝榛樿 false |
+| `mes.allow-plain-qr-code` | 鍚� | 鏄惁鍏佽浜岀淮鐮佺紪鐮佺函鐮佸�硷紝浠呬緵鏈湴鍏煎鍘嗗彶鐮侊紝榛樿 false銆�**鏈夋剰涓嶇撼鍏ュ瓧鍏�**锛氬畠鏄粫杩囥�屾爣绛惧繀椤诲惈杩芥函缃戝潃銆嶇殑寮�鍏筹紝涓嶅簲寮�鏀剧粰鍚庡彴闅忔剰缂栬緫 |
+
+### 鍙栧�间紭鍏堢骇
+
+1. 瀛楀吀 `mes_trace_config` 鏈夊惎鐢ㄩ」涓斻�屽瓧鍏搁敭鍊笺�嶉潪绌� 鈫� 鐢ㄥ瓧鍏稿�硷紱
+2. 瀛楀吀鏈缓 / 鏃犲惎鐢ㄩ」 / 閿�间负绌� 鈫� 鍥為��閰嶇疆鏂囦欢锛�
+3. 杩芥函缃戝潃鐨勫瓧鍏稿�奸潪绌轰絾闈炴硶 鈫� 璁� ERROR 鏃ュ織骞跺洖閫�閰嶇疆鏂囦欢锛堟爣绛惧鍑轰笉涓柇锛夛紱
+4. 瀛楀吀涓庨厤缃枃浠堕兘鎷夸笉鍒拌拷婧綉鍧� 鈫� 鎶涖�屾湭閰嶇疆杩芥函缃戝潃锛屾棤娉曠敓鎴愪簩缁寸爜鏍囩銆嶏紝鎷掔粷瀵煎嚭銆�
## 娉ㄦ剰浜嬮」
- **鍓嶇绂佹鍐嶇‖缂栫爜鐢熶骇缁忚惀鑰呭悕绉颁笌杩芥函缃戝潃**锛屽繀椤婚�氳繃 `/public/mes/trace/config` 鑾峰彇锛屽惁鍒欎細涓庝簩缁寸爜鍐呭涓嶄竴鑷淬��
-- 杩芥函缃戝潃鍦ㄤ簩缁寸爜鍐呭涓庨〉闈㈠睍绀轰箣闂村彧鏈� `mes.trace-base-url` 涓�涓湡鍊兼簮锛屽墠绔睍绀烘椂涓嶈鑷鎷兼帴 `/prod` 绛夎矾寰勩��
+- 杩芥函缃戝潃鍦ㄤ簩缁寸爜鍐呭銆佹爣绛惧嵃鍒蜂笌椤甸潰灞曠ず涔嬮棿鏄悓涓�涓湡鍊兼簮锛堝瓧鍏� `mes_trace_config` 浼樺厛銆侀厤缃枃浠跺厹搴曪級锛屽墠绔睍绀烘椂涓嶈鑷鎷兼帴 `/prod` 绛夎矾寰勩��
- 宸蹭綔搴熺爜鐨勫垽瀹氳鐩栦笁绉嶅叆鍙o細琚嬬爜锛堢姸鎬� 30锛夈�佹墭鐩樼爜锛堢姸鎬� 20锛夈�佹壒娆$爜锛堢姸鎬� 40锛夛紝涓夎�呯殑浣滃簾鏋氫妇鍊间笉鍚岋紝鍓嶇涓嶈鍏辩敤甯搁噺鍒ゆ柇銆�
- **瀛橀噺鏍囩闇�閲嶆柊瀵煎嚭**锛氭敼閫犲墠瀵煎嚭鐨勪簩缁寸爜鍐呭宸插浐鍖栵紝鍏朵腑杩芥函缃戝潃鍙兘涓哄唴缃戝湴鍧�涓旀爣绛剧己灏戝洓瑕佺礌锛岄渶閲嶆柊瀵煎嚭鍗板埛銆�
- **琚嬪簭鍙锋敼涓哄師瀛愬垎閰�**锛氬師鍏堢敱銆屾煡搴撳唴鏈�澶у�煎啀 +1銆嶅緱鍒帮紝骞跺彂鐢熸垚浼氫骇鐢熼噸澶嶈搴忓彿锛堣搴忓彿浼氬嵃鍦ㄦ爣绛句笂锛夛紱鐜板凡鏀逛负 Redis 鍘熷瓙鑷鍒嗛厤锛屽苟鐢ㄥ簱鍐呮渶澶у�兼牎姝e熀鍑嗐�傚簭鍙�**鍙繚璇佷笉閲嶅銆佷笉鍥為��锛屼笉淇濊瘉杩炵画**锛堝苟鍙戜笅涓嶅悓璇锋眰鎷垮埌涓嶅悓鍖烘锛屽睘姝e父锛夈��
diff --git a/sql/mysql/mes_trace_config_init.sql b/sql/mysql/mes_trace_config_init.sql
new file mode 100644
index 0000000..5fa22cd
--- /dev/null
+++ b/sql/mysql/mes_trace_config_init.sql
@@ -0,0 +1,55 @@
+-- ============================================
+-- MES 绉嶅瓙鏍囩杩芥函閰嶇疆瀛楀吀鍒濆鍖栬剼鏈�
+-- ============================================
+-- 鐩殑锛氭妸绉嶅瓙浜岀淮鐮佹爣绛剧殑銆岃拷婧綉鍧�銆嶃�岀敓浜х粡钀ヨ�呭悕绉般�嶇瓑閰嶇疆浠庨厤缃枃浠舵惉鍒板瓧鍏革紝
+-- 绠$悊鍛樺彲鍦ㄣ�岀郴缁熺鐞� 鈫� 瀛楀吀绠$悊銆嶄腑鐩存帴淇敼锛屾敼瀹岀害 1 鍒嗛挓鍐呯敓鏁堬紝鏃犻渶鏀归厤缃噸鍚湇鍔°��
+--
+-- 鍙栧�艰鍒欙紙浠g爜瑙� MesTraceConfigResolver锛夛細
+-- 1. 瀛楀吀椤广�屽惎鐢ㄣ�嶄笖銆屽瓧鍏搁敭鍊笺�嶉潪绌� 鈫� 鐢ㄥ瓧鍏稿�硷紱
+-- 2. 瀛楀吀椤逛笉瀛樺湪 / 琚鐢� / 閿�间负绌� 鈫� 鍥為��閰嶇疆鏂囦欢 mes.trace-base-url銆乵es.operator-name 绛夛紱
+-- 3. 銆岃拷婧綉鍧�銆嶇殑瀛楀吀鍊奸潪绌轰絾涓嶅悎娉曪紙鍗犱綅绗︺�佹湰鏈恒�佸唴缃戝湴鍧�锛夆啋 璁� ERROR 鏃ュ織骞跺洖閫�閰嶇疆鏂囦欢銆�
+--
+-- 閲嶈锛氭湰鑴氭湰鏈夋剰鎶娿�屽瓧鍏搁敭鍊笺�嶅叏閮�**鐣欑┖**锛岃繖鏍峰悇鐜鎵ц鍚庤涓轰笌鏀归�犲墠瀹屽叏涓�鑷达紙浠嶈蛋閰嶇疆鏂囦欢锛夛紝
+-- 灞炰簬闆堕闄╀笂绾裤�傞儴缃插畬鎴愬悗鐢辫繍缁村湪瀛楀吀绠$悊涓寜鐜濉啓瀹為檯鍊煎嵆鍙帴绠°��
+--
+-- 娉ㄦ剰锛氥�屽瓧鍏告爣绛俱�嶆槸浠g爜閲岀殑鍥哄畾鏍囪瘑锛屼笉鍙敼鍚嶏紱鍙紪杈戠殑鏄�屽瓧鍏搁敭鍊笺�嶃��
+--
+-- 璇存槑锛氫笉浣跨敤澶氱鎴凤紝鑴氭湰涓嶅惈 tenant_id 瀛楁銆傝剼鏈彲閲嶅鎵ц銆�
+-- ============================================
+
+-- ----------------------------
+-- 1. 瀛楀吀绫诲瀷
+-- ----------------------------
+INSERT INTO `system_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT 'MES 绉嶅瓙鏍囩杩芥函閰嶇疆', 'mes_trace_config', 0,
+ '浜岀淮鐮佽拷婧綉鍧�銆佺敓浜х粡钀ヨ�呭悕绉扮瓑鏍囩瑕佺礌锛涚暀绌哄垯鍥為��閰嶇疆鏂囦欢', '1', NOW(), '1', NOW(), b'0' FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_dict_type WHERE type = 'mes_trace_config' AND deleted = 0) t);
+
+-- ----------------------------
+-- 2. 瀛楀吀鏁版嵁
+-- 銆屽瓧鍏搁敭鍊笺�嶇暀绌哄嵆鍥為��閰嶇疆鏂囦欢锛涜鎸夌幆澧冨湪瀛楀吀绠$悊涓~鍐欏疄闄呭�笺��
+-- 銆屽瓧鍏告爣绛俱�嶄负浠g爜鍥哄畾鏍囪瘑锛岃鍕挎敼鍚嶃��
+-- ----------------------------
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT 1065370, 1, '杩芥函缃戝潃', '', 'mes_trace_config', 0, '', '', '1', NOW(), '1', NOW(), b'0' FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_dict_data WHERE dict_type = 'mes_trace_config' AND label = '杩芥函缃戝潃' AND deleted = 0) t);
+
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT 1065371, 2, '鐢熶骇缁忚惀鑰呭悕绉�', '', 'mes_trace_config', 0, '', '', '1', NOW(), '1', NOW(), b'0' FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_dict_data WHERE dict_type = 'mes_trace_config' AND label = '鐢熶骇缁忚惀鑰呭悕绉�' AND deleted = 0) t);
+
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT 1065372, 3, '杩涘彛鍟嗗悕绉�', '', 'mes_trace_config', 0, '', '', '1', NOW(), '1', NOW(), b'0' FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_dict_data WHERE dict_type = 'mes_trace_config' AND label = '杩涘彛鍟嗗悕绉�' AND deleted = 0) t);
+
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT 1065373, 4, '鏄惁杩涘彛绉嶅瓙', '', 'mes_trace_config', 0, '', '', '1', NOW(), '1', NOW(), b'0' FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_dict_data WHERE dict_type = 'mes_trace_config' AND label = '鏄惁杩涘彛绉嶅瓙' AND deleted = 0) t);
+
+-- ----------------------------
+-- 3. 鍚勭幆澧冨~鍐欑ず渚嬶紙姝e紡鎵ц鏃惰鍦ㄧ鐞嗗悗鍙板瓧鍏哥鐞嗕腑淇敼锛屽嬁鐩存帴濂楃敤锛�
+-- 杩芥函缃戝潃 : http://1.95.78.124:9008/prod/trace.html
+-- 鐢熶骇缁忚惀鑰呭悕绉� : 鏂扮枂濉旈噷鏈ㄦ渤绉嶄笟鑲′唤鏈夐檺鍏徃
+-- 杩涘彛鍟嗗悕绉� : 浠呰繘鍙g瀛愬満鏅~鍐欙紝鍚﹀垯鐣欑┖
+-- 鏄惁杩涘彛绉嶅瓙 : true / false
+-- ----------------------------
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/trace/MesTraceConfigController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/trace/MesTraceConfigController.java
index 60e075d..1a72e3c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/trace/MesTraceConfigController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/trace/MesTraceConfigController.java
@@ -2,7 +2,8 @@
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.mes.controller.admin.trace.vo.MesTraceQrConfigRespVO;
-import cn.iocoder.yudao.module.mes.framework.config.MesProperties;
+import cn.iocoder.yudao.module.mes.framework.config.MesTraceConfigResolver;
+import cn.iocoder.yudao.module.mes.framework.config.MesTraceConfigSnapshot;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
@@ -17,6 +18,8 @@
* 绠$悊鍚庡彴 - MES 婧簮閰嶇疆
* <p>
* 鍚戠鐞嗙鍓嶇鏆撮湶婧簮 H5 椤甸潰鍩虹鍦板潃锛岀敤浜�"鏌ョ湅浜岀淮鐮�"棰勮/涓嬭浇涓庢墦鍗版爣绛句繚鎸佷竴鑷寸殑浜岀淮鐮佸唴瀹广��
+ * 鍊煎彇鑷� {@link MesTraceConfigResolver} 鐨�**鐢熸晥蹇収**鑰岄潪閰嶇疆鏂囦欢鈥斺�旇拷婧綉鍧�鍙湪瀛楀吀
+ * {@code mes_trace_config} 涓慨鏀癸紝鑻ユ澶勭洿璇婚厤缃枃浠讹紝绠$悊绔瑙堢殑浜岀淮鐮佸氨浼氫笌瀹為檯鎵撳嵃鐨勬爣绛句笉涓�鑷淬��
*/
@Tag(name = "绠$悊鍚庡彴 - MES 婧簮閰嶇疆")
@RestController
@@ -25,13 +28,15 @@
public class MesTraceConfigController {
@Resource
- private MesProperties mesProperties;
+ private MesTraceConfigResolver mesTraceConfigResolver;
@GetMapping("/qr-config")
@Operation(summary = "鑾峰緱婧簮 H5 椤甸潰鍩虹鍦板潃锛堜簩缁寸爜鍐呭鍓嶇紑锛�")
public CommonResult<MesTraceQrConfigRespVO> getQrConfig() {
+ MesTraceConfigSnapshot snapshot = mesTraceConfigResolver.resolveSnapshot();
MesTraceQrConfigRespVO respVO = new MesTraceQrConfigRespVO();
- respVO.setTraceBaseUrl(mesProperties.getTraceBaseUrl());
+ respVO.setTraceBaseUrl(snapshot.traceUrl());
+ respVO.setTraceUrlSource(snapshot.traceUrlSource().name());
return success(respVO);
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/trace/vo/MesTraceQrConfigRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/trace/vo/MesTraceQrConfigRespVO.java
index 73376f9..7a02bbd 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/trace/vo/MesTraceQrConfigRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/trace/vo/MesTraceQrConfigRespVO.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.controller.admin.trace.vo;
+import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
@@ -10,7 +11,21 @@
/**
* 婧簮 H5 椤甸潰鍩虹鍦板潃锛堜簩缁寸爜鍐呭鍓嶇紑锛屽 http://host:port/trace.html锛�
+ * <p>
+ * 浼樺厛鍙栧瓧鍏� {@code mes_trace_config} 涓�岃拷婧綉鍧�銆嶇殑閰嶇疆鍊硷紱瀛楀吀鏈厤缃椂鍥為��閰嶇疆鏂囦欢銆�
*/
+ @Schema(description = "婧簮 H5 椤甸潰鍩虹鍦板潃锛堜簩缁寸爜鍐呭鍓嶇紑锛�",
+ example = "http://1.95.78.124:9008/prod/trace.html")
private String traceBaseUrl;
+ /**
+ * 涓婅堪鍦板潃鐨勫疄闄呯敓鏁堟潵婧愶紝渚夸簬纭瀛楀吀鏀瑰姩鏄惁宸茬敓鏁�
+ * <p>
+ * DICT锛氬彇鑷瓧鍏革紱YAML锛氬瓧鍏告湭閰嶇疆锛屽洖閫�閰嶇疆鏂囦欢锛�
+ * YAML_INVALID_DICT锛氬瓧鍏稿~浜嗗�间絾鏈�氳繃鍚堣鏍¢獙锛屽凡鍥為��閰嶇疆鏂囦欢锛堥渶淇瀛楀吀锛夈��
+ */
+ @Schema(description = "杩芥函缃戝潃鐢熸晥鏉ユ簮锛欴ICT / YAML / YAML_INVALID_DICT",
+ example = "DICT")
+ private String traceUrlSource;
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesProperties.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesProperties.java
index cbbd9d1..f1fdfa5 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesProperties.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesProperties.java
@@ -15,15 +15,20 @@
/**
* H5 婧簮椤甸潰鍦板潃锛坱race.html锛夊熀纭� URL锛岀敤浜庣敓鎴愭惡甯﹁拷婧爜鍙傛暟鐨勪簩缁寸爜鍐呭銆�
* <p>
- * 璇ュ�间负銆岃拷婧綉鍧�銆嶇殑鍞竴鐪熷�兼簮锛氫簩缁寸爜鍐呭涓� H5 椤甸潰灞曠ず鐨勮拷婧綉鍧�鍧囧彇鑷閰嶇疆銆�
- * 蹇呴』涓哄叕浼楁壂鐮佸彲璁块棶鐨勫湴鍧�锛岄潪鏈湴鐜閰嶇疆涓哄唴缃�/鍗犱綅绗﹀湴鍧�鏃跺惎鍔ㄥ皢鐩存帴澶辫触銆�
+ * 杩欐槸銆岃拷婧綉鍧�銆嶇殑**鍏滃簳榛樿鍊�**锛氳繍琛屾椂浼樺厛鍙栧瓧鍏� {@link MesTraceConfigResolver#DICT_TYPE}
+ * 涓�岃拷婧綉鍧�銆嶇殑閰嶇疆鍊硷紝鍙湪绠$悊鍚庡彴銆屽瓧鍏哥鐞嗐�嶄腑淇敼鑰屾棤闇�閲嶅惎锛涘瓧鍏告湭閰嶇疆锛堟垨琚鐢ㄣ��
+ * 鎴栧~浜嗛潪娉曞湴鍧�锛夋椂鎵嶅洖閫�鍒版閰嶇疆銆�
+ * <p>
+ * 鏈」浠嶄负蹇呭~涓斿弬涓庡惎鍔ㄦ牎楠岋細闈炴湰鍦扮幆澧冮厤缃负鍐呯綉/鍗犱綅绗﹀湴鍧�鏃跺惎鍔ㄥ皢鐩存帴澶辫触銆�
+ * 杩欓亾闃茬嚎蹇呴』鍦ㄦ暟鎹簱鍙敤涔嬪墠灏辫兘鎷︿綇涓嶅悎瑙勯厤缃紝鍥犳涓嶈兘鍙緷璧栧瓧鍏搞��
*/
private String traceBaseUrl = "";
/**
* 鐢熶骇缁忚惀鑰呭悕绉帮紙绉嶅瓙鏍囩鍥涜绱犱箣涓�锛夈��
* <p>
- * 浣滀负銆岀敓浜х粡钀ヨ�呭悕绉般�嶇殑鍞竴鐪熷�兼簮锛屾爣绛惧嵃鍒蜂笌 H5 婧簮椤靛潎鍙栨鍊硷紝绂佹鍦ㄥ墠绔‖缂栫爜銆�
+ * 鍚屾牱鏄�**鍏滃簳榛樿鍊�**锛氳繍琛屾椂浼樺厛鍙栧瓧鍏� {@link MesTraceConfigResolver#DICT_TYPE} 涓�
+ * 銆岀敓浜х粡钀ヨ�呭悕绉般�嶇殑閰嶇疆鍊笺�傛爣绛惧嵃鍒蜂笌 H5 婧簮椤靛潎鍙栨鍊硷紝绂佹鍦ㄥ墠绔‖缂栫爜銆�
*/
private String operatorName = "";
@@ -31,11 +36,14 @@
* 杩涘彛鍟嗗悕绉帮紙杩涘彛绉嶅瓙鍦烘櫙锛屾爣绛惧洓瑕佺礌涔嬩竴锛夈��
* <p>
* 杩涘彛绉嶅瓙搴旀爣娉ㄨ繘鍙e晢鍚嶇О锛涗负绌烘椂鍥為��浣跨敤 {@link #operatorName}銆�
+ * 杩愯鏃朵紭鍏堝彇瀛楀吀涓�岃繘鍙e晢鍚嶇О銆嶇殑閰嶇疆鍊笺��
*/
private String importerName = "";
/**
* 鏄惁澶勪簬杩涘彛绉嶅瓙鍦烘櫙锛氫负 true 鏃舵爣绛句笌婧簮椤靛睍绀恒�岃繘鍙e晢鍚嶇О銆嶈�岄潪銆岀敓浜х粡钀ヨ�呭悕绉般��
+ * <p>
+ * 杩愯鏃朵紭鍏堝彇瀛楀吀涓�屾槸鍚﹁繘鍙g瀛愩�嶇殑閰嶇疆鍊笺��
*/
private boolean imported = false;
@@ -43,6 +51,9 @@
* 鍏佽浜岀淮鐮佺紪鐮佺函鐮佸�硷紙涓嶅甫婧簮 URL锛夈��
* <p>
* 浠呬緵鏈湴/娴嬭瘯鐜鍏煎鍘嗗彶鐮佷娇鐢紱鐢熶骇鐜蹇呴』涓� false锛屽惁鍒欎細鍗板嚭涓嶅惈杩芥函缃戝潃鐨勬爣绛俱��
+ * <p>
+ * 鏈」**鏈夋剰涓嶇撼鍏ュ瓧鍏�**锛氬畠鏄�屽厑璁稿嵃鍑轰笉鍚拷婧綉鍧�鐨勬爣绛俱�嶇殑寮�鍏筹紝鏀捐繘鍙悗鍙扮紪杈戠殑瀛楀吀锛�
+ * 绛変簬缁欑瀛愭爣绛剧殑鍚堣闃茬嚎寮�浜嗕竴涓彲琚鎿嶄綔鐨勫悗闂ㄣ�傚洜姝ゅ彧淇濈暀鍦ㄩ厤缃枃浠朵腑锛岀敱寮�鍙�/杩愮淮鎶婃帶銆�
*/
private boolean allowPlainQrCode = false;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesQrCodeConfig.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesQrCodeConfig.java
index e18c9f5..c025482 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesQrCodeConfig.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesQrCodeConfig.java
@@ -9,7 +9,6 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
-import java.net.URI;
import java.util.Arrays;
/**
@@ -22,6 +21,12 @@
* <li>鐢熶骇缁忚惀鑰呭悕绉板繀濉紙鏍囩鍥涜绱犱箣涓�锛岀己澶卞嵆涓轰笉鍚堣鏍囩锛夛紱</li>
* <li>褰撳墠鐜蹇呴』瀛樺湪鍙覆鏌撲腑鏂囩殑瀛椾綋锛堝惁鍒欐爣绛句腑鏂囦細鍙樻垚鏂规锛夈��</li>
* </ul>
+ * 杩芥函缃戝潃涓庣敓浜х粡钀ヨ�呭悕绉扮殑**杩愯鏃�**鍙栧�间紭鍏堟潵鑷瓧鍏� {@code mes_trace_config}锛�
+ * 鍙湪绠$悊鍚庡彴銆屽瓧鍏哥鐞嗐�嶄腑淇敼鑰屾棤闇�閲嶅惎锛涢厤缃枃浠剁殑鍊奸��灞呬负鍏滃簳榛樿鍊笺��
+ * <p>
+ * 浣嗗惎鍔ㄦ牎楠�**鍙拡瀵归厤缃枃浠�**锛屽師鍥犳湁浜岋細涓�鏄繖閬撻槻绾垮繀椤诲湪鏁版嵁搴撳彲鐢ㄤ箣鍓嶅氨鑳芥嫤浣忎笉鍚堣閰嶇疆锛�
+ * 浜屾槸 @PostConstruct 闃舵涓嶅簲璁块棶鏁版嵁搴撱�傚瓧鍏稿�肩殑鍚堣鏍¢獙鐢� {@link MesTraceConfigResolver}
+ * 鍦ㄨ鍙栨椂瀹屾垚锛堥潪娉曞垯璁� ERROR 骞跺洖閫�閰嶇疆鏂囦欢锛岃璇ョ被娉ㄩ噴锛夈��
*/
@Slf4j
@Configuration(proxyBeanMethods = false)
@@ -38,9 +43,12 @@
@Resource
private Environment environment;
+ @Resource
+ private MesTraceConfigResolver mesTraceConfigResolver;
+
@PostConstruct
public void init() {
- // 1. 娉ㄥ叆閰嶇疆锛堝瓧浣撴牎楠岀嫭绔嬩簬涓氬姟閰嶇疆锛屾湰鍦扮幆澧冨悓鏍峰繀椤婚�氳繃锛屽惁鍒欐爣绛句腑鏂囦笉鍙锛�
+ // 1. 娉ㄥ叆閰嶇疆鏂囦欢鐨勫�硷紙瀛椾綋鏍¢獙鐙珛浜庝笟鍔¢厤缃紝鏈湴鐜鍚屾牱蹇呴』閫氳繃锛屽惁鍒欐爣绛句腑鏂囦笉鍙锛�
MesProQrLabelBuilder.setTraceBaseUrl(mesProperties.getTraceBaseUrl());
MesProQrLabelBuilder.setOperatorName(mesProperties.getOperatorName());
MesProQrLabelBuilder.setImporterName(mesProperties.getImporterName());
@@ -48,16 +56,21 @@
MesProQrLabelBuilder.setAllowPlainQrCode(mesProperties.isAllowPlainQrCode());
QrCodeImageUtils.assertCjkFontAvailable();
- // 2. 闈炴湰鍦扮幆澧冩墽琛屼弗鏍兼牎楠�
+ // 2. 鏍¢獙閰嶇疆鏂囦欢銆備笅闈㈢殑鏍¢獙涓庢棩蹇楅兘蹇呴』璇婚厤缃枃浠剁殑鍊硷紝鍥犳瀛楀吀鏉ユ簮涓嶈兘鍦ㄦ涔嬪墠瑁呴厤
if (isLocalProfile()) {
- log.info("[init] 褰撳墠涓烘湰鍦扮幆澧冿紙{}锛夛紝璺宠繃鏍囩鍚堣閰嶇疆鐨勪弗鏍兼牎楠岋紱traceBaseUrl={}",
+ log.info("[init] 褰撳墠涓烘湰鍦扮幆澧冿紙{}锛夛紝璺宠繃鏍囩鍚堣閰嶇疆鐨勪弗鏍兼牎楠岋紱閰嶇疆鏂囦欢 traceBaseUrl={}",
PROFILE_LOCAL, mesProperties.getTraceBaseUrl());
- return;
+ } else {
+ validateTraceBaseUrl();
+ validateOperatorName();
+ log.info("[init] 浜岀淮鐮佹爣绛鹃厤缃牎楠岄�氳繃锛涢厤缃枃浠� traceBaseUrl={}锛岀敓浜х粡钀ヨ��={}",
+ mesProperties.getTraceBaseUrl(), MesProQrLabelBuilder.resolveOperatorName());
}
- validateTraceBaseUrl();
- validateOperatorName();
- log.info("[init] 浜岀淮鐮佹爣绛鹃厤缃牎楠岄�氳繃锛泃raceBaseUrl={}锛岀敓浜х粡钀ヨ��={}",
- mesProperties.getTraceBaseUrl(), MesProQrLabelBuilder.resolveOperatorName());
+
+ // 3. 鏈�鍚庤閰嶅瓧鍏告潵婧愶細姝ゅ悗 resolveTraceUrl()/resolveOperatorName() 浼樺厛璧板瓧鍏�
+ // 锛堝瓧鍏告湭閰嶇疆鍒欒嚜鍔ㄥ洖閫�閰嶇疆鏂囦欢锛夈�傛澶勫彧瑁呴厤鏂规硶寮曠敤銆佺粷涓嶈皟鐢ㄢ�斺��
+ // 瑙f瀽瀛楀吀闇�瑕佽闂暟鎹簱锛屼笉鑳藉湪 @PostConstruct 闃舵瑙﹀彂銆�
+ MesProQrLabelBuilder.setTraceConfigProvider(mesTraceConfigResolver::resolveSnapshot);
}
private boolean isLocalProfile() {
@@ -65,26 +78,17 @@
}
/**
- * 鏍¢獙杩芥函缃戝潃锛氬繀椤讳负鍏紬鎵爜鍙闂殑鍦板潃
+ * 鏍¢獙閰嶇疆鏂囦欢涓殑杩芥函缃戝潃锛氬繀椤讳负鍏紬鎵爜鍙闂殑鍦板潃
+ * <p>
+ * 涓庡瓧鍏稿�煎叡鐢� {@link MesTraceUrlValidator}锛屼繚璇佷袱涓潵婧愮殑鍑嗗叆闂ㄦ瀹屽叏涓�鑷达紝
+ * 涓嶄細鍑虹幇銆岄厤缃枃浠舵嫤寰椾綇銆佸瓧鍏哥粫寰楄繃銆嶇殑缂哄彛銆�
*/
private void validateTraceBaseUrl() {
String url = StrUtil.trim(mesProperties.getTraceBaseUrl());
- if (StrUtil.isEmpty(url)) {
- throw new IllegalStateException("mes.trace-base-url 鏈厤缃�備簩缁寸爜鏍囩蹇呴』鍖呭惈杩芥函缃戝潃锛�"
- + "璇峰湪閰嶇疆涓~鍐欏叕浼楁壂鐮佸彲璁块棶鐨� H5 婧簮椤靛湴鍧�銆�");
- }
- if (StrUtil.containsAnyIgnoreCase(url, "your-domain", "example.com", "localhost")) {
- throw new IllegalStateException("mes.trace-base-url 浠嶄负鍗犱綅绗︽垨鏈満鍦板潃锛�" + url
- + "锛屽嵃鍑虹殑鏍囩娑堣垂鑰呭皢鏃犳硶璁块棶锛岃濉啓鐪熷疄鐨勫缃戝湴鍧�銆�");
- }
- String host = extractHost(url);
- if (StrUtil.isEmpty(host)) {
- throw new IllegalStateException("mes.trace-base-url 鏍煎紡闈炴硶锛屾棤娉曡В鏋愪富鏈哄悕锛�" + url);
- }
- if (isPrivateHost(host)) {
- throw new IllegalStateException("mes.trace-base-url 鎸囧悜鍐呯綉鍦板潃锛�" + host + "锛夛細" + url
- + "锛屽叕浼楁壂鐮佹棤娉曡闂紝璇峰~鍐欏缃戝彲璁块棶鐨勫湴鍧�銆�");
- }
+ MesTraceUrlValidator.invalidReason(url).ifPresent(reason -> {
+ throw new IllegalStateException("mes.trace-base-url " + reason
+ + "锛堣椤规槸瀛楀吀 " + MesTraceConfigResolver.DICT_TYPE + " 鏈厤缃椂鐨勫厹搴曞�硷級");
+ });
}
/**
@@ -96,47 +100,10 @@
*/
private void validateOperatorName() {
if (StrUtil.isBlank(MesProQrLabelBuilder.resolveOperatorName())) {
- log.warn("[init] mes.operator-name锛堢敓浜х粡钀ヨ�呭悕绉帮級鏈厤缃�傝瀛楁涓虹瀛愭爣绛惧洓瑕佺礌涔嬩竴锛�"
- + "缂哄け鏃跺皢鏃犳硶瀵煎嚭浜岀淮鐮佹爣绛撅紝璇峰敖蹇湪閰嶇疆涓ˉ鍏呫��");
+ log.warn("[init] 鐢熶骇缁忚惀鑰呭悕绉版湭閰嶇疆锛堥厤缃枃浠� mes.operator-name 涓庡瓧鍏� {} 鍧囦负绌猴級銆�"
+ + "璇ュ瓧娈典负绉嶅瓙鏍囩鍥涜绱犱箣涓�锛岀己澶辨椂灏嗘棤娉曞鍑轰簩缁寸爜鏍囩锛岃灏藉揩琛ュ厖銆�",
+ MesTraceConfigResolver.DICT_TYPE);
}
- }
-
- /**
- * 浠� URL 涓彁鍙栦富鏈哄悕
- */
- private String extractHost(String url) {
- try {
- URI uri = URI.create(url);
- return uri.getHost();
- } catch (IllegalArgumentException ex) {
- return null;
- }
- }
-
- /**
- * 鍒ゆ柇鏄惁涓烘湰鏈� / 鍐呯綉 / 閾捐矾鏈湴鍦板潃
- */
- private boolean isPrivateHost(String host) {
- if ("localhost".equalsIgnoreCase(host) || host.endsWith(".local")) {
- return true;
- }
- String[] parts = host.split("\\.");
- if (parts.length != 4) {
- return false; // 鍩熷悕褰㈠紡锛屼氦鐢� DNS 瑙f瀽锛屼笉鍦ㄦ澶勬嫤鎴�
- }
- int a, b;
- try {
- a = Integer.parseInt(parts[0]);
- b = Integer.parseInt(parts[1]);
- } catch (NumberFormatException ex) {
- return false;
- }
- // 127.0.0.0/8 鏈満銆�10.0.0.0/8銆�172.16.0.0/12銆�192.168.0.0/16 绉佺綉銆�169.254.0.0/16 閾捐矾鏈湴
- return a == 127
- || a == 10
- || (a == 172 && b >= 16 && b <= 31)
- || (a == 192 && b == 168)
- || (a == 169 && b == 254);
}
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceConfigResolver.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceConfigResolver.java
new file mode 100644
index 0000000..766d547
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceConfigResolver.java
@@ -0,0 +1,192 @@
+package cn.iocoder.yudao.module.mes.framework.config;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
+import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.time.Duration;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 绉嶅瓙鏍囩閰嶇疆瑙f瀽鍣細鎶娿�屽瓧鍏搁厤缃�� + 閰嶇疆鏂囦欢鍏滃簳鍊笺�嶅悎骞朵负涓�浠界敓鏁堝揩鐓с��
+ * <p>
+ * 杩芥函缃戝潃銆佺敓浜х粡钀ヨ�呭悕绉扮瓑鏍囩瑕佺礌姝ゅ墠鍙兘鏀归厤缃枃浠跺啀閲嶅惎鏈嶅姟锛屾湰娆℃敼涓哄彲鍦�
+ * 銆岀郴缁熺鐞� 鈫� 瀛楀吀绠$悊銆嶇殑瀛楀吀 {@code mes_trace_config} 涓洿鎺ヤ慨鏀癸紝鏀瑰畬鏃犻渶閲嶅惎銆�
+ * <p>
+ * 鍚堝苟瑙勫垯锛堝洓椤归厤缃悇鑷嫭绔嬪垽瀹氾級锛�
+ * <ol>
+ * <li>瀛楀吀鏈夊惎鐢ㄩ」涓斿�奸潪绌� 鈫� 鐢ㄥ瓧鍏稿�硷紱</li>
+ * <li>瀛楀吀鏈缓璇ュ瓧鍏哥被鍨� / 鏃犲惎鐢ㄩ」 / 鍊间负绌� 鈫� 鍥為��閰嶇疆鏂囦欢锛堟甯哥殑銆屽皻鏈厤缃�嶈矾寰勶級锛�</li>
+ * <li>杩芥函缃戝潃鐨勫瓧鍏稿�奸潪绌轰絾**涓嶅悎娉�**锛堝崰浣嶇銆佹湰鏈恒�佸唴缃戝湴鍧�绛夛級鈫� 璁� ERROR 鏃ュ織骞跺洖閫�閰嶇疆鏂囦欢銆�</li>
+ * </ol>
+ * 绗� 3 鏉℃槸鏈夋剰涓轰箣鐨勩�岄檷绾ц�屼笉闃绘柇銆嶏細閰嶇疆鏂囦欢鐨勫�煎凡琚惎鍔ㄦ牎楠屼繚璇佸悎娉曪紝鍥為��涓嶄細鍗板嚭涓嶅悎瑙勬爣绛撅紝
+ * 鏍囩鍚堣搴曠嚎涓嶅彈褰卞搷锛涘弽涔嬭嫢鍦ㄦ鎶涘紓甯革紝瀛楀吀閲屼竴娆℃墜璇氨浼氳鍏ㄥ钩鍙扮殑鏍囩瀵煎嚭鍋滄憜锛屼唬浠锋洿澶с��
+ * 銆屽瓧鍏镐笌閰嶇疆鏂囦欢閮芥嬁涓嶅埌杩芥函缃戝潃銆嶄粛鐢� {@code MesProQrLabelBuilder} 鎶涘紓甯告嫆缁濆鍑恒��
+ * <p>
+ * 鏈被**浠讳綍璺緞閮戒笉鍚戝鎶涘紓甯�**锛氳В鏋愬彂鐢熷湪浜岀淮鐮佹壒閲忓鍑虹殑楂橀寰幆涓紝涓�娆℃暟鎹簱鎶栧姩
+ * 鎴栧瓧鍏歌澶辫触閮戒笉搴斾腑鏂暣鎵瑰鍑恒��
+ * <p>
+ * 鎬ц兘锛氬瓧鍏歌鍙栧甫 60 绉掓湰鍦扮紦瀛橈紙{@link CacheUtils#buildAsyncReloadingCache}锛夛紝
+ * 鍗曟蹇収鍙渶涓�娆″唴瀛樻煡琛紱鏁版嵁搴撴煡璇㈡瘡 60 绉掓渶澶氫竴娆★紝涓庢爣绛炬暟閲忔棤鍏炽��
+ * 鍥犳瀛楀吀淇敼鍚�**绾� 1 鍒嗛挓鍐呯敓鏁�**锛屼笖鍥犲紓姝ュ埛鏂帮紝绋�鐤忚皟鐢ㄤ笅鍙兘闇�鍐嶈Е鍙戜竴娆℃墠鍒囨崲銆�
+ */
+@Slf4j
+@Component
+public class MesTraceConfigResolver {
+
+ /**
+ * 瀛楀吀绫诲瀷锛氱瀛愭爣绛捐拷婧厤缃�
+ */
+ public static final String DICT_TYPE = "mes_trace_config";
+
+ /**
+ * 瀛楀吀鏍囩锛堝浐瀹氭爣璇嗭紝绠$悊鍛樺彲鏀圭殑鏄�屽瓧鍏搁敭鍊笺�嶏紝鏍囩鍚嶄笉瑕佹敼锛�
+ */
+ private static final String LABEL_TRACE_URL = "杩芥函缃戝潃";
+ private static final String LABEL_OPERATOR_NAME = "鐢熶骇缁忚惀鑰呭悕绉�";
+ private static final String LABEL_IMPORTER_NAME = "杩涘彛鍟嗗悕绉�";
+ private static final String LABEL_IMPORTED = "鏄惁杩涘彛绉嶅瓙";
+
+ /**
+ * 瀛楀吀閰嶇疆缂撳瓨锛歞ictType 鈫� (瀛楀吀鏍囩 鈫� 瀛楀吀閿��)锛屼粎鍚惎鐢ㄩ」銆�
+ * <p>
+ * 鐢ㄦ湰绫绘寔鏈夌殑鐙珛缂撳瓨鑰岄潪 {@code DictFrameworkUtils}锛屽師鍥犳湁浜岋細涓�鏄悗鑰呮妸瀛楀吀鎶曞奖鎴�
+ * {@code List<String>} 浼氫涪寮� {@code status}锛屾棤娉曡繃婊よ绂佺敤鐨勯厤缃」锛涗簩鏄悗鑰呯殑
+ * {@code dictDataApi} 鏄潤鎬佸瓧娈点�佺敱鑷姩閰嶇疆娉ㄥ叆锛屼笌鏈被棣栨璋冪敤鏃犻『搴忎繚璇侊紝瀛樺湪 NPE 椋庨櫓銆�
+ */
+ private final LoadingCache<String, Map<String, String>> dictCache = CacheUtils.buildAsyncReloadingCache(
+ Duration.ofSeconds(60L),
+ new CacheLoader<String, Map<String, String>>() {
+
+ @Override
+ public Map<String, String> load(String dictType) {
+ return loadDictConfig(dictType);
+ }
+
+ });
+
+ @Resource
+ private DictDataApi dictDataApi;
+
+ @Resource
+ private MesProperties mesProperties;
+
+ /**
+ * 瑙f瀽褰撳墠鐢熸晥鐨勭瀛愭爣绛鹃厤缃揩鐓�
+ *
+ * @return 鐢熸晥蹇収锛屽悇瀛楁鍧囧凡鍚堝苟閰嶇疆鏂囦欢鍏滃簳鍊硷紱缁濅笉杩斿洖 null锛屼篃缁濅笉鎶涘紓甯�
+ */
+ public MesTraceConfigSnapshot resolveSnapshot() {
+ Map<String, String> dict = getDictConfig();
+
+ // 1. 杩芥函缃戝潃锛氬瓧鍏镐紭鍏堬紝浣嗗繀椤诲厛杩囧悎瑙勬牎楠岋紝鍚﹀垯鍥為��閰嶇疆鏂囦欢
+ String dictTraceUrl = dict.get(LABEL_TRACE_URL);
+ String yamlTraceUrl = StrUtil.trimToEmpty(mesProperties.getTraceBaseUrl());
+ String traceUrl;
+ MesTraceUrlSource traceUrlSource;
+ if (StrUtil.isEmpty(dictTraceUrl)) {
+ // 瀛楀吀鏈厤缃紙鎴栫暀绌�/琚鐢級锛氭甯哥殑鍥為��璺緞锛屼笉鏄敊璇�
+ traceUrl = yamlTraceUrl;
+ traceUrlSource = MesTraceUrlSource.YAML;
+ } else if (MesTraceUrlValidator.isValid(dictTraceUrl)) {
+ traceUrl = dictTraceUrl;
+ traceUrlSource = MesTraceUrlSource.DICT;
+ } else {
+ log.error("[resolveSnapshot][瀛楀吀({})鐨勩�寋}銆嶉厤缃簡闈炴硶鍦板潃锛歿}锛坽}锛夛紝宸插洖閫�閰嶇疆鏂囦欢鐨勫�� {}锛岃灏藉揩淇]",
+ DICT_TYPE, LABEL_TRACE_URL, dictTraceUrl,
+ MesTraceUrlValidator.invalidReason(dictTraceUrl).orElse(""), yamlTraceUrl);
+ traceUrl = yamlTraceUrl;
+ traceUrlSource = MesTraceUrlSource.YAML_INVALID_DICT;
+ }
+
+ // 2. 鐢熶骇缁忚惀鑰呭悕绉� / 杩涘彛鍟嗗悕绉帮細鏂囨湰绫婚厤缃棤闇�鍚堣鏍¢獙锛屽瓧鍏搁潪绌哄嵆鐢熸晥
+ String operatorName = firstNonBlank(dict.get(LABEL_OPERATOR_NAME), mesProperties.getOperatorName());
+ String importerName = firstNonBlank(dict.get(LABEL_IMPORTER_NAME), mesProperties.getImporterName());
+
+ return new MesTraceConfigSnapshot(traceUrl, traceUrlSource, operatorName, importerName,
+ resolveImported(dict.get(LABEL_IMPORTED)));
+ }
+
+ /**
+ * 璇诲彇瀛楀吀閰嶇疆锛堝甫缂撳瓨锛夛紝璇诲彇澶辫触鏃惰繑鍥炵┖琛ㄤ互鍥為��閰嶇疆鏂囦欢锛岀粷涓嶆姏寮傚父
+ */
+ private Map<String, String> getDictConfig() {
+ try {
+ Map<String, String> dictConfig = dictCache.get(DICT_TYPE);
+ return dictConfig != null ? dictConfig : Collections.emptyMap();
+ } catch (Exception ex) {
+ log.error("[getDictConfig][璇诲彇瀛楀吀({})澶辫触锛屾湰娆″洖閫�閰嶇疆鏂囦欢鐨勫�糫", DICT_TYPE, ex);
+ return Collections.emptyMap();
+ }
+ }
+
+ /**
+ * 鍔犺浇骞舵暣鐞嗗瓧鍏搁厤缃細鍙繚鐣欏惎鐢ㄩ」锛屾暣鐞嗘垚銆屽瓧鍏告爣绛� 鈫� 瀛楀吀閿�笺�嶇殑鏄犲皠
+ * <p>
+ * 鍚屼竴鏍囩閲嶅閰嶇疆鏃朵互鍏堝嚭鐜拌�呬负鍑嗏�斺�攞@code DictDataApi#getDictDataList} 搴曞眰宸叉寜 {@code sort} 鎺掑簭锛�
+ * 鍥犳銆屽厛鍑虹幇銆嶅嵆銆屾帓搴忓�兼洿灏忋�嶏紝姝ゅ鏃犻渶鍐嶆帓搴忋��
+ */
+ private Map<String, String> loadDictConfig(String dictType) {
+ try {
+ List<DictDataRespDTO> list = dictDataApi.getDictDataList(dictType);
+ if (CollUtil.isEmpty(list)) {
+ return Collections.emptyMap();
+ }
+ Map<String, String> result = new HashMap<>();
+ for (DictDataRespDTO data : list) {
+ // 琚鐢ㄧ殑瀛楀吀椤硅涓恒�屾湭閰嶇疆銆嶏細杩欐槸绠$悊鍛樺湪涓嶅垹鏁版嵁鐨勫墠鎻愪笅鍋滅敤鏌愰」閰嶇疆鐨勬墜娈�
+ if (!Objects.equals(CommonStatusEnum.ENABLE.getStatus(), data.getStatus())) {
+ continue;
+ }
+ if (StrUtil.isBlank(data.getLabel())) {
+ continue;
+ }
+ result.putIfAbsent(data.getLabel().trim(), StrUtil.trimToEmpty(data.getValue()));
+ }
+ return result;
+ } catch (Exception ex) {
+ // 缂撳瓨棣栨 get 鏃跺姞杞藉紓甯镐細鐩存帴鎶涚粰璋冪敤鏂癸紝蹇呴』鍦ㄦ鍚炴帀锛屽惁鍒欎細涓柇鏁存壒鏍囩瀵煎嚭
+ log.error("[loadDictConfig][璇诲彇瀛楀吀({})澶辫触锛屾湰娆″洖閫�閰嶇疆鏂囦欢鐨勫�糫", dictType, ex);
+ return Collections.emptyMap();
+ }
+ }
+
+ /**
+ * 瑙f瀽銆屾槸鍚﹁繘鍙g瀛愩�嶏細瀛楀吀椤逛负绌烘垨鍙栧�兼棤娉曡瘑鍒椂鍥為��閰嶇疆鏂囦欢
+ */
+ private boolean resolveImported(String dictValue) {
+ String value = StrUtil.trim(dictValue);
+ if (StrUtil.isEmpty(value)) {
+ return mesProperties.isImported();
+ }
+ if (StrUtil.equalsAnyIgnoreCase(value, "true", "1", "y", "yes", "鏄�")) {
+ return true;
+ }
+ if (StrUtil.equalsAnyIgnoreCase(value, "false", "0", "n", "no", "鍚�")) {
+ return false;
+ }
+ log.error("[resolveImported][瀛楀吀({})鐨勩�寋}銆嶅彇鍊兼棤娉曡瘑鍒細{}锛屽凡鍥為��閰嶇疆鏂囦欢鐨勫�� {}锛岃濉啓 true 鎴� false]",
+ DICT_TYPE, LABEL_IMPORTED, value, mesProperties.isImported());
+ return mesProperties.isImported();
+ }
+
+ /**
+ * 鍙栭涓潪绌哄�硷細瀛楀吀鍊间紭鍏堬紝涓虹┖鏃跺洖閫�閰嶇疆鏂囦欢鍊�
+ */
+ private static String firstNonBlank(String preferred, String fallback) {
+ return StrUtil.isNotBlank(preferred) ? preferred.trim() : StrUtil.trimToEmpty(fallback);
+ }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceConfigSnapshot.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceConfigSnapshot.java
new file mode 100644
index 0000000..5f280a5
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceConfigSnapshot.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.mes.framework.config;
+
+/**
+ * 绉嶅瓙鏍囩閰嶇疆鐨勭敓鏁堝揩鐓э細瀛楀吀鍊间笌閰嶇疆鏂囦欢鍏滃簳鍊煎悎骞跺悗鐨勭粨鏋溿��
+ * <p>
+ * 銆屽瓧鍏镐紭鍏堛�侀厤缃枃浠跺厹搴曘�嶇殑鍚堝苟瑙勫垯宸插湪 {@link MesTraceConfigResolver} 鍐呭畬鎴愶紝
+ * 鏈揩鐓у彧鎵胯浇**鏈�缁堢敓鏁堝��**锛屾秷璐规柟鏃犻渶鍐嶅叧蹇冩潵婧愩�傚洓椤归厤缃湪蹇収涓悇鑷嫭绔嬪垽瀹氣�斺��
+ * 渚嬪杩芥函缃戝潃鍙栬嚜瀛楀吀銆佽�岀敓浜х粡钀ヨ�呭悕绉板瓧鍏告湭閰嶇疆鏃朵粛鍙洖閫�閰嶇疆鏂囦欢銆�
+ * <p>
+ * 姣忔鍙栧揩鐓т唬琛ㄥ悓涓�鏃跺埢鐨勪竴鑷磋鍥撅紝鏍囩鏋勫缓鏃跺簲**鍙栦竴娆″揩鐓с�佸叏绋嬪鐢�**锛�
+ * 閬垮厤缂撳瓨鎭板ソ鍦ㄤ袱娆¤鍙栦箣闂村埛鏂帮紝瀵艰嚧鍚屼竴寮犳爣绛句笂鏂囧瓧涓庝簩缁寸爜鍐呭涓嶄竴鑷淬��
+ *
+ * @param traceUrl 杩芥函缃戝潃锛堜簩缁寸爜鍐呭鍓嶇紑锛屼笉鍚� code 鍙傛暟锛夛紝鍙兘涓虹┖
+ * @param traceUrlSource 杩芥函缃戝潃鐨勫疄闄呯敓鏁堟潵婧�
+ * @param operatorName 鐢熶骇缁忚惀鑰呭悕绉帮紝鍙兘涓虹┖
+ * @param importerName 杩涘彛鍟嗗悕绉帮紝鍙兘涓虹┖
+ * @param imported 鏄惁杩涘彛绉嶅瓙鍦烘櫙锛氫负 true 鏃舵爣绛句笌婧簮椤靛睍绀恒�岃繘鍙e晢鍚嶇О銆�
+ */
+public record MesTraceConfigSnapshot(String traceUrl,
+ MesTraceUrlSource traceUrlSource,
+ String operatorName,
+ String importerName,
+ boolean imported) {
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceUrlSource.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceUrlSource.java
new file mode 100644
index 0000000..f957b54
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceUrlSource.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.mes.framework.config;
+
+/**
+ * 杩芥函缃戝潃鐨勫疄闄呯敓鏁堟潵婧愩��
+ * <p>
+ * 杩芥函缃戝潃浼樺厛鍙栧瓧鍏� {@code mes_trace_config}锛屽瓧鍏告湭閰嶇疆鏃跺洖閫�閰嶇疆鏂囦欢 {@code mes.trace-base-url}銆�
+ * 绠$悊鍛樺湪瀛楀吀閲屾敼瀹屽湴鍧�鍚庯紝鍙嚟姝ゅ垽鏂敼鍔ㄦ槸鍚︾湡鐨勭敓鏁堚�斺�斿挨鍏� {@link #YAML_INVALID_DICT}
+ * 琛ㄧず瀛楀吀濉簡鍊间絾琚悎瑙勬牎楠屾嫆缁濓紝姝ゆ椂鍚庡彴鐪嬪埌鐨勬槸閰嶇疆鏂囦欢鐨勬棫鍦板潃锛岃�屼笉鏄嚜宸卞垰濉殑銆�
+ */
+public enum MesTraceUrlSource {
+
+ /**
+ * 鍙栬嚜瀛楀吀锛堢鐞嗗憳鍦ㄣ�屽瓧鍏哥鐞嗐�嶄腑閰嶇疆鐨勫�硷紝宸查�氳繃鍚堣鏍¢獙锛�
+ */
+ DICT,
+ /**
+ * 鍙栬嚜閰嶇疆鏂囦欢锛氬瓧鍏告湭閰嶇疆璇ュ�硷紝灞炴甯哥殑鍥為��璺緞
+ */
+ YAML,
+ /**
+ * 鍥為��鑷厤缃枃浠讹細瀛楀吀濉簡鍊间絾鏈�氳繃鍚堣鏍¢獙锛堝崰浣嶇 / 鏈満 / 鍐呯綉鍦板潃绛夛級锛屽凡璁� ERROR 鏃ュ織
+ */
+ YAML_INVALID_DICT;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceUrlValidator.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceUrlValidator.java
new file mode 100644
index 0000000..45d8f07
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceUrlValidator.java
@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.module.mes.framework.config;
+
+import cn.hutool.core.util.StrUtil;
+
+import java.net.URI;
+import java.util.Optional;
+
+/**
+ * 杩芥函缃戝潃鏍¢獙鍣細鍒ゆ柇涓�涓拷婧綉鍧�鏄惁鑳藉嵃鍦ㄧ瀛愭爣绛句笂璁╂秷璐硅�呮壂寰楀紑銆�
+ * <p>
+ * 杩芥函缃戝潃鏈変袱涓潵婧愨�斺�旈厤缃枃浠� {@code mes.trace-base-url} 涓庡瓧鍏� {@code mes_trace_config}銆�
+ * 涓よ�呭繀椤昏蛋**鍚屼竴濂�**鏍¢獙瑙勫垯锛屽惁鍒欎粠瀛楀吀杩涙潵鐨勫�煎氨鑳界粫杩囨爣绛惧悎瑙勬鏌ワ紝
+ * 鍗板嚭娑堣垂鑰呮壂涓嶅紑鐨勬爣绛俱�傚洜姝ゆ妸瑙勫垯鏀舵暃鍒拌繖閲岋紝渚涘惎鍔ㄦ牎楠屼笌瀛楀吀瑙f瀽鍏卞悓澶嶇敤銆�
+ * <p>
+ * 娉ㄦ剰锛氭湰绫诲彧鍋氥�屽湴鍧�鏈韩鏄惁鍙敤銆嶇殑闈欐�佸垽鏂紝涓嶅惈浠讳綍閰嶇疆璇诲彇涓庡紓甯告姏鍑猴紝渚夸簬鍗曟祴銆�
+ */
+public final class MesTraceUrlValidator {
+
+ private MesTraceUrlValidator() {
+ }
+
+ /**
+ * 璇ュ湴鍧�鏄惁鍙敤浜庢爣绛撅細绌恒�佸崰浣嶇銆佹湰鏈哄湴鍧�銆佸唴缃戝湴鍧�銆佹棤娉曡В鏋愮殑鍦板潃鍧囪涓轰笉鍙敤銆�
+ *
+ * @param url 杩芥函缃戝潃
+ * @return true 琛ㄧず鍙嵃鍦ㄦ爣绛句笂
+ */
+ public static boolean isValid(String url) {
+ return invalidReason(url).isEmpty();
+ }
+
+ /**
+ * 鍒ゆ柇杩芥函缃戝潃鏄惁鍙敤锛屼笉鍙敤鏃剁粰鍑轰汉绫诲彲璇荤殑鍘熷洜銆�
+ *
+ * @param url 杩芥函缃戝潃
+ * @return 绌鸿〃绀哄彲鐢紱鏈夊�煎嵆涓轰笉鍙敤鐨勫師鍥�
+ */
+ public static Optional<String> invalidReason(String url) {
+ String trimmed = StrUtil.trim(url);
+ if (StrUtil.isEmpty(trimmed)) {
+ return Optional.of("鏈厤缃�備簩缁寸爜鏍囩蹇呴』鍖呭惈杩芥函缃戝潃锛�"
+ + "璇峰~鍐欏叕浼楁壂鐮佸彲璁块棶鐨� H5 婧簮椤靛湴鍧�銆�");
+ }
+ // 鍗犱綅绗︿笌鏈満鍦板潃锛氬涓轰粠绀轰緥閰嶇疆鐩存帴鎷锋潵銆佸繕璁版浛鎹紝鍗板嚭鍘绘秷璐硅�呭繀鐒舵壂涓嶅紑
+ if (StrUtil.containsAnyIgnoreCase(trimmed, "your-domain", "example.com", "localhost")) {
+ return Optional.of("浠嶄负鍗犱綅绗︽垨鏈満鍦板潃锛�" + trimmed
+ + "锛屽嵃鍑虹殑鏍囩娑堣垂鑰呭皢鏃犳硶璁块棶锛岃濉啓鐪熷疄鐨勫缃戝湴鍧�銆�");
+ }
+ String host = extractHost(trimmed);
+ if (StrUtil.isEmpty(host)) {
+ return Optional.of("鏍煎紡闈炴硶锛屾棤娉曡В鏋愪富鏈哄悕锛�" + trimmed);
+ }
+ if (isPrivateHost(host)) {
+ return Optional.of("鎸囧悜鍐呯綉鍦板潃锛�" + host + "锛夛細" + trimmed
+ + "锛屽叕浼楁壂鐮佹棤娉曡闂紝璇峰~鍐欏缃戝彲璁块棶鐨勫湴鍧�銆�");
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * 浠� URL 涓彁鍙栦富鏈哄悕锛屾棤娉曡В鏋愭椂杩斿洖 null
+ */
+ private static String extractHost(String url) {
+ try {
+ return URI.create(url).getHost();
+ } catch (IllegalArgumentException ex) {
+ return null;
+ }
+ }
+
+ /**
+ * 鍒ゆ柇鏄惁涓烘湰鏈� / 鍐呯綉 / 閾捐矾鏈湴鍦板潃
+ */
+ private static boolean isPrivateHost(String host) {
+ if ("localhost".equalsIgnoreCase(host) || host.endsWith(".local")) {
+ return true;
+ }
+ String[] parts = host.split("\\.");
+ if (parts.length != 4) {
+ return false; // 鍩熷悕褰㈠紡锛屼氦鐢� DNS 瑙f瀽锛屼笉鍦ㄦ澶勬嫤鎴�
+ }
+ int a, b;
+ try {
+ a = Integer.parseInt(parts[0]);
+ b = Integer.parseInt(parts[1]);
+ } catch (NumberFormatException ex) {
+ return false;
+ }
+ // 127.0.0.0/8 鏈満銆�10.0.0.0/8銆�172.16.0.0/12銆�192.168.0.0/16 绉佺綉銆�169.254.0.0/16 閾捐矾鏈湴
+ return a == 127
+ || a == 10
+ || (a == 172 && b >= 16 && b <= 31)
+ || (a == 192 && b == 168)
+ || (a == 169 && b == 254);
+ }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java
index 89bb98d..fcc1855 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO;
+import cn.iocoder.yudao.module.mes.framework.config.MesTraceConfigSnapshot;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import java.net.URLEncoder;
@@ -69,16 +70,30 @@
*/
private static final Pattern UNIT_CODE_PATTERN = Pattern.compile("^[0-9A-Za-z]{1,20}$");
- /** H5 婧簮椤甸潰鍩虹鍦板潃锛岀敱 {@code MesQrCodeConfig} 鍚姩鏃舵敞鍏ワ紱涓虹┖鏃朵簩缁寸爜缂栫爜绾爜鍊� */
+ /**
+ * 閰嶇疆鏉ユ簮锛氱敱 {@code MesQrCodeConfig} 鍚姩鏃惰閰嶏紝鎸囧悜瀛楀吀瑙f瀽鍣ㄣ��
+ * <p>
+ * 涓� null 鏃讹紙鍗曟祴銆侀潪 Spring 涓婁笅鏂囷級鍥為��涓嬮潰鍑犱釜闈欐�佸瓧娈碉紝涔熷氨鏄厤缃枃浠剁殑鍊笺��
+ */
+ private static volatile MesTraceConfigProvider configProvider;
+
+ /** H5 婧簮椤甸潰鍩虹鍦板潃锛岄厤缃枃浠� {@code mes.trace-base-url} 鐨勫�硷紱瀛楀吀鏈厤缃椂鐨勫厹搴� */
private static volatile String traceBaseUrl = "";
- /** 鐢熶骇缁忚惀鑰呭悕绉帮紝鐢� {@code MesQrCodeConfig} 鍚姩鏃舵敞鍏� */
+ /** 鐢熶骇缁忚惀鑰呭悕绉帮紝閰嶇疆鏂囦欢 {@code mes.operator-name} 鐨勫�硷紱瀛楀吀鏈厤缃椂鐨勫厹搴� */
private static volatile String operatorName = "";
- /** 杩涘彛鍟嗗悕绉帮紝杩涘彛绉嶅瓙鍦烘櫙浣跨敤 */
+ /** 杩涘彛鍟嗗悕绉帮紝閰嶇疆鏂囦欢 {@code mes.importer-name} 鐨勫�硷紱瀛楀吀鏈厤缃椂鐨勫厹搴� */
private static volatile String importerName = "";
- /** 鏄惁杩涘彛绉嶅瓙鍦烘櫙 */
+ /** 鏄惁杩涘彛绉嶅瓙鍦烘櫙锛岄厤缃枃浠� {@code mes.imported} 鐨勫�硷紱瀛楀吀鏈厤缃椂鐨勫厹搴� */
private static volatile boolean imported = false;
/** 鏄惁鍏佽浜岀淮鐮侀檷绾х紪鐮佺函鐮佸�硷紙浠呴檺鏈湴鍏煎鍘嗗彶鐮侊級 */
private static volatile boolean allowPlainQrCode = false;
+
+ /**
+ * 瑁呴厤閰嶇疆鏉ユ簮銆備紶 null 鍙仮澶嶄负銆屽彧璇婚厤缃枃浠堕潤鎬佸�笺�嶏紝鍗曟祴鍊熸閬垮厤闈欐�佺姸鎬佷覆鍛炽��
+ */
+ public static void setTraceConfigProvider(MesTraceConfigProvider provider) {
+ MesProQrLabelBuilder.configProvider = provider;
+ }
public static void setTraceBaseUrl(String traceBaseUrl) {
MesProQrLabelBuilder.traceBaseUrl = StrUtil.blankToDefault(traceBaseUrl, "");
@@ -101,21 +116,46 @@
}
/**
+ * 鍙栧綋鍓嶇敓鏁堢殑閰嶇疆蹇収
+ *
+ * @return 蹇収锛涙湭瑁呴厤閰嶇疆鏉ユ簮鏃惰繑鍥� null锛岃皟鐢ㄦ柟鍥為��闈欐�佸瓧娈碉紙閰嶇疆鏂囦欢鐨勫�硷級
+ */
+ private static MesTraceConfigSnapshot currentSnapshot() {
+ MesTraceConfigProvider provider = configProvider;
+ return provider != null ? provider.get() : null;
+ }
+
+ /**
* 褰撳墠搴旀爣娉ㄧ殑鐢熶骇缁忚惀鑰呭悕绉帮細杩涘彛绉嶅瓙鍦烘櫙浼樺厛鍙栬繘鍙e晢鍚嶇О锛屽惁鍒欏彇鐢熶骇缁忚惀鑰呭悕绉�
*
* @return 鍚嶇О锛屾湭閰嶇疆鏃惰繑鍥炵┖涓�
*/
public static String resolveOperatorName() {
- if (imported && StrUtil.isNotBlank(importerName)) {
- return importerName;
+ return resolveOperatorName(currentSnapshot());
+ }
+
+ private static String resolveOperatorName(MesTraceConfigSnapshot snapshot) {
+ boolean importedScene = snapshot != null ? snapshot.imported() : imported;
+ String currentImporterName = snapshot != null ? snapshot.importerName() : importerName;
+ String currentOperatorName = snapshot != null ? snapshot.operatorName() : operatorName;
+ if (importedScene && StrUtil.isNotBlank(currentImporterName)) {
+ return currentImporterName;
}
- return operatorName;
+ return currentOperatorName;
}
/**
* 杩芥函缃戝潃锛堝澶栧睍绀虹敤锛屼笉鍚� code 鍙傛暟锛�
*/
public static String resolveTraceUrl() {
+ return resolveTraceUrl(currentSnapshot());
+ }
+
+ private static String resolveTraceUrl(MesTraceConfigSnapshot snapshot) {
+ // 蹇収涓虹┖鍊兼椂涓嶈鐩栭厤缃枃浠剁殑鍊硷細閰嶇疆鏉ユ簮鍙簲銆岃ˉ鍏呫�嶈�屼笉搴旀妸鍏滃簳鍊兼姽鎴愮┖
+ if (snapshot != null && StrUtil.isNotBlank(snapshot.traceUrl())) {
+ return snapshot.traceUrl().trim();
+ }
return traceBaseUrl;
}
@@ -123,7 +163,11 @@
* 鏄惁杩涘彛绉嶅瓙鍦烘櫙锛氫负 true 鏃舵爣绛句笌婧簮椤靛簲灞曠ず銆岃繘鍙e晢鍚嶇О銆�
*/
public static boolean isImported() {
- return imported;
+ return isImported(currentSnapshot());
+ }
+
+ private static boolean isImported(MesTraceConfigSnapshot snapshot) {
+ return snapshot != null ? snapshot.imported() : imported;
}
/**
@@ -132,13 +176,26 @@
* 鏈厤缃函婧愬湴鍧�涓旀湭寮�鍚檷绾у紑鍏虫椂鐩存帴鎶ラ敊锛岄伩鍏嶅嵃鍑轰笉鍚拷婧綉鍧�鐨勬爣绛俱��
*/
private static String buildQrCodeContent(String code) {
- if (StrUtil.isEmpty(traceBaseUrl)) {
+ return buildQrCodeContent(code, resolveTraceUrl());
+ }
+
+ /**
+ * 缁勮浜岀淮鐮佸唴瀹癸紙浣跨敤璋冪敤鏂圭粰瀹氱殑杩芥函缃戝潃锛�
+ * <p>
+ * 鏍囩涓婃棦鏈変簩缁寸爜鍐呭銆佸張鏈夊彲璇荤殑銆岃拷婧綉鍧�銆嶆枃鏈椂锛屼袱澶勫繀椤讳紶鍚屼竴涓綉鍧�锛�
+ * 鍒嗗埆鍙栧�煎彲鑳借法瓒婄紦瀛樺埛鏂帮紝瀵艰嚧鍚屼竴寮犳爣绛句笂鏂囧瓧涓庝簩缁寸爜鎸囧悜涓嶅悓鍦板潃銆�
+ *
+ * @param code 鐮佸��
+ * @param traceUrl 鏈鐢熸晥鐨勮拷婧綉鍧�锛屼负绌鸿〃绀烘湭閰嶇疆
+ */
+ private static String buildQrCodeContent(String code, String traceUrl) {
+ if (StrUtil.isEmpty(traceUrl)) {
if (allowPlainQrCode) {
return code;
}
throw exception(PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED);
}
- return traceBaseUrl + "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8);
+ return traceUrl + "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8);
}
/**
@@ -217,16 +274,21 @@
MesProBatchDO batch,
Map<Long, MesMdmItemDO> itemMap,
Map<Long, AdminUserRespDTO> userMap) {
+ // 鏁村紶鏍囩鍙彇涓�娆¢厤缃揩鐓э細鍙鏂囨湰涓庝簩缁寸爜鍐呭鍏辩敤鍚屼竴涓拷婧綉鍧�銆�
+ // 鑻ヤ袱澶勫悇鑷彇鍊硷紝缂撳瓨鍙兘鎭板ソ鍦ㄥ叾闂村埛鏂帮紝鍚屼竴寮犳爣绛句笂鐨勬枃瀛椾笌浜岀淮鐮佸氨浼氭寚鍚戜笉鍚屽湴鍧�銆�
+ MesTraceConfigSnapshot snapshot = currentSnapshot();
+ String traceUrl = resolveTraceUrl(snapshot);
+
LinkedHashMap<String, String> keyValues = new LinkedHashMap<>();
// ===== 鍥涜绱狅紙鍥芥爣瑕佹眰锛岄『搴忓浐瀹氾紝缂哄け鍗抽樆鏂鍑猴級=====
putRequired(keyValues, "鍝佺鍚嶇О", resolveItemName(bagCode, batch, itemMap),
PRO_QR_LABEL_ITEM_NAME_MISSING);
- putRequired(keyValues, imported ? "杩涘彛鍟嗗悕绉�" : "鐢熶骇缁忚惀鑰呭悕绉�", resolveOperatorName(),
- PRO_QR_LABEL_OPERATOR_MISSING);
+ putRequired(keyValues, isImported(snapshot) ? "杩涘彛鍟嗗悕绉�" : "鐢熶骇缁忚惀鑰呭悕绉�",
+ resolveOperatorName(snapshot), PRO_QR_LABEL_OPERATOR_MISSING);
putRequired(keyValues, "鍗曞厓璇嗗埆浠g爜", resolveUnitCode(bagCode.getCode()),
PRO_QR_LABEL_UNIT_CODE_MISSING);
- putRequired(keyValues, "杩芥函缃戝潃", resolveTraceUrl(), PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED);
+ putRequired(keyValues, "杩芥函缃戝潃", traceUrl, PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED);
// ===== 浠ヤ笅涓鸿緟鍔╃敓浜т俊鎭� =====
if (bagCode.getBagNo() != null) {
@@ -243,7 +305,7 @@
}
return QrLabelInfo.builder()
.fileName(bagCode.getCode())
- .code(buildQrCodeContent(bagCode.getCode()))
+ .code(buildQrCodeContent(bagCode.getCode(), traceUrl))
.keyValues(keyValues)
.build();
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesTraceConfigProvider.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesTraceConfigProvider.java
new file mode 100644
index 0000000..4e6000a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesTraceConfigProvider.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.mes.framework.qrcode;
+
+import cn.iocoder.yudao.module.mes.framework.config.MesTraceConfigSnapshot;
+
+/**
+ * 绉嶅瓙鏍囩閰嶇疆鏉ユ簮锛氬悜 {@link MesProQrLabelBuilder} 鎻愪緵銆屽瓧鍏� + 閰嶇疆鏂囦欢銆嶅悎骞跺悗鐨勭敓鏁堥厤缃��
+ * <p>
+ * {@code MesProQrLabelBuilder} 鏄潤鎬佸伐鍏风被锛岃浜岀淮鐮佹壒閲忓鍑洪珮棰戝惊鐜皟鐢紝鏃犳硶鐩存帴鍋氫緷璧栨敞鍏ワ紱
+ * 鍥犳鍦ㄥ惎鍔ㄦ椂鐢� {@code MesQrCodeConfig} 瑁呴厤涓�涓湰鎺ュ彛鐨勫疄鐜帮紙鎸囧悜 {@code MesTraceConfigResolver}锛夛紝
+ * 杩愯鏃剁敱鏋勫缓鍣ㄥ洖璋冨彇鍊笺�傝繖鏍锋棦淇濈暀浜嗛潤鎬佽皟鐢ㄧ偣涓嶅彉锛屽張鑳借閰嶇疆闅忓瓧鍏告敼鍔ㄧ敓鏁堛��
+ * <p>
+ * 瀹炵幇鏂�**蹇呴』淇濊瘉涓嶆姏寮傚父**锛屽惁鍒欎細涓柇鏁存壒鏍囩瀵煎嚭銆�
+ */
+@FunctionalInterface
+public interface MesTraceConfigProvider {
+
+ /**
+ * 鍙栧綋鍓嶇敓鏁堢殑閰嶇疆蹇収
+ *
+ * @return 鐢熸晥蹇収锛屼笉搴旇繑鍥� null
+ */
+ MesTraceConfigSnapshot get();
+
+}
diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceConfigResolverTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceConfigResolverTest.java
new file mode 100644
index 0000000..be601c8
--- /dev/null
+++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceConfigResolverTest.java
@@ -0,0 +1,169 @@
+package cn.iocoder.yudao.module.mes.framework.config;
+
+import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * 瀛楀吀瑙f瀽鍣ㄦ祴璇曪細瀹堜綇銆屽瓧鍏镐紭鍏堛�侀厤缃枃浠跺厹搴曘�嶇殑鍚堝苟瑙勫垯涓庛�岀粷涓嶆姏寮傚父銆嶇殑绾﹀畾銆�
+ */
+class MesTraceConfigResolverTest {
+
+ private static final String YAML_URL = "http://yaml.example.org/trace.html";
+ private static final String DICT_URL = "http://1.95.78.124:9008/prod/trace.html";
+
+ private final DictDataApi dictDataApi = mock(DictDataApi.class);
+
+ @Test
+ void dictValueWinsOverYaml() {
+ stubDict(dict("杩芥函缃戝潃", DICT_URL));
+
+ MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
+
+ assertEquals(DICT_URL, snapshot.traceUrl());
+ assertEquals(MesTraceUrlSource.DICT, snapshot.traceUrlSource());
+ }
+
+ @Test
+ void fallsBackToYamlWhenDictTypeMissing() {
+ stubDict(); // 瀛楀吀绫诲瀷灏氭湭鍒濆鍖栵紝灞炰簬姝e父鐨勩�屽皻鏈厤缃�嶈矾寰�
+
+ MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
+
+ assertEquals(YAML_URL, snapshot.traceUrl());
+ assertEquals(MesTraceUrlSource.YAML, snapshot.traceUrlSource());
+ }
+
+ @Test
+ void disabledDictItemIsTreatedAsAbsent() {
+ // 绂佺敤鏄笉鍒犳暟鎹嵆鍋滅敤鏌愰」閰嶇疆鐨勬墜娈碉紝蹇呴』鎸夈�屾湭閰嶇疆銆嶅鐞�
+ DictDataRespDTO disabled = dict("杩芥函缃戝潃", DICT_URL);
+ disabled.setStatus(CommonStatusEnum.DISABLE.getStatus());
+ stubDict(disabled);
+
+ MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
+
+ assertEquals(YAML_URL, snapshot.traceUrl());
+ assertEquals(MesTraceUrlSource.YAML, snapshot.traceUrlSource());
+ }
+
+ @Test
+ void blankDictValueFallsBackToYaml() {
+ stubDict(dict("杩芥函缃戝潃", " "));
+
+ MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
+
+ assertEquals(YAML_URL, snapshot.traceUrl());
+ assertEquals(MesTraceUrlSource.YAML, snapshot.traceUrlSource());
+ }
+
+ @Test
+ void invalidDictValueFallsBackToYamlAndFlagsSource() {
+ // 瀛楀吀閲屽~浜嗗唴缃戝湴鍧�锛氶檷绾у洖閰嶇疆鏂囦欢鑰屼笉鏄姏寮傚父锛岄伩鍏嶄竴娆℃墜璇鍏ㄥ钩鍙板鍑哄仠鎽�
+ stubDict(dict("杩芥函缃戝潃", "http://192.168.1.1/trace.html"));
+
+ MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
+
+ assertEquals(YAML_URL, snapshot.traceUrl());
+ assertEquals(MesTraceUrlSource.YAML_INVALID_DICT, snapshot.traceUrlSource());
+ }
+
+ @Test
+ void dictReadFailureFallsBackToYamlWithoutThrowing() {
+ when(dictDataApi.getDictDataList(MesTraceConfigResolver.DICT_TYPE))
+ .thenThrow(new RuntimeException("鏁版嵁搴撴姈鍔�"));
+
+ MesTraceConfigSnapshot snapshot = assertDoesNotThrow(() -> newResolver().resolveSnapshot());
+
+ assertEquals(YAML_URL, snapshot.traceUrl());
+ assertEquals(MesTraceUrlSource.YAML, snapshot.traceUrlSource());
+ }
+
+ @Test
+ void dictIsReadOncePerCacheWindow() {
+ stubDict(dict("杩芥函缃戝潃", DICT_URL));
+ MesTraceConfigResolver resolver = newResolver();
+
+ resolver.resolveSnapshot();
+ resolver.resolveSnapshot();
+ resolver.resolveSnapshot();
+
+ // 鏍囩瀵煎嚭浼氭垚鍗冧笂涓囨鍙栧揩鐓э紝缁濅笉鑳芥瘡娆¢兘鏌ュ簱
+ verify(dictDataApi, times(1)).getDictDataList(MesTraceConfigResolver.DICT_TYPE);
+ }
+
+ @Test
+ void resolvesOtherFieldsIndependentlyFromDict() {
+ stubDict(dict("杩芥函缃戝潃", DICT_URL),
+ dict("鐢熶骇缁忚惀鑰呭悕绉�", "鏂扮枂濉旈噷鏈ㄦ渤绉嶄笟鑲′唤鏈夐檺鍏徃"),
+ dict("杩涘彛鍟嗗悕绉�", "鏌愯繘鍙e晢"),
+ dict("鏄惁杩涘彛绉嶅瓙", "true"));
+
+ MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
+
+ assertEquals("鏂扮枂濉旈噷鏈ㄦ渤绉嶄笟鑲′唤鏈夐檺鍏徃", snapshot.operatorName());
+ assertEquals("鏌愯繘鍙e晢", snapshot.importerName());
+ assertTrue(snapshot.imported());
+ }
+
+ @Test
+ void fieldsFallBackIndependentlyWhenOnlyUrlIsConfigured() {
+ // 鍙厤浜嗚拷婧綉鍧�锛氬叾浣欎笁椤逛粛搴斿洖閫�閰嶇疆鏂囦欢锛岃�屼笉鏄娓呯┖
+ stubDict(dict("杩芥函缃戝潃", DICT_URL));
+
+ MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot();
+
+ assertEquals(DICT_URL, snapshot.traceUrl());
+ assertEquals("YAML 鐢熶骇缁忚惀鑰�", snapshot.operatorName());
+ assertEquals("YAML 杩涘彛鍟�", snapshot.importerName());
+ assertFalse(snapshot.imported());
+ }
+
+ @Test
+ void unrecognizedImportedValueFallsBackToYaml() {
+ stubDict(dict("鏄惁杩涘彛绉嶅瓙", "涔熻鍚�"));
+
+ assertFalse(newResolver().resolveSnapshot().imported());
+ }
+
+ @Test
+ void firstEnabledItemWinsWhenDuplicated() {
+ // 鍚屼竴鏍囩閲嶅閰嶇疆鏃朵互鎺掑簭闈犲墠鑰呬负鍑嗭紙搴曞眰鏌ヨ宸叉寜 sort 鎺掑簭锛�
+ stubDict(dict("杩芥函缃戝潃", DICT_URL), dict("杩芥函缃戝潃", "http://second.example.org/trace.html"));
+
+ assertEquals(DICT_URL, newResolver().resolveSnapshot().traceUrl());
+ }
+
+ private MesTraceConfigResolver newResolver() {
+ MesTraceConfigResolver resolver = new MesTraceConfigResolver();
+ MesProperties properties = new MesProperties();
+ properties.setTraceBaseUrl(YAML_URL);
+ properties.setOperatorName("YAML 鐢熶骇缁忚惀鑰�");
+ properties.setImporterName("YAML 杩涘彛鍟�");
+ properties.setImported(false);
+ ReflectionTestUtils.setField(resolver, "dictDataApi", dictDataApi);
+ ReflectionTestUtils.setField(resolver, "mesProperties", properties);
+ return resolver;
+ }
+
+ private void stubDict(DictDataRespDTO... data) {
+ when(dictDataApi.getDictDataList(MesTraceConfigResolver.DICT_TYPE)).thenReturn(List.of(data));
+ }
+
+ private static DictDataRespDTO dict(String label, String value) {
+ DictDataRespDTO dto = new DictDataRespDTO();
+ dto.setDictType(MesTraceConfigResolver.DICT_TYPE);
+ dto.setLabel(label);
+ dto.setValue(value);
+ dto.setStatus(CommonStatusEnum.ENABLE.getStatus());
+ return dto;
+ }
+
+}
diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceUrlValidatorTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceUrlValidatorTest.java
new file mode 100644
index 0000000..1ee88e2
--- /dev/null
+++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/config/MesTraceUrlValidatorTest.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.mes.framework.config;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * 杩芥函缃戝潃鏍¢獙鍣ㄦ祴璇曘��
+ * <p>
+ * 璇ユ牎楠屽悓鏃舵妸鍏抽厤缃枃浠朵笌瀛楀吀涓や釜鏉ユ簮锛岃嫢瑙勫垯琚斁鏉撅紝娑堣垂鑰呮壂涓嶅紑鐨勬爣绛惧氨浼氬嵃鍑哄幓涓旀棤娉曞彫鍥烇紝
+ * 鍥犳杩欓噷閫愪釜瀹堜綇姣忎釜鎷掔粷鍒嗘敮銆�
+ */
+class MesTraceUrlValidatorTest {
+
+ @Test
+ void acceptsPublicAccessibleUrls() {
+ assertValid("http://1.95.78.124:9008/prod/trace.html");
+ assertValid("https://trace.example.org/trace.html");
+ assertValid("http://42.63.70.90:9003/trace.html");
+ assertValid(" http://1.95.78.124:9008/prod/trace.html "); // 涓や晶绌烘牸搴旇蹇界暐
+ }
+
+ @Test
+ void rejectsBlankUrl() {
+ assertInvalid("");
+ assertInvalid(" ");
+ assertInvalid(null);
+ }
+
+ @Test
+ void rejectsUnreplacedPlaceholdersAndLocalhost() {
+ // 浠庣ず渚嬮厤缃洿鎺ユ嫹鏉ュ繕璁版浛鎹紝鏄爣绛惧嵃閿欏湴鍧�鏈�甯歌鐨勫師鍥�
+ assertInvalid("https://dev.your-domain.com/trace.html");
+ assertInvalid("http://example.com/trace.html");
+ assertInvalid("http://localhost:48080/trace.html");
+ assertInvalid("http://LOCALHOST/trace.html");
+ }
+
+ @Test
+ void rejectsPrivateAndLoopbackHosts() {
+ assertInvalid("http://127.0.0.1:9008/trace.html");
+ assertInvalid("http://10.0.0.5/trace.html");
+ assertInvalid("http://172.16.0.9/trace.html");
+ assertInvalid("http://172.31.255.254/trace.html");
+ assertInvalid("http://192.168.0.10:48080/trace.html");
+ assertInvalid("http://169.254.1.1/trace.html");
+ assertInvalid("http://printer.local/trace.html");
+ }
+
+ @Test
+ void acceptsHostsJustOutsidePrivateRanges() {
+ // 172.15/172.32 涓嶅湪 172.16.0.0/12 绉佺綉娈靛唴锛屼笉搴旇璇潃
+ assertValid("http://172.15.0.1/trace.html");
+ assertValid("http://172.32.0.1/trace.html");
+ assertValid("http://11.0.0.1/trace.html");
+ }
+
+ @Test
+ void rejectsUnparseableUrl() {
+ assertInvalid("杩欎笉鏄竴涓湴鍧�");
+ assertInvalid("/trace.html");
+ assertInvalid("trace.html");
+ }
+
+ @Test
+ void invalidReasonExplainsWhy() {
+ assertTrue(MesTraceUrlValidator.invalidReason("http://192.168.1.1/x").orElseThrow().contains("鍐呯綉"));
+ assertTrue(MesTraceUrlValidator.invalidReason("").orElseThrow().contains("鏈厤缃�"));
+ assertTrue(MesTraceUrlValidator.invalidReason("http://localhost/x").orElseThrow().contains("鍗犱綅绗�"));
+ }
+
+ private void assertValid(String url) {
+ assertTrue(MesTraceUrlValidator.isValid(url), "搴斿垽瀹氫负鍚堟硶锛�" + url);
+ }
+
+ private void assertInvalid(String url) {
+ assertFalse(MesTraceUrlValidator.isValid(url), "搴斿垽瀹氫负闈炴硶锛�" + url);
+ }
+
+}
diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilderTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilderTest.java
new file mode 100644
index 0000000..c165ac2
--- /dev/null
+++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilderTest.java
@@ -0,0 +1,117 @@
+package cn.iocoder.yudao.module.mes.framework.qrcode;
+
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
+import cn.iocoder.yudao.module.mes.framework.config.MesTraceConfigSnapshot;
+import cn.iocoder.yudao.module.mes.framework.config.MesTraceUrlSource;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * 鏍囩鏋勫缓鍣ㄧ殑閰嶇疆鏉ユ簮娴嬭瘯銆�
+ * <p>
+ * 鏋勫缓鍣ㄦ槸闈欐�佸伐鍏风被锛岄厤缃潵婧愰�氳繃 {@link MesTraceConfigProvider} 瑁呴厤銆傝繖閲岄噸鐐归獙璇�
+ * 銆岄厤缃潵婧愪紭鍏堛�侀厤缃枃浠跺厹搴曘�佷袱鑰呯殕鏃犲垯鎷掔粷瀵煎嚭銆嶈繖鏉¢摼璺紝浠ュ強鏍囩鍙鏂囨湰涓�
+ * 浜岀淮鐮佸唴瀹瑰彇鍒扮殑鏄悓涓�涓拷婧綉鍧�銆�
+ */
+class MesProQrLabelBuilderTest {
+
+ private static final String YAML_URL = "http://yaml.example.org/trace.html";
+ private static final String DICT_URL = "http://1.95.78.124:9008/prod/trace.html";
+
+ @AfterEach
+ void resetStaticState() {
+ // 鏋勫缓鍣ㄧ殑閰嶇疆鏄潤鎬佸瓧娈碉紝涓嶆竻骞插噣浼氫覆鍛冲埌鍏跺畠娴嬭瘯
+ MesProQrLabelBuilder.setTraceConfigProvider(null);
+ MesProQrLabelBuilder.setTraceBaseUrl("");
+ MesProQrLabelBuilder.setOperatorName("");
+ MesProQrLabelBuilder.setImporterName("");
+ MesProQrLabelBuilder.setImported(false);
+ MesProQrLabelBuilder.setAllowPlainQrCode(false);
+ }
+
+ @Test
+ void providerValueWinsOverConfiguredFile() {
+ MesProQrLabelBuilder.setTraceBaseUrl(YAML_URL);
+ MesProQrLabelBuilder.setOperatorName("鐢熶骇缁忚惀鑰�");
+ useProvider(snapshot(DICT_URL, "鐢熶骇缁忚惀鑰�", "", false));
+
+ assertEquals(DICT_URL, MesProQrLabelBuilder.resolveTraceUrl());
+ }
+
+ @Test
+ void fallsBackToConfiguredFileWhenProviderAbsent() {
+ MesProQrLabelBuilder.setTraceBaseUrl(YAML_URL);
+
+ // 鏈閰嶉厤缃潵婧愶紙鍗曟祴 / 闈� Spring 涓婁笅鏂囷級鏃跺簲鍥為��閰嶇疆鏂囦欢
+ assertEquals(YAML_URL, MesProQrLabelBuilder.resolveTraceUrl());
+ }
+
+ @Test
+ void blankProviderValueDoesNotEraseConfiguredFile() {
+ MesProQrLabelBuilder.setTraceBaseUrl(YAML_URL);
+ useProvider(snapshot("", "鐢熶骇缁忚惀鑰�", "", false));
+
+ // 閰嶇疆鏉ユ簮鍙簲銆岃ˉ鍏呫�嶏紝涓嶅簲鎶婂厹搴曞�兼姽鎴愮┖
+ assertEquals(YAML_URL, MesProQrLabelBuilder.resolveTraceUrl());
+ }
+
+ @Test
+ void bagLabelPrintsSameUrlAsItsQrCode() {
+ MesProQrLabelBuilder.setOperatorName("鐢熶骇缁忚惀鑰�");
+ useProvider(snapshot(DICT_URL, "鐢熶骇缁忚惀鑰�", "", false));
+
+ QrLabelInfo label = MesProQrLabelBuilder.buildBagCodeLabel(bagCode(), null, Map.of(), Map.of());
+
+ // 鍙鏂囨湰涓庝簩缁寸爜鍐呭蹇呴』鏄悓涓�涓綉鍧�锛屽惁鍒欐爣绛句細鑷浉鐭涚浘
+ assertEquals(DICT_URL, label.getKeyValues().get("杩芥函缃戝潃"));
+ assertEquals(DICT_URL + "?code=12345678901234567890", label.getCode());
+ }
+
+ @Test
+ void importedSceneLabelsImporterName() {
+ useProvider(snapshot(DICT_URL, "鐢熶骇缁忚惀鑰�", "鏌愯繘鍙e晢", true));
+
+ QrLabelInfo label = MesProQrLabelBuilder.buildBagCodeLabel(bagCode(), null, Map.of(), Map.of());
+
+ assertEquals("鏌愯繘鍙e晢", label.getKeyValues().get("杩涘彛鍟嗗悕绉�"));
+ assertNull(label.getKeyValues().get("鐢熶骇缁忚惀鑰呭悕绉�"));
+ }
+
+ @Test
+ void rejectsExportWhenNeitherSourceHasUrl() {
+ MesProQrLabelBuilder.setOperatorName("鐢熶骇缁忚惀鑰�");
+ useProvider(snapshot("", "鐢熶骇缁忚惀鑰�", "", false));
+
+ // 鍥涜绱犵己澶卞嵆鎷掔粷瀵煎嚭锛氳繖鏄爣绛惧悎瑙勭殑鏈�鍚庝竴閬撻椄闂紝缁濅笉鑳介潤榛橀檷绾�
+ assertThrows(ServiceException.class,
+ () -> MesProQrLabelBuilder.buildBagCodeLabel(bagCode(), null, Map.of(), Map.of()));
+ }
+
+ @Test
+ void providerCannotBypassElementValidation() {
+ // 鍗充娇閰嶇疆鏉ユ簮缁欎簡缃戝潃锛岀敓浜х粡钀ヨ�呭悕绉扮己澶变粛椤绘嫆缁濆鍑�
+ useProvider(snapshot(DICT_URL, "", "", false));
+
+ assertThrows(ServiceException.class,
+ () -> MesProQrLabelBuilder.buildBagCodeLabel(bagCode(), null, Map.of(), Map.of()));
+ }
+
+ private static void useProvider(MesTraceConfigSnapshot snapshot) {
+ MesProQrLabelBuilder.setTraceConfigProvider(() -> snapshot);
+ }
+
+ private static MesTraceConfigSnapshot snapshot(String traceUrl, String operatorName,
+ String importerName, boolean imported) {
+ return new MesTraceConfigSnapshot(traceUrl, MesTraceUrlSource.DICT, operatorName, importerName, imported);
+ }
+
+ private static MesProBagCodeDO bagCode() {
+ return new MesProBagCodeDO().setCode("12345678901234567890").setItemName("鏂伴檰鏃�78鍙�");
+ }
+
+}
diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml
index 60212d2..3f8c1ca 100644
--- a/yudao-server/src/main/resources/application.yaml
+++ b/yudao-server/src/main/resources/application.yaml
@@ -1,16 +1,19 @@
# 绉嶅瓙鏍囩涓� H5 婧簮椤甸厤缃紙鍚� profile 鍙鐩栵級
-# 娉ㄦ剰锛歵race-base-url 涓� operator-name 鏄簩缁寸爜鏍囩鍥涜绱犱腑鐨勩�岃拷婧綉鍧�銆嶃�岀敓浜х粡钀ヨ�呭悕绉般�嶏紝
-# 闈� local 鐜涓� trace-base-url 鑻ヤ负鍐呯綉/鍗犱綅绗﹀湴鍧�灏嗘嫆缁濆惎鍔紝閬垮厤鍗板嚭娑堣垂鑰呮壂涓嶅紑鐨勬爣绛俱��
+# 娉ㄦ剰锛歵race-base-url銆乷perator-name銆乮mporter-name銆乮mported 鏄簩缁寸爜鏍囩鍥涜绱犵浉鍏崇殑閰嶇疆銆�
+# 杩愯鏃惰繖鍑犻」**浼樺厛鍙栧瓧鍏� mes_trace_config**锛堢鐞嗗悗鍙般�屽瓧鍏哥鐞嗐�嶅彲鏀癸紝鏀瑰畬鏃犻渶閲嶅惎锛夛紝
+# 姝ゅ閰嶇疆閫�灞呬负鍏滃簳榛樿鍊硷細瀛楀吀鏈厤缃紙鎴栬绂佺敤銆佹垨濉簡闈炴硶鍦板潃锛夋椂鎵嶇敓鏁堛��
+# 闈� local 鐜涓� trace-base-url 鑻ヤ负鍐呯綉/鍗犱綅绗﹀湴鍧�浠嶅皢鎷掔粷鍚姩鈥斺�旇繖閬撻槻绾垮繀椤诲湪鏁版嵁搴撳彲鐢ㄥ墠鐢熸晥銆�
mes:
- # 杩芥函缃戝潃锛堜簩缁寸爜鍐呭鍓嶇紑锛屽舰濡� http://鍩熷悕:绔彛/trace.html锛夛紝闇�涓哄叕浼楁壂鐮佸彲璁块棶鐨勫湴鍧�
+ # 杩芥函缃戝潃鍏滃簳鍊硷紙浜岀淮鐮佸唴瀹瑰墠缂�锛屽舰濡� http://鍩熷悕:绔彛/trace.html锛夛紝闇�涓哄叕浼楁壂鐮佸彲璁块棶鐨勫湴鍧�
trace-base-url: http://localhost:48080/trace.html
- # 鐢熶骇缁忚惀鑰呭悕绉帮紙鏍囩鍥涜绱犱箣涓�锛屾爣绛句笌婧簮椤电粺涓�鍙栨鍊硷紱鏈厤缃垯鏃犳硶瀵煎嚭鏍囩锛�
+ # 鐢熶骇缁忚惀鑰呭悕绉板厹搴曞�硷紙鏍囩鍥涜绱犱箣涓�锛屾爣绛句笌婧簮椤电粺涓�鍙栨鍊硷紱鏈厤缃垯鏃犳硶瀵煎嚭鏍囩锛�
operator-name: ''
- # 杩涘彛鍟嗗悕绉帮紙杩涘彛绉嶅瓙鍦烘櫙濉啓锛沬mported=true 鏃舵爣绛句笌婧簮椤靛睍绀鸿繘鍙e晢鍚嶇О锛�
+ # 杩涘彛鍟嗗悕绉板厹搴曞�硷紙杩涘彛绉嶅瓙鍦烘櫙濉啓锛沬mported=true 鏃舵爣绛句笌婧簮椤靛睍绀鸿繘鍙e晢鍚嶇О锛�
importer-name: ''
- # 鏄惁杩涘彛绉嶅瓙鍦烘櫙
+ # 鏄惁杩涘彛绉嶅瓙鍦烘櫙鍏滃簳鍊�
imported: false
- # 鏄惁鍏佽浜岀淮鐮佺紪鐮佺函鐮佸�硷紙涓嶅甫婧簮 URL锛夛紝浠呬緵鏈湴鍏煎鍘嗗彶鐮侊紝鐢熶骇鐜蹇呴』涓� false
+ # 鏄惁鍏佽浜岀淮鐮佺紪鐮佺函鐮佸�硷紙涓嶅甫婧簮 URL锛夛紝浠呬緵鏈湴鍏煎鍘嗗彶鐮侊紝鐢熶骇鐜蹇呴』涓� false銆�
+ # 璇ラ」鏈夋剰涓嶇撼鍏ュ瓧鍏革細瀹冩槸缁曡繃銆屾爣绛惧繀椤诲惈杩芥函缃戝潃銆嶇殑寮�鍏筹紝涓嶅簲寮�鏀剧粰鍚庡彴闅忔剰缂栬緫銆�
allow-plain-qr-code: false
# 浜岀淮鐮佹壒閲忓鍑猴細灏忔壒閲忓悓姝ョ鍑猴紝澶ф壒閲忚浆鍚庡彴浠诲姟锛堟棤鏁伴噺涓婇檺锛�
--
Gitblit v1.9.3