| README.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| pom.xml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/hwtd/mes/collect/service/impl/DataCollectionServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/hwtd/mes/collect/util/JackcessTableReader.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
README.md
@@ -1,47 +1,145 @@ ## data-acquisition 亨æºç¹å¯¼MESæ°æ®ééå¨ äº¨æºç¹å¯¼ MES æ°æ®ééå¨ï¼Spring Boot 2.6.3 / Java 8ï¼ ### 串å£ééï¼æå¨çå¬ï¼ - ééæ¹å¼ï¼ä¸²å£ï¼æå¨çå¬ï¼ãAccess æ°æ®åºãPostgreSQL - æå¡ç«¯å£ï¼9527ï¼`server.port`ï¼ - æ¥å¿ç®å½ï¼`logging.file-location`ï¼é»è®¤ `D:\mes-acquistion\logs`ï¼ - ç»ä¸è¿åæ ¼å¼ï¼`{ "code": 0, "msg": null, "data": ... }`ï¼`code=0` æåï¼`code=1` 失败ï¼åå å¨ `msg` éï¼ ä¸²å£ä¸åé项ç®å¯å¨èªå¨çå¬ï¼æ¹ä¸ºæå¨çå¬ï¼è°ç¨é鿥壿¶å¼å¯çå¬ï¼ééç»æåè°ç¨å ³éæ¥å£ã ### ä¸ã串å£ééï¼æå¨çå¬ï¼ 串å£ä¸é项ç®å¯å¨èªå¨çå¬ï¼è°ç¨å¼å¯æ¥å£æå¼ä¸²å£å¹¶åæ°ï¼ééç»æåè°ç¨å ³éæ¥å£éæ¾ä¸²å£ã | æ¥å£ | 说æ | | --- | --- | | `GET /lims/getFile?fileExtension=.serialPort` | é鿥å£ï¼è°ç¨æ¶èªå¨å¼å¯çå¬ï¼å¹¶è¿åæ¬æ¬¡ééå°çæ°æ®ï¼æ²¡ææ°æ®æ¶è¿å空éåï¼ç±åç«¯è½®è¯¢åæ° | | `GET /lims/openSerialPort` | æå¨å¼å¯çå¬ï¼å¹¶è¿åå½åæ¥æ¶å°çæ°æ®å表ï¼å¹çï¼åæ°ä¸è´æ¶å¤ç¨å·²æå¼ç串å£ï¼ | | `GET /lims/closeSerialPort` | æå¨å ³éçå¬ï¼å¹çï¼ééç»æåè°ç¨ï¼é¿å 串å£ä¸ç´è¢«å ç¨ï¼ | | `GET /lims/serialPortStatus` | æ¥è¯¢çå¬ç¶æï¼`listenName` / `listening` / `portOpen` / `dataSize` | | `GET /collection/openSerialPort` | å¼å¯ä¸²å£çå¬å¹¶è¿åå½åæ¥æ¶å°çæ°æ®åè¡¨ï¼æ²¡ææ°æ®æ¶è¿å空éåï¼ç±åç«¯è½®è¯¢åæ°ï¼ãå¹çï¼åæ°ä¸è´æ¶å¤ç¨å·²æå¼ç串å£ï¼åæ°ååæ¶ææ°åæ°éæ°æå¼ï¼å¤çº¿ç¨å¹¶åè°ç¨ä¹åªä¼æå¼ä¸æ¬¡ | | `GET /collection/closeSerialPort` | å ³é串å£çå¬ï¼å¹çï¼æªå¼å¯æ¶è°ç¨ä¹è¿åæåï¼ | | `GET /collection/serialPortStatus` | æ¥è¯¢çå¬ç¶æï¼è¿å `listenName` / `listening` / `portOpen` / `dataSize` | 串å£åæ°å ¨é¨ç±æ¥å£ä¼ å ¥ï¼`/lims/getFile`ï¼`fileExtension=.serialPort` æ¶ï¼å `/lims/openSerialPort` 齿¯æï¼ ä¸ä¼ çåæ®µä½¿ç¨é»è®¤å¼ï¼`listenName` å¿ ä¼ ï¼ãapplication.yml ä¸ç `serialPort` é 置已ä¸åçæï¼å¯ä»¥å é¤ã #### 串å£åæ°ï¼å ¨é¨ç±æ¥å£ä¼ å ¥ï¼ä¸ä¼ çåæ®µç¨é»è®¤å¼ï¼ | åæ° | é»è®¤å¼ | 说æ | | --- | --- | --- | | `listenName` | å¿ ä¼ | 串å£åç§°ï¼å¦ `COM12`ï¼ä¸ä¼ ä¼è¿åâ串å£åç§°ä¸è½ä¸ºç©ºâ | | `serialPortName` | å¿ ä¼ | 串å£åç§°ï¼å¦ `COM12`ï¼ä¸ä¼ ä¼è¿åâ串å£åç§°ä¸è½ä¸ºç©ºâ | | `baudRate` | `9600` | æ³¢ç¹ç | | `dataBits` | `8` | æ°æ®ä½ï¼5~8 | | `stopBits` | `1` | 忢ä½ï¼1ã2ã3(1.5 ä½) | | `parity` | `NONE` | æ ¡éªä½ï¼`NONE`/`ODD`/`EVEN`/`MARK`/`SPACE`ï¼ä¹å ¼å®¹ 0~4 | | `flowControl` | `NONE` | æµæ§ï¼`NONE`/`RTS_CTS`/`XON_XOFF`ï¼ä¹å ¼å®¹æ°å常é | | `parity` | `NONE` | æ ¡éªä½ï¼`NONE`/`ODD`/`EVEN`/`MARK`/`SPACE`ï¼ä¸åºå大å°åï¼ï¼ä¹å ¼å®¹ 0~4 | | `flowControl` | `NONE` | æµæ§ï¼`NONE`/`RTS_CTS`/`XON_XOFF`ï¼ä¸åºå大å°åï¼ï¼ä¹å ¼å®¹æ°å常é | | `readTimeout` | `1000` | 读åè¶ æ¶ï¼æ¯«ç§ï¼ | | `endMark` | `*` | ä¸å¸§æ°æ®çç»ææ å¿ï¼ç¹æ®åç¬¦ç¨ URL ç¼ç ï¼å¦æ¢è¡ `%0A` | | `maxCount` | `6` | æ°æ®ç¼åéåé¿åº¦ï¼éåæ»¡æ¶æå ¥ææ°ä¸æ¡ãç§»é¤ææ§ä¸æ¡ | | `endMark` | `*` | ä¸å¸§æ°æ®çç»ææ å¿ï¼ç¹æ®åç¬¦ç¨ URL ç¼ç ä¼ å ¥ï¼å¦æ¢è¡ `%0A` | | `maxCount` | `1` | æ°æ®ç¼åéåé¿åº¦ï¼éåæ»¡æ¶æå ¥ææ°ä¸æ¡ãç§»é¤ææ§ä¸æ¡ï¼é»è®¤ `1` å³åªè¿åè®¾å¤ææ°ç䏿¡ | | `frameIdleMillis` | `200` | 帧空é²è¡¥å¸§æ¶é´ï¼æ¯«ç§ï¼ï¼è¶ è¿è¯¥æ¶é´æ²¡ææ°æ°æ®ï¼å°±æç¼å²åºå©ä½æ°æ®å½æä¸å¸§ååºï¼`0` è¡¨ç¤ºå ³é | 示ä¾ï¼`/lims/getFile?fileExtension=.serialPort&listenName=COM12&baudRate=19200&parity=EVEN&endMark=%0A&maxCount=6` è°ç¨ç¤ºä¾ï¼ åæ°ä¸å½åçæåæ°ä¸è´æ¶å¤ç¨å·²ç»æå¼ç串å£ï¼ä¸ä¼é夿å¼ï¼ï¼åæ°ååæ¶ä¼èªå¨ææ°åæ°éæ°æå¼ä¸²å£ï¼ åæ°éæ³æ¶æ¥å£ç´æ¥è¿åå ·ä½åå ï¼å¦ `串å£åæ° dataBits éæ³ï¼9ï¼åå¼èå´ 5~8`ï¼ã ``` # å¼å¯çå¬å¹¶åæ°ï¼9600 8N1ãç»ææ å¿ *ï¼é»è®¤åªåææ°ä¸æ¡ï¼ http://127.0.0.1:9527/collection/openSerialPort?serialPortName=COM12 ééå°çæ°æ®æéåç¼åï¼æå ¥ææ°ç䏿¡ï¼è¶ è¿ `maxCount` æ¡ï¼é»è®¤ 6 æ¡ï¼æ¶ç§»é¤ææ§ç䏿¡ï¼ å æ¤æ¥å£æ¯æ¬¡æå¤è¿å `maxCount` æ¡ææ°çæ°æ®ï¼ææ¥æ¶å å顺åºï¼ï¼æ°æ®è¢«åèµ°åé忏 空ï¼éæ°å¼å§ç¼åã # 19200 å¶æ ¡éªã以æ¢è¡ç»å°¾ãæå¤å 6 æ¡ http://127.0.0.1:9527/collection/openSerialPort?serialPortName=COM12&baudRate=19200&parity=EVEN&endMark=%0A&maxCount=6 æ°æ®å¸§æ `endMark` ååãå¦æè®¾å¤æ¯ãæ¯å¸§ä»¥æä¸ªå符å¼å¤´ãè䏿¯ç»å°¾ï¼ä¾å¦å `*0 1 0`ã`*0 2 0`ï¼ `*` ç¨äºåéåå两帧ï¼ï¼é£ä¹æåä¸å¸§å颿²¡ææ°ç `*` æ¥è§¦åååï¼åªé ç»ææ å¿ä¼æ¼ææå䏿¡ï¼ æ¤æ¶ç± `frameIdleMillis`ï¼é»è®¤ 200msï¼å åºï¼ä¸²å£ç©ºé²è¶ è¿è¯¥æ¶é´åï¼ææ¥æ¶ç¼å²åºéå©ä¸çæ°æ®å½ä½ä¸å¸§ååºã å¦æè®¾å¤æ¯å¸§é½ä»¥ç»ææ å¿ç»å°¾ï¼ç¼å²åºæ¯æ¬¡ååå齿¯ç©ºçï¼ç©ºé²è¡¥å¸§ä¸ä¼åä»»ä½äºã # æ¥ççå¬ç¶æ / å ³éä¸²å£ http://127.0.0.1:9527/collection/serialPortStatus http://127.0.0.1:9527/collection/closeSerialPort ``` å¹¶å说æï¼å¼å¯ / å ³é串å£ç±å䏿éä¿æ¤ï¼å¤çº¿ç¨å¹¶åè°ç¨é鿥å£ä¹åªä¼æå¼ä¸æ¬¡ä¸²å£ï¼ä¸ä¼é夿³¨åçå¬å¨ï¼ ééç»æåæ¾å¨åæ¥éåä¸ï¼å¤çº¿ç¨åæ¶åæ°æ¶å䏿¡æ°æ®åªä¼è¢«ä¸ä¸ªçº¿ç¨åèµ°ï¼ä¸ä¼éå¤ä¹ä¸ä¼ä¸¢å¤±ã è°ç¨å ³éæ¥å£å忬¡è°ç¨é鿥å£ä¼éæ°å¼å¯çå¬ï¼å¹¶æ¸ æä¸ä¸è½®æ®ççåå æ°æ®åæªåèµ°çæ°æ®ï¼ä¿è¯æ¯æ¬¡ééçæ°æ®åªå±äºæ¬è½®ã #### åæ°ä¸å帧è§å - æ°æ®æ `endMark` åæä¸å¸§ä¸å¸§ï¼ - ååºæ¥çæ°æ®æ¾è¿éåç¼åï¼æå ¥ææ°ç䏿¡ï¼è¶ è¿ `maxCount` æ¡æ¶ç§»é¤ææ§ç䏿¡ï¼ å æ¤æ¥å£æ¯æ¬¡æå¤è¿å `maxCount` æ¡ææ°çæ°æ®ï¼ææ¥æ¶å å顺åºï¼ï¼æ°æ®è¢«åèµ°åé忏 空ï¼éæ°å¼å§ç¼åï¼ - è®¾å¤æåä¸å¸§å¦æä¸å¸¦ç»ææ å¿ï¼ä¾å¦æ¯å¸§ä»¥ `*` å¼å¤´ãé ä¸ä¸ä¸ª `*` åéåå两帧ï¼ï¼åªé ç»ææ å¿ä¼æ¼ææå䏿¡ï¼ æ¤æ¶ç± `frameIdleMillis` å åºï¼ä¸²å£ç©ºé²è¶ è¿è¯¥æ¶é´å°±ææ¥æ¶ç¼å²åºéå©ä¸çæ°æ®å½ä½ä¸å¸§ååºï¼ å¦æè®¾å¤æ¯å¸§é½ä»¥ç»ææ å¿ç»å°¾ï¼ç¼å²åºæ¯æ¬¡ååå齿¯ç©ºçï¼ç©ºé²è¡¥å¸§ä¸ä¼åä»»ä½äºï¼ - åæ°ä¸å½åçæåæ°ä¸è´æ¶å¤ç¨å·²ç»æå¼ç串å£ï¼ä¸ä¼é夿å¼ï¼ï¼åæ°ååæ¶ææ°åæ°éæ°æå¼ï¼ - 忬¡å¼å¯ç嬿¶ä¼æ¸ æä¸ä¸è½®æ®ççåå æ°æ®åæªåèµ°çæ°æ®ï¼ä¿è¯æ¯æ¬¡ééçæ°æ®åªå±äºæ¬è½®ï¼ - åæ°éæ³æ¶æ¥å£ç´æ¥è¿åå ·ä½åå ï¼å¦ `串å£åæ° dataBits éæ³ï¼9ï¼åå¼èå´ 5~8`ï¼ã #### å¹¶å说æ - å¼å¯ / å ³é串å£ç±å䏿éä¿æ¤ï¼éå¤è°ç¨åªä¼çæ£æå¼ä¸æ¬¡ä¸²å£ï¼ä¸ä¼é夿³¨åçå¬å¨ï¼ - ééç»æåæ¾å¨åæ¥éåä¸ï¼å¤çº¿ç¨åæ¶åæ°æ¶å䏿¡æ°æ®åªä¼è¢«ä¸ä¸ªçº¿ç¨åèµ°ï¼ä¸ä¼éå¤ä¹ä¸ä¼ä¸¢å¤±ï¼ - çå¬ç¶æä½¿ç¨ volatile æ è®°ï¼å ³é串å£ååè°çº¿ç¨ä¸åå¤çæ°æ®ã ### äºãæ°æ®åºéé | æ¥å£ | 说æ | | --- | --- | | `GET /collection/getAccessData` | éé Access æ°æ®åºï¼.mdb/.accdbï¼è¡¨æ°æ®ï¼è¿å `List<Map<String,Object>>` | | `GET /collection/getPostgreSqlData` | éé PostgreSQL è¡¨æ°æ®ï¼è¿å `List<Map<String,Object>>` | åæ°ï¼`DatabaseDTO`ï¼ï¼ | åæ° | 说æ | | --- | --- | | `filePath` | Access æ°æ®åºæä»¶è·¯å¾ï¼ä» `getAccessData`ï¼ | | `ipAddress` / `serverPort` / `databaseName` | PostgreSQL è¿æ¥ä¿¡æ¯ï¼ä» `getPostgreSqlData`ï¼ | | `userName` / `password` | æ°æ®åºç¨æ·åãå¯ç ï¼Access çæä»¶æå¼å¯ç æ¾å¨ `password`ï¼`userName` 对 Access æä»¶å 坿 ä½ç¨ï¼ | | `tableName` | æ°æ®è¡¨å | | `pointColumns` | è¦ééçåæ®µï¼å¤ä¸ªç¨éå·åé | | `mainColumn` + `batchCode` | ææ¹æ¬¡è¿æ»¤ï¼`mainColumn = batchCode` | | `orderColumn` / `orderRule` | æåºå段ä¸è§åï¼`ASC`/`DESC`ï¼ä¸ä¼ é»è®¤ `ASC`ï¼ | #### Access è¯»åæ¹å¼ï¼`mes.access.read-mode`ï¼ - `jackcess`ï¼é»è®¤ï¼ï¼jackcess åªè¯»ç´è¯»ï¼`DatabaseBuilder.setReadOnly(true).setAutoSync(false)`ï¼ï¼æå¼æä»¶åé¡ºåºæ«æå表ãåªåéè¦çåï¼ä¸æå»º HSQLDB éåï¼åªåæå¼ 表å åæ¶æå¿«ï¼ - `ucanaccess`ï¼èµ° UCanAccess ç SQL è·¯å¾ï¼éè¦ Access SQLã龿¥è¡¨ `remap` çåºæ¯æ¶ä½¿ç¨ã jackcess 模å¼çå·®å¼ï¼ - æ `mainColumn` è¿æ»¤æ¯æ´è¡¨æ«æï¼æ¬åºææ¬ç´¢å¼çæåºè§åæ¯ä¸æ 2052ï¼jackcess 䏿¯æï¼ç´¢å¼åªè¯»ãç´¢å¼æ¸¸æ ç´æ¥æå¼å¸¸ï¼ï¼æ²¡æå¯ç¨çç´¢å¼æ¥è¯¢è·¯å¾ï¼`orderColumn` æåºå¨ Java ä¾§å®æï¼ - `OLE` åæ®µä¸åæ¥ä¸æ ·è¿å `null`ï¼ä¸ä¸ä¼è¯»åå®çé¿å¼ï¼ - æ¶é´å段ç»ä¸è¿å `java.sql.Timestamp`ï¼JSON é仿¯ `yyyy-MM-dd HH:mm:ss.SSS`ï¼ï¼ - è®¾ç½®äºæå¼å¯ç çåºä¼èªå¨ç¨ `jackcess-encrypt` æå¼ï¼å¯ç å `password` åæ°ï¼Access çæä»¶å å¯åªæâæå¼å¯ç âä¸ä¸ªæ¦å¿µï¼`userName` ä¸åä¸ï¼.mdw å·¥ä½ç»çº§å®å ¨æ§ jackcess 䏿¯æï¼ï¼`ucanaccess` 模å¼ä¸æ `jackcess-encrypt` å¨ classpath ä¸ï¼`password` åæ°åæ ·å¯¹å å¯åºçæï¼ - 龿¥è¡¨çè¡ä¸ºä¸ UCanAccess ç `remap` ä¸åï¼éå°é®é¢åå `ucanaccess` 模å¼å³å¯ã ### ä¸ãæç徿 ç¨åºå¯å¨å任塿 å³ä¸è§ä¼åºç°æç徿 ï¼`SystemTrayInitializer`ï¼ï¼å³é®èåï¼ | èå项 | 说æ | | --- | --- | | æ¥ç串å£ç¶æ | å¼¹åºç¶æçªå£ï¼ä¸²å£åç§° / çå¬ç¶æ / 串å£è¿æ¥ / ç¼åæ¡æ°ï¼æ¯ç§èªå¨å·æ° | | å ³éä¸²å£ | åªå ³é串å£çå¬ï¼ä¸éåºç¨åºï¼ï¼æ¹ä¾¿ç°åºæä¸²å£éæ¾ç»å«çç¨åºç¨ï¼ç»æç¨æçæ°æ³¡æç¤º | | æå¼æ¥å¿ç®å½ | æå¼ `logging.file-location` é ç½®çæ¥å¿ç®å½ | | éåº | å å ³é Spring 容å¨ï¼è§¦å `@PreDestroy`ï¼å ³é串å£ãåæ¢è¡¥å¸§çº¿ç¨ï¼ï¼åç»æè¿ç¨ | - å廿ç徿 = æ¥ç串å£ç¶æï¼ - èåå¼¹åºå¨å æ 䏿¹ãå³è¾¹ç¼å¯¹é½ï¼ç¹èåå¤é¢ / æ ESC / åå³é®ä¸æ¬¡é½ä¼æ¶èµ·ï¼ - 徿 ä¼å ç¨ `src/main/resources/tray.png`ï¼æ¾ä¸å¼ 32x32 ç png å³å¯æ¢æèªå·±ç徿 ï¼ï¼æ²¡æå°±ç¨ä»£ç ç»çé»è®¤å¾æ ï¼ - æçä¾èµ AWTï¼å¯å¨ç±»éå·²ç» `setHeadless(false)`ï¼ä»¥ Windows æå¡æ¹å¼è¿è¡ï¼æ²¡ææ¡é¢ä¼è¯ï¼æ¶æç䏿¾ç¤ºï¼æ¥å£ä¸åå½±åã ### åãæå»ºä¸è¿è¡ ```bash # æå ï¼äº§ç© target/data-acquisition.jarï¼å¯ç´æ¥ java -jar è¿è¡ï¼ mvn clean package -DskipTests # è¿è¡ï¼éè¦ Java 8ï¼ java -jar target/data-acquisition.jar ``` - **åå» jar è¿è¡**ï¼éè¦æ `.jar` çé»è®¤æå¼æ¹å¼æå **Java 8 ç javaw.exe** ï¼å³é® jar â æå¼æ¹å¼ â éæ©å ¶ä»åºç¨ â æ´å¤åºç¨ â å¨è¿å°çµè䏿¥æ¾å ¶ä»åºç¨ â `<jdk8>\bin\javaw.exe` â å¾éâå§ç»ä½¿ç¨æ¤åºç¨æå¼ .jar æä»¶âï¼ã 注æä¸è¦æåé«çæ¬ JDKï¼æ¬é¡¹ç®æ¯ Spring Boot 2.6.3ï¼åªæ¯æ Java 8~17ï¼ï¼ä¹ä¸è¦ç¨å缩软件æå¼ï¼ - ç¨ javaw å¯å¨æ²¡ææ§å¶å°ï¼**å¯å¨å¤±è´¥ä¼å¼¹çªæç¤º**ï¼ä¾å¦ç«¯å£è¢«å ç¨ãæ¥å¿ç®å½ä¸å¯åï¼ï¼è¯¦ç»é误ç `logging.file-location` ç®å½ä¸çæ¥å¿ï¼ - éåºè¯·ç¨æçèåçãéåºãï¼ä¼æ£å¸¸å ³é串å£ï¼ï¼ç´æ¥ç»æè¿ç¨ä¸ä¼èµ°è¿å¥æ¸ çæµç¨ã ### äºãé ç½®ï¼application.ymlï¼ ```yaml server: port: 9527 # æå¡ç«¯å£ logging: file-location: D:\mes-acquistion\logs # æ¥å¿ç®å½ï¼æçâæå¼æ¥å¿ç®å½âä¹ç¨å®ï¼ #mes: # access: # read-mode: jackcess # Access è¯»åæ¹å¼ï¼jackcessï¼é»è®¤ï¼ç´è¯»å表ï¼/ ucanaccessï¼SQL 模å¼ï¼ ``` > 串å£åæ°ï¼ä¸²å£åãæ³¢ç¹çãæ°æ®ä½ãæ ¡éªä½ãç»ææ å¿çï¼å ¨é¨ç±æ¥å£ä¼ å ¥ï¼é ç½®æä»¶éä¸éè¦ä¸²å£ç¸å ³é ç½®ã pom.xml
@@ -103,6 +103,26 @@ <groupId>net.sf.ucanaccess</groupId> <artifactId>ucanaccess</artifactId> <version>5.0.1</version> <exclusions> <exclusion> <groupId>com.healthmarketscience.jackcess</groupId> <artifactId>jackcess</artifactId> </exclusion> </exclusions> </dependency> <!-- Source: https://mvnrepository.com/artifact/com.healthmarketscience.jackcess/jackcess --> <dependency> <groupId>com.healthmarketscience.jackcess</groupId> <artifactId>jackcess</artifactId> <version>4.0.10</version> <scope>compile</scope> </dependency> <!-- æå¼è®¾ç½®äºå¯ç ç Access æä»¶ï¼.mdb/.accdbï¼éè¦å® --> <dependency> <groupId>com.healthmarketscience.jackcess</groupId> <artifactId>jackcess-encrypt</artifactId> <version>4.0.3</version> <scope>compile</scope> </dependency> <dependency> src/main/java/com/hwtd/mes/collect/service/impl/DataCollectionServiceImpl.java
@@ -2,10 +2,12 @@ import com.hwtd.mes.collect.dto.DatabaseDTO; import com.hwtd.mes.collect.service.DataCollectionService; import com.hwtd.mes.collect.util.JackcessTableReader; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.sql.*; @@ -20,8 +22,24 @@ */ private final static List<String> MDB_EXCLUDE_TYPES = Arrays.asList("java.sql.Blob"); /** * Access è¯»åæ¹å¼ï¼ * jackcessï¼é»è®¤ï¼ââ ç¨ jackcess åªè¯»ç´è¯»å表ãæéååï¼ä¸æå»º HSQLDB éåï¼åªè¦è¡¨éå åæ¶æå¿«ï¼ * ucanaccess ââ èµ°åæ¥ç SQL è·¯å¾ï¼éè¦ Access SQLã龿¥è¡¨ remap çåºæ¯æ¶ä½¿ç¨ï¼ã */ @Value("${mes.access.read-mode:jackcess}") private String accessReadMode = "jackcess"; @Override public List<Map<String, Object>> getAccessData(DatabaseDTO databaseDTO) { if (databaseDTO == null || StringUtils.isBlank(databaseDTO.getFilePath())) { throw new RuntimeException("Accessæ°æ®åºæä»¶è·¯å¾ä¸è½ä¸ºç©º"); } // é»è®¤ jackcess ç´è¯»ï¼åªåæå¼ 表çå åä¸éè¦ SQL 弿ï¼ä¹ä¸ç¨çæ´ä¸ªåºéåè¿ HSQLDB if (!"ucanaccess".equalsIgnoreCase(StringUtils.trimToEmpty(accessReadMode))) { return JackcessTableReader.read(databaseDTO); } List<Map<String,Object>> list = new ArrayList<>(); try{ Properties prop = new Properties(); src/main/java/com/hwtd/mes/collect/util/JackcessTableReader.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,286 @@ package com.hwtd.mes.collect.util; import com.healthmarketscience.jackcess.Column; import com.healthmarketscience.jackcess.Cursor; import com.healthmarketscience.jackcess.CursorBuilder; import com.healthmarketscience.jackcess.DataType; import com.healthmarketscience.jackcess.Database; import com.healthmarketscience.jackcess.DatabaseBuilder; import com.healthmarketscience.jackcess.Row; import com.healthmarketscience.jackcess.Table; import com.healthmarketscience.jackcess.crypt.CryptCodecProvider; import com.healthmarketscience.jackcess.crypt.InvalidCredentialsException; import com.healthmarketscience.jackcess.crypt.InvalidCryptoConfigurationException; import com.hwtd.mes.collect.dto.DatabaseDTO; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.IOException; import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * ç¨ jackcess ç´è¯» .mdb/.accdb è¡¨æ°æ®ï¼åªè¯»ï¼ï¼ç¨äºâåªåæå¼ 表çå åâè¿ç§ç®ååºæ¯ã * <p> * ä¸ UCanAccess çåºå«ï¼UCanAccess æå¼è¿æ¥æ¶è¦ææ´ä¸ªåºéåè¿ HSQLDBï¼è¡¨å¤æ¶ææ ¢ï¼ï¼ * è¿éåªæå¼æä»¶ãé¡ºåºæ«æä¸å¼ 表ãæéååï¼å®å ¨ä¸ç»è¿ SQL 弿ã * <p> * 代价ä¸éå¶ï¼ * <ul> * <li>没æ SQLï¼æåæ®µè¿æ»¤ãæå段æåºé½å¨ Java 侧宿ï¼è¿æ»¤å¿ ç¶æ¯æ´è¡¨æ«æââæ¬åºææ¬ç´¢å¼çæåºè§åæ¯ * 䏿 2052ï¼jackcess 䏿¯æï¼ç´¢å¼åªè¯»ï¼ï¼ç´¢å¼æ¸¸æ ä¼ç´æ¥æå¼å¸¸ï¼æä»¥æ²¡æå¯ç¨çç´¢å¼æ¥è¯¢è·¯å¾ï¼</li> * <li>ç»æå ¨é¨è£ å¨å åéï¼æªå è¿æ»¤æ¡ä»¶çè¶ å¤§è¡¨è¦çæå åå ç¨ï¼</li> * <li>è®¾ç½®äºæå¼å¯ç çåºä¼èªå¨ç¨ jackcess-encrypt æå¼ï¼å¯ç å请æ±åæ° {@code password} * ï¼Access çæä»¶å å¯åªæâæå¼å¯ç âï¼{@code userName} ä¸åä¸ï¼.mdw å·¥ä½ç»çº§å®å ¨æ§ jackcess 䏿¯æï¼ï¼</li> * <li>Access 䏿åå¤é¨åºçâ龿¥è¡¨âè¡ä¸ºä¸ UCanAccess ç remap åæ°ä¸åï¼éå°é®é¢å¯åå * {@code mes.access.read-mode=ucanaccess}ã</li> * </ul> */ @Slf4j public final class JackcessTableReader { private JackcessTableReader() { } public static List<Map<String, Object>> read(DatabaseDTO databaseDTO) { File file = new File(StringUtils.trimToEmpty(databaseDTO.getFilePath())); if (!file.isFile()) { throw new RuntimeException("Accessæ°æ®åºæä»¶ä¸åå¨ï¼" + databaseDTO.getFilePath()); } if (StringUtils.isBlank(databaseDTO.getTableName())) { throw new RuntimeException("Accessæ°æ®åºè¡¨åä¸è½ä¸ºç©º"); } // setReadOnly(true)ï¼åªè¯»æå¼æä»¶ï¼ä¸äº§çéæä»¶ï¼ä¸ä¼å¹²æ° MES çå ¶ä»ç¨åºåå ¥ // setAutoSync(false)ï¼å·ççç¥åªå¨ååºæ¶çæï¼åªè¯»åºæ¯ä¿æå ³é DatabaseBuilder builder = new DatabaseBuilder(file) .setReadOnly(true) .setAutoSync(false); // æå¯ç å°±ç¨ jackcess-encrypt æå¼ï¼Access çæä»¶å å¯åªæâæå¼å¯ç âï¼ç¨æ·åä¸åä¸ String password = StringUtils.trimToNull(databaseDTO.getPassword()); if (password != null) { builder.setCodecProvider(new CryptCodecProvider(password)); } try (Database database = builder.open()) { Table table = findTable(database, databaseDTO.getTableName()); List<Column> columns = resolveColumns(table, databaseDTO.getPointColumns()); // è¿æ»¤å段ï¼çå· Column filterColumn = null; String batchCode = null; if (StringUtils.isNotBlank(databaseDTO.getMainColumn()) && StringUtils.isNotBlank(databaseDTO.getBatchCode())) { filterColumn = requireColumn(table, databaseDTO.getMainColumn(), "åæ®µ"); batchCode = databaseDTO.getBatchCode().trim(); } // æåºå段 Column orderColumn = null; if (StringUtils.isNotBlank(databaseDTO.getOrderColumn())) { orderColumn = requireColumn(table, databaseDTO.getOrderColumn(), "æåºå段"); } // æå½±ï¼åªæéè¦çåè¯»åºæ¥ï¼æªè¯·æ±çåãå°¤å ¶æ¯ OLE/MEMO è¿ç±»é¿å¼åä¸ä¼è¢«è¯»åï¼ Set<String> projection = new LinkedHashSet<>(); for (Column column : columns) { if (!isOle(column)) { projection.add(column.getName()); } } if (filterColumn != null) { projection.add(filterColumn.getName()); } if (orderColumn != null) { projection.add(orderColumn.getName()); } long start = System.currentTimeMillis(); int scanned = 0; List<RowData> rows = new ArrayList<>(); // CursorBuilder.createCursor(Table) æ¯âæ ç´¢å¼âçé¡ºåºæ«ææ¸¸æ ï¼ä¸ä¾èµä»»ä½ç´¢å¼ Cursor cursor = CursorBuilder.createCursor(table); while (cursor.moveToNextRow()) { scanned++; Row row = cursor.getCurrentRow(projection); if (filterColumn != null && !valueMatches(row.get(filterColumn.getName()), batchCode)) { continue; } Map<String, Object> data = new HashMap<>(); for (Column column : columns) { // OLE åæ®µä¸åæ¥ä¸æ ·è¿å nullï¼ä¸ä¸è¯»é¿å¼ data.put(column.getName(), isOle(column) ? null : normalize(row.get(column.getName()))); } rows.add(new RowData(data, (orderColumn == null) ? null : row.get(orderColumn.getName()))); } // æåºï¼Access ååºæ¶ null å¨åï¼éåºæ¶æç¸å顺åºï¼ if (orderColumn != null) { boolean desc = "DESC".equalsIgnoreCase(databaseDTO.getOrderRule()); Comparator<RowData> comparator = (o1, o2) -> compareValues(o1.sortKey, o2.sortKey); rows.sort(desc ? comparator.reversed() : comparator); } List<Map<String, Object>> list = new ArrayList<>(rows.size()); for (RowData row : rows) { list.add(row.data); } log.info("jackcess ç´è¯»å®æï¼è¡¨ {}ï¼æ«æ {} è¡ãè¿å {} è¡ï¼èæ¶ {} ms", table.getName(), scanned, list.size(), System.currentTimeMillis() - start); return list; } catch (InvalidCredentialsException e) { // jackcess-encrypt æ ¡éªæå¼å¯ç å¤±è´¥æ¶æè¿ä¸ªï¼IllegalStateException åç±»ï¼ throw new RuntimeException("Access æä»¶å¯ç 䏿£ç¡®ï¼" + file.getName(), e); } catch (InvalidCryptoConfigurationException e) { throw new RuntimeException("Access æä»¶çå 坿¹å¼ä¸åæ¯æï¼" + e.getMessage(), e); } catch (IOException e) { // .mdbï¼Jet å å¯ï¼æ²¡æå¯ç æ ¡éªä½ï¼å¯ç ä¸å¯¹é常表ç°ä¸ºé¡µé¢/è®°å½è¯»åå¼å¸¸ï¼è¿éè¡¥ä¸å¥æç¤º String hint = (password == null) ? "" : "ï¼è¯¥æä»¶è®¾ç½®äºæå¼å¯ç ï¼è¯·ç¡®è®¤å¯ç æ¯å¦æ£ç¡®ï¼"; throw new RuntimeException("Accessæ°æ®åºè¯»åå¼å¸¸ï¼" + e.getMessage() + hint, e); } } /** * 表åå¹é ï¼å æåååï¼åä¸å°åå䏿¬¡å¿½ç¥å¤§å°åçå¹é ï¼Access ç表åä¸åºå大å°åï¼ */ private static Table findTable(Database database, String tableName) throws IOException { String name = cleanName(tableName); Table table = database.getTable(name); if (table == null) { for (String candidate : database.getTableNames()) { if (candidate.equalsIgnoreCase(name)) { table = database.getTable(candidate); break; } } } if (table == null) { throw new RuntimeException("Accessæ°æ®åºä¸æ²¡ææ¾å°è¡¨ï¼" + tableName); } return table; } /** * è§£æééç¹ä½åæ®µï¼æ¯æéå·åéã*ã以å [åæ®µå] / `åæ®µå` è¿ç§å¸¦è½¬ä¹çåæ³ */ private static List<Column> resolveColumns(Table table, String pointColumns) { List<Column> columns = new ArrayList<>(); if ("*".equals(StringUtils.trimToEmpty(pointColumns))) { columns.addAll(table.getColumns()); return columns; } if (StringUtils.isBlank(pointColumns)) { throw new RuntimeException("ééç¹ä½å段ä¸è½ä¸ºç©º"); } for (String item : pointColumns.split(",")) { String name = cleanName(item); if (name.isEmpty()) { continue; } columns.add(requireColumn(table, name, "åæ®µ")); } if (columns.isEmpty()) { throw new RuntimeException("ééç¹ä½å段ä¸è½ä¸ºç©º"); } return columns; } private static Column requireColumn(Table table, String columnName, String what) { String name = cleanName(columnName); for (Column column : table.getColumns()) { if (column.getName().equalsIgnoreCase(name)) { return column; } } throw new RuntimeException("表 " + table.getName() + " 䏿²¡ææ¾å°" + what + "ï¼" + columnName); } private static String cleanName(String name) { String result = StringUtils.trimToEmpty(name); if (result.length() > 1 && ((result.charAt(0) == '[' && result.endsWith("]")) || (result.charAt(0) == '`' && result.endsWith("`")))) { result = result.substring(1, result.length() - 1).trim(); } return result; } private static boolean isOle(Column column) { return column.getType() == DataType.OLE; } /** * 弿¯è¾ï¼ææ¬æâå»ç©ºæ ¼ + 忽ç¥å¤§å°åâï¼ä¸åæ¥ UCanAccess è¿æ¥ï¼TRIM(åæ®µ) = 'å¼'ãignoreCase é»è®¤ trueï¼ä¿æä¸è´ */ private static boolean valueMatches(Object columnValue, String expected) { if (columnValue == null || expected == null) { return false; } if (columnValue instanceof CharSequence) { return columnValue.toString().trim().equalsIgnoreCase(expected); } return String.valueOf(columnValue).trim().equals(expected); } /** * ç±»åå½ä¸ï¼jackcess é»è®¤è¿å java.util.Dateï¼è¿éç»ä¸è½¬æ java.sql.Timestampï¼ * ä¿æä¸åæ¥ JDBC è·¯å¾è¿åçæ¶é´ç±»åä¸è´ï¼JSON éåæ ·æ¯ "yyyy-MM-dd HH:mm:ss.SSS"ï¼ */ private static Object normalize(Object value) { if (value instanceof Date) { return new Timestamp(((Date) value).getTime()); } if (value instanceof LocalDateTime) { return Timestamp.valueOf((LocalDateTime) value); } return value; } @SuppressWarnings({"unchecked", "rawtypes"}) private static int compareValues(Object value1, Object value2) { if (value1 == null && value2 == null) { return 0; } if (value1 == null) { return -1; } if (value2 == null) { return 1; } if (value1 instanceof Number && value2 instanceof Number) { if (value1 instanceof Double || value2 instanceof Double || value1 instanceof Float || value2 instanceof Float) { return Double.compare(((Number) value1).doubleValue(), ((Number) value2).doubleValue()); } return Long.compare(((Number) value1).longValue(), ((Number) value2).longValue()); } if (value1 instanceof Date && value2 instanceof Date) { return Long.compare(((Date) value1).getTime(), ((Date) value2).getTime()); } if (value1.getClass() == value2.getClass() && value1 instanceof Comparable) { return ((Comparable) value1).compareTo(value2); } return String.valueOf(value1).compareTo(String.valueOf(value2)); } /** * ä¸è¡æ°æ® + æåºé®ï¼æåºå段å¯è½ä¸å¨è¿åç»æéï¼æä»¥åç¬åä¸ä»½ï¼ */ private static final class RowData { private final Map<String, Object> data; private final Object sortKey; private RowData(Map<String, Object> data, Object sortKey) { this.data = data; this.sortKey = sortKey; } } }