☰
鸿蒙设备数据上报服务端搭建:Spring Boot+MyBatis实战
2026/10/2 2:55:51 网站建设 项目流程

1. 为什么设备端都打通了,还要花力气搭一套 Web 服务端

先说一个我自己的真实感受:做鸿蒙智能硬件开发,最容易让人"嗨"起来的时刻,是把开发板上的传感器数据读出来、在屏幕或者调试串口上看到跳动的数值。那一瞬间你会觉得所有熬夜都值了。但是项目做到第五篇这个阶段,你会发现一个尴尬的问题:数据只在设备上有什么用?家里放着三台设备,今天想看看历史曲线,明天想远程配置一下上报频率,后天想让数据在网页端展示给合作方看——这些需求靠设备端那点算力和屏幕根本撑不起来。

所以这一篇的定位非常明确:服务端。我会用springboot+mybatis这套 Java 技术栈,把鸿蒙智能硬件上报的数据接收下来、存进 MySQL、再通过接口提供给前端或 App 使用。整个系列之前的文章(如果你翻过我前四篇)分别是:鸿蒙设备端开发环境搭建、基础外设控制、传感器数据采集、联网通信(Wi-Fi / TCP / HTTP 上报)。到这一篇,数据终于要从设备端"飞"到一个长久稳定的平台上。

这篇内容适合谁?适合两类人。第一类是像我这样设备端比较熟、但 Java 服务端只是"能跑通 Demo"的嵌入式 / 无线开发者——这篇文章尽量把服务端的关键点用大白话讲清楚,不绕弯子。第二类是刚入门的 Java 开发者,想看看智能硬件项目的服务端到底要处理哪些实际问题,而不是教科书里的 CRUD。如果你只是想照着抄一个能接收数据的项目,也可以直接看第 3、4 章,代码和配置都会给到。

顺便说一句:为什么我坚持在这个系列里专门花一篇写服务端?因为鸿蒙智能硬件项目的价值往往不在"采集"这一步,而在"采集之后"。服务端是你所有后续功能——告警、报表、远程控制、固件升级——的地基。地基要是随便糊弄,后面每加一个功能都要返工。这一篇我尽量把地基应该怎么打、哪些坑我已经替你踩过了,一次性说透。

2. 服务端技术选型:为什么偏偏是 Spring Boot + MyBatis

2.1 设备上报场景给服务端提的三个硬性要求

在确定技术栈之前,我们要先弄清楚:一个给智能硬件用的 Web 服务端,和普通的业务管理系统,需求有什么不一样。我总结下来有三个明显的差异点。

吞吐优先,而不是并发优先。家用智能硬件设备量不大,几十台顶天了,但每台设备可能几秒钟上报一次。高峰期一秒几十个请求是常态,但不会像电商秒杀那样出现几万并发。所以你不需要一开始就上微服务、上消息队列,反而应该把精力放在"单机能不能稳得住、数据能不能落得稳"上。Spring Boot 默认的嵌入式 Tomcat 对于这种规模绰绰有余。

数据接口要灵活,但 SQL 要可控。设备上报的数据五花八门:温度、湿度、电压、信号强度、开关状态,有时候还要携带 GPS 坐标。如果用 JPA / Hibernate 这种全自动 ORM,你写的时候很爽,但后面想对 SQL 做精细调优、想联表统计、想兼容不同设备型号的不同字段,会非常痛苦。MyBatis 的定位正好卡在这个点上:SQL 你手写,映射关系你配置,灵活但不会失去控制权。

项目规模小,但必须能平滑演进。一开始就是一个接收数据、存库、查询的小平台。但你可能很快就要加"设备管理""报警规则""数据可视化接口",甚至要对接时序数据库。Spring Boot 的生态在这方面的优势太大了——想加 Shiro/JWT 做鉴权、想加 WebSocket 做实时推送、想加 XXL-Job 做定时任务,都有非常成熟的 starter,依赖一引,配置一写就能用。

