docs/process_feedback_wait_countdown_integration.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,116 @@ # æ¥å·¥çå¾ æ¶é¿åè®¡æ¶ + æ¥å·¥æ°éå¯é - å端èè°æ¹æ¡ ## æ¶åé¡µé¢ - å·¥åºç®¡çï¼`src/views/mes/pro/process`ï¼è¡¨å `modules/form.vue`ï¼ - å¿«éæ¥å·¥ï¼`src/views/mes/pro/workorder-center/modules/quick-feedback.vue`ï¼ - æ¥å·¥è¡¨åï¼`src/views/mes/pro/feedback/modules/form.vue`ï¼ - ç产任å¡ç¸å ³å±ç¤ºï¼å表/详æ ï¼æ¥å·¥å计æ¶ç¶æå±ç¤ºï¼ ## ä¸å¡æµç¨ä¸æ°æ®å¸¦å ¥ 1. **å·¥åºä¸»æ°æ®é ç½®**ï¼å¨å·¥åºç®¡ç页é¢ä¸ºå·¥åºé ç½®ãçå¾ æ¶é¿ï¼å°æ¶ï¼ãä¸ãæ¯å¦éè¦å¡«åæ¥å·¥æ°éã两个æ°å段ãåæ®µä¿åå°å·¥åºä¸»æ°æ®ï¼`mes_pro_process`ï¼ï¼ææå¼ç¨è¯¥å·¥åºçå·¥èºè·¯çº¿å ±ç¨ã 2. **æ¥å·¥çé¢è¯»åå·¥åºé ç½®**ï¼æ¥å·¥è¡¨åï¼æ°å¢/ç¼è¾ï¼éæ©ä»»å¡åï¼éè¿ `getRouteProcessByRouteAndProcess(routeId, processId)`ï¼ç°æè°ç¨ï¼æåå·¥åºé ç½®ï¼ååºæ°å¢è¿å `waitDuration`ã`needReportQuantity`ã - `waitDuration > 0`ï¼è¯¥å·¥åºæ¥å·¥åå¿ é¡»å ç¹å»ãå¼å§å计æ¶ãï¼å计æ¶ç»ææè½æ¥å·¥ã - `needReportQuantity = false`ï¼æ¥å·¥æ°éå 许为空ï¼ä¸æ¾ç¤ºæ°éè¾å ¥é¡¹ãä¸å"å¿ é¡»å¤§äº 0"æ ¡éªï¼ã 3. **å¼å§å计æ¶**ï¼æä½åç¹å»ãå¼å§å计æ¶ãâ è°ç¨æ°æ¥å£ `startWaitCountdown` â å端æå½åæ¶é´åå ¥ä»»å¡ï¼`mes_pro_task.wait_start_time`ï¼ã 4. **ä»»å¡ç¶æåæ¾**ï¼ä»»å¡å表/详æ ååºæ°å¢ `waitStartTime` åæ®µï¼å端ç¨äºå±ç¤ºå计æ¶ç¶æï¼å·²å¼å§/è¿è¡ä¸/å·²ç»æï¼ã 5. **æäº¤æ¥å·¥**ï¼æ¥å·¥æäº¤ï¼å¿«éæ¥å·¥ / æ¥å·¥è¡¨åæäº¤ / 审æ¹éè¿ï¼æ¶ï¼å端ç»ä¸æ ¡éªçå¾ æ¶é¿ä¸æ°éè§åã ## API | æ¹æ³ | è·¯å¾ | 说æ | |------|------|------| | POST | `/mes/pro/task/start-wait-countdown` | å¼å§æ¥å·¥å计æ¶ï¼è®°å½å¼å§æ¶é´å°ä»»å¡ï¼ | | GET | `/mes/pro/route/process/get-by-route-and-process` | ç°ææ¥å£ï¼ååºæ°å¢ `waitDuration`/`needReportQuantity` | ### æ°å¢æ¥å£ï¼å¼å§æ¥å·¥åè®¡æ¶ **请æ±åæ°ï¼** | åæ° | ç±»å | å¿ å¡« | 说æ | |------|------|------|------| | taskId | Long | æ¯ | ç产任å¡ç¼å·ï¼POST body JSON ä¼ éï¼ | **请æ±ç¤ºä¾ï¼** ```json { "taskId": 35 } ``` **ååºï¼** è¿åå计æ¶å¼å§æ¶é´ï¼`yyyy-MM-dd HH:mm:ss`ï¼ã **ååºç¤ºä¾ï¼** ```json { "code": 0, "data": "2026-08-29 10:30:00" } ``` **ä¸å¡åç½®æ ¡éªï¼å端ï¼ï¼** | åºæ¯ | ç»æ | |------|------| | ä»»å¡ä¸åå¨ / å·²ç»æï¼å®æãåæ¶ï¼ | æ¥éï¼ä»»å¡å¼å¸¸ï¼ | | ä»»å¡å·²åå·¥ | æ¥éï¼åå·¥ä¸è½æ¥å·¥ï¼ | | å·¥åºæªé ç½®çå¾ æ¶é¿ï¼ä¸ºç©ºæ 0ï¼ | æ¥éãè¯¥å·¥åºæªé ç½®çå¾ æ¶é¿ï¼æ éå计æ¶ã | | å·¥åºå·²é ç½®çå¾ æ¶é¿ | æåï¼è®°å½å¼å§æ¶é´ï¼éå¤ç¹å»ä¼é置为å½åæ¶é´ | ### ç°ææ¥å£ååºå段æ°å¢ **`get-by-route-and-process` ååºæ°å¢å段ï¼** | åæ®µ | ç±»å | 说æ | |------|------|------| | waitDuration | BigDecimal | çå¾ æ¶é¿ï¼å°æ¶ï¼ã>0 表示æ¥å·¥åéå计æ¶çå¾ | | needReportQuantity | Boolean | æ¯å¦éè¦å¡«åæ¥å·¥æ°éã`false`=æ¥å·¥æ°éå 许为空 | **ç产任å¡ç¸å ³ååºæ°å¢å段ï¼å表/详æ /ä»»å¡éæ©ç»ä»¶ï¼ï¼** | åæ®µ | ç±»å | 说æ | |------|------|------| | waitStartTime | LocalDateTime | æ¥å·¥å计æ¶å¼å§æ¶é´ãä¸ºç©ºè¡¨ç¤ºå°æªå¼å§åè®¡æ¶ | **å·¥åºä¸»æ°æ®ä¿å/ååºæ°å¢å段ï¼** | åæ®µ | ç±»å | 说æ | |------|------|------| | waitDuration | BigDecimal | çå¾ æ¶é¿ï¼å°æ¶ï¼ï¼æ¥å·¥åéå计æ¶çå¾ | | needReportQuantity | Boolean | æ¯å¦éè¦å¡«åæ¥å·¥æ°é | ## åæ®µå±ç¤ºè§å | åæ®µ | å±ç¤ºä½ç½® | 说æ | |------|----------|------| | çå¾ æ¶é¿ï¼å°æ¶ï¼ | å·¥åºç®¡ç表å | æ°åè¾å ¥ï¼å°æ°æå¤ 2 ä½ï¼ç空/0 è¡¨ç¤ºè¯¥å·¥åºæ¥å·¥æ éåè®¡æ¶ | | æ¯å¦éè¦å¡«åæ¥å·¥æ°é | å·¥åºç®¡ç表å | å¼å ³ãå ³éåè¯¥å·¥åºæ¥å·¥æ°éå 许为空 | | å¾ æ¥å·¥æ°é | å¿«éæ¥å·¥ / æ¥å·¥è¡¨å | ç°æåæ®µãè¥å·¥åºæ éå¡«åæ°éï¼æ¥å·¥æ°éè¾å ¥é¡¹å¯ä¸æ¾ç¤º | | æ¬æ¬¡æ¥å·¥æ°é | å¿«éæ¥å·¥ / æ¥å·¥è¡¨å | æå·¥åºé ç½®å³å®æ¯å¦å¿ å¡«/å¯ç©º | | å¼å§åè®¡æ¶ æé® + å计æ¶å©ä½æ¶é´ | å¿«éæ¥å·¥ / æ¥å·¥è¡¨å | å·¥åºé ç½®çå¾ æ¶é¿æ¶å±ç¤ºï¼ç¹å»åå±ç¤ºå计æ¶å©ä½æ¶é´ | | å计æ¶ç¶æ | ç产任å¡å表/详æ ï¼å¯éï¼ | 便® `waitStartTime` ä¸å½åæ¶é´è®¡ç®å©ä½æ¶é´ | ## ä¸å¡è§å说æ | åºæ¯ | è§å | |------|------| | å·¥åº `waitDuration` 为空æ 0 | æ¥å·¥æ éå计æ¶ï¼åæµç¨ä¸å | | å·¥åº `waitDuration > 0`ï¼ä»»å¡æªç¹ãå¼å§å计æ¶ã | æ¥å·¥æ¥éã该工åºè®¾ç½®äºçå¾ æ¶é¿ï¼è¯·å ç¹å»"å¼å§å计æ¶"ã | | å·¥åº `waitDuration > 0`ï¼åè®¡æ¶æªç»æ | æ¥å·¥æ¥éãçå¾ åè®¡æ¶æªç»æï¼è¿éçå¾ Xå°æ¶Xåéã | | å·¥åº `waitDuration > 0`ï¼å计æ¶å·²ç»æ | 坿£å¸¸æ¥å·¥ï¼å¼å§æ¶é´è®°å½å¨ä»»å¡ä¸ï¼æ¯ä»»å¡ä¸æ¬¡ | | éå¤ç¹å»ãå¼å§å计æ¶ã | éç½®å¼å§æ¶é´ä¸ºå½åæ¶é´ | | å·¥åº `needReportQuantity = true` ææªé ç½® | æ¥å·¥æ°éå¿ é¡» > 0ï¼åé»è¾ï¼ | | å·¥åº `needReportQuantity = false` | æ¥å·¥æ°éå 许为空/0ï¼åç«¯è·³è¿æ°éæ ¡éªï¼å®¡æ¹æ¶ä¸çæäº§åºå/æ¹æ¬¡ï¼ä¸æ£åå²ç©æï¼ä» 宿æ¥å·¥ | | å计æ¶éç¨èå´ | æå·¥åºå¤æï¼å¼å§æ¶é´è®°å½å°ç产任å¡ï¼æ¯ä»»å¡ä¸æ¬¡ | ## 注æäºé¡¹ - çå¾ æ¶é¿åä½åºå®ä¸º**å°æ¶**ï¼å端è¾å ¥æ¶æ³¨æåä½æç¤ºï¼ç¤ºä¾ `2.00` 表示 2 å°æ¶ï¼`0.5` 表示 30 åéï¼ã - å计æ¶å¼å§æ¶é´è®°å½å¨**ç产任å¡**ä¸ï¼ä¸å·¥åºçå¾ æ¶é¿ï¼ä¸»æ°æ®ï¼åå¼ç»´æ¤ï¼ä¿®æ¹å·¥åºçå¾ æ¶é¿ä¸å½±åå·²å¼å§çä»»å¡å计æ¶ã - è¥å·¥åºé ç½®äºçå¾ æ¶é¿ä½ä»»å¡æªå¼å§å计æ¶ï¼ç´æ¥æäº¤æ¥å·¥ä¼è¢«åç«¯æ¦æªï¼å建ãå¿«éæ¥å·¥ã审æ¹éè¿ä¸å¤å ¥å£é½æ ¡éªï¼ã - å¿«éæ¥å·¥å½å为æ°éå¿ å¡«ï¼æ¥å ¥åéæ `needReportQuantity` è°æ´ã请è¾å ¥æ¥å·¥æ°éãçå¿ å¡«æ ¡éªä¸æ°éè¾å ¥æ¡å±ç¤ºã - æ¥å·¥æ°é为空/0 çæ¥å·¥ï¼ä¸ä¼çæäº§åºå/æ¹æ¬¡ãä¸ä¼å岿£æï¼ä» ææ¥å·¥æ è®°ä¸ºå·²å®æå¹¶æ´æ°ä»»å¡è¿åº¦ï¼è¯·å¨å端ç¸åºéèæ°éç¸å ³å±ç¤ºï¼é¿å 误导ã - æ¥å·¥è¡¨åç¼è¾/å®¡æ¹æ¶ä»»å¡å¯è½å·²è¿çå¾ æï¼æ¤æ¶æ éåå计æ¶å³å¯æäº¤ã docs/process_feedback_wait_countdown_remind_integration.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,47 @@ # æ¥å·¥å计æ¶ç³»ç»æ¶æ¯æéï¼10åé / 5åé / ç»æï¼ - å端èè°æ¹æ¡ ## æ¶åé¡µé¢ - æ åç«¯é¡µé¢æ¹å¨ãæéç±**åç«¯å®æ¶ä»»å¡**èªå¨åéå°**ç³»ç»ç«å ä¿¡ï¼æ¶æ¯ä¸å¿ï¼**ï¼æä½åå¨ç«å ä¿¡å表æ¥çå³å¯ã ## è¯´æ æ¥å·¥å计æ¶è®¾ç½®ç³»ç»æ¶æ¯æéï¼åä¸ä¸ªç级èªå¨åéç«å ä¿¡ï¼ | ç级 | è§¦åæ¶æº | ç«å ä¿¡æ¨¡æ¿ code | |------|----------|-----------------| | 1 | å计æ¶å©ä½ ⤠10 åé | `mes_task_wait_remind_10` | | 2 | å计æ¶å©ä½ ⤠5 åé | `mes_task_wait_remind_5` | | 3 | å计æ¶ç»æï¼å©ä½ ⤠0ï¼ | `mes_task_wait_remind_end` | å端æ°å¢å®æ¶ä»»å¡ï¼**æ¯åéæ«æä¸æ¬¡**ææå·²å¼å§å计æ¶ï¼`waitStartTime` é空ï¼ä¸æªç»æãæªåå·¥çæ¥å·¥ä»»å¡ï¼å°è¾¾å¯¹åºæ¶é´ç¹èªå¨åéç«å ä¿¡ã ## ç«å ä¿¡å 容 | æ¨¡æ¿ code | å å®¹ç¤ºä¾ | |-----------|----------| | `mes_task_wait_remind_10` | ç产任å¡ã{taskCode}ã{taskName} æ¥å·¥å计æ¶å©ä½ 10 åéï¼è¯·å夿¥å·¥ã | | `mes_task_wait_remind_5` | ç产任å¡ã{taskCode}ã{taskName} æ¥å·¥å计æ¶å©ä½ 5 åéï¼å³å°å¯ä»¥æ¥å·¥ã | | `mes_task_wait_remind_end` | ç产任å¡ã{taskCode}ã{taskName} æ¥å·¥å计æ¶å·²ç»æï¼å¯ä»¥è¿è¡æ¥å·¥ã | å ä½ç¬¦ï¼`taskCode`ï¼ä»»å¡ç¼ç ï¼ã`taskName`ï¼ä»»å¡åç§°ï¼ã`processName`ï¼å·¥åºåç§°ï¼ã ## æ¥æ¶äºº 1. **ä¼å **ï¼ä»»å¡æå®çæ¥å·¥äººåï¼`mes_pro_task_assignee`ï¼ï¼å ¨é¨åéã 2. **å åº**ï¼ä»»å¡æªæå®æ¥å·¥äººåæ¶ï¼åç»ä»»å¡å建人ã ## ä¸å¡è§å说æ | åºæ¯ | è§å | |------|------| | å·¥åºçå¾ æ¶é¿ä¸è¶³ 10 åéï¼å¦ 5 åéï¼ | å¼å§å计æ¶åç«å³è§¦å"10 åé"ä¸"5 åé"æéï¼å©ä½ä¸è¶³å³æéï¼ | | éå¤ç¹å»"å¼å§å计æ¶"éç½®æ¶é´ | æéææ°çå¼å§æ¶é´éæ°è®¡ç®ï¼éæ°è§¦åä¸è½®æé | | ä»»å¡å·²å®æ / 已忶 / å·²åå·¥ | ä¸åæ«ææé | | åä¸ç级åä¸è½®åè®¡æ¶ | åªæé䏿¬¡ï¼Redis å»éï¼24 å°æ¶ï¼ | ## 注æäºé¡¹ - æé为**ç³»ç»ç«å ä¿¡**ï¼æä½åå¨ç³»ç»æ¶æ¯ä¸å¿æ¥çï¼æ å¼¹çª/æ å端æ¥å£è°ç¨ã - ç«å 信模æ¿å¯å¨ãç³»ç»ç®¡ç â ç«å 信模æ¿ã䏿 code æ¥ç/ä¿®æ¹å 容ã - 该åè½ä¸ºæ°å¢ï¼ä¸å½±ååææ¥å·¥åè®¡æ¶æ¥å·¥æ ¡éªé»è¾ã docs/product_receipt_auto_stock_integration.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,111 @@ # æ¥å·¥èªå¨çæäº§åå ¥åºå + å¾éå ¥åº + ä¸åæ ¼ä»åºå åº - å端èè°æ¹æ¡ ## æ¶åé¡µé¢ - 产åå ¥åºï¼`src/views/wls/productreceipt`ï¼å表 `index.vue` + 表å `modules/form.vue`ãè¡ `modules/line-list.vue`ã䏿¶æç» `modules/detail-form.vue`ï¼ - æ¥å·¥å®¡æ¹ï¼ä» æ°æ®æ¥æºï¼æ éæ¹å¨ï¼å®¡æ¹éè¿åèªå¨çæå ¥åºåï¼ ## ä¸å¡æµç¨ä¸æ°æ®å¸¦å ¥ 1. **æ¥å·¥å®¡æ¹ â èªå¨çæå ¥åºå**ï¼æ¥å·¥å®¡æ¹éè¿åï¼å端èªå¨çæä¸æ¡äº§åå ¥åºåï¼`mes_wm_product_receipt`ï¼ï¼ä¸æ¬¡æ¥å·¥=䏿¡å ¥åºåï¼å¹çï¼ã 2. **è¡ = 产åºè¡**ï¼å ¥åºåè¡æ¥èªçäº§å ¥åºå产åºè¡ï¼`mes_wm_product_produce_line`ï¼ï¼æåæ ¼/ä¸åæ ¼åè¡ï¼æ¯è¡èªå¨å¸¦å ¥ `itemId`ã`quantity`ï¼äº§åºæ°éï¼ã`batchId/batchCode`ã`qualityStatus`ï¼è´¨éç¶æåæ ¼/ä¸åæ ¼ï¼ï¼é»è®¤ `isStock=false`ï¼æªå¾éå ¥åºï¼ã 3. **ä¸ä¸ªæ°éä¸è¡**ï¼äº§åå ¥åºé¡µè¡¨æ ¼æ"ä¸è¡=ä¸ä¸ªæ°é"å±ç¤ºï¼è¡å³æ¬æ¬¡æ¥å·¥çä¸ä¸ªäº§åºæ°éï¼åæ ¼è¡/ä¸åæ ¼è¡å䏿¡ï¼ã 4. **æ§è¡å ¥åºåå¾é**ï¼å¨ãå¾ ä¸æ¶ãç¶æçå ¥åºåä¸ï¼æ¯è¡å¾é"æ¯å¦å ¥åº"ï¼å¾éå ¥åºçè¡å±å¼ä¸çº§ï¼ä»åºâåºåºâåºä½ï¼å¤éå¹¶åé æ°éã 5. **æ§è¡ä¸æ¶ä¿å**ï¼è°ç¨æ°æ¥å£ `submit-stock` ä¿åæ¯è¡çå¾éç¶æä¸ä»åº/åºåº/åºä½åé ã 6. **æ§è¡å ¥åºå åº**ï¼æ§è¡å ¥åºæ¶ï¼æªå¾éå ¥åºçè¡æåé æ°éä¸è¶³çè¡ï¼å©ä½æ°éèªå¨è¿ãä¸åæ ¼ä»åºãï¼ä¸çº§ï¼DEFECT_WAREHOUSE / DEFECT_LOCATION / DEFECT_AREAï¼ï¼æ¯ä¸ªæ°éçæä¸æ¡æµæ°´ï¼åºèæçº¿è¾¹åº + å ¥å®é /ä¸åæ ¼ä»åºï¼ã ## API | æ¹æ³ | è·¯å¾ | 说æ | |------|------|------| | POST | `/mes/wm/product-receipt/submit-stock` | æ¹é䏿¶ï¼å¾éæ¯å¦å ¥åº + ä»åº/åºåº/åºä½åé æ°éï¼ï¼æ°å¢ | | PUT | `/mes/wm/product-receipt/submit` | æäº¤ï¼è稿 â å¾ ä¸æ¶ï¼ï¼ç°æ | | PUT | `/mes/wm/product-receipt/stock` | æ§è¡ä¸æ¶ï¼å¾ 䏿¶ â å¾ æ§è¡å ¥åºï¼ï¼ç°æ | | PUT | `/mes/wm/product-receipt/finish` | æ§è¡å ¥åºï¼å¾ æ§è¡å ¥åº â 已宿ï¼ï¼ç°æ | ### æ°å¢æ¥å£ï¼æ¹é䏿¶ **请æ±åæ°ï¼`submit-stock`ï¼ï¼** | åæ° | ç±»å | å¿ å¡« | 说æ | |------|------|------|------| | receiptId | Long | æ¯ | 产åå ¥åºåç¼å· | | rows | List\<Row\> | æ¯ | è¡ä¸æ¶å表 | | rows[].lineId | Long | æ¯ | å ¥åºåè¡ç¼å· | | rows[].isStock | Boolean | æ¯ | æ¯å¦å ¥åºï¼true=å¾éå ¥åºå¹¶åé ä»åº/åºä½ï¼false=æ§è¡å ¥åºæ¶èªå¨è¿ä¸åæ ¼ä»åºï¼ | | rows[].details | List\<Detail\> | æ¡ä»¶ | isStock=true æ¶å¿ é¡»ï¼æ¯ä¸ªåé 䏿¡ï¼æ°éä¹å=è¡æ¶è´§æ°é | | details[].warehouseId | Long | æ¯ | ä»åºç¼å· | | details[].locationId | Long | æ¯ | åºåºç¼å· | | details[].areaId | Long | æ¯ | åºä½ç¼å· | | details[].quantity | BigDecimal | æ¯ | åé æ°é | **请æ±ç¤ºä¾ï¼** ```json { "receiptId": 1, "rows": [ { "lineId": 11, "isStock": true, "details": [ { "warehouseId": 1, "locationId": 1, "areaId": 1, "quantity": 60 }, { "warehouseId": 1, "locationId": 2, "areaId": 2, "quantity": 40 } ] }, { "lineId": 12, "isStock": false } ] } ``` **ååºï¼** `{ "code": 0, "data": true }` ### ç°ææ¥å£ååºå段æ°å¢ **产åå ¥åºå详æ /å页ååºï¼RespVOï¼æ°å¢ï¼** | åæ®µ | ç±»å | 说æ | |------|------|------| | feedbackId | Long | æ¥æºæ¥å·¥ç¼å·ãéç©ºè¡¨ç¤ºç±æ¥å·¥èªå¨çæï¼ä¸å¯æå·¥æ¹è¡ | **产åå ¥åºåè¡ååºï¼LineRespVOï¼æ°å¢ï¼** | åæ®µ | ç±»å | 说æ | |------|------|------| | qualityStatus | Integer | è´¨éç¶æãåå ¸ `MES_WM_QUALITY_STATUS`ï¼åæ ¼/ä¸åæ ¼ï¼ï¼èªå¨çæçå ¥åºåè¡å¸¦å ¥ | | isStock | Boolean | æ¯å¦å ¥åºï¼false/空=æ§è¡å ¥åºæ¶é»è®¤è¿ä¸åæ ¼ä»åºï¼ | ## åæ®µå±ç¤ºè§å | åæ®µ | å±ç¤ºä½ç½® | 说æ | |------|----------|------| | æ¥å·¥æ¥æº | å表 | æ `feedbackId` çå ¥åºåå¯å±ç¤º"èªå¨çæ"æ è¯ | | è´¨éç¶æ | è¡è¡¨æ ¼ | å±ç¤ºåæ ¼/ä¸åæ ¼ï¼æ¥èªäº§åº | | æ¯å¦å ¥åº | è¡è¡¨æ ¼ | æ¯è¡ä¸ä¸ªå¾éï¼ä» å¾ ä¸æ¶ç¶æå¯æ¹ï¼ï¼å¾éåå±å¼ä»åº/åºåº/åºä½åé | | ä»åº/åºåº/åºä½ | è¡å±å¼æç» | ä¸çº§çº§èéæ©ï¼å¯å¤éï¼æ¯æ¡æç»ä¸ä¸ªæ°é | | åé æ°é | è¡å±å¼æç» | å¾éå ¥åºçè¡è¦æ±åé æ°éä¹å = è¡æ¶è´§æ°é | | å©ä½æ°é | è¡å±å¼æç» | è¡æ¶è´§æ°é - å·²åé æ°éï¼æ§è¡å ¥åºæ¶å©ä½èªå¨è¿ä¸åæ ¼ä»åº | ## ä¸å¡è§å说æ | åºæ¯ | è§å | |------|------| | æ¥å·¥å®¡æ¹éè¿ï¼æ è´¨æ£ï¼ | èªå¨çæå ¥åºåï¼è¡=åæ ¼/ä¸åæ ¼äº§åºè¡ï¼ç¶æ=è稿 | | æ¥å·¥å®¡æ¹éè¿ï¼è´¨æ£ï¼IPQC 宿åï¼ | èªå¨çæå ¥åºåï¼è¡=åæ ¼/ä¸åæ ¼æåè¡ | | éå¤è§¦åå䏿¥å·¥ | å¹çï¼å·²åå¨åä¸åçæç¬¬äºæ¡ | | isStock=false ææªåé å® | æ§è¡å ¥åºæ¶å©ä½æ°éèªå¨è¿ä¸åæ ¼ä»åº | | isStock=true ä½åé æ°éä¹å â è¡æ¶è´§æ°é | æ¥éãå¾éå ¥åºçåé æ°éä¸è¡æ¶è´§æ°éä¸ä¸è´ã | | ä¸åæ ¼ä»åºæªé ç½® | æ§è¡å ¥åºæ¥éãæªé ç½®ä¸åæ ¼ä»åºï¼è¯·å å建 DEFECT_WAREHOUSE ä»åºã | | æ§è¡å ¥åº | æ¯æ¡æç»çæä¸æ¡æµæ°´ï¼åºèæçº¿è¾¹åº + å ¥å®é /ä¸åæ ¼ä»åºï¼ï¼ä¸ä¸ªæ°é=䏿¡æµæ°´ | | èªå¨çæçå ¥åºå | è¡ä¸å¯æå·¥å¢å æ¹ï¼å¯æ´ååæ¶ï¼ï¼æå·¥å建çå ¥åºåä¸åå½±å | ## 注æäºé¡¹ - å¾ ä¸æ¶ç¶ææå 许å¾é"æ¯å¦å ¥åº"ä¸åé ä»åº/åºä½ï¼æ§è¡ä¸æ¶ä¿ååç¶æåä¸ºå¾ æ§è¡å ¥åºã - å¾éå ¥åºçè¡å¿ é¡»åé 宿´æ°éï¼åªåé ä¸é¨åæ¶ï¼å©ä½é¨åæ§è¡å ¥åºä»ä¼è¿ä¸åæ ¼ä»åºã - ä¸åæ ¼æ°éé»è®¤ä¸å¾éå ¥åºï¼è¿ä¸åæ ¼ä»åºï¼ï¼åæ ¼æ°éç±æä½åå³å®æ¯å¦å ¥åºåå ¥åªä¸ªä»åºã - èªå¨çæçå ¥åºå `qualityStatus` ç±äº§åºè¡å¸¦å ¥ï¼è¡æ¬èº«ä¸å¯ä¿®æ¹ï¼å¦éè°æ´å ¥åºå»åï¼éè¿å¾éä¸åé å®ç°ã - å ¥åºæµæ°´ä»¥"æ°é"为åä½ï¼åä¸è¡åå°ä¸åä»åº/åºä½ï¼ä¸æ¡æç»å¯¹åºä¸æ¡ OUTï¼èæçº¿è¾¹åºï¼+ INï¼å®é ä»åºï¼æµæ°´ã yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/database/DatabaseBackupController.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,29 @@ package cn.iocoder.yudao.module.system.controller.admin.database; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.system.service.database.DatabaseBackupService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; @Tag(name = "管çåå° - æ°æ®åºå¤ä»½") @RestController @RequestMapping("/system/database") public class DatabaseBackupController { @Resource private DatabaseBackupService databaseBackupService; @GetMapping("/backup") @Operation(summary = "ä¸è½½æ°æ®åºå¤ä»½") @PreAuthorize("@ss.hasPermission('system:database:backup')") public void backupDatabase(HttpServletResponse response) throws Exception { databaseBackupService.backupDatabase(response); } } yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/database/DatabaseBackupService.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,14 @@ package cn.iocoder.yudao.module.system.service.database; import jakarta.servlet.http.HttpServletResponse; public interface DatabaseBackupService { /** * å¤ä»½æ°æ®åºï¼å¹¶å° SQL æä»¶åå ¥å° HTTP ååºä¸ * * @param response HTTP ååº */ void backupDatabase(HttpServletResponse response) throws Exception; } yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/database/DatabaseBackupServiceImpl.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,460 @@ package cn.iocoder.yudao.module.system.service.database; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.exception.ServiceException; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.sql.DataSource; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.Statement; import java.sql.Types; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @Service @Slf4j public class DatabaseBackupServiceImpl implements DatabaseBackupService { /** å¤ä»½è¶ æ¶æ¶é´ï¼åéï¼ */ private static final long BACKUP_TIMEOUT_MINUTES = 10; /** MySQL 容å¨å é¨ç«¯å£ */ private static final String DOCKER_MYSQL_PORT = "3306"; @Value("${spring.datasource.dynamic.datasource.master.url:}") private String dbUrl; @Value("${spring.datasource.dynamic.datasource.master.username:root}") private String dbUsername; @Value("${spring.datasource.dynamic.datasource.master.password:}") private String dbPassword; /** å¤ä»½å½ä»¤ï¼å¯æ¾å¼æå®ï¼å¦ docker exec mysql8 mysqldumpï¼ï¼ç空åèªå¨æ£æµæ¬å° mysqldump æ Docker MySQL å®¹å¨ */ @Value("${yudao.database-backup.command:}") private String backupCommand; /** å¤ä»½ç®æ æ°æ®åº hostï¼ç空å仿°æ®æº JDBC URL è§£æ */ @Value("${yudao.database-backup.host:}") private String backupHost; /** å¤ä»½ç®æ æ°æ®åº portï¼ç空å仿°æ®æº JDBC URL è§£æ */ @Value("${yudao.database-backup.port:}") private String backupPort; /** å¤ä»½ç®æ æ°æ®åºåï¼ç空å仿°æ®æº JDBC URL è§£æ */ @Value("${yudao.database-backup.database:}") private String backupDatabase; /** å¤ä»½è´¦å·ï¼ç空åä½¿ç¨æ°æ®æºè´¦å· */ @Value("${yudao.database-backup.username:}") private String backupUsername; /** å¤ä»½å¯ç ï¼ç空åä½¿ç¨æ°æ®æºå¯ç */ @Value("${yudao.database-backup.password:}") private String backupPassword; /** ä¸»æ°æ®æºï¼ç¨äº JDBC 导åºå åº */ @Resource private DataSource dataSource; @Override public void backupDatabase(HttpServletResponse response) throws Exception { if (StrUtil.isEmpty(dbUrl)) { throw new ServiceException(500, "æªæ¾å°æ°æ®åºè¿æ¥é ç½®"); } // å¤ä»½è¿æ¥çæ°æ®åºä¿¡æ¯ï¼ä¼å ä½¿ç¨ yudao.database-backup é ç½®ï¼æªé ç½®çé¨å仿°æ®æº JDBC URL è§£æå åº String dbHost = backupHost; String dbPort = backupPort; String dbName = backupDatabase; if (StrUtil.isEmpty(dbHost) || StrUtil.isEmpty(dbPort) || StrUtil.isEmpty(dbName)) { Matcher matcher = Pattern.compile("jdbc:mysql://([^:]+):(\\d+)/([^?]+)").matcher(dbUrl); if (matcher.find()) { if (StrUtil.isEmpty(dbHost)) { dbHost = matcher.group(1); } if (StrUtil.isEmpty(dbPort)) { dbPort = matcher.group(2); } if (StrUtil.isEmpty(dbName)) { dbName = matcher.group(3); } } } if (StrUtil.isEmpty(dbHost) || StrUtil.isEmpty(dbPort) || StrUtil.isEmpty(dbName)) { log.warn("æ æ³ä»é ç½®æ JDBC URL è§£æå®æ´çæ°æ®åºä¿¡æ¯: host={}, port={}, db={}", dbHost, dbPort, dbName); throw new ServiceException(500, "æ æ³è§£ææ°æ®åºå¤ä»½è¿æ¥ä¿¡æ¯ï¼è¯·å¨ yudao.database-backup ä¸é ç½® host/port/database"); } // localhost ç»ä¸èµ° TCP 127.0.0.1ï¼mysqldump 对 localhost å¯è½èµ° socket/loopbackï¼ // èä¸åç¯å¢ root@localhost ç认è¯è¦æ±å·®å¼å¤§ï¼å¼ºå¶ TCP æ´ç¨³å® if ("localhost".equalsIgnoreCase(dbHost)) { dbHost = "127.0.0.1"; } String username = StrUtil.isEmpty(backupUsername) ? dbUsername : backupUsername; String password = StrUtil.isEmpty(backupPassword) ? dbPassword : backupPassword; // ç»è£ åéå¤ä»½å½ä»¤ï¼æä¼å çº§ä¾æ¬¡å°è¯ï¼æå¨é ç½® > æ¬æº mysqldump > Docker å®¹å¨ mysqldump List<List<String>> candidates = new ArrayList<>(); List<String> labels = new ArrayList<>(); if (StrUtil.isNotEmpty(backupCommand)) { candidates.add(splitCommand(backupCommand)); labels.add("æå¨é ç½®å½ä»¤[" + backupCommand + "]"); } if (isLocalMysqldumpAvailable()) { candidates.add(buildLocalMysqldumpCommand(dbHost, dbPort, username, password, dbName)); labels.add("æ¬æº mysqldump"); } String container = findMysqlDockerContainer(dbPort); if (StrUtil.isNotEmpty(container)) { candidates.add(buildDockerExecCommand(container, username, password, dbName)); labels.add("Docker 容å¨[" + container + "] mysqldump"); } log.info("æ°æ®åºå¤ä»½åéæ¹å¼ï¼{}", labels); // 便¬¡å°è¯ææå¤é¨å½ä»¤æ¹å¼ List<String> failures = new ArrayList<>(); String timestamp = DateUtil.format(new Date(), "yyyyMMdd_HHmmss"); String fileName = "backup_" + timestamp + ".sql"; for (int i = 0; i < candidates.size(); i++) { File tempFile = File.createTempFile("backup_", ".sql"); try { String error = runMysqldump(candidates.get(i), password, tempFile); if (error == null) { log.info("æ°æ®åºå¤ä»½æåï¼ä½¿ç¨æ¹å¼ï¼{}", labels.get(i)); downloadAttachment(response, tempFile, fileName); return; } log.warn("å¤ä»½æ¹å¼ [{}] 失败ï¼{}", labels.get(i), error); failures.add(labels.get(i) + "ï¼" + error); } finally { FileUtil.del(tempFile); } } // å¤é¨å½ä»¤å ¨é¨å¤±è´¥æä¸å¯ç¨æ¶ï¼ä½¿ç¨ JDBC 导åºå åºï¼ä¿è¯ä»»ä½é¨ç½²ç¯å¢ï¼å¦åºç¨å®¹å¨å æ mysqldump/dockerï¼é½è½å¤ä»½ File tempFile = File.createTempFile("backup_", ".sql"); try { String error = exportByJdbc(tempFile); if (error == null) { log.info("æ°æ®åºå¤ä»½æåï¼ä½¿ç¨æ¹å¼ï¼JDBC 导åºå åº"); downloadAttachment(response, tempFile, fileName); return; } log.warn("JDBC 导åºå åºå¤±è´¥ï¼{}", error); failures.add("JDBC 导åºå åºï¼" + error); } finally { FileUtil.del(tempFile); } throw new ServiceException(500, "æ°æ®åºå¤ä»½å¤±è´¥ï¼" + String.join(" ï½ ", failures)); } /** * éè¿ JDBC ç´è¿æ°æ®åºå¯¼åºå»ºè¡¨è¯å¥ä¸å ¨éæ°æ®ï¼ä½ä¸ºå¤é¨å½ä»¤ä¸å¯ç¨æ¶çå åºå¤ä»½æ¹å¼ * * @return æ§è¡æåè¿å nullï¼å¤±è´¥è¿åé误æè¿° */ private String exportByJdbc(File outputFile) { try (Connection conn = dataSource.getConnection(); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(outputFile), StandardCharsets.UTF_8))) { writer.write("/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n"); writer.write("/*!40101 SET NAMES utf8mb4 */;\n"); writer.write("SET TIME_ZONE='+00:00';\n"); writer.write("SET FOREIGN_KEY_CHECKS=0;\n"); writer.write("SET UNIQUE_CHECKS=0;\n"); writer.write("SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO';\n\n"); try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SHOW TABLES")) { List<String> tables = new ArrayList<>(); while (rs.next()) { tables.add(rs.getString(1)); } for (String table : tables) { exportTable(conn, writer, table); } } writer.write("\nSET FOREIGN_KEY_CHECKS=1;\n"); writer.write("SET UNIQUE_CHECKS=1;\n"); writer.write("/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n"); return null; } catch (Exception e) { log.error("JDBC 导åºå åºå¤±è´¥", e); return "JDBC 导åºå¼å¸¸: " + e.getMessage(); } } /** * 导åºåå¼ è¡¨ç建表è¯å¥ä¸å ¨é¨æ°æ® */ private void exportTable(Connection conn, BufferedWriter writer, String table) throws Exception { String quoted = "`" + table.replace("`", "``") + "`"; // 建表è¯å¥ try (Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SHOW CREATE TABLE " + quoted)) { if (rs.next()) { writer.write(rs.getString(2)); writer.write(";\n\n"); } } // å ¨éæ°æ®ï¼æµå¼è¯»åé¿å 大表å ç¨è¿å¤å å try (Statement st = conn.createStatement()) { st.setFetchSize(Integer.MIN_VALUE); try (ResultSet rs = st.executeQuery("SELECT * FROM " + quoted)) { ResultSetMetaData meta = rs.getMetaData(); int columnCount = meta.getColumnCount(); int[] types = new int[columnCount]; for (int i = 0; i < columnCount; i++) { types[i] = meta.getColumnType(i + 1); } while (rs.next()) { StringBuilder sb = new StringBuilder("INSERT INTO ").append(quoted).append(" VALUES ("); for (int i = 0; i < columnCount; i++) { if (i > 0) { sb.append(','); } if (rs.getObject(i + 1) == null) { sb.append("NULL"); continue; } int type = types[i]; if (type == Types.BIT) { sb.append(rs.getBoolean(i + 1) ? '1' : '0'); } else if (isBinaryType(type)) { sb.append("X'").append(HexUtil.encodeHexStr(rs.getBytes(i + 1))).append("'"); } else { sb.append('\'').append(escapeSql(rs.getString(i + 1))).append('\''); } } sb.append(");\n"); writer.write(sb.toString()); } } } writer.write('\n'); } /** * æ¯å¦äºè¿å¶åç±»åï¼äºè¿å¶æ°æ®ä»¥åå è¿å¶åé¢é导åºé¿å ä¹±ç */ private boolean isBinaryType(int type) { return type == Types.BINARY || type == Types.VARBINARY || type == Types.LONGVARBINARY || type == Types.BLOB; } /** * SQL å符串åé¢éè½¬ä¹ */ private String escapeSql(String value) { return value.replace("\\", "\\\\").replace("'", "''").replace("\0", "\\0"); } /** * æå»ºæ¬æº mysqldump å¤ä»½å½ä»¤ */ private List<String> buildLocalMysqldumpCommand(String host, String port, String username, String password, String dbName) { List<String> command = new ArrayList<>(); command.add("mysqldump"); // MySQL 8 caching_sha2_password 认è¯é»è®¤ä¸è¯·æ±æå¡ç«¯å ¬é¥ï¼ä¼æ¥ Access denied (using password: YES) command.add("--get-server-public-key"); command.add("-h" + host); command.add("-P" + port); command.add("-u" + username); if (StrUtil.isEmpty(password)) { command.add("--skip-password"); } command.add(dbName); return command; } /** * æå»ºéè¿ docker exec è°ç¨å®¹å¨å mysqldump çå¤ä»½å½ä»¤ */ private List<String> buildDockerExecCommand(String container, String username, String password, String dbName) { List<String> command = new ArrayList<>(); command.add("docker"); command.add("exec"); // docker exec ä¸ä¼è½¬å宿主æºç¯å¢åéï¼å¯ç ééè¿ -e æ¾å¼ä¼ å ¥å®¹å¨ if (StrUtil.isNotEmpty(password)) { command.add("-e"); command.add("MYSQL_PWD=" + password); } command.add(container); command.add("mysqldump"); command.add("--get-server-public-key"); // 容å¨å åºå®èµ° 127.0.0.1:3306 ç TCP è¿æ¥èªèº«ï¼ç»å¼ socket/loopback 认è¯å·®å¼ command.add("-h127.0.0.1"); command.add("-P" + DOCKER_MYSQL_PORT); command.add("-u" + username); if (StrUtil.isEmpty(password)) { command.add("--skip-password"); } command.add(dbName); return command; } /** * æ§è¡ mysqldump å½ä»¤ï¼å° stdout åå ¥ä¸´æ¶æä»¶ * * @return æ§è¡æåè¿å nullï¼å¤±è´¥è¿åé误æè¿° */ private String runMysqldump(List<String> command, String password, File outputFile) { try { ProcessBuilder pb = new ProcessBuilder(command); // å¯ç éè¿ç¯å¢åéä¼ éï¼é¿å åºç°å¨å½ä»¤è¡åè¿ç¨å表 if (StrUtil.isNotEmpty(password)) { pb.environment().put("MYSQL_PWD", password); } Process process = pb.start(); // åå°è¯»å stderrï¼é²æ¢ç®¡éç¼å²åºå满导è´è¿ç¨é»å¡ StringBuilder stderr = new StringBuilder(); Thread stderrThread = new Thread(() -> { try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { stderr.append(line).append(System.lineSeparator()); } } catch (IOException ignored) { } }); stderrThread.start(); // mysqldump è¾åºåå ¥æ¬å°ä¸´æ¶æä»¶ï¼docker exec åºæ¯è¾åºä¸ä¼è½å¨å®¹å¨å æä»¶ç³»ç»ï¼ try (InputStream in = process.getInputStream(); OutputStream out = new FileOutputStream(outputFile)) { IoUtil.copy(in, out); } boolean finished = process.waitFor(BACKUP_TIMEOUT_MINUTES, TimeUnit.MINUTES); stderrThread.join(); if (!finished) { process.destroyForcibly(); return "æ§è¡è¶ æ¶ï¼è¶ è¿ " + BACKUP_TIMEOUT_MINUTES + " åéï¼"; } if (process.exitValue() != 0) { return "exitCode=" + process.exitValue() + "ï¼" + stderr.toString().trim(); } return null; } catch (IOException e) { return "å¯å¨å½ä»¤å¤±è´¥: " + e.getMessage(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return "æ§è¡è¢«ä¸æ: " + e.getMessage(); } } /** * å°å¤ä»½æä»¶ä½ä¸ºéä»¶è¾åºå°ååº */ private void downloadAttachment(HttpServletResponse response, File file, String fileName) throws IOException { response.setContentType("application/octet-stream; charset=UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(fileName, "UTF-8") + "\""); response.setContentLengthLong(file.length()); try (FileInputStream fis = new FileInputStream(file); OutputStream os = response.getOutputStream()) { IoUtil.copy(fis, os); } } /** * å°é ç½®çå½ä»¤å符串æç©ºç½ååä¸ºåæ°å表 */ private List<String> splitCommand(String command) { List<String> parts = new ArrayList<>(); for (String token : command.trim().split("\\s+")) { parts.add(token); } return parts; } /** * æ£æµæ¬æº mysqldump æ¯å¦å¯ç¨ */ private boolean isLocalMysqldumpAvailable() { try { Process process = new ProcessBuilder("mysqldump", "--version").redirectErrorStream(true).start(); boolean finished = process.waitFor(5, TimeUnit.SECONDS); if (!finished) { process.destroyForcibly(); return false; } return process.exitValue() == 0; } catch (IOException e) { return false; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } /** * æ¥æ¾è¿è¡ä¸ç MySQL Docker 容å¨ï¼ä¼å éæ©ç«¯å£æ å°ä¸æ°æ®æºç«¯å£ä¸è´çå®¹å¨ * * @return 容å¨åï¼æªæ¾å°è¿å null */ private String findMysqlDockerContainer(String dbPort) { try { Process ps = new ProcessBuilder("docker", "ps", "--format", "{{.ID}}|{{.Names}}|{{.Image}}") .redirectErrorStream(true).start(); String output = IoUtil.read(ps.getInputStream(), StandardCharsets.UTF_8); if (!ps.waitFor(10, TimeUnit.SECONDS) || ps.exitValue() != 0) { return null; } List<String[]> containers = new ArrayList<>(); for (String line : output.split("\n")) { String[] parts = line.trim().split("\\|"); if (parts.length == 3 && parts[2].startsWith("mysql")) { containers.add(parts); } } if (containers.isEmpty()) { return null; } // ä¼å éæ©ç«¯å£æ å°ä¸æ°æ®æºç«¯å£ä¸è´çå®¹å¨ for (String[] container : containers) { Process portProcess = new ProcessBuilder("docker", "port", container[0], "3306/tcp") .redirectErrorStream(true).start(); String portOutput = IoUtil.read(portProcess.getInputStream(), StandardCharsets.UTF_8); if (portProcess.waitFor(10, TimeUnit.SECONDS) && portProcess.exitValue() == 0 && portOutput.contains(dbPort)) { return container[1]; } } log.warn("æªå¹é å°ç«¯å£æ å°ç MySQL 容å¨ï¼ä½¿ç¨ç¬¬ä¸ä¸ª: {}", containers.get(0)[1]); return containers.get(0)[1]; } catch (IOException e) { log.warn("æ£æµ Docker ç¯å¢å¤±è´¥: {}", e.getMessage()); return null; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; } } } yudao-server/src/main/resources/application-dev.yaml
@@ -154,3 +154,10 @@ compress: true # æ¯å¦å缩å¾ç need-compress-size: 10MB # 触åå缩çæä»¶å¤§å°éå¼ compress-quality: 0.5 # å缩质é(0.0-1.0) database-backup: # æ°æ®åºå¤ä»½é ç½® host: 127.0.0.1 # å¤ä»½ç®æ MySQL 主æºï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ port: 3306 # å¤ä»½ç®æ MySQL 端å£ï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ database: ruoyi-vue-pro # å¤ä»½ç®æ æ°æ®åºï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ username: root # å¤ä»½è´¦å·ï¼çç©ºä½¿ç¨æ°æ®æºè´¦å· password: '123456' # å¤ä»½å¯ç ï¼çç©ºä½¿ç¨æ°æ®æºå¯ç command: # mysqldump å½ä»¤ï¼ç空èªå¨æ£æµé¨ç½²ç¯å¢ï¼æ¬æº mysqldump æ Docker MySQL 容å¨ï¼ï¼å¯æå¨æå®ï¼å¦ï¼docker exec mysql8 mysqldump yudao-server/src/main/resources/application-jhhg.yaml
@@ -220,3 +220,10 @@ compress: true # æ¯å¦å缩å¾ç need-compress-size: 10MB # 触åå缩çæä»¶å¤§å°éå¼ compress-quality: 0.5 # å缩质é(0.0-1.0) database-backup: # æ°æ®åºå¤ä»½é ç½® host: 172.17.0.1 # å¤ä»½ç®æ MySQL 主æºï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ port: 9002 # å¤ä»½ç®æ MySQL 端å£ï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ database: ruoyi-vue-pro # å¤ä»½ç®æ æ°æ®åºï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ username: root # å¤ä»½è´¦å·ï¼çç©ºä½¿ç¨æ°æ®æºè´¦å· password: 'jhhg@123456..' # å¤ä»½å¯ç ï¼çç©ºä½¿ç¨æ°æ®æºå¯ç command: # mysqldump å½ä»¤ï¼ç空èªå¨æ£æµé¨ç½²ç¯å¢ï¼æ¬æº mysqldump æ Docker MySQL 容å¨ï¼ï¼å¯æå¨æå®ï¼å¦ï¼docker exec mysql8 mysqldump yudao-server/src/main/resources/application-local.yaml
@@ -198,3 +198,10 @@ compress: true # æ¯å¦å缩å¾ç need-compress-size: 10MB # 触åå缩çæä»¶å¤§å°éå¼ compress-quality: 0.5 # å缩质é(0.0-1.0) database-backup: # æ°æ®åºå¤ä»½é ç½® host: localhost # å¤ä»½ç®æ MySQL 主æºï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ port: 3306 # å¤ä»½ç®æ MySQL 端å£ï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ database: ruoyi-vue-pro # å¤ä»½ç®æ æ°æ®åºï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ username: root # å¤ä»½è´¦å·ï¼çç©ºä½¿ç¨æ°æ®æºè´¦å· password: '123456' # å¤ä»½å¯ç ï¼çç©ºä½¿ç¨æ°æ®æºå¯ç command: # mysqldump å½ä»¤ï¼ç空èªå¨æ£æµé¨ç½²ç¯å¢ï¼æ¬æº mysqldump æ Docker MySQL 容å¨ï¼ï¼å¯æå¨æå®ï¼å¦ï¼docker exec mysql8 mysqldump yudao-server/src/main/resources/application-test.yaml
@@ -220,3 +220,10 @@ compress: true # æ¯å¦å缩å¾ç need-compress-size: 10MB # 触åå缩çæä»¶å¤§å°éå¼ compress-quality: 0.5 # å缩质é(0.0-1.0) database-backup: # æ°æ®åºå¤ä»½é ç½® host: 172.17.0.1 # å¤ä»½ç®æ MySQL 主æºï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ port: 11101 # å¤ä»½ç®æ MySQL 端å£ï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ database: ruoyi-vue-pro # å¤ä»½ç®æ æ°æ®åºï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ username: root # å¤ä»½è´¦å·ï¼çç©ºä½¿ç¨æ°æ®æºè´¦å· password: 'xd@123456..' # å¤ä»½å¯ç ï¼çç©ºä½¿ç¨æ°æ®æºå¯ç command: # mysqldump å½ä»¤ï¼ç空èªå¨æ£æµé¨ç½²ç¯å¢ï¼æ¬æº mysqldump æ Docker MySQL 容å¨ï¼ï¼å¯æå¨æå®ï¼å¦ï¼docker exec mysql8 mysqldump yudao-server/src/main/resources/application-wtxc.yaml
@@ -220,3 +220,10 @@ compress: true # æ¯å¦å缩å¾ç need-compress-size: 10MB # 触åå缩çæä»¶å¤§å°éå¼ compress-quality: 0.5 # å缩质é(0.0-1.0) database-backup: # æ°æ®åºå¤ä»½é ç½® host: 172.17.0.1 # å¤ä»½ç®æ MySQL 主æºï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ port: 9002 # å¤ä»½ç®æ MySQL 端å£ï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ database: mom-wtxc # å¤ä»½ç®æ æ°æ®åºï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ username: root # å¤ä»½è´¦å·ï¼çç©ºä½¿ç¨æ°æ®æºè´¦å· password: 'wtxc@123456..' # å¤ä»½å¯ç ï¼çç©ºä½¿ç¨æ°æ®æºå¯ç command: # mysqldump å½ä»¤ï¼ç空èªå¨æ£æµé¨ç½²ç¯å¢ï¼æ¬æº mysqldump æ Docker MySQL 容å¨ï¼ï¼å¯æå¨æå®ï¼å¦ï¼docker exec mysql8 mysqldump yudao-server/src/main/resources/application-xjln.yaml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,198 @@ server: port: 48080 --- #################### æ°æ®åºç¸å ³é ç½® #################### spring: autoconfigure: # noinspection SpringBootApplicationYaml exclude: - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # é»è®¤ local ç¯å¢ï¼ä¸å¼å¯ Quartz çèªå¨é ç½® - org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # ç¦ç¨ AI 模åç Qdrantï¼æå¨å建 - org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration # ç¦ç¨ AI 模åç Redisï¼æå¨å建 - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # ç¦ç¨ AI 模åç Milvusï¼æå¨å建 - org.dromara.trans.config.TransServiceConfig # TODO èè¿ï¼easy-trans å ¼å®¹ Spring Boot 4 åç§»é¤ï¼TransServiceConfig å¼ç¨äºå·²ç§»é¤ç RestTemplateBuilderï¼ # ç¦ç¨æªé ç½® API Key çæ¨¡åèªå¨é ç½®ï¼åªä¿çéä¹åé® Embedding çæå¨é ç½®ï¼ - org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration - org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration - org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration - org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechAutoConfiguration - org.springframework.ai.model.openai.autoconfigure.OpenAiAudioTranscriptionAutoConfiguration - org.springframework.ai.model.openai.autoconfigure.OpenAiModerationAutoConfiguration - org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration - org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration - org.springframework.ai.model.google.genai.autoconfigure.chat.GoogleGenAiChatAutoConfiguration - org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiEmbeddingConnectionAutoConfiguration - org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiTextEmbeddingAutoConfiguration - org.springframework.ai.model.ollama.autoconfigure.OllamaApiAutoConfiguration - org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration - org.springframework.ai.model.ollama.autoconfigure.OllamaEmbeddingAutoConfiguration - org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration # æ°æ®æºé 置项 datasource: druid: # Druid ãçæ§ãç¸å ³çå ¨å±é ç½® web-stat-filter: enabled: true stat-view-servlet: enabled: true allow: # 设置ç½ååï¼ä¸å¡«åå 许ææè®¿é® url-pattern: /druid/* login-username: # æ§å¶å°ç®¡çç¨æ·ååå¯ç login-password: filter: stat: enabled: true log-slow-sql: true # æ ¢ SQL è®°å½ slow-sql-millis: 100 merge-sql: true wall: config: multi-statement-allow: true dynamic: # 夿°æ®æºé ç½® druid: # Druid ãè¿æ¥æ± ãç¸å ³çå ¨å±é ç½® initial-size: 1 # åå§è¿æ¥æ° min-idle: 1 # æå°è¿æ¥æ± æ°é max-active: 20 # æå¤§è¿æ¥æ± æ°é max-wait: 60000 # é ç½®è·åè¿æ¥çå¾ è¶ æ¶çæ¶é´ï¼åä½ï¼æ¯«ç§ï¼1 åéï¼ time-between-eviction-runs-millis: 60000 # é ç½®é´éå¤ä¹ æè¿è¡ä¸æ¬¡æ£æµï¼æ£æµéè¦å ³éç空é²è¿æ¥ï¼åä½ï¼æ¯«ç§ï¼1 åéï¼ min-evictable-idle-time-millis: 600000 # é ç½®ä¸ä¸ªè¿æ¥å¨æ± 䏿å°çåçæ¶é´ï¼åä½ï¼æ¯«ç§ï¼10 åéï¼ max-evictable-idle-time-millis: 1800000 # é ç½®ä¸ä¸ªè¿æ¥å¨æ± 䏿大çåçæ¶é´ï¼åä½ï¼æ¯«ç§ï¼30 åéï¼ validation-query: SELECT 1 FROM DUAL # é ç½®æ£æµè¿æ¥æ¯å¦ææ test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true # æ¯å¦å¼å¯ PreparedStatement ç¼å max-pool-prepared-statement-per-connection-size: 20 # æ¯ä¸ªè¿æ¥ç¼åç PreparedStatement æ°é primary: master datasource: master: url: jdbc:mysql://172.17.0.1:3306/mom-xjln?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X è¿æ¥çç¤ºä¾ username: root password: xd@123456.. # Redis é ç½®ãRedisson é»è®¤çé 置足å¤ä½¿ç¨ï¼ä¸è¬ä¸éè¦è¿è¡è°ä¼ data: redis: host: 172.17.0.1 # å°å port: 6379 # ç«¯å£ database: 1 # æ°æ®åºç´¢å¼ # password: dev # å¯ç ï¼å»ºè®®ç产ç¯å¢å¼å¯ --- #################### 宿¶ä»»å¡ç¸å ³é ç½® #################### # Quartz é 置项ï¼å¯¹åº QuartzProperties é 置类 spring: quartz: auto-startup: true # æ¬å°å¼åç¯å¢ï¼å°½éä¸è¦å¼å¯ Job scheduler-name: schedulerName # Scheduler ååãé»è®¤ä¸º schedulerName job-store-type: jdbc # Job åå¨å¨ç±»åãé»è®¤ä¸º memory 表示å åï¼å¯é jdbc ä½¿ç¨æ°æ®åºã wait-for-jobs-to-complete-on-shutdown: true # åºç¨å ³éæ¶ï¼æ¯å¦çå¾ å®æ¶ä»»å¡æ§è¡å®æãé»è®¤ä¸º false ï¼å»ºè®®è®¾ç½®ä¸º true properties: # æ·»å Quartz Scheduler éå 屿§ï¼æ´å¤å¯ä»¥ç http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html ææ¡£ org: quartz: # Scheduler ç¸å ³é ç½® scheduler: instanceName: schedulerName instanceId: AUTO # èªå¨çæ instance ID # JobStore ç¸å ³é ç½® jobStore: # JobStore å®ç°ç±»ãå¯è§å客ï¼https://blog.csdn.net/weixin_42458219/article/details/122247162 class: org.springframework.scheduling.quartz.LocalDataSourceJobStore isClustered: true # æ¯éç¾¤æ¨¡å¼ clusterCheckinInterval: 15000 # éç¾¤æ£æ¥é¢çï¼åä½ï¼æ¯«ç§ãé»è®¤ä¸º 15000ï¼å³ 15 ç§ misfireThreshold: 60000 # misfire éå¼ï¼åä½ï¼æ¯«ç§ã # çº¿ç¨æ± ç¸å ³é ç½® threadPool: threadCount: 25 # çº¿ç¨æ± 大å°ãé»è®¤ä¸º 10 ã threadPriority: 5 # 线ç¨ä¼å 级 class: org.quartz.simpl.SimpleThreadPool # çº¿ç¨æ± ç±»å jdbc: # ä½¿ç¨ JDBC ç JobStore çæ¶åï¼JDBC çé ç½® initialize-schema: NEVER # æ¯å¦èªå¨ä½¿ç¨ SQL åå§å Quartz è¡¨ç»æãè¿é设置æ never ï¼æä»¬æå¨åå»ºè¡¨ç»æã --- #################### æå¡ä¿éç¸å ³é ç½® #################### # Lock4j é 置项 lock4j: acquire-timeout: 3000 # è·ååå¸å¼éè¶ æ¶æ¶é´ï¼é»è®¤ä¸º 3000 æ¯«ç§ expire: 30000 # åå¸å¼éçè¶ æ¶æ¶é´ï¼é»è®¤ä¸º 30000 æ¯«ç§ --- #################### çæ§ç¸å ³é ç½® #################### # Actuator çæ§ç«¯ç¹çé 置项 management: endpoints: web: base-path: /actuator # Actuator æä¾ç API æ¥å£çæ ¹ç®å½ãé»è®¤ä¸º /actuator exposure: include: '*' # éè¦å¼æ¾ç端ç¹ãé»è®¤å¼åªæå¼ health å info 两个端ç¹ãéè¿è®¾ç½® * ï¼å¯ä»¥å¼æ¾ææç«¯ç¹ã # Spring Boot Admin é 置项 spring: boot: admin: # Spring Boot Admin Client 客æ·ç«¯çç¸å ³é ç½® client: url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server å°å instance: service-host-type: IP # 注åå®ä¾æ¶ï¼ä¼å ä½¿ç¨ IP [IP, HOST_NAME, CANONICAL_HOST_NAME] username: admin password: admin # Spring Boot Admin Server æå¡ç«¯çç¸å ³é ç½® context-path: /admin # é ç½® Spring # å 许åµå ¥ iframe çååï¼æ¯æéé 符ï¼ï¼å®é é¨ç½²æ¶ï¼å¯ä»¥æ¹ä¸º "'self' [ä½ çå ¬ç½åå]" frame-ancestors: "'self' localhost localhost:48080 127.0.0.1 127.0.0.1:48080" # æ¥å¿æä»¶é ç½® logging: file: name: logs/${spring.application.name}.log # æ¥å¿æä»¶åï¼ç¸å¯¹äºé¡¹ç®æ ¹ç®å½ level: # é ç½®èªå·±åç MyBatis Mapper æå°æ¥å¿ cn.iocoder.yudao.module.bpm.dal.mysql: debug cn.iocoder.yudao.module.infra.dal.mysql: debug cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper: INFO # é ç½® ApiErrorLogMapper çæ¥å¿çº§å«ä¸º infoï¼é¿å å GlobalExceptionHandler éå¤æå° cn.iocoder.yudao.module.infra.dal.mysql.job.JobLogMapper: INFO # é ç½® JobLogMapper çæ¥å¿çº§å«ä¸º info cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper: INFO # é ç½® FileConfigMapper çæ¥å¿çº§å«ä¸º info cn.iocoder.yudao.module.system.dal.mysql: debug cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper: INFO # é ç½® SmsChannelMapper çæ¥å¿çº§å«ä¸º info cn.iocoder.yudao.module.member.dal.mysql: debug cn.iocoder.yudao.module.crm.dal.mysql: debug cn.iocoder.yudao.module.erp.dal.mysql: debug cn.iocoder.yudao.module.mes.dal.mysql: debug cn.iocoder.yudao.module.ai.dal.mysql: debug cn.iocoder.yudao.module.im.dal.mysql: debug org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO èè¿ï¼å ç¦ç¨ï¼Spring Boot 3.X åå¨é¨åé误ç WARN æç¤º debug: false yudao: ai: vector-store: type: milvus # åéåºç±»åï¼milvus milvus: initialize-schema: true database-name: test collection-name: knowledge_segment_xjln host: 36.213.90.123 port: 19530 --- #################### è¶ çº§ç®¡çåç¸å ³é ç½® #################### # è¶ çº§ç®¡çåé 置项ï¼è®¾ç½®å½åé¡¹ç®ææèªå®ä¹çé ç½® yudao: captcha: enable: false # æ¬å°ç¯å¢ï¼ææ¶å ³éå¾çéªè¯ç ï¼æ¹ä¾¿ç»å½çæ¥å£çæµè¯ï¼ security: mock-enable: true access-log: # è®¿é®æ¥å¿çé 置项 enable: false demo: false # å ³éæ¼ç¤ºæ¨¡å¼ storage: # æä»¶ä¸ä¼ é ç½® path: /javaWork/mom/file # æä»¶å卿 ¹ç®å½ url-prefix: /admin-api/system/storage-blob # URLè·¯å¾åç¼ domain: http://114.132.189.42:9061 # åååç¼ï¼æå®é ååé ç½®ï¼ï¼å端é 置好åä¸å¤ªå¯¹ç°å¨æ¯ç´è¿åç«¯ï¼ææè¦åç«¯ç«¯å£ expired: 120 # tokenè¿ææ¶é´ï¼åéï¼ use-limit: 10 # tokenä½¿ç¨æ¬¡æ°ä¸é jwt-secret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG # JWTå¯é¥ compress: true # æ¯å¦å缩å¾ç need-compress-size: 10MB # 触åå缩çæä»¶å¤§å°éå¼ compress-quality: 0.5 # å缩质é(0.0-1.0) yudao-server/src/main/resources/application-yzfx.yaml
@@ -220,3 +220,10 @@ compress: true # æ¯å¦å缩å¾ç need-compress-size: 10MB # 触åå缩çæä»¶å¤§å°éå¼ compress-quality: 0.5 # å缩质é(0.0-1.0) database-backup: # æ°æ®åºå¤ä»½é ç½® host: 172.17.0.1 # å¤ä»½ç®æ MySQL 主æºï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ port: 9002 # å¤ä»½ç®æ MySQL 端å£ï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ database: ruoyi-vue-pro # å¤ä»½ç®æ æ°æ®åºï¼ç空èªå¨ä»æ°æ®æº JDBC URL è§£æ username: root # å¤ä»½è´¦å·ï¼çç©ºä½¿ç¨æ°æ®æºè´¦å· password: 'yzfx@123456..' # å¤ä»½å¯ç ï¼çç©ºä½¿ç¨æ°æ®æºå¯ç command: # mysqldump å½ä»¤ï¼ç空èªå¨æ£æµé¨ç½²ç¯å¢ï¼æ¬æº mysqldump æ Docker MySQL 容å¨ï¼ï¼å¯æå¨æå®ï¼å¦ï¼docker exec mysql8 mysqldump