feat(mes): 种子标签追溯配置改为字典驱动
追溯网址、生产经营者名称、进口商名称、是否进口种子四项配置原先只能改配置文件并重启服务,
而这几项恰是最常变动的运营信息。现改为优先取字典 mes_trace_config,管理员在「字典管理」
中改完约 1 分钟生效,无需改配置重启。配置文件保留为兜底默认值。
取值规则(四项各自独立判定):
- 字典有启用项且值非空且合法 → 用字典值
- 字典未建 / 无启用项 / 值为空 → 回退配置文件,属正常的「尚未配置」路径
- 追溯网址的字典值非空但不合法(占位符 / 本机 / 内网地址)→ 记 ERROR 并回退配置文件。
配置文件的值已被启动校验保证合法,回退不会印出不合规标签;反之若在此抛异常,
字典里一次手误就会让全平台标签导出停摆,代价更大
- 字典与配置文件都拿不到追溯网址 → 抛 PRO_QR_LABEL_TRACE_URL_NOT_CONFIGURED 拒绝导出
配套改动:
- 新增 MesTraceConfigResolver:读字典并合并配置文件兜底值,60 秒本地缓存
(数据库查询每 60 秒至多一次,与标签数量无关),任何路径都不向外抛异常——解析发生在
批量导出的高频循环里,一次数据库抖动不应中断整批导出。不用 DictFrameworkUtils 的原因:
它把字典投影成 List<String> 会丢弃 status 无法过滤禁用项,且其 dictDataApi 为自动配置
注入的静态字段,与本类首次调用无顺序保证
- 新增 MesTraceUrlValidator:配置文件与字典共用同一套合规校验,避免出现「配置文件拦得住、
字典绕得过」的缺口
- MesProQrLabelBuilder 增加配置来源 provider 钩子,静态调用点全部不变;袋码标签只取一次
快照,保证标签可读文本与二维码内容取到同一个网址(否则缓存刷新会让两者指向不同地址)
- MesTraceConfigController 改取生效快照:原先直读 MesProperties 绕过了标签构建器,
字典生效后管理端预览会与实际打印的标签不一致;响应新增 traceUrlSource 便于确认是否生效
- mes.allow-plain-qr-code 有意不纳入字典:它是「允许印出不含追溯网址的标签」的开关,
放进可后台编辑的字典等于给合规防线开后门
- 启动校验仍只针对配置文件,以保证数据库可用之前即可拦截不合规配置,且 @PostConstruct
阶段不访问数据库
另:新增字典初始化脚本 sql/mysql/mes_trace_config_init.sql(键值留空,上线零行为变化)、
前端联调方案 docs/mes_trace_config_dict_integration.md,补 25 个单元测试。
前端无需改动,管理端与 H5 溯源页均只消费接口下发值。
Co-Authored-By: Claude <noreply@anthropic.com>
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # 溯æºé
ç½®æ¹ä¸ºåå
¸é©±å¨ - å端èè°æ¹æ¡ |
| | | |
| | | ## èæ¯ |
| | | |
| | | ç§åäºç»´ç æ ç¾ç**追溯ç½å**ã**ç产ç»è¥è
åç§°**æ¤ååªè½æ¹é
ç½®æä»¶åé坿å¡ãæ¬æ¬¡æ¹ä¸ºå¯å¨ãç³»ç»ç®¡ç â åå
¸ç®¡çãçåå
¸ `mes_trace_config` ä¸ç´æ¥ä¿®æ¹ï¼æ¹å®çº¦ 1 åéå
çæã |
| | | |
| | | **æ¥å£è·¯å¾ä¸è¯·æ±åæ°åæ åå**ï¼ä»
`/mes/trace-config/qr-config` çååºæ°å¢ä¸ä¸ªå段 `traceUrlSource`ãå端**䏿¹é ä¹è½æ£å¸¸è·**ï¼æ°å段å¯å¿½ç¥ï¼ï¼æéæ¥å
¥å³å¯ã |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - çäº§æ¹æ¬¡ç®¡ç - æ¹æ¬¡ãæ¥çäºç»´ç ãå¼¹çª |
| | | - è¢ç 管çï¼èµç 管çï¼ - è¢ç ãæ¥çäºç»´ç ãå¼¹çª |
| | | - æç管ç - æçãæ¥çäºç»´ç ãå¼¹çª |
| | | - H5 æ«ç 溯æºé¡µï¼æ¶è´¹è
æ«ç åæå¼ç页é¢ï¼ |
| | | - ç³»ç»ç®¡ç â åå
¸ç®¡çï¼è¿ç»´/ä¸å¡ä¿®æ¹é
ç½®çå
¥å£ï¼ |
| | | |
| | | ## 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": "" |
| | | } |
| | | ``` |
| | | |
| | | **å
ç»å½æ¥å£ï¼H5 溯æºé¡µä½¿ç¨ï¼ååºæ ååï¼** |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | GET | /public/mes/trace/config | 溯æºé¡µé
ç½®ï¼ç产ç»è¥è
åç§°ã追溯ç½åï¼ | |
| | | | GET | /public/mes/trace/query | æ ¹æ®è¿½æº¯ç æ¥è¯¢å
¨ç¨æº¯æºä¿¡æ¯ | |
| | | |
| | | > è¿ä¸¤ä¸ªæ¥å£è¿åç `traceUrl` / `operatorName` å·²èªå¨è·éåå
¸ï¼**å端æ é任使¹å¨**ã |
| | | |
| | | ## æ°æ®å¸¦å
¥è¯´æ |
| | | |
| | | 1. åå
¸ç®¡çï¼è¿ç»´ä¿®æ¹ã追溯ç½åãï¼â å端ç¼å â 管çç«¯ãæ¥çäºç»´ç ãæ¥å£ï¼ä¿®æ¹å约 1 åéå
ï¼`traceBaseUrl` èªå¨å为æ°å¼ï¼`traceUrlSource` å为 `DICT`ãå端æ éæç¥ï¼æ¯æ¬¡æå¼å¼¹çªæä¸æ¬¡å³å¯ã |
| | | 2. ãæ¥çäºç»´ç ãæ¥å£ â äºç»´ç å¼¹çªï¼`traceBaseUrl` æ¼æ¥è¿½æº¯ç çæäºç»´ç å
容ï¼ä¸åç«¯å¯¼åºæå°çæ ç¾**å®å
¨ä¸è´**ï¼åæºåå¼ï¼ã |
| | | 3. H5 溯æºé¡µé
ç½®æ¥å£ â æº¯æºé¡µã追溯ç½åãå±ç¤ºï¼ä¸æ ç¾å°å·å
å®¹åæºï¼æ«ç å页é¢å±ç¤ºç追溯ç½å䏿 ç¾ä¸å°çä¸è´ã |
| | | |
| | | ## åæ®µå±ç¤ºè§å |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | traceBaseUrl | äºç»´ç å¼¹çªï¼äºç»´ç å
容åç¼ï¼ | å¼ä¸ºç©ºæ¶äºç»´ç åé为纯ç å¼ï¼ä¸æ¥é | |
| | | | traceUrlSource | äºç»´ç å¼¹çªï¼å¯éï¼å»ºè®®ä»
å¼å¸¸æ¶æç¤ºï¼ | ä»
å½å¼ä¸º `YAML_INVALID_DICT` æ¶å»ºè®®ç»åºåè¦ææ¡ | |
| | | | traceUrl | H5 溯æºé¡µã追溯ç½åã | ä½ä¸ºé¾æ¥å±ç¤ºï¼å¯ç¹å»è·³è½¬ | |
| | | | operatorName | H5 溯æºé¡µãç产ç»è¥è
åç§°ã | è¿å£ç§ååºæ¯å±ç¤ºçæ¯è¿å£ååç§° | |
| | | |
| | | ## ä¸å¡è§å说æ |
| | | |
| | | | åºæ¯ | è§å | |
| | | |------|------| |
| | | | åå
¸éé
ç½®äºåæ³ç追溯ç½å | æ ç¾ãäºç»´ç ãH5 页é¢ã管ç端é¢è§å
¨é¨ä½¿ç¨åå
¸å¼ | |
| | | | åå
¸é¡¹ä¸ºç©º / 被ç¦ç¨ / åå
¸ç±»åæªå建 | åé使ç¨é
ç½®æä»¶ `mes.trace-base-url` çå¼ï¼`traceUrlSource` 为 `YAML` | |
| | | | åå
¸éçå°åéæ³ï¼å ä½ç¬¦ãlocalhostãå
ç½ IPï¼ | å端记 ERROR æ¥å¿å¹¶åéé
ç½®æä»¶å¼ï¼`traceUrlSource` 为 `YAML_INVALID_DICT`ï¼**æ ç¾å¯¼åºä¸ä¸æ** | |
| | | | åå
¸ä¸é
ç½®æä»¶é½æ¿ä¸å°è¿½æº¯ç½å | å¯¼åºæ ç¾æ¶æ¥éãæªé
置追溯ç½åï¼æ æ³çæäºç»´ç æ ç¾ãï¼æç»å¯¼åº | |
| | | | åå
¸ãåå
¸æ ç¾ãæ¹å | ä¼å¯¼è´è¯¥é¡¹é
置失æï¼ä»£ç ææ ç¾åå¹é
ï¼ã请æéè¿ç»´åªæ¹ãåå
¸é®å¼ãï¼ä¸è¦æ¹ãåå
¸æ ç¾ã | |
| | | | ä¿®æ¹åå
¸åççææ¶é´ | 约 1 åéï¼å端 60 ç§æ¬å°ç¼åï¼ãè¥ä¸ºç¨çè°ç¨åºæ¯ï¼å¯è½éå触å䏿¬¡æ¥è¯¢æå¯¼åºæåæ¢ | |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | 1. **æ¬æ¬¡å端é¶å¿
æ¹é¡¹**ï¼`traceUrlSource` æ¯å¯éçå¢å¼ºå段ï¼ä¸æ¥å
¥ä¸å½±åä»»ä½åè½ã |
| | | 2. 追溯ç½åæ¯ç§åæ ç¾å½æ åè¦ç´ ä¹ä¸ï¼ä¸æ¦å°åºæ æ³å¬åãå端**ä¸è¦**å¨ä»»ä½å°æ¹å¯¹è¿½æº¯ç½ååå
åºç¡¬ç¼ç ï¼ä¸å¾ä»¥æ¥å£è¿åå¼ä¸ºåã |
| | | 3. äºç»´ç å
容åç¼å¿
é¡»ä¸å端ä¸è´ï¼åç«¯æ¼æ¥æ¹å¼ä¿æ `traceBaseUrl + "?code=" + ç¼ç åçç å¼` ä¸åï¼`traceBaseUrl` 为空æ¶åé为纯ç å¼ã |
| | | 4. æ«ç æªæ«å°æå°æ ç¾ç宿´ H5 龿¥æ¶ï¼å端ä¼èªå¨ä»é¾æ¥ä¸æå `code` åæ°ï¼åç«¯ç´æ¥åæ ·æäº¤å³å¯ã |
| | | 5. `allow-plain-qr-code`ï¼å
许äºç»´ç ç¼ç 纯ç å¼ï¼**æææªçº³å
¥åå
¸**ï¼ä»ç±é
ç½®æä»¶æ§å¶ï¼ä¸å¨å端æ´é²å
¥å£ã |
| | | 6. åå
¸ç®¡çä¸ãåå
¸æ ç¾ãï¼å¦ã追溯ç½åãï¼æ¯ä»£ç åºå®æ è¯ï¼ä¸å¯æ¹åï¼å¯ç¼è¾çæ¯ãåå
¸é®å¼ãã |
| | |
| | | |
| | | ## é
置项 |
| | | |
| | | å端é
置项ï¼`application-*.yaml` ç `mes` åç¼ï¼ï¼ |
| | | ### åå
¸é
ç½®ï¼ä¼å
ï¼å¯å¨ç®¡çåå°ä¿®æ¹ï¼ |
| | | |
| | | 追溯ç½åçå项é
ç½®ç**è¿è¡æ¶åå¼ä¼å
æ¥èªåå
¸** `mes_trace_config`ï¼å¨ãç³»ç»ç®¡ç â åå
¸ç®¡çãä¸ä¿®æ¹ï¼æ¹å®çº¦ 1 åéçæï¼æ éé坿å¡ãåå
¸é¡¹ãåå
¸æ ç¾ã为代ç åºå®æ è¯ä¸å¯æ¹åï¼å¯ç¼è¾çæ¯ãåå
¸é®å¼ãï¼é®å¼ç空å³åéä¸é¢çé
ç½®æä»¶ã |
| | | |
| | | | åå
¸æ ç¾ | 说æ | |
| | | |----------|------| |
| | | | 追溯ç½å | äºç»´ç å
容åç¼ + æ ç¾/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` çè·¯å¾ã |
| | | - å·²ä½åºç çå¤å®è¦çä¸ç§å
¥å£ï¼è¢ç ï¼ç¶æ 30ï¼ãæçç ï¼ç¶æ 20ï¼ãæ¹æ¬¡ç ï¼ç¶æ 40ï¼ï¼ä¸è
çä½åºæä¸¾å¼ä¸åï¼å端ä¸è¦å
±ç¨å¸¸é夿ã |
| | | - **åéæ ç¾é鿰坼åº**ï¼æ¹é å导åºçäºç»´ç å
容已åºåï¼å
¶ä¸è¿½æº¯ç½åå¯è½ä¸ºå
ç½å°å䏿 ç¾ç¼ºå°åè¦ç´ ï¼é鿰坼åºå°å·ã |
| | | - **è¢åºå·æ¹ä¸ºåååé
**ï¼åå
ç±ãæ¥åºå
æå¤§å¼å +1ãå¾å°ï¼å¹¶åçæä¼äº§çéå¤è¢åºå·ï¼è¢åºå·ä¼å°å¨æ ç¾ä¸ï¼ï¼ç°å·²æ¹ä¸º Redis ååèªå¢åé
ï¼å¹¶ç¨åºå
æå¤§å¼æ ¡æ£åºåãåºå·**åªä¿è¯ä¸éå¤ãä¸åéï¼ä¸ä¿è¯è¿ç»**ï¼å¹¶åä¸ä¸åè¯·æ±æ¿å°ä¸ååºæ®µï¼å±æ£å¸¸ï¼ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================ |
| | | -- MES ç§åæ ç¾è¿½æº¯é
ç½®åå
¸åå§åèæ¬ |
| | | -- ============================================ |
| | | -- ç®çï¼æç§åäºç»´ç æ ç¾çã追溯ç½åããç产ç»è¥è
åç§°ãçé
ç½®ä»é
ç½®æä»¶æ¬å°åå
¸ï¼ |
| | | -- 管çåå¯å¨ãç³»ç»ç®¡ç â åå
¸ç®¡çãä¸ç´æ¥ä¿®æ¹ï¼æ¹å®çº¦ 1 åéå
çæï¼æ éæ¹é
ç½®é坿å¡ã |
| | | -- |
| | | -- åå¼è§åï¼ä»£ç è§ MesTraceConfigResolverï¼ï¼ |
| | | -- 1. åå
¸é¡¹ãå¯ç¨ãä¸ãåå
¸é®å¼ãé空 â ç¨åå
¸å¼ï¼ |
| | | -- 2. åå
¸é¡¹ä¸åå¨ / 被ç¦ç¨ / é®å¼ä¸ºç©º â åéé
ç½®æä»¶ mes.trace-base-urlãmes.operator-name çï¼ |
| | | -- 3. ã追溯ç½åãçåå
¸å¼é空ä½ä¸åæ³ï¼å ä½ç¬¦ãæ¬æºãå
ç½å°åï¼â è®° ERROR æ¥å¿å¹¶åéé
ç½®æä»¶ã |
| | | -- |
| | | -- éè¦ï¼æ¬èæ¬æææãåå
¸é®å¼ãå
¨é¨**ç空**ï¼è¿æ ·åç¯å¢æ§è¡åè¡ä¸ºä¸æ¹é åå®å
¨ä¸è´ï¼ä»èµ°é
ç½®æä»¶ï¼ï¼ |
| | | -- å±äºé¶é£é©ä¸çº¿ãé¨ç½²å®æåç±è¿ç»´å¨åå
¸ç®¡ç䏿ç¯å¢å¡«åå®é
å¼å³å¯æ¥ç®¡ã |
| | | -- |
| | | -- 注æï¼ãåå
¸æ ç¾ãæ¯ä»£ç éçåºå®æ è¯ï¼ä¸å¯æ¹åï¼å¯ç¼è¾çæ¯ãåå
¸é®å¼ãã |
| | | -- |
| | | -- 说æï¼ä¸ä½¿ç¨å¤ç§æ·ï¼èæ¬ä¸å« 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. åå
¸æ°æ® |
| | | -- ãåå
¸é®å¼ãç空å³åéé
ç½®æä»¶ï¼è¯·æç¯å¢å¨åå
¸ç®¡çä¸å¡«åå®é
å¼ã |
| | | -- ãåå
¸æ ç¾ã为代ç åºå®æ è¯ï¼è¯·å¿æ¹åã |
| | | -- ---------------------------- |
| | | 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. åç¯å¢å¡«å示ä¾ï¼æ£å¼æ§è¡æ¶è¯·å¨ç®¡çåå°åå
¸ç®¡çä¸ä¿®æ¹ï¼å¿ç´æ¥å¥ç¨ï¼ |
| | | -- 追溯ç½å : http://1.95.78.124:9008/prod/trace.html |
| | | -- ç产ç»è¥è
åç§° : æ°çå¡éæ¨æ²³ç§ä¸è¡ä»½æéå
¬å¸ |
| | | -- è¿å£ååç§° : ä»
è¿å£ç§ååºæ¯å¡«åï¼å¦åç空 |
| | | -- æ¯å¦è¿å£ç§å : true / false |
| | | -- ---------------------------- |
| | |
| | | |
| | | 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; |
| | |
| | | * 管çåå° - MES 溯æºé
ç½® |
| | | * <p> |
| | | * å管ç端å端æ´é²æº¯æº H5 页é¢åºç¡å°åï¼ç¨äº"æ¥çäºç»´ç "é¢è§/ä¸è½½ä¸æå°æ ç¾ä¿æä¸è´çäºç»´ç å
容ã |
| | | * å¼åèª {@link MesTraceConfigResolver} ç**çæå¿«ç
§**èéé
ç½®æä»¶ââ追溯ç½åå¯å¨åå
¸ |
| | | * {@code mes_trace_config} ä¸ä¿®æ¹ï¼è¥æ¤å¤ç´è¯»é
ç½®æä»¶ï¼ç®¡ç端é¢è§çäºç»´ç å°±ä¼ä¸å®é
æå°çæ ç¾ä¸ä¸è´ã |
| | | */ |
| | | @Tag(name = "管çåå° - MES 溯æºé
ç½®") |
| | | @RestController |
| | |
| | | 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); |
| | | } |
| | | |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.trace.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | |
| | | |
| | | /** |
| | | * æº¯æº 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 = "追溯ç½åçææ¥æºï¼DICT / YAML / YAML_INVALID_DICT", |
| | | example = "DICT") |
| | | private String traceUrlSource; |
| | | |
| | | } |
| | |
| | | /** |
| | | * H5 溯æºé¡µé¢å°åï¼trace.htmlï¼åºç¡ URLï¼ç¨äºçææºå¸¦è¿½æº¯ç åæ°çäºç»´ç å
容ã |
| | | * <p> |
| | | * 该å¼ä¸ºã追溯ç½åãçå¯ä¸ç弿ºï¼äºç»´ç å
å®¹ä¸ H5 页é¢å±ç¤ºç追溯ç½åååèªæ¤é
ç½®ã |
| | | * å¿
须为å
¬ä¼æ«ç å¯è®¿é®çå°åï¼éæ¬å°ç¯å¢é
置为å
ç½/å ä½ç¬¦å°åæ¶å¯å¨å°ç´æ¥å¤±è´¥ã |
| | | * è¿æ¯ã追溯ç½åãç**å
åºé»è®¤å¼**ï¼è¿è¡æ¶ä¼å
ååå
¸ {@link MesTraceConfigResolver#DICT_TYPE} |
| | | * ä¸ã追溯ç½åãçé
ç½®å¼ï¼å¯å¨ç®¡çåå°ãåå
¸ç®¡çãä¸ä¿®æ¹èæ ééå¯ï¼åå
¸æªé
ç½®ï¼æè¢«ç¦ç¨ã |
| | | * æå¡«äºéæ³å°åï¼æ¶æåéå°æ¤é
ç½®ã |
| | | * <p> |
| | | * æ¬é¡¹ä»ä¸ºå¿
å¡«ä¸åä¸å¯å¨æ ¡éªï¼éæ¬å°ç¯å¢é
置为å
ç½/å ä½ç¬¦å°åæ¶å¯å¨å°ç´æ¥å¤±è´¥ã |
| | | * è¿éé²çº¿å¿
须卿°æ®åºå¯ç¨ä¹åå°±è½æ¦ä½ä¸åè§é
ç½®ï¼å æ¤ä¸è½åªä¾èµåå
¸ã |
| | | */ |
| | | private String traceBaseUrl = ""; |
| | | |
| | | /** |
| | | * ç产ç»è¥è
åç§°ï¼ç§åæ ç¾åè¦ç´ ä¹ä¸ï¼ã |
| | | * <p> |
| | | * ä½ä¸ºãç产ç»è¥è
åç§°ãçå¯ä¸ç弿ºï¼æ ç¾å°å·ä¸ H5 溯æºé¡µå忤å¼ï¼ç¦æ¢å¨å端硬ç¼ç ã |
| | | * åæ ·æ¯**å
åºé»è®¤å¼**ï¼è¿è¡æ¶ä¼å
ååå
¸ {@link MesTraceConfigResolver#DICT_TYPE} ä¸ |
| | | * ãç产ç»è¥è
åç§°ãçé
ç½®å¼ãæ ç¾å°å·ä¸ H5 溯æºé¡µå忤å¼ï¼ç¦æ¢å¨å端硬ç¼ç ã |
| | | */ |
| | | private String operatorName = ""; |
| | | |
| | |
| | | * è¿å£ååç§°ï¼è¿å£ç§ååºæ¯ï¼æ ç¾åè¦ç´ ä¹ä¸ï¼ã |
| | | * <p> |
| | | * è¿å£ç§ååºæ 注è¿å£ååç§°ï¼ä¸ºç©ºæ¶åéä½¿ç¨ {@link #operatorName}ã |
| | | * è¿è¡æ¶ä¼å
ååå
¸ä¸ãè¿å£ååç§°ãçé
ç½®å¼ã |
| | | */ |
| | | private String importerName = ""; |
| | | |
| | | /** |
| | | * æ¯å¦å¤äºè¿å£ç§ååºæ¯ï¼ä¸º true æ¶æ ç¾ä¸æº¯æºé¡µå±ç¤ºãè¿å£ååç§°ãèéãç产ç»è¥è
åç§°ã |
| | | * <p> |
| | | * è¿è¡æ¶ä¼å
ååå
¸ä¸ãæ¯å¦è¿å£ç§åãçé
ç½®å¼ã |
| | | */ |
| | | private boolean imported = false; |
| | | |
| | |
| | | * å
许äºç»´ç ç¼ç 纯ç å¼ï¼ä¸å¸¦æº¯æº URLï¼ã |
| | | * <p> |
| | | * ä»
便¬å°/æµè¯ç¯å¢å
¼å®¹åå²ç 使ç¨ï¼ç产ç¯å¢å¿
须为 falseï¼å¦åä¼å°åºä¸å«è¿½æº¯ç½åçæ ç¾ã |
| | | * <p> |
| | | * æ¬é¡¹**ææä¸çº³å
¥åå
¸**ï¼å®æ¯ãå
许å°åºä¸å«è¿½æº¯ç½åçæ ç¾ãçå¼å
³ï¼æ¾è¿å¯åå°ç¼è¾çåå
¸ï¼ |
| | | * çäºç»ç§åæ ç¾çåè§é²çº¿å¼äºä¸ä¸ªå¯è¢«è¯¯æä½çåé¨ãå æ¤åªä¿çå¨é
ç½®æä»¶ä¸ï¼ç±å¼å/è¿ç»´ææ§ã |
| | | */ |
| | | private boolean allowPlainQrCode = false; |
| | | |
| | |
| | | import org.springframework.context.annotation.Configuration; |
| | | import org.springframework.core.env.Environment; |
| | | |
| | | import java.net.URI; |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | |
| | | * <li>ç产ç»è¥è
åç§°å¿
å¡«ï¼æ ç¾åè¦ç´ ä¹ä¸ï¼ç¼ºå¤±å³ä¸ºä¸åè§æ ç¾ï¼ï¼</li> |
| | | * <li>å½åç¯å¢å¿
é¡»åå¨å¯æ¸²æä¸æçåä½ï¼å¦åæ ç¾ä¸æä¼åææ¹æ¡ï¼ã</li> |
| | | * </ul> |
| | | * 追溯ç½åä¸ç产ç»è¥è
åç§°ç**è¿è¡æ¶**åå¼ä¼å
æ¥èªåå
¸ {@code mes_trace_config}ï¼ |
| | | * å¯å¨ç®¡çåå°ãåå
¸ç®¡çãä¸ä¿®æ¹èæ ééå¯ï¼é
ç½®æä»¶çå¼éå±
为å
åºé»è®¤å¼ã |
| | | * <p> |
| | | * ä½å¯å¨æ ¡éª**åªé对é
ç½®æä»¶**ï¼åå æäºï¼ä¸æ¯è¿éé²çº¿å¿
须卿°æ®åºå¯ç¨ä¹åå°±è½æ¦ä½ä¸åè§é
ç½®ï¼ |
| | | * äºæ¯ @PostConstruct é¶æ®µä¸åºè®¿é®æ°æ®åºãåå
¸å¼çåè§æ ¡éªç± {@link MesTraceConfigResolver} |
| | | * å¨è¯»åæ¶å®æï¼éæ³åè®° ERROR å¹¶åéé
ç½®æä»¶ï¼è§è¯¥ç±»æ³¨éï¼ã |
| | | */ |
| | | @Slf4j |
| | | @Configuration(proxyBeanMethods = false) |
| | |
| | | @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()); |
| | |
| | | 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] äºç»´ç æ ç¾é
ç½®æ ¡éªéè¿ï¼traceBaseUrl={}ï¼ç产ç»è¥è
={}", |
| | | mesProperties.getTraceBaseUrl(), MesProQrLabelBuilder.resolveOperatorName()); |
| | | |
| | | // 3. æåè£
é
åå
¸æ¥æºï¼æ¤å resolveTraceUrl()/resolveOperatorName() ä¼å
èµ°åå
¸ |
| | | // ï¼åå
¸æªé
ç½®åèªå¨åéé
ç½®æä»¶ï¼ãæ¤å¤åªè£
é
æ¹æ³å¼ç¨ãç»ä¸è°ç¨ââ |
| | | // è§£æåå
¸éè¦è®¿é®æ°æ®åºï¼ä¸è½å¨ @PostConstruct é¶æ®µè§¦åã |
| | | MesProQrLabelBuilder.setTraceConfigProvider(mesTraceConfigResolver::resolveSnapshot); |
| | | } |
| | | |
| | | private boolean isLocalProfile() { |
| | |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éªè¿½æº¯ç½åï¼å¿
须为å
¬ä¼æ«ç å¯è®¿é®çå°å |
| | | * æ ¡éªé
ç½®æä»¶ä¸ç追溯ç½åï¼å¿
须为å
¬ä¼æ«ç å¯è®¿é®çå°å |
| | | * <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 + " æªé
ç½®æ¶çå
åºå¼ï¼"); |
| | | }); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | 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 è§£æï¼ä¸å¨æ¤å¤æ¦æª |
| | | } |
| | | 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); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | |
| | | /** |
| | | * ç§åæ ç¾é
置解æå¨ï¼æãåå
¸é
ç½®å¼ + é
ç½®æä»¶å
åºå¼ãå并为ä¸ä»½çæå¿«ç
§ã |
| | | * <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 = "æ¯å¦è¿å£ç§å"; |
| | | |
| | | /** |
| | | * åå
¸é
ç½®ç¼åï¼dictType â (åå
¸æ ç¾ â åå
¸é®å¼)ï¼ä»
å«å¯ç¨é¡¹ã |
| | | * <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; |
| | | |
| | | /** |
| | | * è§£æå½åçæçç§åæ ç¾é
置快ç
§ |
| | | * |
| | | * @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(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è§£æãæ¯å¦è¿å£ç§åãï¼åå
¸é¡¹ä¸ºç©ºæå弿 æ³è¯å«æ¶åéé
ç½®æä»¶ |
| | | */ |
| | | 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); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.framework.config; |
| | | |
| | | /** |
| | | * ç§åæ ç¾é
ç½®ççæå¿«ç
§ï¼åå
¸å¼ä¸é
ç½®æä»¶å
åºå¼åå¹¶åçç»æã |
| | | * <p> |
| | | * ãåå
¸ä¼å
ãé
ç½®æä»¶å
åºãçåå¹¶è§åå·²å¨ {@link MesTraceConfigResolver} å
å®æï¼ |
| | | * æ¬å¿«ç
§åªæ¿è½½**æç»çæå¼**ï¼æ¶è´¹æ¹æ éåå
³å¿æ¥æºãå项é
ç½®å¨å¿«ç
§ä¸åèªç¬ç«å¤å®ââ |
| | | * ä¾å¦è¿½æº¯ç½ååèªåå
¸ãèç产ç»è¥è
åç§°åå
¸æªé
ç½®æ¶ä»å¯åéé
ç½®æä»¶ã |
| | | * <p> |
| | | * æ¯æ¬¡åå¿«ç
§ä»£è¡¨å䏿¶å»çä¸è´è§å¾ï¼æ ç¾æå»ºæ¶åº**å䏿¬¡å¿«ç
§ãå
¨ç¨å¤ç¨**ï¼ |
| | | * é¿å
ç¼åæ°å¥½å¨ä¸¤æ¬¡è¯»åä¹é´å·æ°ï¼å¯¼è´åä¸å¼ æ ç¾ä¸æåä¸äºç»´ç å
容ä¸ä¸è´ã |
| | | * |
| | | * @param traceUrl 追溯ç½åï¼äºç»´ç å
容åç¼ï¼ä¸å« code åæ°ï¼ï¼å¯è½ä¸ºç©º |
| | | * @param traceUrlSource 追溯ç½åçå®é
çææ¥æº |
| | | * @param operatorName ç产ç»è¥è
åç§°ï¼å¯è½ä¸ºç©º |
| | | * @param importerName è¿å£ååç§°ï¼å¯è½ä¸ºç©º |
| | | * @param imported æ¯å¦è¿å£ç§ååºæ¯ï¼ä¸º true æ¶æ ç¾ä¸æº¯æºé¡µå±ç¤ºãè¿å£ååç§°ã |
| | | */ |
| | | public record MesTraceConfigSnapshot(String traceUrl, |
| | | MesTraceUrlSource traceUrlSource, |
| | | String operatorName, |
| | | String importerName, |
| | | boolean imported) { |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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}ã |
| | | * 两è
å¿
须走**åä¸å¥**æ ¡éªè§åï¼å¦åä»åå
¸è¿æ¥çå¼å°±è½ç»è¿æ ç¾åè§æ£æ¥ï¼ |
| | | * å°åºæ¶è´¹è
æ«ä¸å¼çæ ç¾ãå æ¤æè§åæ¶æå°è¿éï¼ä¾å¯å¨æ ¡éªä¸åå
¸è§£æå
±åå¤ç¨ã |
| | | * <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 è§£æï¼ä¸å¨æ¤å¤æ¦æª |
| | | } |
| | | 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); |
| | | } |
| | | |
| | | } |
| | |
| | | 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; |
| | |
| | | */ |
| | | private static final Pattern UNIT_CODE_PATTERN = Pattern.compile("^[0-9A-Za-z]{1,20}$"); |
| | | |
| | | /** H5 溯æºé¡µé¢åºç¡å°åï¼ç± {@code MesQrCodeConfig} å¯å¨æ¶æ³¨å
¥ï¼ä¸ºç©ºæ¶äºç»´ç ç¼ç 纯ç å¼ */ |
| | | /** |
| | | * é
ç½®æ¥æºï¼ç± {@code MesQrCodeConfig} å¯å¨æ¶è£
é
ï¼æååå
¸è§£æå¨ã |
| | | * <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, ""); |
| | |
| | | } |
| | | |
| | | /** |
| | | * åå½åçæçé
置快ç
§ |
| | | * |
| | | * @return å¿«ç
§ï¼æªè£
é
é
ç½®æ¥æºæ¶è¿å nullï¼è°ç¨æ¹åééæå段ï¼é
ç½®æä»¶çå¼ï¼ |
| | | */ |
| | | private static MesTraceConfigSnapshot currentSnapshot() { |
| | | MesTraceConfigProvider provider = configProvider; |
| | | return provider != null ? provider.get() : null; |
| | | } |
| | | |
| | | /** |
| | | * å½ååºæ 注çç产ç»è¥è
åç§°ï¼è¿å£ç§ååºæ¯ä¼å
åè¿å£ååç§°ï¼å¦ååç产ç»è¥è
åç§° |
| | | * |
| | | * @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; |
| | | } |
| | | |
| | |
| | | * æ¯å¦è¿å£ç§ååºæ¯ï¼ä¸º true æ¶æ ç¾ä¸æº¯æºé¡µåºå±ç¤ºãè¿å£ååç§°ã |
| | | */ |
| | | public static boolean isImported() { |
| | | return imported; |
| | | return isImported(currentSnapshot()); |
| | | } |
| | | |
| | | private static boolean isImported(MesTraceConfigSnapshot snapshot) { |
| | | return snapshot != null ? snapshot.imported() : imported; |
| | | } |
| | | |
| | | /** |
| | |
| | | * æªé
置溯æºå°å䏿ªå¼å¯é级å¼å
³æ¶ç´æ¥æ¥éï¼é¿å
å°åºä¸å«è¿½æº¯ç½åçæ ç¾ã |
| | | */ |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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, "åå
è¯å«ä»£ç ", 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) { |
| | |
| | | } |
| | | return QrLabelInfo.builder() |
| | | .fileName(bagCode.getCode()) |
| | | .code(buildQrCodeContent(bagCode.getCode())) |
| | | .code(buildQrCodeContent(bagCode.getCode(), traceUrl)) |
| | | .keyValues(keyValues) |
| | | .build(); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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(); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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.*; |
| | | |
| | | /** |
| | | * åå
¸è§£æå¨æµè¯ï¼å®ä½ãåå
¸ä¼å
ãé
ç½®æä»¶å
åºãçåå¹¶è§åä¸ãç»ä¸æå¼å¸¸ãç约å®ã |
| | | */ |
| | | 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(); // åå
¸ç±»åå°æªåå§åï¼å±äºæ£å¸¸çãå°æªé
ç½®ãè·¯å¾ |
| | | |
| | | 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("è¿å£ååç§°", "æè¿å£å"), |
| | | dict("æ¯å¦è¿å£ç§å", "true")); |
| | | |
| | | MesTraceConfigSnapshot snapshot = newResolver().resolveSnapshot(); |
| | | |
| | | assertEquals("æ°çå¡éæ¨æ²³ç§ä¸è¡ä»½æéå
¬å¸", snapshot.operatorName()); |
| | | assertEquals("æè¿å£å", 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; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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, "ç产ç»è¥è
", "æè¿å£å", true)); |
| | | |
| | | QrLabelInfo label = MesProQrLabelBuilder.buildBagCodeLabel(bagCode(), null, Map.of(), Map.of()); |
| | | |
| | | assertEquals("æè¿å£å", 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å·"); |
| | | } |
| | | |
| | | } |
| | |
| | | # ç§åæ ç¾ä¸ H5 溯æºé¡µé
ç½®ï¼å profile å¯è¦çï¼ |
| | | # 注æï¼trace-base-url ä¸ operator-name æ¯äºç»´ç æ ç¾åè¦ç´ ä¸çã追溯ç½åããç产ç»è¥è
åç§°ãï¼ |
| | | # é local ç¯å¢ä¸ trace-base-url è¥ä¸ºå
ç½/å ä½ç¬¦å°åå°æç»å¯å¨ï¼é¿å
å°åºæ¶è´¹è
æ«ä¸å¼çæ ç¾ã |
| | | # 注æï¼trace-base-urlãoperator-nameãimporter-nameãimported æ¯äºç»´ç æ ç¾åè¦ç´ ç¸å
³çé
ç½®ã |
| | | # è¿è¡æ¶è¿å 项**ä¼å
ååå
¸ mes_trace_config**ï¼ç®¡çåå°ãåå
¸ç®¡çã坿¹ï¼æ¹å®æ ééå¯ï¼ï¼ |
| | | # æ¤å¤é
ç½®éå±
为å
åºé»è®¤å¼ï¼åå
¸æªé
ç½®ï¼æè¢«ç¦ç¨ãæå¡«äºéæ³å°åï¼æ¶æçæã |
| | | # é local ç¯å¢ä¸ trace-base-url è¥ä¸ºå
ç½/å ä½ç¬¦å°åä»å°æç»å¯å¨ââè¿éé²çº¿å¿
须卿°æ®åºå¯ç¨åçæã |
| | | mes: |
| | | # 追溯ç½åï¼äºç»´ç å
容åç¼ï¼å½¢å¦ http://åå:端å£/trace.htmlï¼ï¼é为å
¬ä¼æ«ç å¯è®¿é®çå°å |
| | | # 追溯ç½åå
åºå¼ï¼äºç»´ç å
容åç¼ï¼å½¢å¦ http://åå:端å£/trace.htmlï¼ï¼é为å
¬ä¼æ«ç å¯è®¿é®çå°å |
| | | trace-base-url: http://localhost:48080/trace.html |
| | | # ç产ç»è¥è
åç§°ï¼æ ç¾åè¦ç´ ä¹ä¸ï¼æ ç¾ä¸æº¯æºé¡µç»ä¸åæ¤å¼ï¼æªé
ç½®åæ æ³å¯¼åºæ ç¾ï¼ |
| | | # ç产ç»è¥è
åç§°å
åºå¼ï¼æ ç¾åè¦ç´ ä¹ä¸ï¼æ ç¾ä¸æº¯æºé¡µç»ä¸åæ¤å¼ï¼æªé
ç½®åæ æ³å¯¼åºæ ç¾ï¼ |
| | | operator-name: '' |
| | | # è¿å£ååç§°ï¼è¿å£ç§ååºæ¯å¡«åï¼imported=true æ¶æ ç¾ä¸æº¯æºé¡µå±ç¤ºè¿å£ååç§°ï¼ |
| | | # è¿å£ååç§°å
åºå¼ï¼è¿å£ç§ååºæ¯å¡«åï¼imported=true æ¶æ ç¾ä¸æº¯æºé¡µå±ç¤ºè¿å£ååç§°ï¼ |
| | | importer-name: '' |
| | | # æ¯å¦è¿å£ç§ååºæ¯ |
| | | # æ¯å¦è¿å£ç§ååºæ¯å
åºå¼ |
| | | imported: false |
| | | # æ¯å¦å
许äºç»´ç ç¼ç 纯ç å¼ï¼ä¸å¸¦æº¯æº URLï¼ï¼ä»
便¬å°å
¼å®¹åå²ç ï¼ç产ç¯å¢å¿
须为 false |
| | | # æ¯å¦å
许äºç»´ç ç¼ç 纯ç å¼ï¼ä¸å¸¦æº¯æº URLï¼ï¼ä»
便¬å°å
¼å®¹åå²ç ï¼ç产ç¯å¢å¿
须为 falseã |
| | | # 该项ææä¸çº³å
¥åå
¸ï¼å®æ¯ç»è¿ãæ ç¾å¿
é¡»å«è¿½æº¯ç½åãçå¼å
³ï¼ä¸åºå¼æ¾ç»åå°éæç¼è¾ã |
| | | allow-plain-qr-code: false |
| | | |
| | | # äºç»´ç æ¹é导åºï¼å°æ¹é忥ç§åºï¼å¤§æ¹é转åå°ä»»å¡ï¼æ æ°éä¸éï¼ |