所以我的结论很直接:中小规模的鸿蒙智能硬件服务端,Spring Boot负责 Web 容器和生态整合,MyBatis负责数据访问层的灵活 SQL,MySQL负责持久化存储,这套组合是目前最省心、后续扩展成本最低的方案。

2.2 那用 Node.js / Go 行不行?

这是个很实际的问题。我见过不少做硬件的人说"我用 Node.js 写个接口不就行了"或者"Go 性能好"。确实行,但分情况。

如果你只是临时调试用,拿 Python Flask 或者 Node.js 几十行代码就能把数据接进来,我都这么干过。但一旦你要做的正经产品有后续迭代——多用户、权限、设备分组、历史数据统计、告警分发——你会发现所有"轻量方案"最后都在重复造轮子:用户体系要自己写,ORM 要自己选,鉴权要自己搞。而 Spring Boot 把这些都变成了"选一个 starter,写两行配置"。

还有一个现实问题:团队招聘。做鸿蒙智能硬件项目的公司,服务端大概率还是要靠 Java 技术栈的人来维护。你用一个冷门技术栈,后面接手的人会想打人。我这句话虽然功利,但做项目不是做实验,稳定和可维护性优先。

顺便回应一下热搜词里提到"springboot版本太高"这个问题。现在 Spring Boot 3.x 是主流,但 3.x 默认要求 Java 17,而且把javax.*换成了jakarta.*。如果你手头还有老项目要兼容,或者公司 JDK 还停在 8,那就老老实实用 Spring Boot 2.7.x 系列,别硬上 3.x。我下面给出的所有配置,2.7.x 和 3.x都通用,只有依赖坐标的写法略微不同,我会在代码里标注。

3. 骨架搭建实录:项目结构、关键配置与版本避坑

3.1 一套能直接跑通的版本组合

先给你一套我已经验证过的版本组合,照着用不会翻车。我本地环境是 Windows + JDK 1.8 的时候用 Spring Boot 2.7.18;后来换了 Mac、装了 JDK 17,就切到 Spring Boot 3.2.x,代码层面基本没改。

组件版本建议说明
JDK8(配 Boot 2.7) / 17(配 Boot 3.x)3.x 必须 JDK 17,别挣扎
Spring Boot2.7.18 或 3.2.x3.x 注意jakarta包名
MyBatis Spring Boot Starter2.3.x(对应 Boot 2.x)/ 3.0.x(对应 Boot 3.x)版本别配错,否则启动报错
MySQL5.7 或 8.x建议 8.x,驱动要用com.mysql.cj.jdbc.Driver
数据库连接池HikariCP(Boot 默认)不用额外配,调参即可
Lombok随 Boot 版本能省很多 getter/setter 代码

创建项目最简单的方式是 start.spring.io 生成,或者用 IDEA 的 Spring Initializr。依赖只需要勾选Spring Web、MyBatis Framework、MySQL Driver和Lombok,够用就好,别贪多。

3.2 目录结构:给设备端服务定一个清晰的分层

我不太喜欢那种把所有代码塞进四五个包的做法,但也不建议过度设计。下面这个结构我用了很多项目,清晰且不会让人觉得繁琐:

src/main/java/com/example/iotserver/ ├── controller/ // HTTP 接口层,只做参数接收和响应封装 ├── service/ // 业务逻辑层,处理数据校验、业务规则 ├── mapper/ // MyBatis Mapper 接口,对应 XML 里的 SQL ├── entity/ // 数据库表实体 ├── dto/ // 接口入参出参对象,和 entity 解耦 └── config/ // 配置类(分页、跨域、JSON 序列化等)

对应地,resources/mapper/目录放每个 Mapper 接口的 XML 文件,application.yml里指定这些 XML 的位置。这个分层习惯会在后面"设备上报接口设计"那一章体现出价值:接口入参变了,不影响数据库实体;数据库加字段了,不影响对外接口结构。

3.3 application.yml 里最容易踩的三个配置坑

第一是mapper-locations。很多人配了个寂寞,MyBatis 扫描不到 XML,启动不报错,但一调用 Mapper 就报Invalid bound statement (not found)。正确写法:

