当前位置: 首页 > news >正文

基于Java开发的物联网云平台:开源可二次开发,工业设备远程控制,数据采集与视频接入,支持多种...

物联网云平台工业设备对接远程控制数据采集视频接入开源可二次开发 该物联网云平台使用 Java 语言、主流技术组合开发,支持多数据源,支持代码一键生成,方便快速开发。 1、内含物联网云平台全套源码(源码全部开放,无任何加密,可二次开发)、MQTT服务、数据库设计、相关资料、相关工具软件等; 2、使用基于发布/订阅模式的轻量级通讯协议 MQTT,具有自动重连机制,具有设备上下线提醒功能; 3、支持 Modbus RTU、Modbus TCP 协议03、05功能码,亦可自定义协议接入;支持各类 DTU、透传模块以及各类工业设备接入等; 4、支持云固件升级、远程重启等;可存储各路开关状态,设备断电或重启后可自动同步云端各路开关状态,具有开关状态记忆功能; 5、云平台可对各类传感器管理,适用各种业务场景;对传感器数据有多种展示方式; 6、可配置报警规则,符合报警规则可执行自定义动作、联动报警、报警通知等; 7、控制命令下达后有执行状态反馈,可确保设备控制命令执行成功; 8、具有可视化在线定时任务配置功能,可指定某一时刻执行、周期执行、自定义 Cron 表达式执行等操作; 9、具有场景管理功能,可一键执行设定的动作; 10、具有视频管理功能,支持萤石云协议的摄像头均可接入云平台,可在云平台直接预览视频画面; 11、可记录设备、用户所有操作记录,设备、用户上下线记录等,并具有多种类型的数据统计展示; 12、云平台可对所有用户管理,每个用户可配置不同角色、不同权限,具有权限分配功能;支持对人员进行菜单、按钮及数据权限控制,亦可自定义数据权限; 13、前端采用完全响应式布局,支持电脑、平板、手机等所有主流设备; 14、Maven 多项目依赖,模块及插件分项目,尽量松耦合,方便模块升级、增减模块; 等等……内容较多,在此无法一一列举

—— 逐行拆解,让每一行代码都说话

(全文约 2.2 万字,建议收藏后按需检索)


0 前言:为什么一定要“读代码”而不是“读文档”

物联网业务本身并不复杂:设备上线 → 上报数据 → 触发规则 → 执行动作。真正让团队痛苦的是“通用能力”重复造轮子:

  • 分页、缓存、权限、多数据源、Excel、MQTT、XSS、防重、日志……
  • 每开一个新项目都要复制一遍,BUG 也随之复制。

KSoft 把上述能力沉淀到ksoft-common,并全部开源无加密

本文不再重复“功能列表”,而是一行一行把代码读给你听

  • 类签名为什么这么写?
  • 字段为什么加transient/ThreadLocal
  • 哪一行隐藏了性能陷阱?
  • 哪一行做了物联网场景的特殊补偿?

读完你能:

  1. 直接调试到 common 内部,不再“黑盒调用”;
  2. 抄走任意片段到自己项目,避免“拿来即坑”;
  3. 贡献 PR 时知道作者意图,不会“好心办坏事”。

1 包结构总览 + 阅读顺序建议

com.ksoft.common ├─ annotation ┐ 先读:所有“标记”语义,后面 AOP 会反复出现 ├─ constant │ 再读:纯常量池,零逻辑,一眼扫过 ├─ core │ 核心模型:AjaxResult、BaseEntity、分页 ├─ enums │ 枚举值与数据库字典 100% 对齐 ├─ exception │ 异常体系:一句话概括“错误码即 i18n key” ├─ config │ 配置属性:@ConfigurationProperties 用法典范 ├─ json │ 对 Jackson 的二次封装,解决“写多行”痛点 ├─ utils │ 工具大杂烩,挑高频的逐行读 └─ xss │ 过滤器链最后一环,读完后端防线就完整了

2 注解层:Java 注解如何变成“运行时能力”

2.1 `@DataScope` —— 数据权限的“语法糖”

源码位置:com.ksoft.common.annotation.DataScope

@Target(ElementType.METHOD) // 仅作用于方法 @Retention(RetentionPolicy.RUNTIME) // 运行期保留 public @interface DataScope { String deptAlias() default ""; // 表别名 String userAlias() default ""; }
  • 为什么只能标注在方法?
    数据权限需要拿到MethodSignature,类级别拿不到参数名(JDK 8 仅-parameters模式可保留),所以强制方法级。
  • 为什么默认值是空串而不是null
    空串可直接拼 SQL,省去下游StringUtils.defaultString()的防御。
2.2 `@Log` —— 操作日志的“元数据”

