## data-acquisition 亨旺特导 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 /collection/openSerialPort` | 开启串口监听并返回当前接收到的数据列表(没有数据时返回空集合,由前端轮询取数)。幂等:参数一致时复用已打开的串口,参数变化时按新参数重新打开,多线程并发调用也只会打开一次 | | `GET /collection/closeSerialPort` | 关闭串口监听(幂等,未开启时调用也返回成功) | | `GET /collection/serialPortStatus` | 查询监听状态,返回 `listenName` / `listening` / `portOpen` / `dataSize` | #### 串口参数(全部由接口传入,不传的字段用默认值) | 参数 | 默认值 | 说明 | | --- | --- | --- | | `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`(不区分大小写),也兼容数字常量 | | `readTimeout` | `1000` | 读取超时(毫秒) | | `endMark` | `*` | 一帧数据的结束标志,特殊字符用 URL 编码传入,如换行 `%0A` | | `maxCount` | `1` | 数据缓存队列长度:队列满时插入最新一条、移除最旧一条;默认 `1` 即只返回设备最新的一条 | | `frameIdleMillis` | `200` | 帧空闲补帧时间(毫秒):超过该时间没有新数据,就把缓冲区剩余数据当成一帧取出;`0` 表示关闭 | 调用示例: ``` # 开启监听并取数(9600 8N1、结束标志 *,默认只取最新一条) http://127.0.0.1:9527/collection/openSerialPort?serialPortName=COM12 # 19200 偶校验、以换行结尾、最多取 6 条 http://127.0.0.1:9527/collection/openSerialPort?serialPortName=COM12&baudRate=19200&parity=EVEN&endMark=%0A&maxCount=6 # 查看监听状态 / 关闭串口 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>` | | `GET /collection/getPostgreSqlData` | 采集 PostgreSQL 表数据,返回 `List>` | 参数(`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 → 打开方式 → 选择其他应用 → 更多应用 → 在这台电脑上查找其他应用 → `\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 模式) ``` > 串口参数(串口名、波特率、数据位、校验位、结束标志等)全部由接口传入,配置文件里不需要串口相关配置。