mybatis: # Mapper 接口对应的 XML 文件位置 mapper-locations: classpath:mapper/*.xml # 实体类别名包,这样 XML 里直接用类名,不用写全限定名 type-aliases-package: com.example.iotserver.entity

第二是 MySQL 连接串的时区。这是设备数据最常见的"灵异事件"——数据库存的时间比实际慢了 8 小时或者反过来。原因很简单:JDBC 驱动默认时区和 MySQL 服务器时区不一致。直接写上Asia/Shanghai,并且保证 MySQL 连接参数里带了serverTimezone:

spring: datasource: url: jdbc:mysql://localhost:3306/iot_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver

第三是端口冲突。开发板上报接口默认如果用 8080,本机又起了别的服务(比如前端npm run dev也占 8080),端口冲突报错一大堆。我习惯给 IoT 服务单独指定端口,比如8101,这样调试期同时跑多个服务也不会乱:

server: port: 8101

另外一个容易被忽略的细节是 Jackson 的 JSON 序列化配置。设备上报时间戳的时候,经常传一个整型毫秒值(比如1699999999000),如果你服务端返回给前端的时间是一个乱七八糟的对象格式,前端很难用。建议在application.yml里加上:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai

这样接口返回的时间字段就是标准的2024-11-15 14:30:00这种格式,前端不用再做转换。这类配置问题在热搜词里反复出现(我看有不少人在搜springboot配置、springboot版本太高),可见确实是新手重灾区,提前写在这里能帮你省两小时排查时间。

4. 设备上报接口设计:从裸数据到结构化入库

4.1 一个通用的设备数据上报接口长什么样

设备端上报数据,最常见的格式是 JSON。鸿蒙设备端用HttpURLConnection或者Ohos.Net.Http发送 POST 请求就可以了,服务端这边只需要定义一个接收接口。

我在真实项目里用的上报数据结构是这样的:

{ "deviceId": "DEVICE-001", "timestamp": 1699999999000, "type": "env", "data": { "temperature": 25.6, "humidity": 62.3, "voltage": 3.95 } }

对应到代码,就是下面这种 DTO:

@Data public class DeviceReportDTO { private String deviceId; private Long timestamp; // 毫秒时间戳 private String type; // 数据类型:env / status / alarm... private Map<String, Object> data; // 动态字段,不同设备上报不同数据 }

很多人在这里会纠结一个问题:MAP 不支持类型安全,能不能直接定义一个固化类?我的建议是:不要过早固化。智能硬件最大的特点就是数据形态不稳定——今天加一个传感器,明天换一个固件版本,字段可能就变了。用Map<String, Object>接收通用数据,入库时按需解析,远比定义十几个半死不活的具体 DTO 可行。

对应的接口层代码也非常简单:

@RestController @RequestMapping("/api/v1/device") public class DeviceReportController { @PostMapping("/report") public Result<Void> report(@RequestBody @Valid DeviceReportDTO dto) { deviceReportService.handleReport(dto); return Result.success(); } }

注意我用了一个自定义的Result<T>统一包装返回结构,这属于服务端基本功,后面下文会说。

4.2 DTO、Entity、VO 分离:为什么不能把数据库表结构直接暴露给设备端

我知道有人图省事,直接把DeviceDataEntity拿来当接口入参。小 Demo 无所谓,一旦项目变复杂就会出问题:数据库表加了字段,设备端接口也就跟着变了;你想在接口层隐藏某个敏感字段(比如设备所属用户 ID),也做不到。

我的分层习惯是:

  • DTO:接口入参,只包含设备端需要传给你的字段。
  • Entity:数据库实体,和表字段一一对应。
  • VO:接口出参,只包含前端 / 调用方需要的字段。

拿上报接口来说,设备端传过来的是一个"数据点",而你可能需要拆成两条记录:一条存到device_report表做明细,一条更新到device_status表做设备最新状态。DTO 一旦设计好了,怎么拆、怎么存,都是 service 层的事,接口不用动。

在 service 层里,我会做参数校验——比如deviceId是否为空、timestamp是否在合理范围内、data是否有内容。这类校验放在接口层做也可以,但一旦多个接口都要用,提取到 service 层更省事。还有一个小技巧:给你的服务加个统一的异常处理,用@RestControllerAdvice把校验失败、业务异常、系统异常分别映射到不同的 HTTP 状态码和错误码。设备端开发同学会非常感激你,因为调试的时候他终于能看到明确的错误原因,而不是一个笼统的 500。

4.3 建表设计:设备表、数据表、状态表怎么拆

智能硬件的表结构不需要多复杂,但有几张表是必须有的。给你们看一下我在项目里的设计,简洁但够用:

设备表device:存设备的基本静态信息(设备 ID、名称、型号、固件版本、所属用户、创建时间)。这个表是数据表的维度表。

CREATE TABLE `device` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `device_id` VARCHAR(64) NOT NULL UNIQUE, `device_name` VARCHAR(128) DEFAULT NULL, `model` VARCHAR(64) DEFAULT NULL, `firmware_version` VARCHAR(32) DEFAULT NULL, `owner_id` BIGINT DEFAULT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

上报明细表device_report:核心数据表,一条设备上报数据一条记录。注意data字段我用JSON类型(MySQL 5.7.8+ 支持),避免为了兼容不同设备类型建几十个列。

CREATE TABLE `device_report` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `device_id` VARCHAR(64) NOT NULL, `report_type` VARCHAR(32) NOT NULL, `content` JSON DEFAULT NULL, `report_time` DATETIME NOT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, KEY `idx_device_time` (`device_id`, `report_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

设备最新状态表device_status:存设备最后一次上报的摘要(温度、湿度、电压等),供仪表盘展示用。每次上报都 UPSERT 一次,查询最新的就非常快,不用ORDER BY report_time DESC LIMIT 1去翻明细表。

这两张表分工明确:明细表负责"全量历史",状态表负责"当前快照"。你后面不管做曲线图还是做设备列表,都不会被慢查询拖住。

4.4 服务端埋点:记录接口耗时和上报频率

这一节算是"经验外送"了。设备上报服务上线后,最怕的不是功能报错,而是"设备端明明连着网,但数据就是没上来"这种灰色故障。我的习惯是在 service 层打个简单的切入点,记录每个秒级的上报处理耗时,并且按deviceId做个简易计数器,发现某台设备上报频率异常(比如 30 秒没上报)就直接记日志告警。

不要一上来就整 Prometheus + Grafana,那个对你当前阶段太重了。先用日志加一个简单的内存计数就能发现问题。比如:

// 简单示例:每个设备上报后,用缓存记录最后上报时间 // 用 Caffeine 或者 ConcurrentHashMap 都行,设备量不大

如果你的设备量级能到几千台以上,再考虑接真正的监控体系。这个'后期再升级'的思路,贯穿整个服务端设计:能用简单方案解决的,绝不提前引入重组件。

5. MyBatis 从能跑到跑好:SQL 打印、自动映射、TypeHandler 与缓存

5.1 把 SQL 打到日志里:别再用 debug 猜来猜去

我接手过不少项目,看他们的application.yml,连 SQL 日志都不开。排查问题的时候全靠肉眼读 MyBatis 源码,效率极低。实际上只需要两行配置:

logging: level: com.example.iotserver.mapper: debug

这样每执行一条 SQL,MyBatis 就会把Preparing: SELECT * FROM device WHERE device_id = ?和Parameters: DEVICE-001(String)打印出来。做设备的同学可能不太理解这一步多重要——等你发现"设备上报的数据不知道存哪了"的时候,看到 SQL 日志里的参数值,你一眼就能定位是设备端传参错了还是 SQL 写错了。这是排查链路的第一步,也是最后一根救命稻草。

如果你还嫌日志吵,可以只对某个写错的 Mapper 单独开 debug,其他 Mapper 保持 info:

logging: level: com.example.iotserver.mapper.DeviceReportMapper: debug

5.2 千篇一律的坑:数据库下划线与 Java 驼峰映射

MySQL 字段命名习惯是snake_case(下划线分隔),Java 字段命名习惯是camelCase(驼峰)。不做配置的话,MyBatis 查出来的device_id列无法自动赋给deviceId属性,你会拿到一堆 null。解决方案是把自动驼峰映射打开:

mybatis: configuration: map-underscore-to-camel-case: true

就这么一行配置,很多项目恰恰漏了。开了之后,查询结果里的report_time列就会自动映射到reportTime属性上。另外再提醒下:这条配置只对下划线转驼峰有效,对resultType的自动映射有效,但 XML 里用resultMap手动指定列映射是完全不受影响的,你可以放心大胆混用。

5.3 TypeHandler:处理嵌入式设备的时间戳与布尔值

热搜词里有人专门搜mybatis中typehandler的工作流程图,说明大家已经开始抠细节了。TypeHandler 是 MyBatis 一个非常实用的扩展点,它负责 Java 类型和 JDBC 类型之间的转换。默认的类型转换其实覆盖了大多数场景,但设备上报数据里有两个特殊场景需要自定义 TypeHandler。

第一个是时间戳。设备端上报的时间经常是毫秒时间戳(Long 类型),但数据库要存DATETIME。如果你不想每次都在 SQL 或者 service 里手动转,就可以写一个TimestampTypeHandler,在写入的时候把 Long 转成java.sql.Timestamp。我还见过直接把时间戳存成 VARCHAR 的,后面查数据的时候想按时间范围查询就只能SUBSTRING了,千万别这么干。

第二个是位运算布尔值。嵌入式设备为了省流量,经常用一个整数表示多个开关状态,比如1表示开、0表示关,或者用 bit 位表示多个 GPIO 状态。你用 Java 的Boolean去接收肯定不行,需要自定义 TypeHandler 把0/1/2映射成具体的状态枚举值。这种需求在不同的智能硬件项目里千差万别,MyBatis 的BaseTypeHandler<T>提供了四个方法让你实现,思路是固定的:读出来转换成 Java 类型,写进去把 Java 类型转换成 JDBC 能识别的类型。

5.4 二级缓存:要不要开?我的结论是先别开

MyBatis 的缓存机制是经典面试题(mybatis缓存、mybatis二级缓存实现都是高热搜词),我实际开发中也会被问到。你作为硬件项目的服务端,我的建议很明确:默认别开二级缓存。

原因有两条。第一,智能硬件项目的数据实时性太强——设备温度每分钟都在变,你完全不希望读到缓存里五分钟前的旧数据。第二,二级缓存最大的坑是分布式环境下数据不一致:你一个服务单机跑没问题,一旦后面为了性能部署多实例,每台机器缓存各自为政,数据就乱套了。到时候排查起来比收益要痛苦得多。

MyBatis 一级缓存(默认开启的 SqlSession 级缓存)在单次会话内对你写接口有微小的帮助,但也不是数据一致性依赖的重点。真实的性能提升应该靠数据库索引、SQL 优化、以及必要时引入外部的 Caffeine / Redis 缓存,而不是依赖 MyBatis 自带的二级缓存。

下面给一个对应"设备最新状态"的 Mapper XML 示例,注意查询条件里对device_id建了索引,查询效率有保障:

<select id="selectLatestStatusByDeviceId" resultType="com.example.iotserver.entity.DeviceStatusEntity"> SELECT device_id, report_time, content FROM device_status WHERE device_id = #{deviceId} ORDER BY report_time DESC LIMIT 1 </select>

6. 端到端联调:鸿蒙设备真机数据入库的完整链路与排错思路

6.1 我用的完整联调流程:从串口打印到数据库 SELECT

很多文章会跳过联调这一步,但我认为对一个硬件系列来说,这是真正检验成果的地方。我的联调流程是四步走。

第一步,先确定设备端的 HTTP 发送代码没问题。用鸿蒙的@ohos.net.http模块发 POST 请求,核心代码大致是:

// 鸿蒙设备端示例 import http from '@ohos.net.http'; let httpRequest = http.createHttp(); let options = { method: http.RequestMethod.POST, header: { 'Content-Type': 'application/json' }, extraData: JSON.stringify({ deviceId: 'DEVICE-001', timestamp: Date.now(), type: 'env', data: { temperature: 25.6, humidity: 62.3 } }) }; httpRequest.request('http://192.168.1.100:8101/api/v1/device/report', options) .then((response) => { if (response.responseCode === 200) { // 上报成功 } });

注意:开发板上报请求尽量用内网 IP,别用localhost——localhost指的是设备自己,不是电脑。

第二步,在服务端日志看一眼有没有收到请求。如果你配置了 SQL debug 日志,直接看 IDEA 控制台:

2024-11-15 14:30:01.123 INFO 5172 --- [nio-8101-exec-1] c.e.iotserver.mapper.DeviceReportMapper : ==> Preparing: INSERT INTO device_report(device_id, report_type, content, report_time) VALUES (?, ?, ?, ?) 2024-11-15 14:30:01.125 INFO 5172 --- [nio-8101-exec-1] c.e.iotserver.mapper.DeviceReportMapper : ==> Parameters: DEVICE-001(String), env(String), {"temperature":25.6,"humidity":62.3}(String), 2024-11-15 14:30:01.0(Timestamp)

看到Parameters里字段都对,说明链路已经通了一半。

第三步,去 MySQL 里查数据。我习惯直接跑一条:

SELECT * FROM device_report ORDER BY id DESC LIMIT 5;

看到记录乖乖躺在表里,这才算真正打通。

第四步,验证自己写的查询接口。浏览器或者 Postman 访问GET http://localhost:8101/api/v1/device/{deviceId}/reports?page=1&size=10,返回 JSON 数组。到这一步,从设备到数据库到接口的全链路就闭环了。

6.2 三次典型的线上事故排查,新手最容易踩

事故一:数据丢失,日志却显示成功。

有一次我同事反馈,设备上报后明细表里找不到数据。查了半天,发现他用的是INSERT OR REPLACE,而因为业务主键设计得有问题(把自动递增 ID 当成了唯一键),导致新数据不断覆盖旧数据。排查思路就是先看日志里的 SQL,再看主键设计。这个教训是:老后悔当初没把device_id + report_time设成联合唯一键。

事故二:时间不对,全部少了 8 小时。

这个非常典型,我在前面配置里已经提前埋了解决方案:数据库连接串加serverTimezone=Asia/Shanghai,Spring Jackson 配time-zone: Asia/Shanghai,MySQL 服务器时区也要顺手确认。如果设备端传的是 UTC 时间,服务端拿到的还是 UTC,你转换不对就全部偏 8 小时。排查方法:先确认设备端Date.now()出的是什么,再确认服务端收到的是什么,最后看数据库存的是什么,三步一对比,问题出在哪个环节一目了然。

事故三:Invalid bound statement (not found)。

这个问题在热搜词里高频出现。现象是启动不报错,一调接口就报错。原因基本就是mapper-locations配错了,或者 XML 文件里的 namespace 和 Mapper 接口全限定名不一致。我排查的顺序是:先看target/classes下面有没有把 XML 打进去(IDEA 经常把 XML 漏打包),再看 namespace,最后看 XML 里的 statement id 和接口方法名是不是一致。三步下来基本能定位。

6.3 稳定性自查清单:上线前的五个小检查

服务端可以"跑通"和可以"上线",中间隔着一整套检查清单。我的个人习惯清单如下:

  • [ ] 数据库索引有没有建?device_report表按device_id + report_time建联合索引了吗?
  • [ ] 时间字段到底统一成什么时区?代码里有没有到处Date new Date()而没有统一转换?
  • [ ] 参数校验是否覆盖?deviceId为空、timestamp 为负数、content 超过长度,都有对应的错误响应吗?
  • [ ] 统一异常处理有没有配?数据库连接失败、SQL 执行失败,返回给设备端的是什么?
  • [ ] 连接池参数要不要调一调?HikariCP 默认 10 个连接,几十台设备够用了,但如果你还要做管理后台查询,可以适当加到 20。

这套清单里,最容易被忽略也最致命的是第一条。设备量一大,SELECT * FROM device_report WHERE device_id = ? ORDER BY report_time DESC没有索引就会全表扫描,直接拖死数据库。你哪怕只写一句"建索引",也要在项目里落实。

7. 服务端只是起点:后续设备管理平台的扩展思路

写到这,正文的"硬核部分"其实已经结束了。但我知道你一定会问:数据入库了,然后呢?

我给你的建议是走一条"简单到复杂"的渐进路线。当下最急的不是上各种中间件,而是把三件小事做好:一是把设备在线状态做出来(离线 30 秒自动打标记),二是把最近 24 小时的数据曲线接口做了,三是把设备的远程配置接口预留出来。这三件事全都可以在现有的 Spring Boot + MyBatis + MySQL 架构上完成,不需要引入任何新组件。

等设备量真正涨到一定程度,再考虑往下两条路扩展:

第一条路是接入时序数据库。上报数据本质上就是时序数据。InfluxDB 或者 TDengine 在存储效率、聚合查询、按时间下采样这些能力上,比 MySQL 强一个量级。到时候 MySQL 保留设备档案、人员等关系型数据,时序库存设备原始数据,两者各司其职。

第二条路是引入消息队列。当上报频率高到单机 Tomcat 线程池扛不住,或者你想做设备数据的分流处理(一个写明细、一个算告警、一个推前端),就可以在设备端和服务端之间加一层 Kafka 或 RocketMQ。这个改造对现有的接口层是透明的——因为设备端本来就是往 HTTP 接口 POST 数据,你只需要在 service 层把同步写入改为投递到 MQ,消费端再做入库,设备端代码都不用动。

不过,这些都是后面几篇的内容。就目前这个阶段,你只要把这篇的 Spring Boot + MyBatis 服务端跑起来,把鸿蒙设备上报的数据稳稳存进数据库,就已经走完了智能硬件项目里"从设备到云端"最核心的一环。后面不管是做 App、做小程序还是做 Web 管理后台,你都有一条健康的数据管道可以用。

最后再分享一个小技巧。我每次搭新的服务端项目,都会在service层故意留一个"上报成功但内容为空"的日志分支,用于测试预警逻辑。这样既能确认链路活着,又不会因为满屏日志把真正的问题淹没。这种“构造问题”的思路,和设备端写测试用例是相通的——你别等故障真的发生了才开始调试,平时就要给自己留一双眼睛。

8. 系列回顾与下一步预告

写到这里,第五篇的内容基本划上句号了。这个系列从鸿蒙设备端的环境搭建开始,到传感器数据采集、设备联网通信,再到这一篇的 Spring Boot + MyBatis 服务端——设备上报的数据终于有了一个稳定的"家"。我自己跑通这条链路的时候,那种"设备端的灯一亮,数据库里的记录就多一条"的实感,是纯做服务端或者纯做嵌入式都体会不到的项目完整感。

基于我个人经验,再给你几个实操层面的提醒:如果你现在还卡在环境配置那一步,别急着往下抄代码,先把application.yml的三个坑(mapper-locations、时区、端口)填了,至少能节省你后面两个小时的排查时间;如果你已经能跑通上报接口,我建议你优先把设备状态表和明细表分开设计,这个决策会直接决定你后面做设备列表和曲线图时的SQL复杂度。

接下来如果这个系列继续往下写,我会优先安排设备管理后台的前端页面(Vue 或者鸿蒙自身的 ArkUI 都有可能),配合服务端接口做一套可视化看板。如果你更想了解服务端如何对接 TCP 长连接上报、或者怎么用 WebSocket 把设备状态实时推到前端,也可以留言告诉我,我会挑一个最有价值的场景作为下一篇的主题。路都是一步一步走出来的,系列连载的好处就在这——每一篇都留一个钩子,让你有期待,也让我有方向。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询