源码位置:com.ksoft.common.annotation.Log

@Target({ElementType.PARAMETER, ElementType.METHOD}) public @interface Log { String title() default ""; BusinessType businessType() default BusinessType.OTHER; OperatorType operatorType() default OperatorType.MANAGE; boolean isSaveRequestData() default true; }
  • 罕见地把注解打在PARAMETER
    为了支持“记录单个参数”场景:
    public AjaxResult upload(@Log(title="文件上传") MultipartFile file)
    此时 Aspect 通过((MethodSignature) pjp.getSignature()).getMethod().getParameters()能拿到参数名与注解的映射。

3 常量池:一行常量背后可能省一次 SQL

3.1 `Constants.java`
public static final String SYS_DICT_KEY = "sys_dict:";
  • 缓存 key 的前缀拼写错误会导致字典翻译失效,所以集中成常量,编译期即报错
3.2 `UserConstants.java`
int PASSWORD_MIN_LENGTH = 5; int PASSWORD_MAX_LENGTH = 20;
  • 与 Hibernate Validator 注解@Size(min = 5, max = 20)两处保持一致,否则会出现“前端提示 6-20,后端却允许 5” 的诡异体验。

4 核心模型:BaseEntity 如何“隐形”解决 90% 更新字段遗漏

源码位置:com.ksoft.common.core.domain.BaseEntity

public class BaseEntity implements Serializable { private String searchValue; // 模糊查询关键字 private String remark; // 备注 private Long createBy; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date createTime; private Long updateBy; private Date updateTime; private Integer deleteFlag; // 逻辑删除 private Map<String, Object> params; // 扩展参数(数据权限、临时排序) }
  • params设计亮点
    数据权限 SQL 片段、临时排序字段、前端自定义过滤条件,不额外建字段,全部塞进params
    MyBatis XML 里直接${params.dataScope}即可,防止实体类被非业务字段污染
  • deleteFlag采用Integer而不是Boolean
    预留“删除操作人”审计扩展:
    0=未删 1=已删 2=待审核 3=级联删 …可直接复用同一字段。

5 分页链路:PageHelper 与 TableDataInfo 的“零魔法”协作

源码位置:com.ksoft.common.core.controller.BaseController.startPage()

protected void startPage() { PageDomain pageDomain = TableSupport.buildPageRequest(); // ① Integer pageNum = pageDomain.getPageNum(); Integer pageSize = pageDomain.getPageSize(); if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize)) { String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); // ② PageHelper.startPage(pageNum, pageSize, orderBy); // ③ } }
  • ① 如何“无感”拿到前端分页参数?
    TableSupport统一从HttpServletRequestpageNum/pageSize/orderByColumn/isAsc不管 GET/POST/JSON 都能取

`java

public static PageDomain getPageDomain() {

PageDomain pageDomain = new PageDomain();

pageDomain.setPageNum(ServletUtils.getParameterToInt(Constants.PAGE_NUM));

pageDomain.setPageSize(ServletUtils.getParameterToInt(Constants.PAGE_SIZE));

pageDomain.setOrderByColumn(ServletUtils.getParameter(Constants.ORDERBYCOLUMN));

物联网云平台工业设备对接远程控制数据采集视频接入开源可二次开发 该物联网云平台使用 Java 语言、主流技术组合开发,支持多数据源,支持代码一键生成,方便快速开发。 1、内含物联网云平台全套源码(源码全部开放,无任何加密,可二次开发)、MQTT服务、数据库设计、相关资料、相关工具软件等; 2、使用基于发布/订阅模式的轻量级通讯协议 MQTT,具有自动重连机制,具有设备上下线提醒功能; 3、支持 Modbus RTU、Modbus TCP 协议03、05功能码,亦可自定义协议接入;支持各类 DTU、透传模块以及各类工业设备接入等; 4、支持云固件升级、远程重启等;可存储各路开关状态,设备断电或重启后可自动同步云端各路开关状态,具有开关状态记忆功能; 5、云平台可对各类传感器管理,适用各种业务场景;对传感器数据有多种展示方式; 6、可配置报警规则,符合报警规则可执行自定义动作、联动报警、报警通知等; 7、控制命令下达后有执行状态反馈,可确保设备控制命令执行成功; 8、具有可视化在线定时任务配置功能,可指定某一时刻执行、周期执行、自定义 Cron 表达式执行等操作; 9、具有场景管理功能,可一键执行设定的动作; 10、具有视频管理功能,支持萤石云协议的摄像头均可接入云平台,可在云平台直接预览视频画面; 11、可记录设备、用户所有操作记录,设备、用户上下线记录等,并具有多种类型的数据统计展示; 12、云平台可对所有用户管理,每个用户可配置不同角色、不同权限,具有权限分配功能;支持对人员进行菜单、按钮及数据权限控制,亦可自定义数据权限; 13、前端采用完全响应式布局,支持电脑、平板、手机等所有主流设备; 14、Maven 多项目依赖,模块及插件分项目,尽量松耦合,方便模块升级、增减模块; 等等……内容较多,在此无法一一列举

pageDomain.setIsAsc(ServletUtils.getParameter(Constants.IS_ASC));

return pageDomain;

}

`

  • ② 为什么必须SqlUtil.escapeOrderBySql()
    前端orderByColumn可能直接透传“createtime desc--”,会导致 SQL 注入。
    SqlUtil用正则[a-zA-Z0-9
    \\ \\,\\.]+暴力白名单,拒绝任何函数、子查询
  • ③ PageHelper 线程安全吗?
    PageHelper 使用ThreadLocal保存分页参数,请求结束必须finally清理,但 KSoft 借助PageHelper.startPage()的自动清理机制(MyBatis 拦截器执行后即 remove),业务代码无需手动清理

6 多数据源:1 个注解 + 2 个类完成“读写分离”

6.1 注解定义
@Target({ ElementType.METHOD, ElementType.TYPE }) public @interface DataSource { DataSourceType value() default DataSourceType.MASTER; }
6.2 切面
@Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { DataSource dataSource = getDataSource(point); if (StringUtils.isNotNull(dataSource)) { DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); } try { return point.proceed(); } finally { DynamicDataSourceContextHolder.clearDataSourceType(); // ① } }
  • ① 为什么放在finally
    防止业务异常后 ThreadLocal 未清理,导致下一次请求拿到旧数据源的“串库”事故。
6.3 动态数据源
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
  • 只读库宕机怎么办?
    Druid 会抛SQLException,业务方捕获后前端提示“查询服务繁忙”,主库仍正常写入,实现“读写分离降级”。

7 数据权限:把“可见部门”翻译成 SQL 片段

7.1 切面核心逻辑(节选)
public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) { StringBuilder sqlString = new StringBuilder(); for (SysRole role : user.getRoles()) { String dataScope = role.getDataScope(); if (DATA_SCOPE_ALL.equals(dataScope)) { sqlString = new StringBuilder(); break; // ① 拥有“全部数据权限”直接清空 } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) { sqlString.append(StringUtils.format( " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, user.getDeptId(), user.getDeptId())); } } // 拼接到 params if (StringUtils.isNotBlank(sqlString.toString())) { Object params = joinPoint.getArgs()[0]; if (params instanceof BaseEntity) { ((BaseEntity) params).getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); } } }
  • ① 为什么遇到“全部权限”就清空?
    性能:一旦角色里出现“全部数据权限”,其他角色再限制已无意义,直接return ""让 SQL 无附加条件,走索引全表扫描最快。
  • findinset 的坑
    MySQL 8.0 以前对findinset无法走索引,数据量 10w+ 会全表扫
    解决方案:
    a) 升级 8.0 并创建函数索引;
    b) 把“祖先链”拆成中间表,用IN代替findinset

8 工具类:每行代码都藏着“物联网血泪”

8.1 `CRC16Util.java` —— Modbus 校验必用
public static String getCRC16(byte[] bytes) { int CRC = 0x0000ffff; int POLYNOMIAL = 0x0000a001; for (byte b : bytes) { CRC ^= (b & 0x00ff); for (int i = 0; i < 8; i++) { if ((CRC & 0x0001) != 0) { CRC >>= 1; CRC ^= POLYNOMIAL; } else { CRC >>= 1; } } } return Integer.toHexString(CRC).toUpperCase(); }
  • 为什么高位在前?
    Modbus RTU 协议规定 CRC 低字节先发,但工控屏大多高位在前,所以工具类直接return result.substring(2, 4) + result.substring(0, 2)省得每个司机再倒一次
8.2 `DataFormatUtils.java` —— 字节序、位序、符号位一次到位
public static Float hexStr2Float(String hexStr) { hexStr = doDataWork(hexStr); return Float.intBitsToFloat(new BigInteger(hexStr, 16).intValue()); }
  • BigInteger 而不是Long.parseLong
    支持无符号 32 位(如0xFF000000超过Long.MAX_VALUE但 BigInteger 仍可解析)。
  • 字节序转换
    hexStrConvertByteOrder(hexStr, byteOrder)支持 0~7 共 8 种排列,兼容所有 PLC 厂商
8.3 `ExcelUtil.java` —— 导出 65536+ 行内存不炸
public void createWorkbook() { this.wb = new SXSSFWorkbook(500); // ① 保留 500 行在内存,其余刷盘 }
  • ① SXSSF 原理
    底层维护一个滑动窗口,窗口外行立即写入临时文件,内存占用 < 50 MB即可导出 100 万行。
    代价:临时文件需手动wb.dispose(),否则/tmp/poifiles把磁盘打满。

9 JSON 封装:让 Jackson 写多行变成“一句话”

源码位置:com.ksoft.common.json.JSON

private static final ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter(); public static String marshal(Object value) throws Exception { return objectWriter.writeValueAsString(value); }
  • ObjectWriter 线程安全
    Jackson 官方文档:ObjectWriter不可变且线程安全的,可全局单例。
    如果每次new ObjectMapper()QPS 1k 时 YoungGC 会暴涨 30%
  • 自定义 JSONObject
    提供value(name, defaultValue)多级路径访问,兼容前端 lodash.get写法:
    json.value("device.sensor.temperature", 0)
    底层用正则(\\w+)((\\[\\d+\\])+)) 解析数组下标,零依赖实现“树形取值”

10 XSS 过滤器:最后 1 道“后端保命”防线

源码位置:com.ksoft.common.xss.XssHttpServletRequestWrapper

@Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values != null) { int length = values.length; String[] escapseValues = new String[length]; for (int i = 0; i < length; i++) { escapseValues[i] = EscapeUtil.clean(values[i]).trim(); } return escapseValues; } return super.getParameterValues(name); }
  • EscapeUtil.clean 逻辑
    基于白名单其他标签全部转义
http://www.jsqmd.com/news/686045/

相关文章:

  • 2026年武汉云熵讯灵AI搜索平台费用多少钱 - 工业设备
  • 边缘计算网络架构
  • Qwen3.5-9B-GGUF快速部署:5分钟完成start.sh执行+WebUI响应验证
  • 告别联网焦虑!用HLK-V20-SUIT离线语音模块给STM32设备加个‘嘴’(附完整烧录避坑指南)
  • WeDLM-7B-Base实际作品:技术博客续写、古诗新创、科幻短篇生成效果集
  • Qwen3.5-4B-AWQ部署案例:地方政府12345热线智能应答系统落地实践
  • 从ONNX到NCNN:Android端模型部署的完整环境搭建与转换实战
  • UE5.1/5.2 Android打包:除了SDK路径,别忘了检查这三个隐藏设置
  • Oumuamua-7b-RP详细步骤:基于start.sh脚本的零基础Web UI启动教程
  • FLUX.1-Krea-Extracted-LoRA入门指南:如何用‘golden hour lighting‘增强质感
  • 2026年武汉、宜昌等地实力强的武汉云熵讯灵AI搜索方案公司Top10 - 工业品网
  • 面向对象的测试层理分类
  • 2026年安庆汽车贴膜费用大揭秘,安庆哪里贴车衣是专车专用裁膜 - 工业品网
  • RAG赋能Agent:告别业务盲区,让AI真正理解你的世界!
  • 说说常州好用的改善水质的净水活性炭,江苏竹溪活性炭靠谱吗 - 工业品牌热点
  • PyTorch炼丹时遇到OMP报错?别慌,三步搞定libiomp5md.dll冲突(附环境变量与文件删除两种方案)
  • Intv_ai_mk11处理复杂网络请求:应对Traefik网关代理的配置实践
  • STM32F103C8T6连接ZH03B传感器:一个串口采集PM2.5数据的完整流程(附代码)
  • 2026年聊聊华聊能不能执行下去,深圳靠谱的社交电商公司排名 - 工业品牌热点
  • 【实测指南】英文文章AI率86%怎么救?好用的降AI软件推荐与重构技巧
  • picclp32.ocx文件丢失找不到怎么办?免费下载方法分享
  • 2026年口碑好的网带式抛丸机/抛丸机精选厂家推荐 - 行业平台推荐
  • 【大模型微调实战】第4期:从失败到迭代终局——SFT三轮修复与DPO复盘全记录前言
  • 为什么 Cortex-M3 需要向量表?向量表为什么必须放在地址 0 附近?
  • 聊聊2026年华聊可不可以运作,深圳哪些社交软件性价比高? - 工业推荐榜
  • 前端资源加载管理
  • 用户故事管理化技术中的用户故事计划用户故事实施用户故事验证
  • 别再用暴力枚举了!PTA L1-006连续因子题,用数学优化把复杂度降下来
  • 宁波推荐工商注册公司服务费用大概多少钱 - myqiye
  • 别再只用timeNow了!CAPL时间函数全解析:从毫秒到纳秒,精准掌控你的CANoe测试时序