commit 572ad1205f978bba52964f5d3aad3ded57f79ad5 Author: rqian <1206436827@qq.com> Date: Fri Apr 17 10:09:03 2026 +0800 first commit diff --git a/maibu-admin/pom.xml b/maibu-admin/pom.xml new file mode 100644 index 0000000..3c496f6 --- /dev/null +++ b/maibu-admin/pom.xml @@ -0,0 +1,93 @@ + + + 4.0.0 + + com.maibu + MiddlePlatform + 4.0.0 + + + maibu-admin + + + + + org.springframework.boot + spring-boot-devtools + true + + + + com.maibu + maibu-netty-server + ${maibu.version} + + + com.maibu + maibu-webrtc-server + ${maibu.version} + + + + com.maibu + maibu-web-middleware + ${maibu.version} + + + + com.maibu + maibu-external + ${maibu.version} + + + + com.maibu + maibu-iotDA + ${maibu.version} + + + + + io.springfox + springfox-boot-starter + + + + + io.swagger + swagger-models + 1.6.2 + + + + + mysql + mysql-connector-java + + + + + com.maibu + maibu-framework + + + + com.maibu + maibu-common + + + + com.github.ben-manes.caffeine + caffeine + + + + + 8 + 8 + UTF-8 + + + \ No newline at end of file diff --git a/maibu-admin/src/main/java/com/maibu/MiddlePlatformApplication.java b/maibu-admin/src/main/java/com/maibu/MiddlePlatformApplication.java new file mode 100644 index 0000000..157652b --- /dev/null +++ b/maibu-admin/src/main/java/com/maibu/MiddlePlatformApplication.java @@ -0,0 +1,48 @@ +package com.maibu; + +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; +import com.dtflys.forest.springboot.annotation.ForestScan; +import com.fastbee.netty.NettyServer; +import com.fastbee.websocket.WebsocketHandler; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * 启动程序 + * + * @author ruoyi + */ +@SpringBootApplication(exclude = { DruidDataSourceAutoConfigure.class }) +@ForestScan(basePackages = "com.maibu") +public class MiddlePlatformApplication +{ + + public static void main(String[] args) + { + // System.setProperty("spring.devtools.restart.enabled", "false"); + ConfigurableApplicationContext context = SpringApplication.run(MiddlePlatformApplication.class, args); + + WebsocketHandler websocketHandler = new WebsocketHandler(9002); + websocketHandler.start(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + // 关闭WebSocket服务(核心:释放9002端口) + System.out.println("开始释放websocket资源"); + try { + websocketHandler.stopServer(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + // 关闭Spring上下文 + context.close(); + }, "shutdown-hook-thread")); + + NettyServer nettyServer = context.getBean(NettyServer.class); + nettyServer.start(); + + + + } + +} diff --git a/maibu-admin/src/main/resources/META-INF/spring-devtools.properties b/maibu-admin/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 0000000..37e7b58 --- /dev/null +++ b/maibu-admin/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1 @@ +restart.include.json=/com.alibaba.fastjson2.*.jar \ No newline at end of file diff --git a/maibu-admin/src/main/resources/application-dev.yml b/maibu-admin/src/main/resources/application-dev.yml new file mode 100644 index 0000000..80aebdd --- /dev/null +++ b/maibu-admin/src/main/resources/application-dev.yml @@ -0,0 +1,112 @@ +# 数据源配置 +spring: + datasource: + dynamic: + primary: master + strict: false + datasource: + master: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://1.95.137.212:3306/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: fastbee + password: maibu520 + # slave: + # type: com.alibaba.druid.pool.DruidDataSource + # driver-class-name: com.mysql.cj.jdbc.Driver + # url: jdbc:mysql://localhost:3306/fastbee1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + # username: root + # password: fastbee + sharding: + url: jdbc:shardingsphere:classpath:sharding-sphere-config.yaml + driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver + taos: # 配置 taos 数据源 + enabled: false + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.taosdata.jdbc.TSDBDriver + url: jdbc:TAOS://fastbee:6030/fastbee_log?timezone=UTC-8&charset=utf-8 + username: root + password: taosdata + dbName: fastbee_log + + # redis 配置 + redis: + host: localhost # 地址 + port: 6379 # 端口,默认为6379 + database: 3 # 数据库索引 + #password: # 密码 + timeout: 10s # 连接超时时间 + lettuce: + pool: + min-idle: 0 # 连接池中的最小空闲连接 + max-idle: 8 # 连接池中的最大空闲连接 + max-active: 8 # 连接池的最大数据库连接数 + max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) + # mqtt 配置 + mqtt: + username: fastbee # 账号 + password: fastbee # 密码 + host-url: tcp://localhost:1883 # mqtt连接tcp地址 + client-id: ${random.int} # 客户端Id,不能相同,采用随机数 ${random.value} + default-topic: test # 默认主题 + timeout: 30 # 超时时间 + keepalive: 30 # 保持连接 + clearSession: true # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) + servlet: + multipart: + enabled: true + max-file-size: 10MB + max-request-size: 10MB + +# sip 配置 +sip: + enabled: false # 是否启用视频监控SIP,true为启用 + ## 本地调试时,绑定网卡局域网IP,设备在同一局域网,设备接入IP填写绑定IP + ## 部署服务端时,默认绑定容器IP,设备接入IP填写服务器公网IP + ip: 177.7.0.13 + port: 5061 # SIP端口(保持默认) + domain: 3402000000 # 由省级、市级、区级、基层编号组成 + id: 34020000002000000001 # 同上,另外增加编号,(可保持默认) + password: 12345678 # 监控设备接入的密码 + log: true + zlmRecordPath: /opt/media/bin/www + +# 日志配置 +logging: + level: + com.fastbee: debug + com.yomahub: debug + org.dromara: warn + org.springframework: warn + +# Swagger配置 +swagger: + enabled: true # 是否开启swagger + pathMapping: /dev-api # 请求前缀 + + +ali: + sms: + access-key-id: LTAI5tFxmTZAeFBNa7sqcHUj + access-key-secret: vtHYhEWrh585jleku55TbHhtYscmGU + endpoint: dysmsapi.aliyuncs.com + sign-name: 飒沓机器人 + template-code: SMS_498590140 + expired-time: 1 + +UAV: + token: eyJhbGciOiJIUzUxMiIsImNyaXQiOlsidHlwIiwiYWxnIiwia2lkIl0sImtpZCI6IjhiZmRiZmRkLWM4OGYtNGE5Yi04NzI3LWQ0ZGYzYWE5OTJlOSIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50IjoiODQ5MDczQHFxLmNvbSIsImV4cCI6MjA1OTg2ODc3NCwibmJmIjoxNzQ0MzM1OTc0LCJvcmdhbml6YXRpb25fdXVpZCI6ImYwMmIwZTM4LWUyZjgtNGNhMi04ZTBmLWM1YmVlMTM3NDk2ZiIsInByb2plY3RfdXVpZCI6IiIsInN1YiI6ImZoMiIsInVzZXJfaWQiOiIxMDEyOSJ9.t9KH3SOBNxJwi3bZwA7diGV7qeLZY2cXI9ovT5FnkWXLjSHNYHD4KQObGi1iGJ5igAxZZaWIooT8v8VcPwsrSQ + projectUId: bb191b8f-5110-4536-82e3-f56730e93e4c + baseUrl: https://es-flight-api-cn.djigate.com + +ioTDA: + serverIp: 2d8fd0f2a9.st1.iotda-device.cn-north-4.myhuaweicloud.com + deviceId: 698551ef7f2e6c302f519144_MC700AIR-CN-JS-1762486208349-0000001A-FD + secret: d940bccd7b591848eadab9a24d332d52 + serviceId: MowingRobot + REGION_ID: cn-north-4 + ENDPOINT: 2d8fd0f2a9.st1.iotda-app.cn-north-4.myhuaweicloud.com + ak: HPUAQHOL0WPXJ1BX3SDV + sk: 06B2ex4JkeGjE7PfWLsYt7PdXN2xvBxEjotPbkaQ + projectId: df32e2f94c15428a9763f01f4f0d337c + productId: 698551ef7f2e6c302f519144 \ No newline at end of file diff --git a/maibu-admin/src/main/resources/application-prod.yml b/maibu-admin/src/main/resources/application-prod.yml new file mode 100644 index 0000000..c61757c --- /dev/null +++ b/maibu-admin/src/main/resources/application-prod.yml @@ -0,0 +1,91 @@ +# 数据源配置 +spring: + datasource: + dynamic: + primary: master + strict: false + datasource: + master: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: maibu520 + filters: stat,wall + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true +# slave: +# type: com.alibaba.druid.pool.DruidDataSource +# driver-class-name: com.mysql.cj.jdbc.Driver +# url: jdbc:mysql://localhost:3306/fastbee1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 +# username: root +# password: fastbee + sharding: + url: jdbc:shardingsphere:classpath:sharding-sphere-config.yaml + driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver + taos: # 配置 taos 数据源 + enabled: true + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.taosdata.jdbc.TSDBDriver + url: jdbc:TAOS://fastbee:6030/fastbee_log?timezone=UTC-8&charset=utf-8 + username: root + password: taosdata + dbName: fastbee_log + # redis 配置 + redis: + host: 177.7.0.10 # 地址 + port: 6379 # 端口,默认为6379 + database: 0 # 数据库索引 + password: fastbee # 密码 + timeout: 10s # 连接超时时间 + lettuce: + pool: + min-idle: 0 # 连接池中的最小空闲连接 + max-idle: 8 # 连接池中的最大空闲连接 + max-active: 8 # 连接池的最大数据库连接数 + max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) + # mqtt 配置 + mqtt: + username: fastbee # 账号(仅用于后端自认证) + password: fastbee # 密码(仅用于后端自认证) + host-url: tcp://177.7.0.12:1883 # 连接 Emqx 消息服务器地址 + # host-url: tcp://177.7.0.13:1883 # 内置netty mqtt broker地址 + client-id: ${random.int} # 客户端Id,不能相同,采用随机数 ${random.value} + default-topic: test # 默认主题 + timeout: 30 # 超时时间 + keepalive: 30 # 保持连接 + clearSession: true # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) + +# sip 配置 +sip: + enabled: true # 是否启用视频监控SIP,true为启用 + ## 本地调试时,绑定网卡局域网IP,设备在同一局域网,设备接入IP填写绑定IP + ## 部署服务端时,默认绑定容器IP,设备接入IP填写服务器公网IP + ip: 177.7.0.13 + port: 5061 # SIP端口(保持默认) + domain: 3402000000 # 由省级、市级、区级、基层编号组成 + id: 34020000002000000001 # 同上,另外增加编号,(可保持默认) + password: 12345678 # 监控设备接入的密码 + log: false + zlmRecordPath: /opt/media/bin/www + +# 日志配置 +logging: + level: + com.fastbee: debug + com.yomahub: warn + org.dromara: warn + org.springframework: warn + +# Swagger配置 +swagger: + enabled: true # 是否开启swagger + pathMapping: /prod-api # 请求前缀 diff --git a/maibu-admin/src/main/resources/application-sql.yml b/maibu-admin/src/main/resources/application-sql.yml new file mode 100644 index 0000000..98c80e1 --- /dev/null +++ b/maibu-admin/src/main/resources/application-sql.yml @@ -0,0 +1,91 @@ +# 数据源配置 +spring: + datasource: + dynamic: + primary: master + strict: false + datasource: + master: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: maibu520 + filters: stat,wall + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true +# slave: +# type: com.alibaba.druid.pool.DruidDataSource +# driver-class-name: com.mysql.cj.jdbc.Driver +# url: jdbc:mysql://localhost:3306/fastbee1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 +# username: root +# password: fastbee + sharding: + url: jdbc:shardingsphere:classpath:sharding-sphere-config.yaml + driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver + taos: # 配置 taos 数据源 + enabled: true + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.taosdata.jdbc.TSDBDriver + url: jdbc:TAOS://fastbee:6030/fastbee_log?timezone=UTC-8&charset=utf-8 + username: root + password: taosdata + dbName: fastbee_log + # redis 配置 + redis: + host: localhost # 地址 + port: 6379 # 端口,默认为6379 + database: 3 # 数据库索引 + password: # 密码 + timeout: 10s # 连接超时时间 + lettuce: + pool: + min-idle: 0 # 连接池中的最小空闲连接 + max-idle: 8 # 连接池中的最大空闲连接 + max-active: 8 # 连接池的最大数据库连接数 + max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) + # mqtt 配置 + mqtt: + username: fastbee # 账号 + password: fastbee # 密码 + host-url: tcp://localhost:1883 # mqtt连接tcp地址 + client-id: ${random.int} # 客户端Id,不能相同,采用随机数 ${random.value} + default-topic: test # 默认主题 + timeout: 30 # 超时时间 + keepalive: 30 # 保持连接 + clearSession: true # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) + +# sip 配置 +sip: + enabled: true # 是否启用视频监控SIP,true为启用 + ## 默认为容器IP,IDE启动可以写本地网卡内网IP或者127.0.0.1 + ## 本地调试需保持设备与服务器在同一局域网 + ip: 192.168.31.165 + port: 5061 # SIP端口(保持默认) + domain: 3402000000 # 由省级、市级、区级、基层编号组成 + id: 34020000002000000001 # 同上,另外增加编号,(可保持默认) + password: 12345678 # 监控设备接入的密码 + log: true + zlmRecordPath: /opt/media/bin/www + +# 日志配置 +logging: + level: + com.fastbee: debug + com.yomahub: debug + org.dromara: warn + org.springframework: warn + +# Swagger配置 +swagger: + enabled: true # 是否开启swagger + pathMapping: /dev-api # 请求前缀 + diff --git a/maibu-admin/src/main/resources/application.yml b/maibu-admin/src/main/resources/application.yml new file mode 100644 index 0000000..3969893 --- /dev/null +++ b/maibu-admin/src/main/resources/application.yml @@ -0,0 +1,218 @@ +# 项目相关配置 +fastbee: + name: fastbee # 名称 + version: 3.8.5 # 版本 + copyrightYear: 2023 # 版权年份 + demoEnabled: true # 实例演示开关 + # 文件路径,以uploadPath结尾 示例( Windows配置 D:/uploadPath,Linux配置 /uploadPath) + profile: D:/uploadPath + addressEnabled: true # 获取ip地址开关 + captchaType: math # 验证码类型 math 数组计算 char 字符验证 + +# 开发环境配置 +server: + port: 8081 # 服务器的HTTP端口,默认为8080 + servlet: + context-path: / # 应用的访问路径 + tomcat: + uri-encoding: UTF-8 # tomcat的URI编码 + accept-count: 1000 # 连接数满后的排队数,默认为100 + threads: + max: 800 # tomcat最大线程数,默认为200 + min-spare: 100 # Tomcat启动初始化的线程数,默认值10 + # 基于netty的服务器 + broker: + enabled: false # mqttBroker类型选择, true: 基于netty的mqttBroker和webSocket false: emq的mqttBroker + port: 1883 + websocket-port: 8083 + websocket-path: /mqtt + keep-alive: 70 # 默认的全部客户端心跳上传时间 + #TCP服务端口 + tcp: + enabled: true # 控制tcp端口是否开启 + port: 8888 + keep-alive: 70 + delimiter: 0x7e + udp: + enabled: false # 控制udp端口是否开启 + port: 8889 + read-idle: 300 # udp保活时间 默认5分钟 + http: + enabled: false + port: 8081 + auth: + type: Basic # 支持Basic,Digest + user: + name: fastbee + password: fastbee + coap: + enabled: false + port: 5683 + +# Spring配置 +spring: + # 环境配置,dev=开发环境,prod=生产环境 + profiles: + active: dev # 环境配置,dev=开发环境,prod=生产环境 + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + # 文件上传 + servlet: + multipart: + max-file-size: 10MB # 单个文件大小 + max-request-size: 20MB # 设置总上传的文件大小 + # 服务模块 + devtools: + restart: + enabled: true # 热部署开关 + task: + execution: + pool: + core-size: 20 # 最小连接数 + max-size: 200 # 最大连接数 + queue-capacity: 3000 # 最大容量 + keep-alive: 60 + # 缓存配置 + cache: + enable: false + type: none # none=不使用缓存 redis=使用redis缓存 + ttl: 1800 # 缓存过期时间(默认60秒) + datasource: + druid: + filters: stat,wall + filter: + stat: + enabled: true + webStatFilter: + enabled: true + stat-view-servlet: + enabled: true + allow: + url-pattern: /druid/* + loginUsername: fastbee + loginPassword: fastbee + + +# 用户配置 +user: + password: + maxRetryCount: 5 # 密码最大错误次数 + lockTime: 10 # 密码锁定时间(默认10分钟) + +# token配置 +token: + header: Authorization # 令牌自定义标识 + secret: abcdefghijklfastbeesmartrstuvwxyz # 令牌密钥 + expireTime: 1440 # 令牌有效期(默认30分钟)1440为一天 + +# MyBatis配置 +#mybatis: +# typeAliasesPackage: com.fastbee.**.domain # 搜索指定包别名 +# mapperLocations: classpath*:mapper/**/*Mapper.xml # 配置mapper的扫描,找到所有的mapper.xml映射文件 +# configLocation: classpath:mybatis/mybatis-config.xml # 加载全局的配置文件 + +# mybatis-plus配置 +mybatis-plus: + typeAliasesPackage: com.fastbee.**.domain # 搜索指定包别名 + mapperLocations: classpath*:mapper/**/*Mapper.xml # 配置mapper的扫描,找到所有的mapper.xml映射文件 + configLocation: classpath:mybatis/mybatis-config.xml # 加载全局的配置文件 + global-config: + db-config: + id-type: AUTO # 自增 ID + logic-delete-value: 1 # 逻辑已删除值(默认为 1) + logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) + +# PageHelper分页插件 +pagehelper: + helperDialect: mysql + supportMethodsArguments: true + params: count=countSql + +# 防止XSS攻击 +xss: + enabled: true # 过滤开关 + excludes: /system/notice # 排除链接(多个用逗号分隔) + urlPatterns: /system/*,/monitor/*,/tool/* # 匹配链接 + +# EMQX API配置需要在运行emqx之后去EMQX管理后台手动创建API Key和Secret,并配置到application.yml中 +emqx: + host: localhost # EMQX服务器地址 + port: 18083 + ApiKey: ApiKey # EMQX API Key + ApiSecret: ApiSecret # EMQX API Secret + +# redisson 配置 +redisson: + # redis key前缀 + keyPrefix: sql_cache + # 线程池数量 + threads: 16 + # Netty线程池数量 + nettyThreads: 32 + # 单节点配置 + singleServerConfig: + # 客户端名称 + clientName: ${fastbee.name} + # 最小空闲连接数 + connectionMinimumIdleSize: 32 + # 连接池大小 + connectionPoolSize: 64 + # 连接空闲超时,单位:毫秒 + idleConnectionTimeout: 10000 + # 命令等待超时,单位:毫秒 + timeout: 3000 + # 发布和订阅连接池大小 + subscriptionConnectionPoolSize: 50 + +forest: # Forest配置 版本为1.5.36 + backend: okhttp3 # 后端HTTP框架(默认为 okhttp3) + max-connections: 1000 # 连接池最大连接数(默认为 500) + max-route-connections: 500 # 每个路由的最大连接数(默认为 500) + max-request-queue-size: 100 # [自v1.5.22版本起可用] 最大请求等待队列大小 + max-async-thread-size: 300 # [自v1.5.21版本起可用] 最大异步线程数 + max-async-queue-size: 16 # [自v1.5.22版本起可用] 最大异步线程池队列大小 + connect-timeout: 3000 # 连接超时时间,单位为毫秒(默认为 timeout) + read-timeout: 3000 # 数据读取超时时间,单位为毫秒(默认为 timeout) + max-retry-count: 0 # 请求失败后重试次数(默认为 0 次不重试) + ssl-protocol: TLS # 单向验证的HTTPS的默认TLS协议(默认为 TLS) + log-enabled: true # 打开或关闭日志(默认为 true) + log-request: true # 打开/关闭Forest请求日志(默认为 true) + log-response-status: true # 打开/关闭Forest响应状态日志(默认为 true) + log-response-content: true # 打开/关闭Forest响应内容日志(默认为 false) + async-mode: platform # [自v1.5.27版本起可用] 异步模式(默认为 platform) + +liteflow: + #FlowExecutor的execute2Future的线程数,默认为64 + main-executor-works: 64 + #FlowExecutor的execute2Future的自定义线程池Builder + main-executor-class: com.fastbee.ruleEngine.config.MainExecutorBuilder + #并行节点的线程池Builder + thread-executor-class: com.fastbee.ruleEngine.config.WhenExecutorBuilder + rule-source-ext-data-map: + # 应用名称,规则链和脚本组件名称需要一致,不要修改 + applicationName: fastbee + #是否开启SQL日志 + sqlLogEnabled: true + # 规则多时,启用快速加载模式 + fast-load: false + #是否开启SQL数据轮询自动刷新机制 默认不开启 + pollingEnabled: false + pollingIntervalSeconds: 60 + pollingStartSeconds: 60 + #以下是chain表的配置 + chainTableName: iot_scene + chainApplicationNameField: application_name + chainNameField: chain_name + elDataField: el_data + chainEnableField: enable + #以下是script表的配置 + scriptTableName: iot_script + scriptApplicationNameField: application_name + scriptIdField: script_id + scriptNameField: script_name + scriptDataField: script_data + scriptTypeField: script_type + scriptLanguageField: script_language + scriptEnableField: enable diff --git a/maibu-admin/src/main/resources/banner.txt b/maibu-admin/src/main/resources/banner.txt new file mode 100644 index 0000000..b8ebf46 --- /dev/null +++ b/maibu-admin/src/main/resources/banner.txt @@ -0,0 +1,2 @@ +Application Version: ${fastbee.version} +Spring Boot Version: ${spring-boot.version} \ No newline at end of file diff --git a/maibu-admin/src/main/resources/i18n/messages.properties b/maibu-admin/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..a17c1e4 --- /dev/null +++ b/maibu-admin/src/main/resources/i18n/messages.properties @@ -0,0 +1,210 @@ +#\u9519\u8BEF\u6D88\u606F +not.null=* \u5FC5\u987B\u586B\u5199 +user.jcaptcha.error=\u9A8C\u8BC1\u7801\u9519\u8BEF +user.jcaptcha.expire=\u9A8C\u8BC1\u7801\u5DF2\u5931\u6548 +user.not.exists=\u7528\u6237\u4E0D\u5B58\u5728/\u5BC6\u7801\u9519\u8BEF +user.password.not.match=\u7528\u6237\u4E0D\u5B58\u5728/\u5BC6\u7801\u9519\u8BEF +user.password.retry.limit.count=\u5BC6\u7801\u8F93\u5165\u9519\u8BEF{0}\u6B21 +user.password.retry.limit.exceed=\u5BC6\u7801\u8F93\u5165\u9519\u8BEF{0}\u6B21\uFF0C\u5E10\u6237\u9501\u5B9A{1}\u5206\u949F +user.password.delete=\u5BF9\u4E0D\u8D77\uFF0C\u60A8\u7684\u8D26\u53F7\u5DF2\u88AB\u5220\u9664 +user.blocked=\u7528\u6237\u5DF2\u5C01\u7981\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +role.blocked=\u89D2\u8272\u5DF2\u5C01\u7981\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +user.logout.success=\u9000\u51FA\u6210\u529F + +length.not.valid=\u957F\u5EA6\u5FC5\u987B\u5728{min}\u5230{max}\u4E2A\u5B57\u7B26\u4E4B\u95F4 + +user.username.not.valid=* 2\u523020\u4E2A\u6C49\u5B57\u3001\u5B57\u6BCD\u3001\u6570\u5B57\u6216\u4E0B\u5212\u7EBF\u7EC4\u6210\uFF0C\u4E14\u5FC5\u987B\u4EE5\u975E\u6570\u5B57\u5F00\u5934 +user.password.not.valid=* 5-50\u4E2A\u5B57\u7B26 + +user.email.not.valid=\u90AE\u7BB1\u683C\u5F0F\u9519\u8BEF +user.mobile.phone.number.not.valid=\u624B\u673A\u53F7\u683C\u5F0F\u9519\u8BEF +user.login.success=\u767B\u5F55\u6210\u529F +user.register.success=\u6CE8\u518C\u6210\u529F +user.notfound=\u8BF7\u91CD\u65B0\u767B\u5F55 +user.forcelogout=\u7BA1\u7406\u5458\u5F3A\u5236\u9000\u51FA\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55 +user.unknown.error=\u672A\u77E5\u9519\u8BEF\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55 + +##\u6743\u9650 +no.permission=\u60A8\u6CA1\u6709\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.create.permission=\u60A8\u6CA1\u6709\u521B\u5EFA\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.update.permission=\u60A8\u6CA1\u6709\u4FEE\u6539\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.delete.permission=\u60A8\u6CA1\u6709\u5220\u9664\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.export.permission=\u60A8\u6CA1\u6709\u5BFC\u51FA\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.view.permission=\u60A8\u6CA1\u6709\u67E5\u770B\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] + +##\u6587\u4EF6\u4E0A\u4F20\u6D88\u606F +upload.exceed.maxSize=\u4E0A\u4F20\u7684\u6587\u4EF6\u5927\u5C0F\u8D85\u51FA\u9650\u5236\u7684\u6587\u4EF6\u5927\u5C0F\uFF01
\u5141\u8BB8\u7684\u6587\u4EF6\u6700\u5927\u5927\u5C0F\u662F\uFF1A{0}MB\uFF01 +upload.filename.exceed.length=\u4E0A\u4F20\u7684\u6587\u4EF6\u540D\u6700\u957F{0}\u4E2A\u5B57\u7B26 +upload.success=\u4E0A\u4F20\u6210\u529F + +##\u6587\u4EF6\u4E0B\u8F7D\u6D88\u606F +download.filename.not.valid=\u6587\u4EF6\u540D\u79F0[{}]\u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u4E0B\u8F7D +download.file.failed=\u4E0B\u8F7D\u6587\u4EF6\u5931\u8D25 +download.resource.not.valid=\u8D44\u6E90\u6587\u4EF6[{}]\u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u4E0B\u8F7D + +##Dept +dept.add.failed.name.exists=\u65B0\u589E\u673A\u6784[{}]\u5931\u8D25\uFF0C\u673A\u6784\u540D\u79F0\u5DF2\u5B58\u5728 +dept.update.failed.name.exists=\u4FEE\u6539\u673A\u6784[{}]\u5931\u8D25\uFF0C\u673A\u6784\u540D\u79F0\u5DF2\u5B58\u5728 +dept.update.failed.parent.not.valid=\u4FEE\u6539\u673A\u6784[{}]\u5931\u8D25\uFF0C\u4E0A\u7EA7\u673A\u6784\u4E0D\u80FD\u662F\u81EA\u5DF1 +dept.update.failed.child.not.valid=\u8BE5\u673A\u6784\u5305\u542B\u672A\u505C\u7528\u7684\u5B50\u673A\u6784\uFF01 +dept.delete.failed.child.exists=\u5B58\u5728\u4E0B\u7EA7\u673A\u6784\uFF0C\u4E0D\u5141\u8BB8\u5220\u9664 +dept.delete.failed.user.exists=\u673A\u6784\u5B58\u5728\u7528\u6237\uFF0C\u4E0D\u5141\u8BB8\u5220\u9664 + +##Dict +dict.add.failed.type.exists=\u65B0\u589E\u5B57\u5178[{}]\u5931\u8D25\uFF0C\u5B57\u5178\u7C7B\u578B\u5DF2\u5B58\u5728 +dict.update.failed.type.exists=\u65B0\u589E\u5B57\u5178[{}]\u5931\u8D25\uFF0C\u5B57\u5178\u7C7B\u578B\u5DF2\u5B58\u5728 + +##Index +index.welcome.message=\u6B22\u8FCE\u4F7F\u7528{}\u540E\u53F0\u7BA1\u7406\u6846\u67B6\uFF0C\u5F53\u524D\u7248\u672C\uFF1Av{}\uFF0C\u8BF7\u901A\u8FC7\u524D\u7AEF\u5730\u5740\u8BBF\u95EE\u3002 + +##Menu +menu.add.failed.name.exists=\u65B0\u589E\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u83DC\u5355\u540D\u79F0\u5DF2\u5B58\u5728 +menu.add.failed.path.not.valid=\u65B0\u589E\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u5730\u5740\u5FC5\u987B\u4EE5http(s)://\u5F00\u5934 +menu.update.failed.name.exists=\u4FEE\u6539\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u83DC\u5355\u540D\u79F0\u5DF2\u5B58\u5728 +menu.update.failed.path.not.valid=\u4FEE\u6539\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u5730\u5740\u5FC5\u987B\u4EE5http(s)://\u5F00\u5934 +menu.update.failed.parent.not.valid=\u4FEE\u6539\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u4E0A\u7EA7\u83DC\u5355\u4E0D\u80FD\u9009\u62E9\u81EA\u5DF1 +menu.delete.failed.child.exists=\u5B58\u5728\u5B50\u83DC\u5355,\u4E0D\u5141\u8BB8\u5220\u9664 +menu.delete.failed.role.exists=\u83DC\u5355\u5DF2\u5206\u914D,\u4E0D\u5141\u8BB8\u5220\u9664 + +##Post +post.add.failed.name.exists=\u65B0\u589E\u5C97\u4F4D[{}]\u5931\u8D25\uFF0C\u5C97\u4F4D\u540D\u79F0\u5DF2\u5B58\u5728 +post.add.failed.code.exists=\u65B0\u589E\u5C97\u4F4D[{}]\u5931\u8D25\uFF0C\u5C97\u4F4D\u7F16\u7801\u5DF2\u5B58\u5728 +post.update.failed.name.exists=\u4FEE\u6539\u5C97\u4F4D[{}]\u5931\u8D25\uFF0C\u5C97\u4F4D\u540D\u79F0\u5DF2\u5B58\u5728 +post.update.failed.code.exists=\u4FEE\u6539\u5C97\u4F4D[{}]\u5931\u8D25\uFF0C\u5C97\u4F4D\u7F16\u7801\u5DF2\u5B58\u5728 + +##User +user.username.exists=\u7CFB\u7EDF\u8D26\u53F7\u540D\u79F0\u5DF2\u5B58\u5728\uFF0C\u8BF7\u4FEE\u6539\u540E\u91CD\u8BD5 +user.password.differ=\u4E24\u6B21\u5BC6\u7801\u4E0D\u4E00\u81F4\uFF0C\u8BF7\u91CD\u65B0\u8F93\u5165 +user.add.failed.name.exists=\u65B0\u589E\u7528\u6237[{}]\u5931\u8D25\uFF0C\u767B\u5F55\u8D26\u53F7\u5DF2\u5B58\u5728 +user.add.failed.phone.exists=\u65B0\u589E\u7528\u6237[{}]\u5931\u8D25\uFF0C\u624B\u673A\u53F7\u7801\u5DF2\u5B58\u5728 +user.add.failed.email.exists=\u65B0\u589E\u7528\u6237[{}]\u5931\u8D25\uFF0C\u90AE\u7BB1\u8D26\u53F7\u5DF2\u5B58\u5728 +user.update.failed.password.wrong=\u4FEE\u6539\u5BC6\u7801\u5931\u8D25\uFF0C\u65E7\u5BC6\u7801\u9519\u8BEF +user.update.failed.password.repeat=\u65B0\u5BC6\u7801\u4E0D\u80FD\u4E0E\u65E7\u5BC6\u7801\u76F8\u540C +user.update.password.failed=\u4FEE\u6539\u5BC6\u7801\u5F02\u5E38\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +user.update.failed.name.exists=\u65B0\u589E\u7528\u6237[{}]\u5931\u8D25\uFF0C\u767B\u5F55\u8D26\u53F7\u5DF2\u5B58\u5728 +user.update.failed.phone.exists=\u4FEE\u6539\u7528\u6237[{}]\u5931\u8D25\uFF0C\u624B\u673A\u53F7\u7801\u5DF2\u5B58\u5728 +user.update.failed.email.exists=\u4FEE\u6539\u7528\u6237[{}]\u5931\u8D25\uFF0C\u90AE\u7BB1\u8D26\u53F7\u5DF2\u5B58\u5728 +user.update.failed=\u4FEE\u6539\u4E2A\u4EBA\u4FE1\u606F\u5F02\u5E38\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +user.delete.failed=\u5F53\u524D\u7528\u6237\u4E0D\u80FD\u5220\u9664 +user.upload.avatar.failed=\u4E0A\u4F20\u56FE\u7247\u5F02\u5E38\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +user.not.login=\u8BF7\u767B\u5F55\u540E\u91CD\u8BD5 +user.access.denied=\u7528\u6237\u62D2\u7EDD\u8BBF\u95EE + +##Role +role.add.manager.failed=\u4E0D\u5141\u8BB8\u8BBE\u7F6E\u7BA1\u7406\u5458\u89D2\u8272\u6807\u8BC6 +role.add.failed.name.exists=\u65B0\u589E\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u89D2\u8272\u540D\u79F0\u5DF2\u5B58\u5728 +role.add.failed.key.exists=\u65B0\u589E\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u89D2\u8272\u6743\u9650\u5DF2\u5B58\u5728 +role.update.failed.name.exists=\u4FEE\u6539\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u89D2\u8272\u540D\u79F0\u5DF2\u5B58\u5728 +role.update.failed.key.exists=\u4FEE\u6539\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u89D2\u8272\u6743\u9650\u5DF2\u5B58\u5728 +role.update.failed=\u4FEE\u6539\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 + +##Import +import.failed.file.null=\u5BFC\u5165\u5931\u8D25\uFF0C\u8BF7\u5148\u4E0A\u4F20\u6587\u4EF6\uFF01 +import.failed.data.null=\u5BFC\u5165\u5931\u8D25\uFF0C\u5BFC\u5165\u6570\u636E\u4E3A\u7A7A\uFF01 +import.failed.nettyDevice.name.null=\u5BFC\u5165\u5931\u8D25\uFF0C\u6A21\u677F\u91CC\u8BBE\u5907\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\uFF01 +import.success=\u5BFC\u5165\u6210\u529F + +##General +success=\u6210\u529F +fail=\u5931\u8D25 +query.success=\u67E5\u8BE2\u6210\u529F +operate.success=\u64CD\u4F5C\u6210\u529F +create.success=\u521B\u5EFA\u6210\u529F +create.failed=\u521B\u5EFA\u5931\u8D25 +save.success=\u4FDD\u5B58\u6210\u529F +save.failed=\u4FDD\u5B58\u5931\u8D25 +authorization.success=\u6388\u6743\u6210\u529F + +##Email +email.format.error=\u90AE\u7BB1\u683C\u5F0F\u9519\u8BEF +email.verification.code.send=\u90AE\u7BB1\u9A8C\u8BC1\u7801\u5DF2\u53D1\u9001 + +##Firmware +firmware.task.upgrade.failed.time.not.valid=\u9884\u5B9A\u5347\u7EA7\u65F6\u95F4\u5E94\u5927\u4E8E\u5F53\u524D\u65F6\u95F4 +##Media +media.record.query.failed=\u8FDE\u63A5\u8D85\u65F6\u6216\u53D1\u751F\u9519\u8BEF\uFF0C\u672A\u83B7\u53D6\u5230\u6570\u636E +##Modbus +modbus.type.null=\u7C7B\u578B\u4E3A\u7A7A +##Netty +netty.client.not.exists=\u5BA2\u6237\u7AEF\u4E0D\u5B58\u5728 +##Runtime +runtime.message.id.null=\u6D88\u606Fid\u4E3A\u7A7A +##Wechat +wechat.verify.type.null=\u8BF7\u4F20\u5165\u9A8C\u8BC1\u65B9\u5F0F +wechat.bind.message.id.null=\u8BF7\u4F20\u5165\u7ED1\u5B9A\u4FE1\u606FID +##AuthResource +auth.resource.product.query.success=\u67E5\u8BE2\u4EA7\u54C1\u5217\u8868\u6210\u529F +##Device +nettyDevice.user.id.null=\u7528\u6237ID\u4E0D\u80FD\u4E3A\u7A7A +nettyDevice.product.id.null=\u8BBE\u5907\u7F16\u53F7\u548C\u4EA7\u54C1ID\u4E0D\u80FD\u4E3A\u7A7A +nettyDevice.dept.id.null=\u8BF7\u9009\u62E9\u5206\u914D\u673A\u6784 +nettyDevice.id.null=\u8BF7\u9009\u62E9\u8BBE\u5907 +##DeviceJob +job.add.failed.cron.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0CCron\u8868\u8FBE\u5F0F\u4E0D\u6B63\u786E +job.add.failed.rmi.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'rmi'\u8C03\u7528 +job.add.failed.ldap.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'ldap(s)'\u8C03\u7528 +job.add.failed.http.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'http(s)'\u8C03\u7528 +job.add.failed.string.error=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u5B58\u5728\u8FDD\u89C4 +job.add.failed.string.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5728\u767D\u540D\u5355\u5185 +job.update.failed.cron.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0CCron\u8868\u8FBE\u5F0F\u4E0D\u6B63\u786E +job.update.failed.rmi.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'rmi'\u8C03\u7528 +job.update.failed.ldap.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'ldap(s)'\u8C03\u7528 +job.update.failed.http.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'http(s)'\u8C03\u7528 +job.update.failed.string.error=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u5B58\u5728\u8FDD\u89C4 +job.update.failed.string.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5728\u767D\u540D\u5355\u5185 +job.not.exists=\u4EFB\u52A1\u4E0D\u5B58\u5728\u6216\u5DF2\u8FC7\u671F +##DeviceUser +nettyDevice.user.delete.failed.user.not.valid=\u8BBE\u5907\u6240\u6709\u8005\u4E0D\u80FD\u5220\u9664 +##GoviewProject +goview.project.data.save.failed.id.null=\u6CA1\u6709\u8BE5\u9879\u76EEID +goview.project.data.execute.sql.failed=\u8BF7\u7F16\u5199sql\u8BED\u53E5 +##ThingsModel +things.model.identifier.repeat=\u4EA7\u54C1\u4E0B\u7684\u6807\u8BC6\u7B26\u4E0D\u80FD\u91CD\u590D +things.model.import.failed.identifier.repeat=[{}]\u6761\u6570\u636E\u672A\u5BFC\u5165\uFF0C\u6807\u8BC6\u7B26\u91CD\u590D +##MQTT +mqtt.unauthorized=mqtt\u8D26\u53F7\u548C\u5BC6\u7801\u4E0E\u8BA4\u8BC1\u670D\u52A1\u5668\u914D\u7F6E\u4E0D\u5339\u914D +##Oauth +oauth.response.type.not.valid=response_type\u53C2\u6570\u503C\u53EA\u5141\u8BB8code\u548Ctoken +oauth.grant.type.null=\u672A\u77E5\u6388\u6743\u7C7B\u578B +oauth.grant.type.implicit.not.support=Token\u63A5\u53E3\u4E0D\u652F\u6301implicit\u6388\u6743\u6A21\u5F0F +oauth.access.token.null=\u8BBF\u95EE\u4EE4\u724C\u4E0D\u80FD\u4E3A\u7A7A +obtain.basic.authorization.failed=client_id\u6216client_secret\u672A\u6B63\u786E\u4F20\u9012 +##Record +record.app.null=app\u4E0D\u80FD\u4E3A\u7A7A +record.stream.null=stream\u4E0D\u80FD\u4E3A\u7A7A +record.time.not.valid=\u9519\u8BEF\u7684\u5F00\u59CB\u65F6\u95F4\u6216\u7ED3\u675F\u65F6\u95F4 +record.file.null=\u672A\u627E\u5230\u89C6\u9891\u6587\u4EF6 + +##ErrorCodeConstants +app.not.found=App \u4E0D\u5B58\u5728 +app.is.disable=App \u5DF2\u7ECF\u88AB\u7981\u7528 +app.exist.order.cant.delete=\u652F\u4ED8\u5E94\u7528\u5B58\u5728\u652F\u4ED8\u8BA2\u5355\uFF0C\u65E0\u6CD5\u5220\u9664 +app.exist.refund.cant.delete=\u652F\u4ED8\u5E94\u7528\u5B58\u5728\u9000\u6B3E\u8BA2\u5355\uFF0C\u65E0\u6CD5\u5220\u9664 +channel.not.found=\u652F\u4ED8\u6E20\u9053\u7684\u914D\u7F6E\u4E0D\u5B58\u5728 +channel.is.disable=\u652F\u4ED8\u6E20\u9053\u5DF2\u7ECF\u7981\u7528 +channel.exists.same.channel.error=\u5DF2\u5B58\u5728\u76F8\u540C\u7684\u6E20\u9053 +order.not.found=\u652F\u4ED8\u8BA2\u5355\u4E0D\u5B58\u5728 +order.status.is.not.waiting=\u652F\u4ED8\u8BA2\u5355\u4E0D\u5904\u4E8E\u5F85\u652F\u4ED8 +order.status.is.success=\u8BA2\u5355\u5DF2\u652F\u4ED8\uFF0C\u8BF7\u5237\u65B0\u9875\u9762 +order.is.expired=\u652F\u4ED8\u8BA2\u5355\u5DF2\u7ECF\u8FC7\u671F +order.submit.channel.error=\u53D1\u8D77\u652F\u4ED8\u62A5\u9519\uFF0C\u9519\u8BEF\u7801\uFF1A{}\uFF0C\u9519\u8BEF\u63D0\u793A\uFF1A{} +order.refund.fail.status.error=\u652F\u4ED8\u8BA2\u5355\u9000\u6B3E\u5931\u8D25\uFF0C\u539F\u56E0\uFF1A\u72B6\u6001\u4E0D\u662F\u5DF2\u652F\u4ED8\u6216\u5DF2\u9000\u6B3E +order.extension.not.found=\u652F\u4ED8\u4EA4\u6613\u62D3\u5C55\u5355\u4E0D\u5B58\u5728 +order.extension.status.is.not.waiting=\u652F\u4ED8\u4EA4\u6613\u62D3\u5C55\u5355\u4E0D\u5904\u4E8E\u5F85\u652F\u4ED8 +order.extension.is.paid=\u8BA2\u5355\u5DF2\u652F\u4ED8\uFF0C\u8BF7\u7B49\u5F85\u652F\u4ED8\u7ED3\u679C +refund.price.exceed=\u9000\u6B3E\u91D1\u989D\u8D85\u8FC7\u8BA2\u5355\u53EF\u9000\u6B3E\u91D1\u989D +refund.has.refunding=\u5DF2\u7ECF\u6709\u9000\u6B3E\u5728\u5904\u7406\u4E2D +refund.exists=\u5DF2\u7ECF\u5B58\u5728\u9000\u6B3E\u5355 +refund.not.found=\u652F\u4ED8\u9000\u6B3E\u5355\u4E0D\u5B58\u5728 +refund.statue.is.not.waiting=\u652F\u4ED8\u9000\u6B3E\u5355\u4E0D\u5904\u4E8E\u5F85\u9000\u6B3E +demo.order.not.found=\u793A\u4F8B\u8BA2\u5355\u4E0D\u5B58\u5728 +demo.order.update.paid.status.not.unpaid=\u793A\u4F8B\u8BA2\u5355\u66F4\u65B0\u652F\u4ED8\u72B6\u6001\u5931\u8D25\uFF0C\u8BA2\u5355\u4E0D\u662F\u3010\u672A\u652F\u4ED8\u3011\u72B6\u6001 +demo.order.update.paid.fail.pay.order.id.error=\u793A\u4F8B\u8BA2\u5355\u66F4\u65B0\u652F\u4ED8\u72B6\u6001\u5931\u8D25\uFF0C\u652F\u4ED8\u5355\u7F16\u53F7\u4E0D\u5339\u914D +demo.order.update.paid.fail.pay.order.status.not.success=\u793A\u4F8B\u8BA2\u5355\u66F4\u65B0\u652F\u4ED8\u72B6\u6001\u5931\u8D25\uFF0C\u652F\u4ED8\u5355\u72B6\u6001\u4E0D\u662F\u3010\u652F\u4ED8\u6210\u529F\u3011\u72B6\u6001 +demo.order.update.paid.fail.pay.price.not.match=\u793A\u4F8B\u8BA2\u5355\u66F4\u65B0\u652F\u4ED8\u72B6\u6001\u5931\u8D25\uFF0C\u652F\u4ED8\u5355\u91D1\u989D\u4E0D\u5339\u914D +demo.order.refund.fail.not.paid=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u793A\u4F8B\u8BA2\u5355\u672A\u652F\u4ED8 +demo.order.refund.fail.refunded=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u793A\u4F8B\u8BA2\u5355\u5DF2\u9000\u6B3E +demo.order.refund.fail.refund.not.found=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u9000\u6B3E\u8BA2\u5355\u4E0D\u5B58\u5728 +demo.order.refund.fail.refund.not.success=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u9000\u6B3E\u8BA2\u5355\u672A\u9000\u6B3E\u6210\u529F +demo.order.refund.fail.refund.order.id.error=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u9000\u6B3E\u5355\u7F16\u53F7\u4E0D\u5339\u914D +demo.order.refund.fail.refund.price.not.match=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u9000\u6B3E\u5355\u91D1\u989D\u4E0D\u5339\u914D diff --git a/maibu-admin/src/main/resources/i18n/messages_en_US.properties b/maibu-admin/src/main/resources/i18n/messages_en_US.properties new file mode 100644 index 0000000..eea9a3a --- /dev/null +++ b/maibu-admin/src/main/resources/i18n/messages_en_US.properties @@ -0,0 +1,211 @@ +#\u9519\u8BEF\u6D88\u606F +not.null=* \u5FC5\u987B\u586B\u5199 +user.jcaptcha.error=\u9A8C\u8BC1\u7801\u9519\u8BEF +user.jcaptcha.expire=\u9A8C\u8BC1\u7801\u5DF2\u5931\u6548 +user.not.exists=\u7528\u6237\u4E0D\u5B58\u5728/\u5BC6\u7801\u9519\u8BEF +user.password.not.match=\u7528\u6237\u4E0D\u5B58\u5728/\u5BC6\u7801\u9519\u8BEF +user.password.retry.limit.count=\u5BC6\u7801\u8F93\u5165\u9519\u8BEF{0}\u6B21 +user.password.retry.limit.exceed=\u5BC6\u7801\u8F93\u5165\u9519\u8BEF{0}\u6B21\uFF0C\u5E10\u6237\u9501\u5B9A{1}\u5206\u949F +user.password.delete=\u5BF9\u4E0D\u8D77\uFF0C\u60A8\u7684\u8D26\u53F7\u5DF2\u88AB\u5220\u9664 +user.blocked=\u7528\u6237\u5DF2\u5C01\u7981\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +role.blocked=\u89D2\u8272\u5DF2\u5C01\u7981\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +user.logout.success=\u9000\u51FA\u6210\u529F + +length.not.valid=\u957F\u5EA6\u5FC5\u987B\u5728{min}\u5230{max}\u4E2A\u5B57\u7B26\u4E4B\u95F4 + +user.username.not.valid=* 2\u523020\u4E2A\u6C49\u5B57\u3001\u5B57\u6BCD\u3001\u6570\u5B57\u6216\u4E0B\u5212\u7EBF\u7EC4\u6210\uFF0C\u4E14\u5FC5\u987B\u4EE5\u975E\u6570\u5B57\u5F00\u5934 +user.password.not.valid=* 5-50\u4E2A\u5B57\u7B26 + +user.email.not.valid=\u90AE\u7BB1\u683C\u5F0F\u9519\u8BEF +user.mobile.phone.number.not.valid=\u624B\u673A\u53F7\u683C\u5F0F\u9519\u8BEF +user.login.success=\u767B\u5F55\u6210\u529F +user.register.success=\u6CE8\u518C\u6210\u529F +user.notfound=\u8BF7\u91CD\u65B0\u767B\u5F55 +user.forcelogout=\u7BA1\u7406\u5458\u5F3A\u5236\u9000\u51FA\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55 +user.unknown.error=\u672A\u77E5\u9519\u8BEF\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55 + +##\u6743\u9650 +no.permission=\u60A8\u6CA1\u6709\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.create.permission=\u60A8\u6CA1\u6709\u521B\u5EFA\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.update.permission=\u60A8\u6CA1\u6709\u4FEE\u6539\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.delete.permission=\u60A8\u6CA1\u6709\u5220\u9664\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.export.permission=\u60A8\u6CA1\u6709\u5BFC\u51FA\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.view.permission=\u60A8\u6CA1\u6709\u67E5\u770B\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] + +##\u6587\u4EF6\u4E0A\u4F20\u6D88\u606F +upload.exceed.maxSize=\u4E0A\u4F20\u7684\u6587\u4EF6\u5927\u5C0F\u8D85\u51FA\u9650\u5236\u7684\u6587\u4EF6\u5927\u5C0F\uFF01
\u5141\u8BB8\u7684\u6587\u4EF6\u6700\u5927\u5927\u5C0F\u662F\uFF1A{0}MB\uFF01 +upload.filename.exceed.length=\u4E0A\u4F20\u7684\u6587\u4EF6\u540D\u6700\u957F{0}\u4E2A\u5B57\u7B26 +upload.success=\u4E0A\u4F20\u6210\u529F + +##\u6587\u4EF6\u4E0B\u8F7D\u6D88\u606F +download.filename.not.valid=\u6587\u4EF6\u540D\u79F0[{}]\u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u4E0B\u8F7D +download.file.failed=\u4E0B\u8F7D\u6587\u4EF6\u5931\u8D25 +download.resource.not.valid=\u8D44\u6E90\u6587\u4EF6[{}]\u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u4E0B\u8F7D + +##Dept +dept.add.failed.name.exists=\u65B0\u589E\u673A\u6784[{}]\u5931\u8D25\uFF0C\u673A\u6784\u540D\u79F0\u5DF2\u5B58\u5728 +dept.update.failed.name.exists=\u4FEE\u6539\u673A\u6784[{}]\u5931\u8D25\uFF0C\u673A\u6784\u540D\u79F0\u5DF2\u5B58\u5728 +dept.update.failed.parent.not.valid=\u4FEE\u6539\u673A\u6784[{}]\u5931\u8D25\uFF0C\u4E0A\u7EA7\u673A\u6784\u4E0D\u80FD\u662F\u81EA\u5DF1 +dept.update.failed.child.not.valid=\u8BE5\u673A\u6784\u5305\u542B\u672A\u505C\u7528\u7684\u5B50\u673A\u6784\uFF01 +dept.delete.failed.child.exists=\u5B58\u5728\u4E0B\u7EA7\u673A\u6784\uFF0C\u4E0D\u5141\u8BB8\u5220\u9664 +dept.delete.failed.user.exists=\u673A\u6784\u5B58\u5728\u7528\u6237\uFF0C\u4E0D\u5141\u8BB8\u5220\u9664 + +##Dict +dict.add.failed.type.exists=\u65B0\u589E\u5B57\u5178[{}]\u5931\u8D25\uFF0C\u5B57\u5178\u7C7B\u578B\u5DF2\u5B58\u5728 +dict.update.failed.type.exists=\u65B0\u589E\u5B57\u5178[{}]\u5931\u8D25\uFF0C\u5B57\u5178\u7C7B\u578B\u5DF2\u5B58\u5728 + +##Index +index.welcome.message=\u6B22\u8FCE\u4F7F\u7528{}\u540E\u53F0\u7BA1\u7406\u6846\u67B6\uFF0C\u5F53\u524D\u7248\u672C\uFF1Av{}\uFF0C\u8BF7\u901A\u8FC7\u524D\u7AEF\u5730\u5740\u8BBF\u95EE\u3002 + +##Menu +menu.add.failed.name.exists=\u65B0\u589E\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u83DC\u5355\u540D\u79F0\u5DF2\u5B58\u5728 +menu.add.failed.path.not.valid=\u65B0\u589E\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u5730\u5740\u5FC5\u987B\u4EE5http(s)://\u5F00\u5934 +menu.update.failed.name.exists=\u4FEE\u6539\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u83DC\u5355\u540D\u79F0\u5DF2\u5B58\u5728 +menu.update.failed.path.not.valid=\u4FEE\u6539\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u5730\u5740\u5FC5\u987B\u4EE5http(s)://\u5F00\u5934 +menu.update.failed.parent.not.valid=\u4FEE\u6539\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u4E0A\u7EA7\u83DC\u5355\u4E0D\u80FD\u9009\u62E9\u81EA\u5DF1 +menu.delete.failed.child.exists=\u5B58\u5728\u5B50\u83DC\u5355,\u4E0D\u5141\u8BB8\u5220\u9664 +menu.delete.failed.role.exists=\u83DC\u5355\u5DF2\u5206\u914D,\u4E0D\u5141\u8BB8\u5220\u9664 + +##Post +post.add.failed.name.exists=\u65B0\u589E\u5C97\u4F4D[{}]\u5931\u8D25\uFF0C\u5C97\u4F4D\u540D\u79F0\u5DF2\u5B58\u5728 +post.add.failed.code.exists=\u65B0\u589E\u5C97\u4F4D[{}]\u5931\u8D25\uFF0C\u5C97\u4F4D\u7F16\u7801\u5DF2\u5B58\u5728 +post.update.failed.name.exists=\u4FEE\u6539\u5C97\u4F4D[{}]\u5931\u8D25\uFF0C\u5C97\u4F4D\u540D\u79F0\u5DF2\u5B58\u5728 +post.update.failed.code.exists=\u4FEE\u6539\u5C97\u4F4D[{}]\u5931\u8D25\uFF0C\u5C97\u4F4D\u7F16\u7801\u5DF2\u5B58\u5728 + +##User +user.username.exists=\u7CFB\u7EDF\u8D26\u53F7\u540D\u79F0\u5DF2\u5B58\u5728\uFF0C\u8BF7\u4FEE\u6539\u540E\u91CD\u8BD5 +user.password.differ=\u4E24\u6B21\u5BC6\u7801\u4E0D\u4E00\u81F4\uFF0C\u8BF7\u91CD\u65B0\u8F93\u5165 +user.add.failed.name.exists=\u65B0\u589E\u7528\u6237[{}]\u5931\u8D25\uFF0C\u767B\u5F55\u8D26\u53F7\u5DF2\u5B58\u5728 +user.add.failed.phone.exists=\u65B0\u589E\u7528\u6237[{}]\u5931\u8D25\uFF0C\u624B\u673A\u53F7\u7801\u5DF2\u5B58\u5728 +user.add.failed.email.exists=\u65B0\u589E\u7528\u6237[{}]\u5931\u8D25\uFF0C\u90AE\u7BB1\u8D26\u53F7\u5DF2\u5B58\u5728 +user.update.failed.password.wrong=\u4FEE\u6539\u5BC6\u7801\u5931\u8D25\uFF0C\u65E7\u5BC6\u7801\u9519\u8BEF +user.update.failed.password.repeat=\u65B0\u5BC6\u7801\u4E0D\u80FD\u4E0E\u65E7\u5BC6\u7801\u76F8\u540C +user.update.password.failed=\u4FEE\u6539\u5BC6\u7801\u5F02\u5E38\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +user.update.failed.name.exists=\u65B0\u589E\u7528\u6237[{}]\u5931\u8D25\uFF0C\u767B\u5F55\u8D26\u53F7\u5DF2\u5B58\u5728 +user.update.failed.phone.exists=\u4FEE\u6539\u7528\u6237[{}]\u5931\u8D25\uFF0C\u624B\u673A\u53F7\u7801\u5DF2\u5B58\u5728 +user.update.failed.email.exists=\u4FEE\u6539\u7528\u6237[{}]\u5931\u8D25\uFF0C\u90AE\u7BB1\u8D26\u53F7\u5DF2\u5B58\u5728 +user.update.failed=\u4FEE\u6539\u4E2A\u4EBA\u4FE1\u606F\u5F02\u5E38\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +user.delete.failed=\u5F53\u524D\u7528\u6237\u4E0D\u80FD\u5220\u9664 +user.upload.avatar.failed=\u4E0A\u4F20\u56FE\u7247\u5F02\u5E38\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +user.not.login=\u8BF7\u767B\u5F55\u540E\u91CD\u8BD5 +user.access.denied=\u7528\u6237\u62D2\u7EDD\u8BBF\u95EE + +##Role +role.add.manager.failed=\u4E0D\u5141\u8BB8\u8BBE\u7F6E\u7BA1\u7406\u5458\u89D2\u8272\u6807\u8BC6 +role.add.failed.name.exists=\u65B0\u589E\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u89D2\u8272\u540D\u79F0\u5DF2\u5B58\u5728 +role.add.failed.key.exists=\u65B0\u589E\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u89D2\u8272\u6743\u9650\u5DF2\u5B58\u5728 +role.update.failed.name.exists=\u4FEE\u6539\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u89D2\u8272\u540D\u79F0\u5DF2\u5B58\u5728 +role.update.failed.key.exists=\u4FEE\u6539\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u89D2\u8272\u6743\u9650\u5DF2\u5B58\u5728 +role.update.failed=\u4FEE\u6539\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 + +##Import +import.failed.file.null=\u5BFC\u5165\u5931\u8D25\uFF0C\u8BF7\u5148\u4E0A\u4F20\u6587\u4EF6\uFF01 +import.failed.data.null=\u5BFC\u5165\u5931\u8D25\uFF0C\u5BFC\u5165\u6570\u636E\u4E3A\u7A7A\uFF01 +import.failed.nettyDevice.name.null=\u5BFC\u5165\u5931\u8D25\uFF0C\u6A21\u677F\u91CC\u8BBE\u5907\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\uFF01 +import.success=\u5BFC\u5165\u6210\u529F + +##General +success=\u6210\u529F +fail=\u5931\u8D25 +query.success=\u67E5\u8BE2\u6210\u529F +operate.success=\u64CD\u4F5C\u6210\u529F +create.success=\u521B\u5EFA\u6210\u529F +create.failed=\u521B\u5EFA\u5931\u8D25 +save.success=\u4FDD\u5B58\u6210\u529F +save.failed=\u4FDD\u5B58\u5931\u8D25 +authorization.success=\u6388\u6743\u6210\u529F + +##Email +email.format.error=\u90AE\u7BB1\u683C\u5F0F\u9519\u8BEF +email.verification.code.send=\u90AE\u7BB1\u9A8C\u8BC1\u7801\u5DF2\u53D1\u9001 + +##Firmware +firmware.task.upgrade.failed.time.not.valid=\u9884\u5B9A\u5347\u7EA7\u65F6\u95F4\u5E94\u5927\u4E8E\u5F53\u524D\u65F6\u95F4 +##Media +media.record.query.failed=\u8FDE\u63A5\u8D85\u65F6\u6216\u53D1\u751F\u9519\u8BEF\uFF0C\u672A\u83B7\u53D6\u5230\u6570\u636E +##Modbus +modbus.type.null=\u7C7B\u578B\u4E3A\u7A7A +##Netty +netty.client.not.exists=\u5BA2\u6237\u7AEF\u4E0D\u5B58\u5728 +##Runtime +runtime.message.id.null=\u6D88\u606Fid\u4E3A\u7A7A +##Wechat +wechat.verify.type.null=\u8BF7\u4F20\u5165\u9A8C\u8BC1\u65B9\u5F0F +wechat.bind.message.id.null=\u8BF7\u4F20\u5165\u7ED1\u5B9A\u4FE1\u606FID +##AuthResource +auth.resource.product.query.success=\u67E5\u8BE2\u4EA7\u54C1\u5217\u8868\u6210\u529F +##Device +nettyDevice.user.id.null=\u7528\u6237ID\u4E0D\u80FD\u4E3A\u7A7A +nettyDevice.product.id.null=\u8BBE\u5907\u7F16\u53F7\u548C\u4EA7\u54C1ID\u4E0D\u80FD\u4E3A\u7A7A +nettyDevice.dept.id.null=\u8BF7\u9009\u62E9\u5206\u914D\u673A\u6784 +nettyDevice.id.null=\u8BF7\u9009\u62E9\u8BBE\u5907 +##DeviceJob +job.add.failed.cron.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0CCron\u8868\u8FBE\u5F0F\u4E0D\u6B63\u786E +job.add.failed.rmi.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'rmi'\u8C03\u7528 +job.add.failed.ldap.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'ldap(s)'\u8C03\u7528 +job.add.failed.http.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'http(s)'\u8C03\u7528 +job.add.failed.string.error=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u5B58\u5728\u8FDD\u89C4 +job.add.failed.string.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5728\u767D\u540D\u5355\u5185 +job.update.failed.cron.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0CCron\u8868\u8FBE\u5F0F\u4E0D\u6B63\u786E +job.update.failed.rmi.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'rmi'\u8C03\u7528 +job.update.failed.ldap.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'ldap(s)'\u8C03\u7528 +job.update.failed.http.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'http(s)'\u8C03\u7528 +job.update.failed.string.error=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u5B58\u5728\u8FDD\u89C4 +job.update.failed.string.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5728\u767D\u540D\u5355\u5185 +job.not.exists=\u4EFB\u52A1\u4E0D\u5B58\u5728\u6216\u5DF2\u8FC7\u671F +##DeviceUser +nettyDevice.user.delete.failed.user.not.valid=\u8BBE\u5907\u6240\u6709\u8005\u4E0D\u80FD\u5220\u9664 +##GoviewProject +goview.project.data.save.failed.id.null=\u6CA1\u6709\u8BE5\u9879\u76EEID +goview.project.data.execute.sql.failed=\u8BF7\u7F16\u5199sql\u8BED\u53E5 +##ThingsModel +things.model.identifier.repeat=\u4EA7\u54C1\u4E0B\u7684\u6807\u8BC6\u7B26\u4E0D\u80FD\u91CD\u590D +things.model.import.failed.identifier.repeat=[{}]\u6761\u6570\u636E\u672A\u5BFC\u5165\uFF0C\u6807\u8BC6\u7B26\u91CD\u590D +##MQTT +mqtt.unauthorized=mqtt\u8D26\u53F7\u548C\u5BC6\u7801\u4E0E\u8BA4\u8BC1\u670D\u52A1\u5668\u914D\u7F6E\u4E0D\u5339\u914D +##Oauth +oauth.response.type.not.valid=response_type\u53C2\u6570\u503C\u53EA\u5141\u8BB8code\u548Ctoken +oauth.grant.type.null=\u672A\u77E5\u6388\u6743\u7C7B\u578B +oauth.grant.type.implicit.not.support=Token\u63A5\u53E3\u4E0D\u652F\u6301implicit\u6388\u6743\u6A21\u5F0F +oauth.access.token.null=\u8BBF\u95EE\u4EE4\u724C\u4E0D\u80FD\u4E3A\u7A7A +obtain.basic.authorization.failed=client_id\u6216client_secret\u672A\u6B63\u786E\u4F20\u9012 +##Record +record.app.null=app\u4E0D\u80FD\u4E3A\u7A7A +record.stream.null=stream\u4E0D\u80FD\u4E3A\u7A7A +record.time.not.valid=\u9519\u8BEF\u7684\u5F00\u59CB\u65F6\u95F4\u6216\u7ED3\u675F\u65F6\u95F4 +record.file.null=\u672A\u627E\u5230\u89C6\u9891\u6587\u4EF6 + +##ErrorCodeConstants +app.not.found=App \u4E0D\u5B58\u5728 +app.is.disable=App \u5DF2\u7ECF\u88AB\u7981\u7528 +app.exist.order.cant.delete=\u652F\u4ED8\u5E94\u7528\u5B58\u5728\u652F\u4ED8\u8BA2\u5355\uFF0C\u65E0\u6CD5\u5220\u9664 +app.exist.refund.cant.delete=\u652F\u4ED8\u5E94\u7528\u5B58\u5728\u9000\u6B3E\u8BA2\u5355\uFF0C\u65E0\u6CD5\u5220\u9664 +channel.not.found=\u652F\u4ED8\u6E20\u9053\u7684\u914D\u7F6E\u4E0D\u5B58\u5728 +channel.is.disable=\u652F\u4ED8\u6E20\u9053\u5DF2\u7ECF\u7981\u7528 +channel.exists.same.channel.error=\u5DF2\u5B58\u5728\u76F8\u540C\u7684\u6E20\u9053 +order.not.found=\u652F\u4ED8\u8BA2\u5355\u4E0D\u5B58\u5728 +order.status.is.not.waiting=\u652F\u4ED8\u8BA2\u5355\u4E0D\u5904\u4E8E\u5F85\u652F\u4ED8 +order.status.is.success=\u8BA2\u5355\u5DF2\u652F\u4ED8\uFF0C\u8BF7\u5237\u65B0\u9875\u9762 +order.is.expired=\u652F\u4ED8\u8BA2\u5355\u5DF2\u7ECF\u8FC7\u671F +order.submit.channel.error=\u53D1\u8D77\u652F\u4ED8\u62A5\u9519\uFF0C\u9519\u8BEF\u7801\uFF1A{}\uFF0C\u9519\u8BEF\u63D0\u793A\uFF1A{} +order.refund.fail.status.error=\u652F\u4ED8\u8BA2\u5355\u9000\u6B3E\u5931\u8D25\uFF0C\u539F\u56E0\uFF1A\u72B6\u6001\u4E0D\u662F\u5DF2\u652F\u4ED8\u6216\u5DF2\u9000\u6B3E +order.extension.not.found=\u652F\u4ED8\u4EA4\u6613\u62D3\u5C55\u5355\u4E0D\u5B58\u5728 +order.extension.status.is.not.waiting=\u652F\u4ED8\u4EA4\u6613\u62D3\u5C55\u5355\u4E0D\u5904\u4E8E\u5F85\u652F\u4ED8 +order.extension.is.paid=\u8BA2\u5355\u5DF2\u652F\u4ED8\uFF0C\u8BF7\u7B49\u5F85\u652F\u4ED8\u7ED3\u679C +refund.price.exceed=\u9000\u6B3E\u91D1\u989D\u8D85\u8FC7\u8BA2\u5355\u53EF\u9000\u6B3E\u91D1\u989D +refund.has.refunding=\u5DF2\u7ECF\u6709\u9000\u6B3E\u5728\u5904\u7406\u4E2D +refund.exists=\u5DF2\u7ECF\u5B58\u5728\u9000\u6B3E\u5355 +refund.not.found=\u652F\u4ED8\u9000\u6B3E\u5355\u4E0D\u5B58\u5728 +refund.statue.is.not.waiting=\u652F\u4ED8\u9000\u6B3E\u5355\u4E0D\u5904\u4E8E\u5F85\u9000\u6B3E +demo.order.not.found=\u793A\u4F8B\u8BA2\u5355\u4E0D\u5B58\u5728 +demo.order.update.paid.status.not.unpaid=\u793A\u4F8B\u8BA2\u5355\u66F4\u65B0\u652F\u4ED8\u72B6\u6001\u5931\u8D25\uFF0C\u8BA2\u5355\u4E0D\u662F\u3010\u672A\u652F\u4ED8\u3011\u72B6\u6001 +demo.order.update.paid.fail.pay.order.id.error=\u793A\u4F8B\u8BA2\u5355\u66F4\u65B0\u652F\u4ED8\u72B6\u6001\u5931\u8D25\uFF0C\u652F\u4ED8\u5355\u7F16\u53F7\u4E0D\u5339\u914D +demo.order.update.paid.fail.pay.order.status.not.success=\u793A\u4F8B\u8BA2\u5355\u66F4\u65B0\u652F\u4ED8\u72B6\u6001\u5931\u8D25\uFF0C\u652F\u4ED8\u5355\u72B6\u6001\u4E0D\u662F\u3010\u652F\u4ED8\u6210\u529F\u3011\u72B6\u6001 +demo.order.update.paid.fail.pay.price.not.match=\u793A\u4F8B\u8BA2\u5355\u66F4\u65B0\u652F\u4ED8\u72B6\u6001\u5931\u8D25\uFF0C\u652F\u4ED8\u5355\u91D1\u989D\u4E0D\u5339\u914D +demo.order.refund.fail.not.paid=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u793A\u4F8B\u8BA2\u5355\u672A\u652F\u4ED8 +demo.order.refund.fail.refunded=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u793A\u4F8B\u8BA2\u5355\u5DF2\u9000\u6B3E +demo.order.refund.fail.refund.not.found=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u9000\u6B3E\u8BA2\u5355\u4E0D\u5B58\u5728 +demo.order.refund.fail.refund.not.success=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u9000\u6B3E\u8BA2\u5355\u672A\u9000\u6B3E\u6210\u529F +demo.order.refund.fail.refund.order.id.error=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u9000\u6B3E\u5355\u7F16\u53F7\u4E0D\u5339\u914D +demo.order.refund.fail.refund.price.not.match=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u9000\u6B3E\u5355\u91D1\u989D\u4E0D\u5339\u914D +nettyDevice.can.send=No permission operation at present diff --git a/maibu-admin/src/main/resources/i18n/messages_zh_CN.properties b/maibu-admin/src/main/resources/i18n/messages_zh_CN.properties new file mode 100644 index 0000000..3d1f15d --- /dev/null +++ b/maibu-admin/src/main/resources/i18n/messages_zh_CN.properties @@ -0,0 +1,211 @@ +#\u9519\u8BEF\u6D88\u606F +not.null=* \u5FC5\u987B\u586B\u5199 +user.jcaptcha.error=\u9A8C\u8BC1\u7801\u9519\u8BEF +user.jcaptcha.expire=\u9A8C\u8BC1\u7801\u5DF2\u5931\u6548 +user.not.exists=\u7528\u6237\u4E0D\u5B58\u5728/\u5BC6\u7801\u9519\u8BEF +user.password.not.match=\u7528\u6237\u4E0D\u5B58\u5728/\u5BC6\u7801\u9519\u8BEF +user.password.retry.limit.count=\u5BC6\u7801\u8F93\u5165\u9519\u8BEF{0}\u6B21 +user.password.retry.limit.exceed=\u5BC6\u7801\u8F93\u5165\u9519\u8BEF{0}\u6B21\uFF0C\u5E10\u6237\u9501\u5B9A{1}\u5206\u949F +user.password.delete=\u5BF9\u4E0D\u8D77\uFF0C\u60A8\u7684\u8D26\u53F7\u5DF2\u88AB\u5220\u9664 +user.blocked=\u7528\u6237\u5DF2\u5C01\u7981\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +role.blocked=\u89D2\u8272\u5DF2\u5C01\u7981\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +user.logout.success=\u9000\u51FA\u6210\u529F + +length.not.valid=\u957F\u5EA6\u5FC5\u987B\u5728{min}\u5230{max}\u4E2A\u5B57\u7B26\u4E4B\u95F4 + +user.username.not.valid=* 2\u523020\u4E2A\u6C49\u5B57\u3001\u5B57\u6BCD\u3001\u6570\u5B57\u6216\u4E0B\u5212\u7EBF\u7EC4\u6210\uFF0C\u4E14\u5FC5\u987B\u4EE5\u975E\u6570\u5B57\u5F00\u5934 +user.password.not.valid=* 5-50\u4E2A\u5B57\u7B26 + +user.email.not.valid=\u90AE\u7BB1\u683C\u5F0F\u9519\u8BEF +user.mobile.phone.number.not.valid=\u624B\u673A\u53F7\u683C\u5F0F\u9519\u8BEF +user.login.success=\u767B\u5F55\u6210\u529F +user.register.success=\u6CE8\u518C\u6210\u529F +user.notfound=\u8BF7\u91CD\u65B0\u767B\u5F55 +user.forcelogout=\u7BA1\u7406\u5458\u5F3A\u5236\u9000\u51FA\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55 +user.unknown.error=\u672A\u77E5\u9519\u8BEF\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55 + +##\u6743\u9650 +no.permission=\u60A8\u6CA1\u6709\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.create.permission=\u60A8\u6CA1\u6709\u521B\u5EFA\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.update.permission=\u60A8\u6CA1\u6709\u4FEE\u6539\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.delete.permission=\u60A8\u6CA1\u6709\u5220\u9664\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.export.permission=\u60A8\u6CA1\u6709\u5BFC\u51FA\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] +no.view.permission=\u60A8\u6CA1\u6709\u67E5\u770B\u6570\u636E\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u6DFB\u52A0\u6743\u9650 [{0}] + +##\u6587\u4EF6\u4E0A\u4F20\u6D88\u606F +upload.exceed.maxSize=\u4E0A\u4F20\u7684\u6587\u4EF6\u5927\u5C0F\u8D85\u51FA\u9650\u5236\u7684\u6587\u4EF6\u5927\u5C0F\uFF01
\u5141\u8BB8\u7684\u6587\u4EF6\u6700\u5927\u5927\u5C0F\u662F\uFF1A{0}MB\uFF01 +upload.filename.exceed.length=\u4E0A\u4F20\u7684\u6587\u4EF6\u540D\u6700\u957F{0}\u4E2A\u5B57\u7B26 +upload.success=\u4E0A\u4F20\u6210\u529F + +##\u6587\u4EF6\u4E0B\u8F7D\u6D88\u606F +download.filename.not.valid=\u6587\u4EF6\u540D\u79F0[{}]\u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u4E0B\u8F7D +download.file.failed=\u4E0B\u8F7D\u6587\u4EF6\u5931\u8D25 +download.resource.not.valid=\u8D44\u6E90\u6587\u4EF6[{}]\u975E\u6CD5\uFF0C\u4E0D\u5141\u8BB8\u4E0B\u8F7D + +##Dept +dept.add.failed.name.exists=\u65B0\u589E\u673A\u6784[{}]\u5931\u8D25\uFF0C\u673A\u6784\u540D\u79F0\u5DF2\u5B58\u5728 +dept.update.failed.name.exists=\u4FEE\u6539\u673A\u6784[{}]\u5931\u8D25\uFF0C\u673A\u6784\u540D\u79F0\u5DF2\u5B58\u5728 +dept.update.failed.parent.not.valid=\u4FEE\u6539\u673A\u6784[{}]\u5931\u8D25\uFF0C\u4E0A\u7EA7\u673A\u6784\u4E0D\u80FD\u662F\u81EA\u5DF1 +dept.update.failed.child.not.valid=\u8BE5\u673A\u6784\u5305\u542B\u672A\u505C\u7528\u7684\u5B50\u673A\u6784\uFF01 +dept.delete.failed.child.exists=\u5B58\u5728\u4E0B\u7EA7\u673A\u6784\uFF0C\u4E0D\u5141\u8BB8\u5220\u9664 +dept.delete.failed.user.exists=\u673A\u6784\u5B58\u5728\u7528\u6237\uFF0C\u4E0D\u5141\u8BB8\u5220\u9664 + +##Dict +dict.add.failed.type.exists=\u65B0\u589E\u5B57\u5178[{}]\u5931\u8D25\uFF0C\u5B57\u5178\u7C7B\u578B\u5DF2\u5B58\u5728 +dict.update.failed.type.exists=\u65B0\u589E\u5B57\u5178[{}]\u5931\u8D25\uFF0C\u5B57\u5178\u7C7B\u578B\u5DF2\u5B58\u5728 + +##Index +index.welcome.message=\u6B22\u8FCE\u4F7F\u7528{}\u540E\u53F0\u7BA1\u7406\u6846\u67B6\uFF0C\u5F53\u524D\u7248\u672C\uFF1Av{}\uFF0C\u8BF7\u901A\u8FC7\u524D\u7AEF\u5730\u5740\u8BBF\u95EE\u3002 + +##Menu +menu.add.failed.name.exists=\u65B0\u589E\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u83DC\u5355\u540D\u79F0\u5DF2\u5B58\u5728 +menu.add.failed.path.not.valid=\u65B0\u589E\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u5730\u5740\u5FC5\u987B\u4EE5http(s)://\u5F00\u5934 +menu.update.failed.name.exists=\u4FEE\u6539\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u83DC\u5355\u540D\u79F0\u5DF2\u5B58\u5728 +menu.update.failed.path.not.valid=\u4FEE\u6539\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u5730\u5740\u5FC5\u987B\u4EE5http(s)://\u5F00\u5934 +menu.update.failed.parent.not.valid=\u4FEE\u6539\u83DC\u5355[{}]\u5931\u8D25\uFF0C\u4E0A\u7EA7\u83DC\u5355\u4E0D\u80FD\u9009\u62E9\u81EA\u5DF1 +menu.delete.failed.child.exists=\u5B58\u5728\u5B50\u83DC\u5355,\u4E0D\u5141\u8BB8\u5220\u9664 +menu.delete.failed.role.exists=\u83DC\u5355\u5DF2\u5206\u914D,\u4E0D\u5141\u8BB8\u5220\u9664 + +##Post +post.add.failed.name.exists=\u65B0\u589E\u5C97\u4F4D[{}]\u5931\u8D25\uFF0C\u5C97\u4F4D\u540D\u79F0\u5DF2\u5B58\u5728 +post.add.failed.code.exists=\u65B0\u589E\u5C97\u4F4D[{}]\u5931\u8D25\uFF0C\u5C97\u4F4D\u7F16\u7801\u5DF2\u5B58\u5728 +post.update.failed.name.exists=\u4FEE\u6539\u5C97\u4F4D[{}]\u5931\u8D25\uFF0C\u5C97\u4F4D\u540D\u79F0\u5DF2\u5B58\u5728 +post.update.failed.code.exists=\u4FEE\u6539\u5C97\u4F4D[{}]\u5931\u8D25\uFF0C\u5C97\u4F4D\u7F16\u7801\u5DF2\u5B58\u5728 + +##User +user.username.exists=\u7CFB\u7EDF\u8D26\u53F7\u540D\u79F0\u5DF2\u5B58\u5728\uFF0C\u8BF7\u4FEE\u6539\u540E\u91CD\u8BD5 +user.password.differ=\u4E24\u6B21\u5BC6\u7801\u4E0D\u4E00\u81F4\uFF0C\u8BF7\u91CD\u65B0\u8F93\u5165 +user.add.failed.name.exists=\u65B0\u589E\u7528\u6237[{}]\u5931\u8D25\uFF0C\u767B\u5F55\u8D26\u53F7\u5DF2\u5B58\u5728 +user.add.failed.phone.exists=\u65B0\u589E\u7528\u6237[{}]\u5931\u8D25\uFF0C\u624B\u673A\u53F7\u7801\u5DF2\u5B58\u5728 +user.add.failed.email.exists=\u65B0\u589E\u7528\u6237[{}]\u5931\u8D25\uFF0C\u90AE\u7BB1\u8D26\u53F7\u5DF2\u5B58\u5728 +user.update.failed.password.wrong=\u4FEE\u6539\u5BC6\u7801\u5931\u8D25\uFF0C\u65E7\u5BC6\u7801\u9519\u8BEF +user.update.failed.password.repeat=\u65B0\u5BC6\u7801\u4E0D\u80FD\u4E0E\u65E7\u5BC6\u7801\u76F8\u540C +user.update.password.failed=\u4FEE\u6539\u5BC6\u7801\u5F02\u5E38\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +user.update.failed.name.exists=\u65B0\u589E\u7528\u6237[{}]\u5931\u8D25\uFF0C\u767B\u5F55\u8D26\u53F7\u5DF2\u5B58\u5728 +user.update.failed.phone.exists=\u4FEE\u6539\u7528\u6237[{}]\u5931\u8D25\uFF0C\u624B\u673A\u53F7\u7801\u5DF2\u5B58\u5728 +user.update.failed.email.exists=\u4FEE\u6539\u7528\u6237[{}]\u5931\u8D25\uFF0C\u90AE\u7BB1\u8D26\u53F7\u5DF2\u5B58\u5728 +user.update.failed=\u4FEE\u6539\u4E2A\u4EBA\u4FE1\u606F\u5F02\u5E38\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +user.delete.failed=\u5F53\u524D\u7528\u6237\u4E0D\u80FD\u5220\u9664 +user.upload.avatar.failed=\u4E0A\u4F20\u56FE\u7247\u5F02\u5E38\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 +user.not.login=\u8BF7\u767B\u5F55\u540E\u91CD\u8BD5 +user.access.denied=\u7528\u6237\u62D2\u7EDD\u8BBF\u95EE + +##Role +role.add.manager.failed=\u4E0D\u5141\u8BB8\u8BBE\u7F6E\u7BA1\u7406\u5458\u89D2\u8272\u6807\u8BC6 +role.add.failed.name.exists=\u65B0\u589E\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u89D2\u8272\u540D\u79F0\u5DF2\u5B58\u5728 +role.add.failed.key.exists=\u65B0\u589E\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u89D2\u8272\u6743\u9650\u5DF2\u5B58\u5728 +role.update.failed.name.exists=\u4FEE\u6539\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u89D2\u8272\u540D\u79F0\u5DF2\u5B58\u5728 +role.update.failed.key.exists=\u4FEE\u6539\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u89D2\u8272\u6743\u9650\u5DF2\u5B58\u5728 +role.update.failed=\u4FEE\u6539\u89D2\u8272[{}]\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 + +##Import +import.failed.file.null=\u5BFC\u5165\u5931\u8D25\uFF0C\u8BF7\u5148\u4E0A\u4F20\u6587\u4EF6\uFF01 +import.failed.data.null=\u5BFC\u5165\u5931\u8D25\uFF0C\u5BFC\u5165\u6570\u636E\u4E3A\u7A7A\uFF01 +import.failed.nettyDevice.name.null=\u5BFC\u5165\u5931\u8D25\uFF0C\u6A21\u677F\u91CC\u8BBE\u5907\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A\uFF01 +import.success=\u5BFC\u5165\u6210\u529F + +##General +success=\u6210\u529F +fail=\u5931\u8D25 +query.success=\u67E5\u8BE2\u6210\u529F +operate.success=\u64CD\u4F5C\u6210\u529F +create.success=\u521B\u5EFA\u6210\u529F +create.failed=\u521B\u5EFA\u5931\u8D25 +save.success=\u4FDD\u5B58\u6210\u529F +save.failed=\u4FDD\u5B58\u5931\u8D25 +authorization.success=\u6388\u6743\u6210\u529F + +##Email +email.format.error=\u90AE\u7BB1\u683C\u5F0F\u9519\u8BEF +email.verification.code.send=\u90AE\u7BB1\u9A8C\u8BC1\u7801\u5DF2\u53D1\u9001 + +##Firmware +firmware.task.upgrade.failed.time.not.valid=\u9884\u5B9A\u5347\u7EA7\u65F6\u95F4\u5E94\u5927\u4E8E\u5F53\u524D\u65F6\u95F4 +##Media +media.record.query.failed=\u8FDE\u63A5\u8D85\u65F6\u6216\u53D1\u751F\u9519\u8BEF\uFF0C\u672A\u83B7\u53D6\u5230\u6570\u636E +##Modbus +modbus.type.null=\u7C7B\u578B\u4E3A\u7A7A +##Netty +netty.client.not.exists=\u5BA2\u6237\u7AEF\u4E0D\u5B58\u5728 +##Runtime +runtime.message.id.null=\u6D88\u606Fid\u4E3A\u7A7A +##Wechat +wechat.verify.type.null=\u8BF7\u4F20\u5165\u9A8C\u8BC1\u65B9\u5F0F +wechat.bind.message.id.null=\u8BF7\u4F20\u5165\u7ED1\u5B9A\u4FE1\u606FID +##AuthResource +auth.resource.product.query.success=\u67E5\u8BE2\u4EA7\u54C1\u5217\u8868\u6210\u529F +##Device +nettyDevice.user.id.null=\u7528\u6237ID\u4E0D\u80FD\u4E3A\u7A7A +nettyDevice.product.id.null=\u8BBE\u5907\u7F16\u53F7\u548C\u4EA7\u54C1ID\u4E0D\u80FD\u4E3A\u7A7A +nettyDevice.dept.id.null=\u8BF7\u9009\u62E9\u5206\u914D\u673A\u6784 +nettyDevice.id.null=\u8BF7\u9009\u62E9\u8BBE\u5907 +##DeviceJob +job.add.failed.cron.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0CCron\u8868\u8FBE\u5F0F\u4E0D\u6B63\u786E +job.add.failed.rmi.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'rmi'\u8C03\u7528 +job.add.failed.ldap.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'ldap(s)'\u8C03\u7528 +job.add.failed.http.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'http(s)'\u8C03\u7528 +job.add.failed.string.error=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u5B58\u5728\u8FDD\u89C4 +job.add.failed.string.not.valid=\u65B0\u589E\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5728\u767D\u540D\u5355\u5185 +job.update.failed.cron.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0CCron\u8868\u8FBE\u5F0F\u4E0D\u6B63\u786E +job.update.failed.rmi.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'rmi'\u8C03\u7528 +job.update.failed.ldap.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'ldap(s)'\u8C03\u7528 +job.update.failed.http.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5141\u8BB8'http(s)'\u8C03\u7528 +job.update.failed.string.error=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u5B58\u5728\u8FDD\u89C4 +job.update.failed.string.not.valid=\u4FEE\u6539\u4EFB\u52A1[{}]\u5931\u8D25\uFF0C\u76EE\u6807\u5B57\u7B26\u4E32\u4E0D\u5728\u767D\u540D\u5355\u5185 +job.not.exists=\u4EFB\u52A1\u4E0D\u5B58\u5728\u6216\u5DF2\u8FC7\u671F +##DeviceUser +nettyDevice.user.delete.failed.user.not.valid=\u8BBE\u5907\u6240\u6709\u8005\u4E0D\u80FD\u5220\u9664 +##GoviewProject +goview.project.data.save.failed.id.null=\u6CA1\u6709\u8BE5\u9879\u76EEID +goview.project.data.execute.sql.failed=\u8BF7\u7F16\u5199sql\u8BED\u53E5 +##ThingsModel +things.model.identifier.repeat=\u4EA7\u54C1\u4E0B\u7684\u6807\u8BC6\u7B26\u4E0D\u80FD\u91CD\u590D +things.model.import.failed.identifier.repeat=[{}]\u6761\u6570\u636E\u672A\u5BFC\u5165\uFF0C\u6807\u8BC6\u7B26\u91CD\u590D +##MQTT +mqtt.unauthorized=mqtt\u8D26\u53F7\u548C\u5BC6\u7801\u4E0E\u8BA4\u8BC1\u670D\u52A1\u5668\u914D\u7F6E\u4E0D\u5339\u914D +##Oauth +oauth.response.type.not.valid=response_type\u53C2\u6570\u503C\u53EA\u5141\u8BB8code\u548Ctoken +oauth.grant.type.null=\u672A\u77E5\u6388\u6743\u7C7B\u578B +oauth.grant.type.implicit.not.support=Token\u63A5\u53E3\u4E0D\u652F\u6301implicit\u6388\u6743\u6A21\u5F0F +oauth.access.token.null=\u8BBF\u95EE\u4EE4\u724C\u4E0D\u80FD\u4E3A\u7A7A +obtain.basic.authorization.failed=client_id\u6216client_secret\u672A\u6B63\u786E\u4F20\u9012 +##Record +record.app.null=app\u4E0D\u80FD\u4E3A\u7A7A +record.stream.null=stream\u4E0D\u80FD\u4E3A\u7A7A +record.time.not.valid=\u9519\u8BEF\u7684\u5F00\u59CB\u65F6\u95F4\u6216\u7ED3\u675F\u65F6\u95F4 +record.file.null=\u672A\u627E\u5230\u89C6\u9891\u6587\u4EF6 + +##ErrorCodeConstants +app.not.found=App \u4E0D\u5B58\u5728 +app.is.disable=App \u5DF2\u7ECF\u88AB\u7981\u7528 +app.exist.order.cant.delete=\u652F\u4ED8\u5E94\u7528\u5B58\u5728\u652F\u4ED8\u8BA2\u5355\uFF0C\u65E0\u6CD5\u5220\u9664 +app.exist.refund.cant.delete=\u652F\u4ED8\u5E94\u7528\u5B58\u5728\u9000\u6B3E\u8BA2\u5355\uFF0C\u65E0\u6CD5\u5220\u9664 +channel.not.found=\u652F\u4ED8\u6E20\u9053\u7684\u914D\u7F6E\u4E0D\u5B58\u5728 +channel.is.disable=\u652F\u4ED8\u6E20\u9053\u5DF2\u7ECF\u7981\u7528 +channel.exists.same.channel.error=\u5DF2\u5B58\u5728\u76F8\u540C\u7684\u6E20\u9053 +order.not.found=\u652F\u4ED8\u8BA2\u5355\u4E0D\u5B58\u5728 +order.status.is.not.waiting=\u652F\u4ED8\u8BA2\u5355\u4E0D\u5904\u4E8E\u5F85\u652F\u4ED8 +order.status.is.success=\u8BA2\u5355\u5DF2\u652F\u4ED8\uFF0C\u8BF7\u5237\u65B0\u9875\u9762 +order.is.expired=\u652F\u4ED8\u8BA2\u5355\u5DF2\u7ECF\u8FC7\u671F +order.submit.channel.error=\u53D1\u8D77\u652F\u4ED8\u62A5\u9519\uFF0C\u9519\u8BEF\u7801\uFF1A{}\uFF0C\u9519\u8BEF\u63D0\u793A\uFF1A{} +order.refund.fail.status.error=\u652F\u4ED8\u8BA2\u5355\u9000\u6B3E\u5931\u8D25\uFF0C\u539F\u56E0\uFF1A\u72B6\u6001\u4E0D\u662F\u5DF2\u652F\u4ED8\u6216\u5DF2\u9000\u6B3E +order.extension.not.found=\u652F\u4ED8\u4EA4\u6613\u62D3\u5C55\u5355\u4E0D\u5B58\u5728 +order.extension.status.is.not.waiting=\u652F\u4ED8\u4EA4\u6613\u62D3\u5C55\u5355\u4E0D\u5904\u4E8E\u5F85\u652F\u4ED8 +order.extension.is.paid=\u8BA2\u5355\u5DF2\u652F\u4ED8\uFF0C\u8BF7\u7B49\u5F85\u652F\u4ED8\u7ED3\u679C +refund.price.exceed=\u9000\u6B3E\u91D1\u989D\u8D85\u8FC7\u8BA2\u5355\u53EF\u9000\u6B3E\u91D1\u989D +refund.has.refunding=\u5DF2\u7ECF\u6709\u9000\u6B3E\u5728\u5904\u7406\u4E2D +refund.exists=\u5DF2\u7ECF\u5B58\u5728\u9000\u6B3E\u5355 +refund.not.found=\u652F\u4ED8\u9000\u6B3E\u5355\u4E0D\u5B58\u5728 +refund.statue.is.not.waiting=\u652F\u4ED8\u9000\u6B3E\u5355\u4E0D\u5904\u4E8E\u5F85\u9000\u6B3E +demo.order.not.found=\u793A\u4F8B\u8BA2\u5355\u4E0D\u5B58\u5728 +demo.order.update.paid.status.not.unpaid=\u793A\u4F8B\u8BA2\u5355\u66F4\u65B0\u652F\u4ED8\u72B6\u6001\u5931\u8D25\uFF0C\u8BA2\u5355\u4E0D\u662F\u3010\u672A\u652F\u4ED8\u3011\u72B6\u6001 +demo.order.update.paid.fail.pay.order.id.error=\u793A\u4F8B\u8BA2\u5355\u66F4\u65B0\u652F\u4ED8\u72B6\u6001\u5931\u8D25\uFF0C\u652F\u4ED8\u5355\u7F16\u53F7\u4E0D\u5339\u914D +demo.order.update.paid.fail.pay.order.status.not.success=\u793A\u4F8B\u8BA2\u5355\u66F4\u65B0\u652F\u4ED8\u72B6\u6001\u5931\u8D25\uFF0C\u652F\u4ED8\u5355\u72B6\u6001\u4E0D\u662F\u3010\u652F\u4ED8\u6210\u529F\u3011\u72B6\u6001 +demo.order.update.paid.fail.pay.price.not.match=\u793A\u4F8B\u8BA2\u5355\u66F4\u65B0\u652F\u4ED8\u72B6\u6001\u5931\u8D25\uFF0C\u652F\u4ED8\u5355\u91D1\u989D\u4E0D\u5339\u914D +demo.order.refund.fail.not.paid=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u793A\u4F8B\u8BA2\u5355\u672A\u652F\u4ED8 +demo.order.refund.fail.refunded=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u793A\u4F8B\u8BA2\u5355\u5DF2\u9000\u6B3E +demo.order.refund.fail.refund.not.found=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u9000\u6B3E\u8BA2\u5355\u4E0D\u5B58\u5728 +demo.order.refund.fail.refund.not.success=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u9000\u6B3E\u8BA2\u5355\u672A\u9000\u6B3E\u6210\u529F +demo.order.refund.fail.refund.order.id.error=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u9000\u6B3E\u5355\u7F16\u53F7\u4E0D\u5339\u914D +demo.order.refund.fail.refund.price.not.match=\u53D1\u8D77\u9000\u6B3E\u5931\u8D25\uFF0C\u9000\u6B3E\u5355\u91D1\u989D\u4E0D\u5339\u914D +nettyDevice.can.send=\u6682\u65E0\u6743\u9650\u64CD\u4F5C diff --git a/maibu-admin/src/main/resources/logback.xml b/maibu-admin/src/main/resources/logback.xml new file mode 100644 index 0000000..cdc92e9 --- /dev/null +++ b/maibu-admin/src/main/resources/logback.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + ${log.pattern} + + + + + + + ${log.path}/sys-debug.log + + + + ${log.path}/sys-debug.%d{yyyy-MM-dd}.log + + 10 + + + ${log.pattern} + + + + DEBUG + + ACCEPT + + DENY + + + + + + ${log.path}/sys-info.log + + + + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 10 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/sys-error.log + + + + ${log.path}/sys-error.%d{yyyy-MM-dd}.log + + 10 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log.path}/sys-user.log + + + ${log.path}/sys-user.%d{yyyy-MM-dd}.log + + 10 + + + ${log.pattern} + + + + + + + scriptId + 0 + + + + ${log.path}/script/${scriptId}.log + true + --> + + ${log.path}/rule.%d{yyyy-MM}.log + + 3 + + + %d{HH:mm:ss.SSS} [%method,%line] - %msg%n + + + INFO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/maibu-admin/src/main/resources/mybatis/mybatis-config.xml b/maibu-admin/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 0000000..ac47c03 --- /dev/null +++ b/maibu-admin/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/maibu-admin/src/main/resources/sharding-sphere-config.yaml b/maibu-admin/src/main/resources/sharding-sphere-config.yaml new file mode 100644 index 0000000..1b33482 --- /dev/null +++ b/maibu-admin/src/main/resources/sharding-sphere-config.yaml @@ -0,0 +1,59 @@ +mode: + type: Standalone + repository: + type: JDBC +props: + # 是否显示 ShardingSpher 的sql,用于Debug + sql-show: true +datasource: + # 配置真实数据源 + names: ds0,ds1 + ds0: # 配置 mysql 数据源 + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: fastbee + filters: stat,wall + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + ds1: # 配置 mysql 数据源 + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/information_schema?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: fastbee + +rules: # 配置表规则 + sharding: + # 表策略配置 + tables: + # iot_device_log 是逻辑表 + iot_device_log: + actualDataNodes: ds0.iot_device_log_$->{2024..2030}0$->{1..9},ds0.iot_device_log_$->{2024..2030}1$->{0..2} + tableStrategy: + # 使用标准分片策略 + standard: + # 配置分片字段 + shardingColumn: create_time + # 分片算法名称,不支持大写字母和下划线,否则启动就会报错 + shardingAlgorithmName: time-sharding-algorithm + # 分片算法配置 + shardingAlgorithms: + # 分片算法名称,不支持大写字母和下划线,否则启动就会报错 + time-sharding-algorithm: + # 类型:自定义策略 + type: CLASS_BASED + props: + # 分片策略 + strategy: standard + # 分片算法类 + algorithmClassName: com.fastbee.framework.config.sharding.TimeShardingAlgorithm diff --git a/maibu-common/pom.xml b/maibu-common/pom.xml new file mode 100644 index 0000000..db2d0b6 --- /dev/null +++ b/maibu-common/pom.xml @@ -0,0 +1,219 @@ + + + 4.0.0 + + com.maibu + MiddlePlatform + 4.0.0 + + + maibu-common + + + + + org.springframework + spring-context-support + + + + + org.springframework + spring-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + com.baomidou + mybatis-plus-boot-starter + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus-generator.version} + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.apache.commons + commons-lang3 + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.alibaba.fastjson2 + fastjson2 + + + + + commons-io + commons-io + + + + + commons-fileupload + commons-fileupload + + + + + org.apache.poi + poi-ooxml + + + + + org.yaml + snakeyaml + + + + + io.jsonwebtoken + jjwt + + + + + javax.xml.bind + jaxb-api + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + + + + + eu.bitwalker + UserAgentUtils + + + + + javax.servlet + javax.servlet-api + + + + org.projectlombok + lombok + + + + io.swagger + swagger-annotations + 1.6.2 + compile + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.5 + compile + + + + cn.hutool + hutool-all + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.15.2 + + + + + com.google.guava + guava + + + + com.alibaba + easyexcel-core + + + + + org.dromara.sms4j + sms4j-spring-boot-starter + 3.0.4 + + + + org.mapstruct + mapstruct + + + org.mapstruct + mapstruct-jdk8 + + + org.mapstruct + mapstruct-processor + + + + com.google.zxing + core + + + + com.google.zxing + javase + + + + io.minio + minio + 7.1.4 + + + + + + 8 + 8 + UTF-8 + + + \ No newline at end of file diff --git a/maibu-common/src/main/java/com/maibu/annotation/Anonymous.java b/maibu-common/src/main/java/com/maibu/annotation/Anonymous.java new file mode 100644 index 0000000..b99c1ac --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/annotation/Anonymous.java @@ -0,0 +1,15 @@ +package com.maibu.annotation; + +import java.lang.annotation.*; + +/** + * 匿名访问不鉴权注解 + * + * @author ruoyi + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Anonymous +{ +} diff --git a/maibu-common/src/main/java/com/maibu/annotation/DataScope.java b/maibu-common/src/main/java/com/maibu/annotation/DataScope.java new file mode 100644 index 0000000..eb66dbb --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/annotation/DataScope.java @@ -0,0 +1,29 @@ +package com.maibu.annotation; + +import java.lang.annotation.*; + +/** + * 数据权限过滤注解 + * + * @author ruoyi + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataScope +{ + /** + * 部门表的别名 + */ + public String deptAlias() default ""; + + /** + * 用户表的别名 + */ + public String userAlias() default ""; + + /** + * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来 + */ + public String permission() default ""; +} diff --git a/maibu-common/src/main/java/com/maibu/annotation/DataSource.java b/maibu-common/src/main/java/com/maibu/annotation/DataSource.java new file mode 100644 index 0000000..c969cb8 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/annotation/DataSource.java @@ -0,0 +1,25 @@ +package com.maibu.annotation; + + +import com.maibu.enums.DataSourceType; + +import java.lang.annotation.*; + +/** + * 自定义多数据源切换注解 + * + * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 + * + * @author ruoyi + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DataSource +{ + /** + * 切换数据源名称 + */ + public DataSourceType value() default DataSourceType.master; +} diff --git a/maibu-common/src/main/java/com/maibu/annotation/DictFormat.java b/maibu-common/src/main/java/com/maibu/annotation/DictFormat.java new file mode 100644 index 0000000..d58017c --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/annotation/DictFormat.java @@ -0,0 +1,22 @@ +package com.maibu.annotation; + +import java.lang.annotation.*; + +/** + * 字典格式化 + * + * 实现将字典数据的值,格式化成字典数据的标签 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface DictFormat { + + /** + * 例如说,SysDictTypeConstants、InfDictTypeConstants + * + * @return 字典类型 + */ + String value(); + +} diff --git a/maibu-common/src/main/java/com/maibu/annotation/Excel.java b/maibu-common/src/main/java/com/maibu/annotation/Excel.java new file mode 100644 index 0000000..e87f760 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/annotation/Excel.java @@ -0,0 +1,188 @@ +package com.maibu.annotation; + +import com.maibu.utils.poi.ExcelHandlerAdapter; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.math.BigDecimal; + +/** + * 自定义导出Excel数据注解 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Excel +{ + /** + * 导出时在excel中排序 + */ + public int sort() default Integer.MAX_VALUE; + + /** + * 导出到Excel中的名字. + */ + public String name() default ""; + + /** + * 日期格式, 如: yyyy-MM-dd + */ + public String dateFormat() default ""; + + /** + * 如果是字典类型,请设置字典的type值 (如: sys_user_sex) + */ + public String dictType() default ""; + + /** + * 读取内容转表达式 (如: 0=男,1=女,2=未知) + */ + public String readConverterExp() default ""; + + /** + * 分隔符,读取字符串组内容 + */ + public String separator() default ","; + + /** + * BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化) + */ + public int scale() default -1; + + /** + * BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN + */ + public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; + + /** + * 导出时在excel中每个列的高度 单位为字符 + */ + public double height() default 14; + + /** + * 导出时在excel中每个列的宽 单位为字符 + */ + public double width() default 16; + + /** + * 文字后缀,如% 90 变成90% + */ + public String suffix() default ""; + + /** + * 当值为空时,字段的默认值 + */ + public String defaultValue() default ""; + + /** + * 提示信息 + */ + public String prompt() default ""; + + /** + * 设置只能选择不能输入的列内容. + */ + public String[] combo() default {}; + + /** + * 是否需要纵向合并单元格,应对需求:含有list集合单元格) + */ + public boolean needMerge() default false; + + /** + * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. + */ + public boolean isExport() default true; + + /** + * 另一个类中的属性名称,支持多级获取,以小数点隔开 + */ + public String targetAttr() default ""; + + /** + * 是否自动统计数据,在最后追加一行统计数据总和 + */ + public boolean isStatistics() default false; + + /** + * 导出类型(0数字 1字符串 2图片) + */ + public ColumnType cellType() default ColumnType.STRING; + + /** + * 导出列头背景色 + */ + public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT; + + /** + * 导出列头字体颜色 + */ + public IndexedColors headerColor() default IndexedColors.WHITE; + + /** + * 导出单元格背景色 + */ + public IndexedColors backgroundColor() default IndexedColors.WHITE; + + /** + * 导出单元格字体颜色 + */ + public IndexedColors color() default IndexedColors.BLACK; + + /** + * 导出字段对齐方式 + */ + public HorizontalAlignment align() default HorizontalAlignment.CENTER; + + /** + * 自定义数据处理器 + */ + public Class handler() default ExcelHandlerAdapter.class; + + /** + * 自定义数据处理器参数 + */ + public String[] args() default {}; + + /** + * 字段类型(0:导出导入;1:仅导出;2:仅导入) + */ + Type type() default Type.ALL; + + public enum Type + { + ALL(0), EXPORT(1), IMPORT(2); + private final int value; + + Type(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } + + public enum ColumnType + { + NUMERIC(0), STRING(1), IMAGE(2); + private final int value; + + ColumnType(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } +} \ No newline at end of file diff --git a/maibu-common/src/main/java/com/maibu/annotation/Excels.java b/maibu-common/src/main/java/com/maibu/annotation/Excels.java new file mode 100644 index 0000000..a78fca7 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/annotation/Excels.java @@ -0,0 +1,18 @@ +package com.maibu.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excel注解集 + * + * @author ruoyi + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Excels +{ + public Excel[] value(); +} diff --git a/maibu-common/src/main/java/com/maibu/annotation/Log.java b/maibu-common/src/main/java/com/maibu/annotation/Log.java new file mode 100644 index 0000000..79f60f0 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/annotation/Log.java @@ -0,0 +1,43 @@ +package com.maibu.annotation; + +import com.maibu.enums.BusinessType; +import com.maibu.enums.OperatorType; + +import java.lang.annotation.*; + +/** + * 自定义操作日志记录注解 + * + * @author ruoyi + * + */ +@Target({ ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Log +{ + /** + * 模块 + */ + public String title() default ""; + + /** + * 功能 + */ + public BusinessType businessType() default BusinessType.OTHER; + + /** + * 操作人类别 + */ + public OperatorType operatorType() default OperatorType.MANAGE; + + /** + * 是否保存请求的参数 + */ + public boolean isSaveRequestData() default true; + + /** + * 是否保存响应的参数 + */ + public boolean isSaveResponseData() default true; +} diff --git a/maibu-common/src/main/java/com/maibu/annotation/RateLimiter.java b/maibu-common/src/main/java/com/maibu/annotation/RateLimiter.java new file mode 100644 index 0000000..2ad8d8c --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/annotation/RateLimiter.java @@ -0,0 +1,38 @@ +package com.maibu.annotation; + + +import com.maibu.constant.CacheConstants; +import com.maibu.enums.LimitType; + +import java.lang.annotation.*; + +/** + * 限流注解 + * + * @author ruoyi + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter +{ + /** + * 限流key + */ + public String key() default CacheConstants.RATE_LIMIT_KEY; + + /** + * 限流时间,单位秒 + */ + public int time() default 60; + + /** + * 限流次数 + */ + public int count() default 100; + + /** + * 限流类型 + */ + public LimitType limitType() default LimitType.DEFAULT; +} diff --git a/maibu-common/src/main/java/com/maibu/annotation/RepeatSubmit.java b/maibu-common/src/main/java/com/maibu/annotation/RepeatSubmit.java new file mode 100644 index 0000000..eeca9f6 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/annotation/RepeatSubmit.java @@ -0,0 +1,26 @@ +package com.maibu.annotation; + +import java.lang.annotation.*; + +/** + * 自定义注解防止表单重复提交 + * + * @author ruoyi + * + */ +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit +{ + /** + * 间隔时间(ms),小于此时间视为重复提交 + */ + public int interval() default 5000; + + /** + * 提示消息 + */ + public String message() default "不允许重复提交,请稍候再试"; +} diff --git a/maibu-common/src/main/java/com/maibu/annotation/SysProtocol.java b/maibu-common/src/main/java/com/maibu/annotation/SysProtocol.java new file mode 100644 index 0000000..6a6bd56 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/annotation/SysProtocol.java @@ -0,0 +1,21 @@ +package com.maibu.annotation; + +import java.lang.annotation.*; + +/** + * 表示系统内部协议解析器 + * @author gsb + * @date 2022/10/24 10:33 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface SysProtocol { + + /*协议名*/ + String name() default ""; + /*协议编码*/ + String protocolCode() default ""; + //协议描述 + String description() default ""; +} diff --git a/maibu-common/src/main/java/com/maibu/config/DeviceTask.java b/maibu-common/src/main/java/com/maibu/config/DeviceTask.java new file mode 100644 index 0000000..53660a5 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/config/DeviceTask.java @@ -0,0 +1,121 @@ +package com.maibu.config; + +import com.maibu.constant.FastBeeConstant; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 设备报文处理线程池 + * + * @author bill + */ +@Configuration +@EnableAsync +@ConfigurationProperties(prefix = "spring.task.execution.pool") +@Data +public class DeviceTask { + + private int coreSize; + + private int maxSize; + + private int queueCapacity; + + private int keepAlive; + + /*设备状态池*/ + @Bean(FastBeeConstant.TASK.DEVICE_STATUS_TASK) + public Executor deviceStatusTaskExecutor() { + return builder(FastBeeConstant.TASK.DEVICE_STATUS_TASK); + } + + /*平台自动获取线程池(例如定时获取设备信息)*/ + @Bean(FastBeeConstant.TASK.DEVICE_FETCH_PROP_TASK) + public Executor deviceFetchTaskExecutor() { + return builder(FastBeeConstant.TASK.DEVICE_FETCH_PROP_TASK); + } + + /*设备回调信息(下发指令(服务)设备应答信息)*/ + @Bean(FastBeeConstant.TASK.DEVICE_REPLY_MESSAGE_TASK) + public Executor deviceReplyTaskExecutor() { + return builder(FastBeeConstant.TASK.DEVICE_REPLY_MESSAGE_TASK); + } + + /*设备主动上报(设备数据有变化主动上报)*/ + @Bean(FastBeeConstant.TASK.DEVICE_UP_MESSAGE_TASK) + public Executor deviceUpMessageTaskExecutor() { + return builder(FastBeeConstant.TASK.DEVICE_UP_MESSAGE_TASK); + } + + /*指令下发(服务下发)*/ + @Bean(FastBeeConstant.TASK.FUNCTION_INVOKE_TASK) + public Executor functionInvokeTaskExecutor() { + return builder(FastBeeConstant.TASK.FUNCTION_INVOKE_TASK); + } + + /*内部消费线程*/ + @Bean(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK) + public Executor messageConsumeTaskExecutor() { + return builder(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK); + } + + @Bean(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK_PUB) + public Executor messageConsumePubTaskExecutor() { + return builder(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK_PUB); + } + + @Bean(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK_FETCH) + public Executor messageConsumeFetchTaskExecutor() { + return builder(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK_FETCH); + } + + @Bean(FastBeeConstant.TASK.DELAY_UPGRADE_TASK) + public Executor delayedTaskExecutor() { + return builder(FastBeeConstant.TASK.DELAY_UPGRADE_TASK); + } + + /*设备其他消息处理*/ + @Bean(FastBeeConstant.TASK.DEVICE_OTHER_TASK) + public Executor deviceOtherTaskExecutor() { + return builder(FastBeeConstant.TASK.DEVICE_OTHER_TASK); + } + + @Bean(FastBeeConstant.TASK.DEVICE_TEST_TASK) + public Executor deviceTestTaskExecutor() { + return builder(FastBeeConstant.TASK.DEVICE_TEST_TASK); + } + + @Bean(FastBeeConstant.TASK.DEVICE_ERROR_MONITOR) + public Executor deviceErrorMonitor() { + return builder(FastBeeConstant.TASK.DEVICE_ERROR_MONITOR); + } + + @Bean(FastBeeConstant.TASK.DEVICE_TASK_HANDLER) + public Executor deviceTaskHandler() { + return builder(FastBeeConstant.TASK.DEVICE_TASK_HANDLER); + } + + + /*组装线程池*/ + private ThreadPoolTaskExecutor builder(String threadNamePrefix) { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(coreSize); + executor.setMaxPoolSize(maxSize); + executor.setKeepAliveSeconds(keepAlive); + executor.setQueueCapacity(queueCapacity); + // 线程池对拒绝任务的处理策略 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy()); + //线程池名的前缀 + executor.setThreadNamePrefix(threadNamePrefix); + executor.initialize(); + return executor; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/config/MinioConfig.java b/maibu-common/src/main/java/com/maibu/config/MinioConfig.java new file mode 100644 index 0000000..0c641a0 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/config/MinioConfig.java @@ -0,0 +1,18 @@ +package com.maibu.config; + +import io.minio.MinioClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MinioConfig { + + @Bean + public MinioClient minioClient() { + return MinioClient.builder() + .endpoint("https://oss.satabot.com") + .build(); + } +} + + diff --git a/maibu-common/src/main/java/com/maibu/config/RuoYiConfig.java b/maibu-common/src/main/java/com/maibu/config/RuoYiConfig.java new file mode 100644 index 0000000..0e12bfa --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/config/RuoYiConfig.java @@ -0,0 +1,135 @@ +package com.maibu.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 读取项目相关配置 + * + * @author ruoyi + */ +@Component +@ConfigurationProperties(prefix = "fastbee") +public class RuoYiConfig +{ + /** 项目名称 */ + private String name; + + /** 版本 */ + private String version; + + /** 版权年份 */ + private String copyrightYear; + + /** 实例演示开关 */ + private boolean demoEnabled; + + /** 上传路径 */ + private static String profile; + + /** 获取地址开关 */ + private static boolean addressEnabled; + + /** 验证码类型 */ + private static String captchaType; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getCopyrightYear() + { + return copyrightYear; + } + + public void setCopyrightYear(String copyrightYear) + { + this.copyrightYear = copyrightYear; + } + + public boolean isDemoEnabled() + { + return demoEnabled; + } + + public void setDemoEnabled(boolean demoEnabled) + { + this.demoEnabled = demoEnabled; + } + + public static String getProfile() + { + return profile; + } + + public void setProfile(String profile) + { + RuoYiConfig.profile = profile; + } + + public static boolean isAddressEnabled() + { + return addressEnabled; + } + + public void setAddressEnabled(boolean addressEnabled) + { + RuoYiConfig.addressEnabled = addressEnabled; + } + + public static String getCaptchaType() { + return captchaType; + } + + public void setCaptchaType(String captchaType) { + RuoYiConfig.captchaType = captchaType; + } + + /** + * 获取导入上传路径 + */ + public static String getImportPath() + { + return getProfile() + "/import"; + } + + /** + * 获取头像上传路径 + */ + public static String getAvatarPath() + { + return getProfile() + "/avatar"; + } + + /** + * 获取下载路径 + */ + public static String getDownloadPath() + { + return getProfile() + "/download/"; + } + + /** + * 获取上传路径 + */ + public static String getUploadPath() + { + return getProfile() + "/upload"; + } +} diff --git a/maibu-common/src/main/java/com/maibu/constant/CacheConstants.java b/maibu-common/src/main/java/com/maibu/constant/CacheConstants.java new file mode 100644 index 0000000..2928ff2 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/CacheConstants.java @@ -0,0 +1,90 @@ +package com.maibu.constant; + +/** + * 缓存的key 常量 + * + * @author ruoyi + */ +public class CacheConstants +{ + /** + * 登录用户 redis key + */ + public static final String LOGIN_TOKEN_KEY = "login_tokens:"; + + /** + * 登录用户 redis key + */ + public static final String LOGIN_USERID_KEY = "login_userId:"; + + /** + * 登录用户 平台 redis key + */ + public static final String LOGIN_USERID_SOURCE_KEY = "login_userId_source:"; + + /** + * 验证码 redis key + */ + public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config:"; + + /** + * 字典管理 cache key + */ + public static final String SYS_DICT_KEY = "sys_dict:"; + + /** + * 数据库查询 cache key + */ + public static final String SQL_CACHE_KEY = "sql_cache:"; + + /** + * 防重提交 redis key + */ + public static final String REPEAT_SUBMIT_KEY = "repeat_submit:"; + + /** + * 限流 redis key + */ + public static final String RATE_LIMIT_KEY = "rate_limit:"; + + /** + * 登录账户密码错误次数 redis key + */ + public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; + + /** + * 短信登录验证码 redis key + */ + public static final String LOGIN_SMS_CAPTCHA_PHONE = "login_sms_captcha_phone:"; + + /** + * 邮箱登录验证码 redis key + */ + public static final String LOGIN_EMAIL_CAPTCHA = "login_email_captcha:"; + + /** + * 微信获取accessToken redis key + */ + public static final String WECHAT_GET_ACCESS_TOKEN_APPID = "wechat_get_accessToken:"; + + /** + * 短信注册验证码 redis key + */ + public static final String REGISTER_SMS_CAPTCHA_PHONE = "register_sms_captcha_phone:"; + + /** + * 邮箱注册验证码 redis key + */ + public static final String REGISTER_EMAIL_CAPTCHA = "register_email_captcha:"; + + /**设备OTA升级实时数据*/ + public static final String DEVICE_OTA_DATA = "device:ota:"; + +} + + diff --git a/maibu-common/src/main/java/com/maibu/constant/Constants.java b/maibu-common/src/main/java/com/maibu/constant/Constants.java new file mode 100644 index 0000000..59f59d9 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/Constants.java @@ -0,0 +1,163 @@ +package com.maibu.constant; + +import io.jsonwebtoken.Claims; + +/** + * 通用常量信息 + * + * @author ruoyi + */ +public class Constants +{ + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * www主域 + */ + public static final String WWW = "www."; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 通用成功标识 + */ + public static final String SUCCESS = "0"; + + /** + * 通用失败标识 + */ + public static final String FAIL = "1"; + + /** + * 登录成功 + */ + public static final String LOGIN_SUCCESS = "Success"; + + /** + * 注销 + */ + public static final String LOGOUT = "Logout"; + + /** + * 注册 + */ + public static final String REGISTER = "Register"; + + /** + * 登录失败 + */ + public static final String LOGIN_FAIL = "Error"; + + /** + * 验证码有效期(分钟) + */ + public static final Integer CAPTCHA_EXPIRATION = 2; + + /** + * 令牌 + */ + public static final String TOKEN = "token"; + + /** + * 令牌前缀 + */ + public static final String TOKEN_PREFIX = "Bearer "; + + /** + * 令牌前缀 + */ + public static final String LOGIN_USER_KEY = "login_user_key"; + + /** + * 用户ID + */ + public static final String JWT_USERID = "userid"; + + /** + * 用户名称 + */ + public static final String JWT_USERNAME = Claims.SUBJECT; + + /** + * 用户头像 + */ + public static final String JWT_AVATAR = "avatar"; + + /** + * 创建时间 + */ + public static final String JWT_CREATED = "created"; + + /** + * 用户权限 + */ + public static final String JWT_AUTHORITIES = "authorities"; + + /** + * 资源映射路径 前缀 + */ + public static final String RESOURCE_PREFIX = "/profile"; + + /** + * RMI 远程方法调用 + */ + public static final String LOOKUP_RMI = "rmi:"; + + /** + * LDAP 远程方法调用 + */ + public static final String LOOKUP_LDAP = "ldap:"; + + /** + * LDAPS 远程方法调用 + */ + public static final String LOOKUP_LDAPS = "ldaps:"; + + /** + * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) + */ + public static final String[] JOB_WHITELIST_STR = { "com.fastbee" }; + + /** + * 定时任务违规的字符 + */ + public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", + "org.springframework", "org.apache", "com.fastbee.common.utils.file", "com.fastbee.common.config" }; + + /** + * 语言类型 + */ + public static final String LANGUAGE = "language"; + public static final String ZH_CN = "zh-CN"; + public static final String EN_US = "en-US"; + + /** + * 翻译数据类型 + */ + public static final String MENU = "menu"; + public static final String DICT_DATA = "dict_data"; + public static final String DICT_TYPE = "dict_type"; + public static final String THINGS_MODEL = "things_model"; + public static final String THINGS_MODEL_TEMPLATE = "things_model_template"; + + /** + * 组态分享缓存token-key + */ + public static final String SCADA_SHARE_KEY = "scada_share_key"; +} diff --git a/maibu-common/src/main/java/com/maibu/constant/FastBeeConstant.java b/maibu-common/src/main/java/com/maibu/constant/FastBeeConstant.java new file mode 100644 index 0000000..0b53109 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/FastBeeConstant.java @@ -0,0 +1,356 @@ +package com.maibu.constant; + +/** + * 常量 + * + * @author bill + */ +public interface FastBeeConstant { + + interface SERVER { + String UFT8 = "UTF-8"; + String GB2312 = "GB2312"; + + + String MQTT = "mqtt"; + String PORT = "port"; + String ADAPTER = "adapter"; + String FRAMEDECODER = "frameDecoder"; + String DISPATCHER = "dispatcher"; + String DECODER = "decoder"; + String ENCODER = "encoder"; + String MAXFRAMELENGTH = "maxFrameLength"; + String SLICER = "slicer"; + String DELIMITERS = "delimiters"; + String IDLE = "idle"; + String WS_PREFIX = "web-"; + String WM_PREFIX = "server-"; + String FAST_PHONE = "phone-"; + + /*MQTT平台判定离线时间 keepAlive*1.5 */ + Long DEVICE_PING_EXPIRED = 90000L; + } + + interface CLIENT { + //加盐 + String TOKEN = "fastbee-smart!@#$123"; + } + + /*webSocket配置*/ + interface WS { + String HEART_BEAT = "heartbeat"; + String HTTP_SERVER_CODEC = "httpServerCodec"; + String AGGREGATOR = "aggregator"; + String COMPRESSOR = "compressor"; + String PROTOCOL = "protocol"; + String MQTT_WEBSOCKET = "mqttWebsocket"; + String DECODER = "decoder"; + String ENCODER = "encoder"; + String BROKER_HANDLER = "brokerHandler"; + + } + + interface TASK { + /** + * 设备上下线任务 + */ + String DEVICE_STATUS_TASK = "deviceStatusTask"; + /** + * 设备主动上报任务 + */ + String DEVICE_UP_MESSAGE_TASK = "deviceUpMessageTask"; + /** + * 设备回调任务 + */ + String DEVICE_REPLY_MESSAGE_TASK = "deviceReplyMessageTask"; + /** + * 设备下行任务 + */ + String DEVICE_DOWN_MESSAGE_TASK = "deviceDownMessageTask"; + /** + * 服务调用(指令下发)任务 + */ + String FUNCTION_INVOKE_TASK = "functionInvokeTask"; + /** + * 属性读取任务,区分服务调用 + */ + String DEVICE_FETCH_PROP_TASK = "deviceFetchPropTask"; + /** + * 设备其他消息处理 + */ + String DEVICE_OTHER_TASK = "deviceOtherMsgTask"; + /** + * 数据调试任务 + */ + String DEVICE_TEST_TASK = "deviceTestMsgTask"; + /** + * 消息消费线程 + */ + String MESSAGE_CONSUME_TASK = "messageConsumeTask"; + /*内部消费线程publish*/ + String MESSAGE_CONSUME_TASK_PUB = "messageConsumeTaskPub"; + /*内部消费线程Fetch*/ + String MESSAGE_CONSUME_TASK_FETCH = "messageConsumeTaskFetch"; + /*OTA升级延迟队列*/ + String DELAY_UPGRADE_TASK = "delayUpgradeTask"; + /*OTA升级线程池*/ + String OTA_THREAD_POOL = "otaThreadPoolTaskExecutor"; + + /** + * 设备错误 + */ + String DEVICE_ERROR_MONITOR = "deviceErrorMonitor"; + /** + * 设备任务处理线程 + */ + String DEVICE_TASK_HANDLER = "deviceTaskHandler"; + + } + + interface MQTT { + + String PREDIX = "/+/+"; + + /*OTA消息回复*/ + String OTA_REPLY = "upgrade/reply"; + + } + + /*集群,全局发布的消息类型*/ + interface CHANNEL { + /*设备状态*/ + String DEVICE_STATUS = "device_status"; + /*平台读取属性*/ + String PROP_READ = "prop_read"; + /*推送消息*/ + String PUBLISH = "publish"; + /*服务下发*/ + String FUNCTION_INVOKE = "function_invoke"; + /*事件*/ + String EVENT = "event"; + /*other*/ + String OTHER = "other"; + /*Qos1 推送应答*/ + String PUBLISH_ACK = "publish_ack"; + /*Qos2 发布消息收到*/ + String PUB_REC = "pub_rec"; + /*Qos 发布消息释放*/ + String PUB_REL = "pub_rel"; + /*Qos2 发布消息完成*/ + String PUB_COMP = "pub_comp"; + + String UPGRADE = "upgrade"; + + /*-------------------------ROCKETMQ-------------------------*/ + String SUFFIX = "group"; + /*设备状态*/ + String DEVICE_STATUS_GROUP = DEVICE_STATUS + SUFFIX; + String PROP_READ_GROUP = PROP_READ + SUFFIX; + /*服务下发*/ + String FUNCTION_INVOKE_GROUP = FUNCTION_INVOKE + SUFFIX; + /*推送消息*/ + String PUBLISH_GROUP = PUBLISH + SUFFIX; + /*Qos1 推送应答*/ + String PUBLISH_ACK_GROUP = PUBLISH_ACK + SUFFIX; + /*Qos2 发布消息收到*/ + String PUB_REC_GROUP = PUB_REC + SUFFIX; + /*Qos 发布消息释放*/ + String PUB_REL_GROUP = PUB_REL + SUFFIX; + /*Qos2 发布消息完成*/ + String PUB_COMP_GROUP = PUB_COMP + SUFFIX; + /*OTA升级*/ + String UPGRADE_GROUP = UPGRADE + SUFFIX; + } + + + /** + * redisKey 定义 + */ + interface REDIS { + /*在线设备列表*/ + String DEVICE_ONLINE_LIST = "device:online:list"; + /*设备实时状态key*/ + String DEVICE_RUNTIME_DATA = "device:runtime:"; + /*通讯协议参数*/ + String DEVICE_PROTOCOL_PARAM = "device:param:"; + /** + * 设备消息id缓存key + */ + String DEVICE_MESSAGE_ID = "device:messageId:"; + /** + * 固件版本key + */ + String FIRMWARE_VERSION = "device:firmware:"; + /** + * 设备信息 + */ + String DEVICE_MSG = "device:msg:"; + /** + * 属性下发回调 + */ + String PROP_READ_STORE = "prop:read:store:"; + /** + * sip + */ + String RECORDINFO_KEY = "sip:recordinfo:"; + String DEVICEID_KEY = "sip:deviceid:"; + String STREAM_KEY = "sip:stream:"; + String INVITE_KEY = "sip:invite:"; + String SIP_CSEQ_PREFIX = "sip:CSEQ:"; + String DEFAULT_SIP_CONFIG = "sip:config"; + String DEFAULT_MEDIA_CONFIG = "sip:mediaconfig"; + + /** + * rule + */ + String RULE_SILENT_TIME = "rule:SilentTime"; + + /** + * 总保留消息 + */ + String MESSAGE_RETAIN_TOTAL = "message:retain:total"; + + /*发送消息数*/ + String MESSAGE_SEND_TOTAL = "message:send:total"; + /*接收消息数*/ + String MESSAGE_RECEIVE_TOTAL = "message:receive:total"; + /*连接次数*/ + String MESSAGE_CONNECT_TOTAL = "message:connect:total"; + /** + * 认证次数 + */ + String MESSAGE_AUTH_TOTAL = "message:auth:total"; + /** + * 订阅次数 + */ + String MESSAGE_SUBSCRIBE_TOTAL = "message:subscribe:total"; + + /** + * 今日接收消息 + */ + String MESSAGE_RECEIVE_TODAY = "message:receive:today"; + /** + * 今日发送消息 + */ + String MESSAGE_SEND_TODAY = "message:send:today"; + + /** + * 物模型值缓存 + */ + String DEVICE_PRE_KEY = "TSLV:"; + + /** + * 物模型缓存 + */ + String TSL_PRE_KEY = "TSL:"; + + String MODBUS_PRE_KEY = "MODBUS:"; + + /** + * modbus缓存指令 + */ + String POLL_MODBUS_KEY = "MODBUS:POLL:"; + /** + * modbus运行时数据 + */ + String MODBUS_RUNTIME = "MODBUS:RUNTIME:"; + + String MODBUS_LOCK = "MODBUS:LOCK:"; + + /** + * 通知企业微信应用消息accessToken缓存key + */ + String NOTIFY_WECOM_APPLY_ACCESSTOKEN = "notify:wecom:apply:"; + + // 场景变量命名空间:Key:SMTV:{sceneModelTagId} + String SCENE_MODEL_TAG_ID = "SMTV:"; + + /** + * modbus运行时数据 + */ + String MODBUS_TCP = "MODBUS:TCP:"; + + /** + * modbus运行时数据 + */ + String MODBUS_TCP_RUNTIME = "MODBUS:TCP:RUNTIME:"; + /** + * OTA实时数据 + */ + String DEVICE_OTA_DATA = "device:ota:"; + + } + + interface PROTOCOL { + String ModbusRtu = "MODBUS-RTU"; + String YinErDa = "YinErDa"; + String JsonObject = "JSONOBJECT"; + String JsonArray = "JSON"; + String ModbusRtuPak = "MODBUS-RTU-PAK"; + String NetOTA = "OTA-NET"; + String FlowMeter = "FlowMeter"; + String RJ45 = "RJ45"; + String ModbusToJson = "MODBUS-JSON"; + String ModbusToJsonHP = "MODBUS-JSON-HP"; + String ModbusToJsonZQWL = "MODBUS-JSON-ZQWL"; + String JsonObject_ChenYi = "JSONOBJECT-CHENYI"; + String GEC6100D = "MODBUS-JSON-GEC6100D"; + String SGZ = "SGZ"; + String CH = "CH"; + String ModbusTcp = "MODBUS-TCP"; + + + } + + interface URL { + /** + * 微信小程序订阅消息推送url前缀 + */ + String WX_MINI_PROGRAM_PUSH_URL_PREFIX = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send"; + /** + * 微信网站、移动应用登录获取用户access_token + */ + String WX_GET_ACCESS_TOKEN_URL_PREFIX = "https://api.weixin.qq.com/sns/oauth2/access_token"; + /** + * 微信小程序登录获取用户会话参数 + */ + String WX_MINI_PROGRAM_GET_USER_SESSION_URL_PREFIX = "https://api.weixin.qq.com/sns/jscode2session"; + /** + * 微信小程序、公众号获取access_token + */ + String WX_MINI_PROGRAM_GET_ACCESS_TOKEN_URL_PREFIX = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"; + /** + * 微信获取用户信息 + */ + String WX_GET_USER_INFO_URL_PREFIX = "https://api.weixin.qq.com/sns/userinfo"; + /** + * 获取用户手机号信息 + */ + String WX_GET_USER_PHONE_URL_PREFIX = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token="; + /** + * 企业微信获取accessToken + */ + String WECOM_GET_ACCESSTOKEN = "https://qyapi.weixin.qq.com/cgi-bin/gettoken"; + /** + * 企业微信发送应用消息 + */ + String WECOM_APPLY_SEND = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token="; + /** + * 微信公众号获取用户信息 + */ + String WX_PUBLIC_ACCOUNT_GET_USER_INFO_URL_PREFIX = "https://api.weixin.qq.com/cgi-bin/user/info"; + /** + * 微信公众号发送模版消息 + */ + String WX_PUBLIC_ACCOUNT_TEMPLATE_SEND_URL_PREFIX = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="; + } + + interface TRANSPORT { + String MQTT = "MQTT"; + String TCP = "TCP"; + String COAP = "COAP"; + String UDP = "UDP"; + String GB28181 = "GB28181"; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/constant/GenConstants.java b/maibu-common/src/main/java/com/maibu/constant/GenConstants.java new file mode 100644 index 0000000..9ab9f06 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/GenConstants.java @@ -0,0 +1,117 @@ +package com.maibu.constant; + +/** + * 代码生成通用常量 + * + * @author ruoyi + */ +public class GenConstants +{ + /** 单表(增删改查) */ + public static final String TPL_CRUD = "crud"; + + /** 树表(增删改查) */ + public static final String TPL_TREE = "tree"; + + /** 主子表(增删改查) */ + public static final String TPL_SUB = "sub"; + + /** 树编码字段 */ + public static final String TREE_CODE = "treeCode"; + + /** 树父编码字段 */ + public static final String TREE_PARENT_CODE = "treeParentCode"; + + /** 树名称字段 */ + public static final String TREE_NAME = "treeName"; + + /** 上级菜单ID字段 */ + public static final String PARENT_MENU_ID = "parentMenuId"; + + /** 上级菜单名称字段 */ + public static final String PARENT_MENU_NAME = "parentMenuName"; + + /** 数据库字符串类型 */ + public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" }; + + /** 数据库文本类型 */ + public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" }; + + /** 数据库时间类型 */ + public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" }; + + /** 数据库数字类型 */ + public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer", + "bit", "bigint", "float", "double", "decimal" }; + + /** 页面不需要编辑字段 */ + public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" }; + + /** 页面不需要显示的列表字段 */ + public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by", + "update_time" }; + + /** 页面不需要查询字段 */ + public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by", + "update_time", "remark" }; + + /** Entity基类字段 */ + public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" }; + + /** Tree基类字段 */ + public static final String[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors", "children" }; + + /** 文本框 */ + public static final String HTML_INPUT = "input"; + + /** 文本域 */ + public static final String HTML_TEXTAREA = "textarea"; + + /** 下拉框 */ + public static final String HTML_SELECT = "select"; + + /** 单选框 */ + public static final String HTML_RADIO = "radio"; + + /** 复选框 */ + public static final String HTML_CHECKBOX = "checkbox"; + + /** 日期控件 */ + public static final String HTML_DATETIME = "datetime"; + + /** 图片上传控件 */ + public static final String HTML_IMAGE_UPLOAD = "imageUpload"; + + /** 文件上传控件 */ + public static final String HTML_FILE_UPLOAD = "fileUpload"; + + /** 富文本控件 */ + public static final String HTML_EDITOR = "editor"; + + /** 字符串类型 */ + public static final String TYPE_STRING = "String"; + + /** 整型 */ + public static final String TYPE_INTEGER = "Integer"; + + /** 长整型 */ + public static final String TYPE_LONG = "Long"; + + /** 浮点型 */ + public static final String TYPE_DOUBLE = "Double"; + + /** 高精度计算类型 */ + public static final String TYPE_BIGDECIMAL = "BigDecimal"; + + /** 时间类型 */ + public static final String TYPE_DATE = "Date"; + + /** 模糊查询 */ + public static final String QUERY_LIKE = "LIKE"; + + /** 相等查询 */ + public static final String QUERY_EQ = "EQ"; + + /** 需要 */ + public static final String REQUIRE = "1"; +} diff --git a/maibu-common/src/main/java/com/maibu/constant/HttpStatus.java b/maibu-common/src/main/java/com/maibu/constant/HttpStatus.java new file mode 100644 index 0000000..4bc6f91 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/HttpStatus.java @@ -0,0 +1,105 @@ +package com.maibu.constant; + +/** + * 返回状态码 + * + * @author ruoyi + */ +public class HttpStatus +{ + /** + * 操作成功 + */ + public static final int SUCCESS = 200; + + /** + * 对象创建成功 + */ + public static final int CREATED = 201; + + /** + * 请求已经被接受 + */ + public static final int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + public static final int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + public static final int MOVED_PERM = 301; + + /** + * 重定向 + */ + public static final int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + public static final int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + public static final int BAD_REQUEST = 400; + + /** + * 未授权 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + public static final int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + public static final int NOT_FOUND = 404; + + /** + * 不允许的http方法 + */ + public static final int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + public static final int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + public static final int UNSUPPORTED_TYPE = 415; + + /** + * 用户不存在 + */ + public static final int USER_NO_EXIST = 450; + + /** + * 系统内部错误 + */ + public static final int ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; + + /** + * 不弹窗显示 + */ + public static final int NO_MESSAGE_ALERT = 502; + + + /** + * 系统警告消息 + */ + public static final int WARN = 601; +} diff --git a/maibu-common/src/main/java/com/maibu/constant/IoTCommonDeviceConstant.java b/maibu-common/src/main/java/com/maibu/constant/IoTCommonDeviceConstant.java new file mode 100644 index 0000000..b5b15d6 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/IoTCommonDeviceConstant.java @@ -0,0 +1,19 @@ +package com.maibu.constant; + +public class IoTCommonDeviceConstant { + + /** + * 定义下用到的topic 格式 + */ + public static final String property_topic = "/iot/device/+/property"; + + /** + * 定义下用到的topic 格式 + */ + public static final String event_topic = "/iot/device/+/event"; + + /** + * 定义下用到的topic 格式 + */ + public static final String action_topic = "/iot/device/+/action"; +} diff --git a/maibu-common/src/main/java/com/maibu/constant/MagicValueConstants.java b/maibu-common/src/main/java/com/maibu/constant/MagicValueConstants.java new file mode 100644 index 0000000..08903e3 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/MagicValueConstants.java @@ -0,0 +1,21 @@ +package com.maibu.constant; + +/** + * 魔法值常量 + * + * @author ruoyi + */ +public class MagicValueConstants +{ + /** + * 3600 + */ + public static final Integer VALUE_3600 = 3600; + + /** + * 60 + */ + public static final Integer VALUE_60 = 60; + + +} diff --git a/maibu-common/src/main/java/com/maibu/constant/ProductAuthConstant.java b/maibu-common/src/main/java/com/maibu/constant/ProductAuthConstant.java new file mode 100644 index 0000000..89b9316 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/ProductAuthConstant.java @@ -0,0 +1,41 @@ +package com.maibu.constant; + +/** + * + * @author fastb + * @date 2023-08-03 10:20 + */ +public class ProductAuthConstant { + + /** + * 产品设备认证方式-简单认证 + */ + public static final Integer AUTH_WAY_SIMPLE = 1; + /** + * 产品设备认证方式-简单认证 + */ + public static final Integer AUTH_WAY_ENCRYPT = 2; + /** + * 产品设备认证方式-简单认证 + */ + public static final Integer AUTH_WAY_SIMPLE_AND_ENCRYPT = 3; + + /** + * 产品设备客户端ID认证类型-简单认证 + */ + public static final String CLIENT_ID_AUTH_TYPE_SIMPLE = "S"; + + /** + * 产品设备客户端ID认证类型-简单认证 + */ + public static final String CLIENT_ID_AUTH_TYPE_ENCRYPT = "E"; + /** + * 设备授权 + */ + public static final Integer AUTHORIZE = 1; + /** + * 设备没有授权 + */ + public static final Integer NO_AUTHORIZE = 1; + +} diff --git a/maibu-common/src/main/java/com/maibu/constant/ScadaConstant.java b/maibu-common/src/main/java/com/maibu/constant/ScadaConstant.java new file mode 100644 index 0000000..88546da --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/ScadaConstant.java @@ -0,0 +1,14 @@ +package com.maibu.constant; + +/** + * @author fastb + * @version 1.0 + * @description: 组态常量类 + * @date 2024-01-02 14:40 + */ +public class ScadaConstant { + + public static final String COMPONENT_TEMPLATE_DEFAULT = "
\n

自定义组件案例

\n

支持element ui、样式自定义、vue的语法等

\n 点击按钮\n
"; + public static final String COMPONENT_SCRIPT_DEFAULT = "export default {\n data() {\n return {}\n },\n created() {\n\n },\n mounted(){\n\n },\n methods:{\n handleClick(){\n this.$message('这是一条消息提示');\n }\n }\n}"; + public static final String COMPONENT_STYLE_DEFAULT = "h2 {\n color:#409EFF\n}\n\nh4 {\n color:#F56C6C\n}"; +} diff --git a/maibu-common/src/main/java/com/maibu/constant/SceneModelConstants.java b/maibu-common/src/main/java/com/maibu/constant/SceneModelConstants.java new file mode 100644 index 0000000..9324176 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/SceneModelConstants.java @@ -0,0 +1,47 @@ +package com.maibu.constant; + +/** + * @author fastb + * @version 1.0 + * @description: 场景相关的场景 + * @date 2024-06-04 15:26 + */ +public class SceneModelConstants { + + /** + * 时 + */ + public static final String CYCLE_HOUR = "hour"; + + /** + * 日 + */ + public static final String CYCLE_DAY = "day"; + + /** + * 周 + */ + public static final String CYCLE_WEEK = "week"; + + /** + * 月 + */ + public static final String CYCLE_MONTH = "month"; + + /** + * 当日 + */ + public static final Integer CYCLE_TO_TYPE_NOW_DAY = 1; + /** + * 次日 + */ + public static final Integer CYCLE_TO_TYPE_SECOND_DAY = 2; + /** + * 周 + */ + public static final Integer CYCLE_TO_TYPE_WEEK = 3; + /** + * 月 + */ + public static final Integer CYCLE_TO_TYPE_MONTH = 4; +} diff --git a/maibu-common/src/main/java/com/maibu/constant/ScheduleConstants.java b/maibu-common/src/main/java/com/maibu/constant/ScheduleConstants.java new file mode 100644 index 0000000..12b9833 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/ScheduleConstants.java @@ -0,0 +1,50 @@ +package com.maibu.constant; + +/** + * 任务调度通用常量 + * + * @author ruoyi + */ +public class ScheduleConstants +{ + public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME"; + + /** 执行目标key */ + public static final String TASK_PROPERTIES = "TASK_PROPERTIES"; + + /** 默认 */ + public static final String MISFIRE_DEFAULT = "0"; + + /** 立即触发执行 */ + public static final String MISFIRE_IGNORE_MISFIRES = "1"; + + /** 触发一次执行 */ + public static final String MISFIRE_FIRE_AND_PROCEED = "2"; + + /** 不触发立即执行 */ + public static final String MISFIRE_DO_NOTHING = "3"; + + public enum Status + { + /** + * 正常 + */ + NORMAL("0"), + /** + * 暂停 + */ + PAUSE("1"); + + private String value; + + private Status(String value) + { + this.value = value; + } + + public String getValue() + { + return value; + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/constant/SipConstants.java b/maibu-common/src/main/java/com/maibu/constant/SipConstants.java new file mode 100644 index 0000000..8b8b903 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/SipConstants.java @@ -0,0 +1,9 @@ +package com.maibu.constant; + +public class SipConstants { + public static final String MESSAGE_CATALOG = "Catalog"; + public static final String MESSAGE_KEEP_ALIVE = "Keepalive"; + public static final String MESSAGE_DEVICE_INFO = "DeviceInfo"; + public static final String MESSAGE_RECORD_INFO = "RecordInfo"; + public static final String MESSAGE_MEDIA_STATUS = "MediaStatus"; +} diff --git a/maibu-common/src/main/java/com/maibu/constant/UserConstants.java b/maibu-common/src/main/java/com/maibu/constant/UserConstants.java new file mode 100644 index 0000000..c2757e3 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/constant/UserConstants.java @@ -0,0 +1,78 @@ +package com.maibu.constant; + +/** + * 用户常量信息 + * + * @author ruoyi + */ +public class UserConstants +{ + /** + * 平台内系统用户的唯一标志 + */ + public static final String SYS_USER = "SYS_USER"; + + /** 正常状态 */ + public static final String NORMAL = "0"; + + /** 异常状态 */ + public static final String EXCEPTION = "1"; + + /** 用户封禁状态 */ + public static final String USER_DISABLE = "1"; + + /** 角色封禁状态 */ + public static final String ROLE_DISABLE = "1"; + + /** 部门正常状态 */ + public static final String DEPT_NORMAL = "0"; + + /** 部门停用状态 */ + public static final String DEPT_DISABLE = "1"; + + /** 字典正常状态 */ + public static final String DICT_NORMAL = "0"; + + /** 是否为系统默认(是) */ + public static final String YES = "Y"; + + /** 是否菜单外链(是) */ + public static final String YES_FRAME = "0"; + + /** 是否菜单外链(否) */ + public static final String NO_FRAME = "1"; + + /** 菜单类型(目录) */ + public static final String TYPE_DIR = "M"; + + /** 菜单类型(菜单) */ + public static final String TYPE_MENU = "C"; + + /** 菜单类型(按钮) */ + public static final String TYPE_BUTTON = "F"; + + /** Layout组件标识 */ + public final static String LAYOUT = "Layout"; + + /** ParentView组件标识 */ + public final static String PARENT_VIEW = "ParentView"; + + /** InnerLink组件标识 */ + public final static String INNER_LINK = "InnerLink"; + + /** 校验返回结果码 */ + public final static String UNIQUE = "0"; + public final static String NOT_UNIQUE = "1"; + + /** + * 用户名长度限制 + */ + public static final int USERNAME_MIN_LENGTH = 2; + public static final int USERNAME_MAX_LENGTH = 20; + + /** + * 密码长度限制 + */ + public static final int PASSWORD_MIN_LENGTH = 5; + public static final int PASSWORD_MAX_LENGTH = 20; +} diff --git a/maibu-common/src/main/java/com/maibu/core/controller/BaseController.java b/maibu-common/src/main/java/com/maibu/core/controller/BaseController.java new file mode 100644 index 0000000..c278df3 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/controller/BaseController.java @@ -0,0 +1,206 @@ +package com.maibu.core.controller; + +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.maibu.constant.CacheConstants; +import com.maibu.constant.HttpStatus; +import com.maibu.core.domain.AjaxResult; +import com.maibu.core.domain.model.LoginUser; +import com.maibu.core.page.PageDomain; +import com.maibu.core.page.TableDataInfo; +import com.maibu.core.page.TableSupport; +import com.maibu.core.redis.RedisCache; +import com.maibu.utils.*; +import com.maibu.utils.sql.SqlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; + +import javax.annotation.Resource; +import java.beans.PropertyEditorSupport; +import java.util.Date; +import java.util.List; + +/** + * web层通用数据处理 + * + * @author ruoyi + */ +public class BaseController { + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Resource + private RedisCache redisCache; + + /** + * 将前台传递过来的日期格式的字符串,自动转化为Date类型 + */ + @InitBinder + public void initBinder(WebDataBinder binder) { + // Date 类型转换 + binder.registerCustomEditor(Date.class, new PropertyEditorSupport() { + @Override + public void setAsText(String text) { + setValue(DateUtils.parseDate(text)); + } + }); + } + + /** + * 设置请求分页数据 + */ + protected void startPage() { + PageUtils.startPage(); + } + + /** + * 设置请求排序数据 + */ + protected void startOrderBy() { + PageDomain pageDomain = TableSupport.buildPageRequest(); + if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) { + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + PageHelper.orderBy(orderBy); + } + } + + /** + * 清理分页的线程变量 + */ + protected void clearPage() { + PageUtils.clearPage(); + } + + /** + * 响应请求分页数据 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + protected TableDataInfo getDataTable(List list) { + TableDataInfo rspData = new TableDataInfo(); + rspData.setCode(HttpStatus.SUCCESS); + rspData.setMsg(MessageUtils.message("query.success")); + rspData.setRows(list); + rspData.setTotal(new PageInfo(list).getTotal()); + return rspData; + } + + protected TableDataInfo getDataTable(List list, Long total) { + TableDataInfo rspData = new TableDataInfo(); + rspData.setCode(HttpStatus.SUCCESS); + rspData.setMsg("查询成功"); + rspData.setRows(list); + rspData.setTotal(total); + return rspData; + } + + /** + * 返回成功 + */ + public AjaxResult success() { + return AjaxResult.success(); + } + + /** + * 返回失败消息 + */ + public AjaxResult error() { + return AjaxResult.error(); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(String message) { + return AjaxResult.success(message); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(Object data) { + return AjaxResult.success(data); + } + + /** + * 返回失败消息 + */ + public AjaxResult error(String message) { + return AjaxResult.error(message); + } + + /** + * 返回警告消息 + */ + public AjaxResult warn(String message) { + return AjaxResult.warn(message); + } + + /** + * 响应返回结果 + * + * @param rows 影响行数 + * @return 操作结果 + */ + protected AjaxResult toAjax(int rows) { + return rows > 0 ? AjaxResult.success() : AjaxResult.error(); + } + + /** + * 响应返回结果 + * + * @param result 结果 + * @return 操作结果 + */ + protected AjaxResult toAjax(boolean result) { + return result ? success() : error(); + } + + protected AjaxResult toAjax(Object result) { + return result != null ? success(result) : error(); + } + + /** + * 页面跳转 + */ + public String redirect(String url) { + return StringUtils.format("redirect:{}", url); + } + + /** + * 获取用户缓存信息 + * 由于不同端不能获取最新用户信息,所以优先以用户id缓存key获取用户信息 + */ + public LoginUser getLoginUser() { + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (loginUser != null) { + Long userId = loginUser.getUserId(); + if (userId != null) { + String userKey = CacheConstants.LOGIN_USERID_KEY + userId; + return redisCache.getCacheObject(userKey); + } + } + return loginUser; + } + + /** + * 获取登录用户id + */ + public Long getUserId() { + return getLoginUser().getUserId(); + } + + /** + * 获取登录部门id + */ + public Long getDeptId() { + return getLoginUser().getDeptId(); + } + + /** + * 获取登录用户名 + */ + public String getUsername() { + return getLoginUser().getUsername(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/device/DeviceAndProtocol.java b/maibu-common/src/main/java/com/maibu/core/device/DeviceAndProtocol.java new file mode 100644 index 0000000..3a29788 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/device/DeviceAndProtocol.java @@ -0,0 +1,66 @@ +package com.maibu.core.device; + +import lombok.Data; + +/** + * @author gsb + * @date 2024/6/14 9:25 + */ +@Data +public class DeviceAndProtocol { + + /** + * 子设备编号 + */ + private Long deviceId; + /** + * 设备编号 + */ + private String serialNumber; + /** + * 协议编号 + */ + private String protocolCode; + /** + * 产品id + */ + private Long productId; + + private String transport; + + /** + * 设备类型 + */ + private Integer deviceType; + /** + * 子设备地址 + */ + private Integer slaveId; + /** + * 网关绑定的子设备地址 + */ + private Integer proSlaveId; + /** + * 网关设备id + */ + private Long gwDeviceId; + /** + * 网关设备产品id + */ + private Long gwProductId; + /** + * 网关设备编号 + */ + private String gwSerialNumber; + /** + * 网关设备名 + */ + private String gwDeviceName; + /** + * 网关产品名 + */ + private String gwProductName; + + private Long tenantId; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/AjaxResult.java b/maibu-common/src/main/java/com/maibu/core/domain/AjaxResult.java new file mode 100644 index 0000000..b28c745 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/AjaxResult.java @@ -0,0 +1,216 @@ +package com.maibu.core.domain; + + +import com.maibu.constant.HttpStatus; +import com.maibu.utils.StringUtils; + +import java.util.HashMap; + +/** + * 操作消息提醒 + * + * @author ruoyi + */ +public class AjaxResult extends HashMap +{ + private static final long serialVersionUID = 1L; + + /** 状态码 */ + public static final String CODE_TAG = "code"; + + /** 返回内容 */ + public static final String MSG_TAG = "msg"; + + /** 数据对象 */ + public static final String DATA_TAG = "data"; + + /** + * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 + */ + public AjaxResult() + { + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public AjaxResult(int code, String msg) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public AjaxResult(int code, String msg, Object data) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + if (StringUtils.isNotNull(data)) + { + super.put(DATA_TAG, data); + } + } + + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public AjaxResult(int code, String msg, Object data,int total) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + if (StringUtils.isNotNull(data)) + { + super.put(DATA_TAG, data); + } + super.put("total",total); + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static AjaxResult success() + { + return AjaxResult.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static AjaxResult success(Object data) + { + return AjaxResult.success("操作成功", data); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static AjaxResult success(Object data,int total) + { + return new AjaxResult(HttpStatus.SUCCESS, "操作成功", data,total); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @return 成功消息 + */ + public static AjaxResult success(String msg) + { + return AjaxResult.success(msg, null); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 成功消息 + */ + public static AjaxResult success(String msg, Object data) + { + return new AjaxResult(HttpStatus.SUCCESS, msg, data); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult warn(String msg) + { + return AjaxResult.warn(msg, null); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static AjaxResult warn(String msg, Object data) + { + return new AjaxResult(HttpStatus.WARN, msg, data); + } + + /** + * 返回错误消息 + * + * @return 错误消息 + */ + public static AjaxResult error() + { + return AjaxResult.error("操作失败"); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @return 错误消息 + */ + public static AjaxResult error(String msg) + { + return AjaxResult.error(msg, null); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 错误消息 + */ + public static AjaxResult error(String msg, Object data) + { + return new AjaxResult(HttpStatus.ERROR, msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 错误消息 + */ + public static AjaxResult error(int code, String msg) + { + return new AjaxResult(code, msg, null); + } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public AjaxResult put(String key, Object value) + { + super.put(key, value); + return this; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/BaseDO.java b/maibu-common/src/main/java/com/maibu/core/domain/BaseDO.java new file mode 100644 index 0000000..39066f7 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/BaseDO.java @@ -0,0 +1,43 @@ +package com.maibu.core.domain; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 基类,时间类型改为LocalDateTime + * @author fastb + * @date 2023-08-22 9:11 + */ +@Data +public class BaseDO implements Serializable { + private static final long serialVersionUID = 1L; + + /** 创建者 */ + @ApiModelProperty("创建者") + private String createBy; + + /** 创建时间 */ + @ApiModelProperty("创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + /** 更新者 */ + @ApiModelProperty("更新者") + private String updateBy; + + /** 更新时间 */ + @ApiModelProperty("更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + /** 逻辑删除 */ + @ApiModelProperty("逻辑删除") + @TableLogic + private Boolean delFlag; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/BaseEntity.java b/maibu-common/src/main/java/com/maibu/core/domain/BaseEntity.java new file mode 100644 index 0000000..e981649 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/BaseEntity.java @@ -0,0 +1,153 @@ +package com.maibu.core.domain; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.annotations.ApiModelProperty; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * Entity基类 + * + * @author ruoyi + */ +public class BaseEntity implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 搜索值 */ + @TableField(exist = false) + @ApiModelProperty("搜索值") + @JsonIgnore + private String searchValue; + + /** 创建者 */ + @ApiModelProperty("创建者") + private String createBy; + + /** 创建时间 */ + @ApiModelProperty("创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** 更新者 */ + @ApiModelProperty("更新者") + private String updateBy; + + /** 更新时间 */ + @ApiModelProperty("更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** 备注 */ + @ApiModelProperty("备注") + @TableField(exist = false) + private String remark; + + public Integer getPageNum() { + return pageNum; + } + + public void setPageNum(Integer pageNum) { + this.pageNum = pageNum; + } + + public Integer getPageSize() { + return pageSize; + } + + public void setPageSize(Integer pageSize) { + this.pageSize = pageSize; + } + + @TableField(exist = false) + private Integer pageNum; + + @TableField(exist = false) + private Integer pageSize; + + /** 请求参数 */ + @TableField(exist = false) + @ApiModelProperty("请求参数") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map params; + + public String getSearchValue() + { + return searchValue; + } + + public void setSearchValue(String searchValue) + { + this.searchValue = searchValue; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } + + public Map getParams() + { + if (params == null) + { + params = new HashMap<>(); + } + return params; + } + + public void setParams(Map params) + { + this.params = params; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/CommonResult.java b/maibu-common/src/main/java/com/maibu/core/domain/CommonResult.java new file mode 100644 index 0000000..e4578c1 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/CommonResult.java @@ -0,0 +1,112 @@ +package com.maibu.core.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.maibu.enums.GlobalErrorCodeConstants; +import com.maibu.exception.ErrorCode; +import com.maibu.exception.ServiceException; +import lombok.Data; +import org.springframework.util.Assert; + +import java.io.Serializable; +import java.util.Objects; + +/** + * 通用返回 + * + * @param 数据泛型 + */ +@Data +public class CommonResult implements Serializable { + + /** + * 错误码 + * + * @see ErrorCode#getCode() + */ + private Integer code; + /** + * 返回数据 + */ + private T data; + /** + * 错误提示,用户可阅读 + * + * @see ErrorCode#getMsg() () + */ + private String msg; + + /** + * 将传入的 result 对象,转换成另外一个泛型结果的对象 + * + * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。 + * + * @param result 传入的 result 对象 + * @param 返回的泛型 + * @return 新的 CommonResult 对象 + */ + public static CommonResult error(CommonResult result) { + return error(result.getCode(), result.getMsg()); + } + + public static CommonResult error(Integer code, String message) { + Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code), "code 必须是错误的!"); + CommonResult result = new CommonResult<>(); + result.code = code; + result.msg = message; + return result; + } + + public static CommonResult error(ErrorCode errorCode) { + return error(errorCode.getCode(), errorCode.getMsg()); + } + + public static CommonResult success(T data) { + CommonResult result = new CommonResult<>(); + result.code = GlobalErrorCodeConstants.SUCCESS.getCode(); + result.data = data; + result.msg = ""; + return result; + } + + public static boolean isSuccess(Integer code) { + return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode()); + } + + @JsonIgnore // 避免 jackson 序列化 + public boolean isSuccess() { + return isSuccess(code); + } + + @JsonIgnore // 避免 jackson 序列化 + public boolean isError() { + return !isSuccess(); + } + + // ========= 和 Exception 异常体系集成 ========= + + /** + * 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常 + */ + public void checkError() throws ServiceException { + if (isSuccess()) { + return; + } + // 业务异常 + throw new ServiceException(code, msg); + } + + /** + * 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常 + * 如果没有,则返回 {@link #data} 数据 + */ + @JsonIgnore // 避免 jackson 序列化 + public T getCheckedData() { + checkError(); + return data; + } + + public static CommonResult error(ServiceException serviceException) { + return error(serviceException.getCode(), serviceException.getMessage()); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/ImportExcelVO.java b/maibu-common/src/main/java/com/maibu/core/domain/ImportExcelVO.java new file mode 100644 index 0000000..4ba1be2 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/ImportExcelVO.java @@ -0,0 +1,36 @@ +package com.maibu.core.domain; + +import com.maibu.annotation.Excel; +import lombok.Data; + +/** + * @author admin + * @version 1.0 + * @description: TODO + * @date 2024-07-12 16:20 + */ +@Data +public class ImportExcelVO { + + @Excel(name = "ID") + private Long id; + + @Excel(name = "城市ID") + private String code; + + @Excel(name = "行政归属") + private String city; + + @Excel(name = "城市简称") + private String simCity; + + @Excel(name = "拼音") + private String cn; + + @Excel(name = "lat") + private String lat; + + @Excel(name = "lon") + private String lon; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/OutputExcelVO.java b/maibu-common/src/main/java/com/maibu/core/domain/OutputExcelVO.java new file mode 100644 index 0000000..83b9aaf --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/OutputExcelVO.java @@ -0,0 +1,25 @@ +package com.maibu.core.domain; + +import lombok.Data; + +import java.util.List; + +/** + * @author admin + * @version 1.0 + * @description: TODO + * @date 2024-07-12 16:25 + */ +@Data +public class OutputExcelVO { + + private String code; + + private String name; + + private String lat; + + private String lon; + + private List children; +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/PageParam.java b/maibu-common/src/main/java/com/maibu/core/domain/PageParam.java new file mode 100644 index 0000000..ace6c8e --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/PageParam.java @@ -0,0 +1,25 @@ +package com.maibu.core.domain; + +import lombok.Data; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import java.io.Serializable; + +@Data +public class PageParam implements Serializable { + + private static final Integer PAGE_NO = 1; + private static final Integer PAGE_SIZE = 10; + + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小值为 1") + private Integer pageNo = PAGE_NO; + + @NotNull(message = "每页条数不能为空") + @Min(value = 1, message = "每页条数最小值为 1") + @Max(value = 100, message = "每页条数最大值为 100") + private Integer pageSize = PAGE_SIZE; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/PageResult.java b/maibu-common/src/main/java/com/maibu/core/domain/PageResult.java new file mode 100644 index 0000000..0d85017 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/PageResult.java @@ -0,0 +1,42 @@ +package com.maibu.core.domain; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@Api(tags = "分页结果") +@Data +public final class PageResult implements Serializable { + + @ApiModelProperty(value = "数据", required = true) + private List list; + + @ApiModelProperty(value = "总量", required = true) + private Long total; + + public PageResult() { + } + + public PageResult(List list, Long total) { + this.list = list; + this.total = total; + } + + public PageResult(Long total) { + this.list = new ArrayList<>(); + this.total = total; + } + + public static PageResult empty() { + return new PageResult<>(0L); + } + + public static PageResult empty(Long total) { + return new PageResult<>(total); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/R.java b/maibu-common/src/main/java/com/maibu/core/domain/R.java new file mode 100644 index 0000000..271a68e --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/R.java @@ -0,0 +1,117 @@ +package com.maibu.core.domain; + + +import com.maibu.constant.HttpStatus; + +import java.io.Serializable; + +/** + * 响应信息主体 + * + * @author ruoyi + */ +public class R implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 成功 */ + public static final int SUCCESS = HttpStatus.SUCCESS; + + /** 失败 */ + public static final int FAIL = HttpStatus.ERROR; + + private int code; + + private String msg; + + private T data; + + public static R ok() + { + return restResult(null, SUCCESS, "操作成功"); + } + + public static R ok(T data) + { + return restResult(data, SUCCESS, "操作成功"); + } + + public static R ok(T data, String msg) + { + return restResult(data, SUCCESS, msg); + } + + public static R fail() + { + return restResult(null, FAIL, "操作失败"); + } + + public static R fail(String msg) + { + return restResult(null, FAIL, msg); + } + + public static R fail(T data) + { + return restResult(data, FAIL, "操作失败"); + } + + public static R fail(T data, String msg) + { + return restResult(data, FAIL, msg); + } + + public static R fail(int code, String msg) + { + return restResult(null, code, msg); + } + + private static R restResult(T data, int code, String msg) + { + R apiResult = new R<>(); + apiResult.setCode(code); + apiResult.setData(data); + apiResult.setMsg(msg); + return apiResult; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } + + public T getData() + { + return data; + } + + public void setData(T data) + { + this.data = data; + } + + public static Boolean isError(R ret) + { + return !isSuccess(ret); + } + + public static Boolean isSuccess(R ret) + { + return R.SUCCESS == ret.getCode(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/SortingField.java b/maibu-common/src/main/java/com/maibu/core/domain/SortingField.java new file mode 100644 index 0000000..f986d60 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/SortingField.java @@ -0,0 +1,56 @@ +package com.maibu.core.domain; + +import java.io.Serializable; + +/** + * 排序字段 DTO + * + * 类名加了 ing 的原因是,避免和 ES SortField 重名。 + */ +public class SortingField implements Serializable { + + /** + * 顺序 - 升序 + */ + public static final String ORDER_ASC = "asc"; + /** + * 顺序 - 降序 + */ + public static final String ORDER_DESC = "desc"; + + /** + * 字段 + */ + private String field; + /** + * 顺序 + */ + private String order; + + // 空构造方法,解决反序列化 + public SortingField() { + } + + public SortingField(String field, String order) { + this.field = field; + this.order = order; + } + + public String getField() { + return field; + } + + public SortingField setField(String field) { + this.field = field; + return this; + } + + public String getOrder() { + return order; + } + + public SortingField setOrder(String order) { + this.order = order; + return this; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/TenantBaseDO.java b/maibu-common/src/main/java/com/maibu/core/domain/TenantBaseDO.java new file mode 100644 index 0000000..5627745 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/TenantBaseDO.java @@ -0,0 +1,20 @@ +package com.maibu.core.domain; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 拓展多租户的 BaseDO 基类 + * + * @author fastbee + */ +@Data +@EqualsAndHashCode(callSuper = true) +public abstract class TenantBaseDO extends BaseDO { + + /** + * 多租户编号 + */ + private Long tenantId; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/TreeEntity.java b/maibu-common/src/main/java/com/maibu/core/domain/TreeEntity.java new file mode 100644 index 0000000..c3e6d32 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/TreeEntity.java @@ -0,0 +1,79 @@ +package com.maibu.core.domain; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tree基类 + * + * @author ruoyi + */ +public class TreeEntity extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 父菜单名称 */ + private String parentName; + + /** 父菜单ID */ + private Long parentId; + + /** 显示顺序 */ + private Integer orderNum; + + /** 祖级列表 */ + private String ancestors; + + /** 子部门 */ + private List children = new ArrayList<>(); + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public String getAncestors() + { + return ancestors; + } + + public void setAncestors(String ancestors) + { + this.ancestors = ancestors; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/TreeSelect.java b/maibu-common/src/main/java/com/maibu/core/domain/TreeSelect.java new file mode 100644 index 0000000..8db221f --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/TreeSelect.java @@ -0,0 +1,78 @@ +package com.maibu.core.domain; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.maibu.core.domain.entity.SysDept; +import com.maibu.core.domain.entity.SysMenu; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Treeselect树结构实体类 + * + * @author ruoyi + */ +public class TreeSelect implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 节点ID */ + private Long id; + + /** 节点名称 */ + private String label; + + /** 子节点 */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List children; + + public TreeSelect() + { + + } + + public TreeSelect(SysDept dept) + { + this.id = dept.getDeptId(); + this.label = dept.getDeptName(); + this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public TreeSelect(SysMenu menu) + { + this.id = menu.getMenuId(); + this.label = menu.getMenuName(); + this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getLabel() + { + return label; + } + + public void setLabel(String label) + { + this.label = label; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/entity/SysDept.java b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysDept.java new file mode 100644 index 0000000..d3348a0 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysDept.java @@ -0,0 +1,311 @@ +package com.maibu.core.domain.entity; + +import com.maibu.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.ArrayList; +import java.util.List; + +/** + * 部门表 sys_dept + * + * @author ruoyi + */ +@ApiModel(value = "SysDept", description = "部门表 sys_dept") +public class SysDept extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 部门ID */ + @ApiModelProperty("部门ID") + private Long deptId; + + /** + * 机构系统账号ID + */ + private Long deptUserId; + + /** 父部门ID */ + @ApiModelProperty("父部门ID") + private Long parentId; + + /** 祖级列表 */ + @ApiModelProperty("祖级列表") + private String ancestors; + + /** 部门名称 */ + @ApiModelProperty("部门名称") + private String deptName; + + /** 显示顺序 */ + @ApiModelProperty("显示顺序") + private Integer orderNum; + + /** 负责人 */ + @ApiModelProperty("负责人") + private String leader; + + /** 联系电话 */ + @ApiModelProperty("联系电话") + private String phone; + + /** 邮箱 */ + @ApiModelProperty("邮箱") + private String email; + + /** 部门状态:0正常,1停用 */ + @ApiModelProperty("部门状态:0正常,1停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志(0代表存在 2代表删除)") + private String delFlag; + + /** 父部门名称 */ + @ApiModelProperty("父部门名称") + private String parentName; + + /** 子部门 */ + @ApiModelProperty("子部门") + private List children = new ArrayList(); + + /** + * 系统账号名称 + */ + private String userName; + + /** + * 系统账号密码 + */ + private String password; + + /** + * 确认密码 + */ + private String confirmPassword; + + /** + * 机构类型 + */ + private Integer deptType; + + public Boolean getShowOwner() { + return showOwner; + } + + public void setShowOwner(Boolean showOwner) { + this.showOwner = showOwner; + } + + /** + * 是否显示自己 + */ + private Boolean showOwner; + + /** + * 管理员姓名 + */ + private String deptUserName; + + public String getDeptUserName() { + return deptUserName; + } + + public void setDeptUserName(String deptUserName) { + this.deptUserName = deptUserName; + } + + public Integer getDeptType() { + return deptType; + } + + public void setDeptType(Integer deptType) { + this.deptType = deptType; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getConfirmPassword() { + return confirmPassword; + } + + public void setConfirmPassword(String confirmPassword) { + this.confirmPassword = confirmPassword; + } + + public Long getDeptUserId() { + return deptUserId; + } + + public void setDeptUserId(Long deptUserId) { + this.deptUserId = deptUserId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public String getAncestors() + { + return ancestors; + } + + public void setAncestors(String ancestors) + { + this.ancestors = ancestors; + } + + @NotBlank(message = "部门名称不能为空") + @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符") + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public String getLeader() + { + return leader; + } + + public void setLeader(String leader) + { + this.leader = leader; + } + + @Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符") + public String getPhone() + { + return phone; + } + + public void setPhone(String phone) + { + this.phone = phone; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("deptId", getDeptId()) + .append("deptUserId", getDeptUserId()) + .append("parentId", getParentId()) + .append("ancestors", getAncestors()) + .append("deptName", getDeptName()) + .append("orderNum", getOrderNum()) + .append("leader", getLeader()) + .append("phone", getPhone()) + .append("email", getEmail()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/entity/SysDictData.java b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysDictData.java new file mode 100644 index 0000000..b25b552 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysDictData.java @@ -0,0 +1,209 @@ +package com.maibu.core.domain.entity; + +import com.maibu.annotation.Excel; +import com.maibu.constant.UserConstants; +import com.maibu.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +/** + * 字典数据表 sys_dict_data + * + * @author ruoyi + */ +@Data +@ApiModel(value = "SysDictData", description = "字典数据表 sys_dict_data") +public class SysDictData extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典编码 */ + @ApiModelProperty("字典编码") + @Excel(name = "字典编码", cellType = Excel.ColumnType.NUMERIC) + private Long dictCode; + + /** 字典排序 */ + @ApiModelProperty("字典排序") + @Excel(name = "字典排序", cellType = Excel.ColumnType.NUMERIC) + private Long dictSort; + + /** 字典标签 */ + @ApiModelProperty("字典标签") + @Excel(name = "字典标签") + private String dictLabel; + + /** 字典标签 */ + @ApiModelProperty("中文字典标签") + private String dictLabel_zh_CN; + + /** 字典标签 */ + @ApiModelProperty("英文字典标签") + private String dictLabel_en_US; + + /** 字典键值 */ + @ApiModelProperty("字典键值") + @Excel(name = "字典键值") + private String dictValue; + + /** 字典类型 */ + @ApiModelProperty("字典类型") + @Excel(name = "字典类型") + private String dictType; + + /** 样式属性(其他样式扩展) */ + @ApiModelProperty("样式属性(其他样式扩展)") + private String cssClass; + + /** 表格字典样式 */ + @ApiModelProperty("表格字典样式") + private String listClass; + + /** 是否默认(Y是 N否) */ + @ApiModelProperty("是否默认(Y是 N否)") + @Excel(name = "是否默认", readConverterExp = "Y=是,N=否") + private String isDefault; + + /** 状态(0正常 1停用) */ + @ApiModelProperty("状态(0正常 1停用)") + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + @Deprecated + private String language; + + public Long getDictCode() + { + return dictCode; + } + + public void setDictCode(Long dictCode) + { + this.dictCode = dictCode; + } + + public Long getDictSort() + { + return dictSort; + } + + public void setDictSort(Long dictSort) + { + this.dictSort = dictSort; + } + + @NotBlank(message = "字典标签不能为空") + @Size(min = 0, max = 100, message = "字典标签长度不能超过100个字符") + public String getDictLabel() + { + return dictLabel; + } + + public void setDictLabel(String dictLabel) + { + this.dictLabel = dictLabel; + } + + @NotBlank(message = "字典键值不能为空") + @Size(min = 0, max = 100, message = "字典键值长度不能超过100个字符") + public String getDictValue() + { + return dictValue; + } + + public void setDictValue(String dictValue) + { + this.dictValue = dictValue; + } + + @NotBlank(message = "字典类型不能为空") + @Size(min = 0, max = 100, message = "字典类型长度不能超过100个字符") + public String getDictType() + { + return dictType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + @Size(min = 0, max = 100, message = "样式属性长度不能超过100个字符") + public String getCssClass() + { + return cssClass; + } + + public void setCssClass(String cssClass) + { + this.cssClass = cssClass; + } + + public String getListClass() + { + return listClass; + } + + public void setListClass(String listClass) + { + this.listClass = listClass; + } + + public boolean getDefault() + { + return UserConstants.YES.equals(this.isDefault); + } + + public String getIsDefault() + { + return isDefault; + } + + public void setIsDefault(String isDefault) + { + this.isDefault = isDefault; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("dictCode", getDictCode()) + .append("dictSort", getDictSort()) + .append("dictLabel", getDictLabel()) + .append("dictValue", getDictValue()) + .append("dictType", getDictType()) + .append("cssClass", getCssClass()) + .append("listClass", getListClass()) + .append("isDefault", getIsDefault()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/entity/SysDictType.java b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysDictType.java new file mode 100644 index 0000000..b7ada3d --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysDictType.java @@ -0,0 +1,117 @@ +package com.maibu.core.domain.entity; + +import com.maibu.annotation.Excel; +import com.maibu.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +/** + * 字典类型表 sys_dict_type + * + * @author ruoyi + */ +@ApiModel(value = "SysDictType", description = "字典类型表 sys_dict_type") +public class SysDictType extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典主键 */ + @ApiModelProperty("字典主键") + @Excel(name = "字典主键", cellType = Excel.ColumnType.NUMERIC) + private Long dictId; + + /** 字典名称 */ + @ApiModelProperty("字典名称") + @Excel(name = "字典名称") + private String dictName; + + /** 字典类型 */ + @ApiModelProperty("字典类型") + @Excel(name = "字典类型") + private String dictType; + + /** 状态(0正常 1停用) */ + @ApiModelProperty("状态(0正常 1停用)") + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + @Deprecated + private String language; + + + public Long getDictId() + { + return dictId; + } + + public void setDictId(Long dictId) + { + this.dictId = dictId; + } + + @NotBlank(message = "字典名称不能为空") + @Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符") + public String getDictName() + { + return dictName; + } + + public void setDictName(String dictName) + { + this.dictName = dictName; + } + + @NotBlank(message = "字典类型不能为空") + @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符") + @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)") + public String getDictType() + { + return dictType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("dictId", getDictId()) + .append("dictName", getDictName()) + .append("dictType", getDictType()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/entity/SysMenu.java b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysMenu.java new file mode 100644 index 0000000..5c54ece --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysMenu.java @@ -0,0 +1,305 @@ +package com.maibu.core.domain.entity; + +import com.maibu.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.ArrayList; +import java.util.List; + +/** + * 菜单权限表 sys_menu + * + * @author ruoyi + */ +@ApiModel(value = "SysMenu", description = "菜单权限表 sys_menu") +public class SysMenu extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 菜单ID */ + @ApiModelProperty("菜单ID") + private Long menuId; + + /** 菜单名称 */ + @ApiModelProperty("菜单名称") + private String menuName; + + /** 父菜单名称 */ + @ApiModelProperty("父菜单名称") + private String parentName; + + /** 父菜单ID */ + @ApiModelProperty("父菜单ID") + private Long parentId; + + /** 显示顺序 */ + @ApiModelProperty("显示顺序") + private Integer orderNum; + + /** 路由地址 */ + @ApiModelProperty("路由地址") + private String path; + + /** 组件路径 */ + @ApiModelProperty("组件路径") + private String component; + + /** 路由参数 */ + @ApiModelProperty("路由参数") + private String query; + + /** 是否为外链(0是 1否) */ + @ApiModelProperty("是否为外链(0是 1否)") + private String isFrame; + + /** 是否缓存(0缓存 1不缓存) */ + @ApiModelProperty("是否缓存(0缓存 1不缓存)") + private String isCache; + + /** 类型(M目录 C菜单 F按钮) */ + @ApiModelProperty("类型(M目录 C菜单 F按钮)") + private String menuType; + + /** 显示状态(0显示 1隐藏) */ + @ApiModelProperty("显示状态(0显示 1隐藏)") + private String visible; + + /** 菜单状态(0正常 1停用) */ + @ApiModelProperty("菜单状态(0正常 1停用)") + private String status; + + /** 权限字符串 */ + @ApiModelProperty("权限字符串") + private String perms; + + /** 菜单图标 */ + @ApiModelProperty("菜单图标") + private String icon; + + /** 子菜单 */ + @ApiModelProperty("子菜单") + private List children = new ArrayList(); + + /** + * 部门id + */ + private Long deptId; + + /** 菜单语言 */ + @Deprecated + private String language; + + public void setLanguage(String language) { + this.language = language; + } + + public String getLanguage() { + return language; + } + + + public Long getDeptId() { + return deptId; + } + + public void setDeptId(Long deptId) { + this.deptId = deptId; + } + + public Long getMenuId() + { + return menuId; + } + + public void setMenuId(Long menuId) + { + this.menuId = menuId; + } + + @NotBlank(message = "菜单名称不能为空") + @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符") + public String getMenuName() + { + return menuName; + } + + public void setMenuName(String menuName) + { + this.menuName = menuName; + } + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + @Size(min = 0, max = 200, message = "路由地址不能超过200个字符") + public String getPath() + { + return path; + } + + public void setPath(String path) + { + this.path = path; + } + + @Size(min = 0, max = 200, message = "组件路径不能超过255个字符") + public String getComponent() + { + return component; + } + + public void setComponent(String component) + { + this.component = component; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public String getIsFrame() + { + return isFrame; + } + + public void setIsFrame(String isFrame) + { + this.isFrame = isFrame; + } + + public String getIsCache() + { + return isCache; + } + + public void setIsCache(String isCache) + { + this.isCache = isCache; + } + + @NotBlank(message = "菜单类型不能为空") + public String getMenuType() + { + return menuType; + } + + public void setMenuType(String menuType) + { + this.menuType = menuType; + } + + public String getVisible() + { + return visible; + } + + public void setVisible(String visible) + { + this.visible = visible; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符") + public String getPerms() + { + return perms; + } + + public void setPerms(String perms) + { + this.perms = perms; + } + + public String getIcon() + { + return icon; + } + + public void setIcon(String icon) + { + this.icon = icon; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("menuId", getMenuId()) + .append("menuName", getMenuName()) + .append("parentId", getParentId()) + .append("orderNum", getOrderNum()) + .append("path", getPath()) + .append("component", getComponent()) + .append("isFrame", getIsFrame()) + .append("IsCache", getIsCache()) + .append("menuType", getMenuType()) + .append("visible", getVisible()) + .append("status ", getStatus()) + .append("perms", getPerms()) + .append("icon", getIcon()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/entity/SysRole.java b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysRole.java new file mode 100644 index 0000000..61cf2b9 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysRole.java @@ -0,0 +1,321 @@ +package com.maibu.core.domain.entity; + +import com.maibu.annotation.Excel; +import com.maibu.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.util.Set; + +/** + * 角色表 sys_role + * + * @author ruoyi + */ +@ApiModel(value = "SysRole", description = "角色表 sys_role") +public class SysRole extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 角色ID */ + @ApiModelProperty("角色ID") + @Excel(name = "角色序号", cellType = Excel.ColumnType.NUMERIC) + private Long roleId; + + /** 角色名称 */ + @ApiModelProperty("角色名称") + @Excel(name = "角色名称") + private String roleName; + + /** 角色权限 */ + @ApiModelProperty("角色权限") + @Excel(name = "角色权限") + private String roleKey; + + /** 角色排序 */ + @ApiModelProperty("角色排序") + @Excel(name = "角色排序") + private Integer roleSort; + + /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ + @ApiModelProperty(value = "数据范围", notes = "(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)") + @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") + private String dataScope; + + /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */ + @ApiModelProperty(value = "菜单树选择项是否关联显示", notes = "( 0:父子不互相关联显示 1:父子互相关联显示)") + private boolean menuCheckStrictly; + + /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */ + @ApiModelProperty(value = "部门树选择项是否关联显示", notes = "(0:父子不互相关联显示 1:父子互相关联显示 )") + private boolean deptCheckStrictly; + + /** 角色状态(0正常 1停用) */ + @ApiModelProperty("角色状态(0正常 1停用)") + @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志") + private String delFlag; + + /** 用户是否存在此角色标识 默认不存在 */ + private boolean flag = false; + + /** 菜单组 */ + @ApiModelProperty("菜单组") + private Long[] menuIds; + + /** 部门组(数据权限) */ + @ApiModelProperty("部门组") + private Long[] deptIds; + + /** 角色菜单权限 */ + @ApiModelProperty("角色菜单权限") + private Set permissions; + + /** + * 部门id + */ + private Long deptId; + + /** + * 部门名称 + */ + private String deptName; + + /** + * 是否显示下级机构数据 + */ + private Boolean showChild; + + /** + * 是否可以修改用户角色 + */ + private Boolean canEditRole; + + /** + * 是否是机构管理员角色 + */ + private Boolean manager; + + public Boolean getManager() { + return manager; + } + + public void setManager(Boolean manager) { + this.manager = manager; + } + + public Boolean getCanEditRole() { + return canEditRole; + } + + public void setCanEditRole(Boolean canEditRole) { + this.canEditRole = canEditRole; + } + + public Boolean getShowChild() { + return showChild; + } + + public void setShowChild(Boolean showChild) { + this.showChild = showChild; + } + + public Long getDeptId() { + return deptId; + } + + public void setDeptId(Long deptId) { + this.deptId = deptId; + } + + public String getDeptName() { + return deptName; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public SysRole() + { + + } + + public SysRole(Long roleId) + { + this.roleId = roleId; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public boolean isAdmin() + { + return isAdmin(this.roleId); + } + + public static boolean isAdmin(Long roleId) + { + return roleId != null && 1L == roleId; + } + + @NotBlank(message = "角色名称不能为空") + @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符") + public String getRoleName() + { + return roleName; + } + + public void setRoleName(String roleName) + { + this.roleName = roleName; + } + + @NotBlank(message = "权限字符不能为空") + @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符") + public String getRoleKey() + { + return roleKey; + } + + public void setRoleKey(String roleKey) + { + this.roleKey = roleKey; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getRoleSort() + { + return roleSort; + } + + public void setRoleSort(Integer roleSort) + { + this.roleSort = roleSort; + } + + public String getDataScope() + { + return dataScope; + } + + public void setDataScope(String dataScope) + { + this.dataScope = dataScope; + } + + public boolean isMenuCheckStrictly() + { + return menuCheckStrictly; + } + + public void setMenuCheckStrictly(boolean menuCheckStrictly) + { + this.menuCheckStrictly = menuCheckStrictly; + } + + public boolean isDeptCheckStrictly() + { + return deptCheckStrictly; + } + + public void setDeptCheckStrictly(boolean deptCheckStrictly) + { + this.deptCheckStrictly = deptCheckStrictly; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public boolean isFlag() + { + return flag; + } + + public void setFlag(boolean flag) + { + this.flag = flag; + } + + public Long[] getMenuIds() + { + return menuIds; + } + + public void setMenuIds(Long[] menuIds) + { + this.menuIds = menuIds; + } + + public Long[] getDeptIds() + { + return deptIds; + } + + public void setDeptIds(Long[] deptIds) + { + this.deptIds = deptIds; + } + + public Set getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("roleName", getRoleName()) + .append("roleKey", getRoleKey()) + .append("roleSort", getRoleSort()) + .append("dataScope", getDataScope()) + .append("menuCheckStrictly", isMenuCheckStrictly()) + .append("deptCheckStrictly", isDeptCheckStrictly()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/entity/SysTranslate.java b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysTranslate.java new file mode 100644 index 0000000..d946a85 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysTranslate.java @@ -0,0 +1,34 @@ +package com.maibu.core.domain.entity; + +import com.maibu.annotation.Excel; +import com.maibu.core.domain.BaseEntity; +import lombok.Data; + +/** + * 翻译对象 sys_translate + * + * @author ruoyi + */ +@Data +public class SysTranslate extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @Excel(name = "ID") + private Long id; + + /** zh_CN */ + @Excel(name = "zh-CN") + private String zh_CN; + + /** en_US */ + @Excel(name = "en-US") + private String en_US; + + /** 物模型翻译表使用 */ + private Long productId; + + private String tableName; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/entity/SysUser.java b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysUser.java new file mode 100644 index 0000000..e62200f --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/entity/SysUser.java @@ -0,0 +1,426 @@ +package com.maibu.core.domain.entity; + +import com.maibu.annotation.Excel; +import com.maibu.annotation.Excels; +import com.maibu.core.domain.BaseEntity; +import com.maibu.xss.Xss; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.validation.constraints.*; +import java.util.Date; +import java.util.List; + +/** + * 用户对象 sys_user + * + * @author ruoyi + */ +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "SysUser", description = "用户对象 sys_user") +@Data +public class SysUser extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + @ApiModelProperty("用户ID") + @Excel(name = "用户序号", cellType = Excel.ColumnType.NUMERIC, prompt = "用户编号") + private Long userId; + + /** + * 部门ID + */ + @ApiModelProperty("部门ID") + @Excel(name = "部门编号", type = Excel.Type.IMPORT) + private Long deptId; + + /** + * 用户账号 + */ + @ApiModelProperty("用户账号") + @Excel(name = "登录名称") + private String userName; + + /** + * 用户昵称 + */ + @ApiModelProperty("用户昵称") + @Excel(name = "用户名称") + private String nickName; + + /** + * 用户邮箱 + */ + @ApiModelProperty("用户邮箱") + @Excel(name = "用户邮箱") + private String email; + + /** + * 手机号码 + */ + @ApiModelProperty("手机号码") + @Excel(name = "手机号码") + private String phonenumber; + + /** + * 用户性别 + */ + @ApiModelProperty("用户性别") + @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") + private String sex; + + /** + * 用户头像 + */ + @ApiModelProperty("用户头像") + private String avatar; + + /** + * 密码 + */ + @ApiModelProperty("密码") + private String password; + + + private String displayPassword; + + /** + * 帐号状态(0正常 1停用) + */ + @ApiModelProperty("帐号状态(0正常 1停用)") + @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** + * 删除标志(0代表存在 2代表删除) + */ + @ApiModelProperty("删除标志") + private String delFlag; + + /** + * 最后登录IP + */ + @ApiModelProperty("最后登录IP") + @Excel(name = "最后登录IP", type = Excel.Type.EXPORT) + private String loginIp; + + /** + * 最后登录时间 + */ + @ApiModelProperty("最后登录时间") + @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Excel.Type.EXPORT) + private Date loginDate; + + /** + * 部门对象 + */ + @ApiModelProperty("部门对象") + @Excels({ + @Excel(name = "部门名称", targetAttr = "deptName", type = Excel.Type.EXPORT), + @Excel(name = "部门负责人", targetAttr = "leader", type = Excel.Type.EXPORT) + }) + private SysDept dept; + + /** + * 角色对象 + */ + @ApiModelProperty("角色对象") + private List roles; + + /** + * 角色组 + */ + @ApiModelProperty("角色组") + private Long[] roleIds; + + /** + * 岗位组 + */ + @ApiModelProperty("岗位组") + private Long[] postIds; + + /** + * 角色ID + */ + @ApiModelProperty("角色ID") + private Long roleId; + + /** + * 用,拼接的角色名称 + */ + @ApiModelProperty("用,拼接的角色名称") + private String roleNames; + + /** + * 用,拼接的角色名称 + */ + @ApiModelProperty("sessionId") + private String sessionId; + + /** + * 微信openid + */ + private String openId; + + /** + * 是否显示下级机构数据 + */ + private Boolean showChild; + + /** + * 是否是机构管理员 + */ + private Boolean manager; + /** + * 语言 + */ + private String language; + /** + * 时区 + */ + private String timeZone; + + /** + * ai助手问答使用条数 + */ + private Integer aiChatNum; + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getTimeZone() { + return timeZone; + } + + public void setTimeZone(String timeZone) { + this.timeZone = timeZone; + } + + public Boolean getManager() { + return manager; + } + + public void setManager(Boolean manager) { + this.manager = manager; + } + + public Boolean getShowChild() { + return showChild; + } + + public void setShowChild(Boolean showChild) { + this.showChild = showChild; + } + + public SysUser() { + + } + + public SysUser(Long userId) { + this.userId = userId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public boolean isAdmin() { + return isAdmin(this.userId); + } + + public static boolean isAdmin(Long userId) { + return userId != null && 1L == userId; + } + + public Long getDeptId() { + return deptId; + } + + public void setDeptId(Long deptId) { + this.deptId = deptId; + } + + @Xss(message = "用户昵称不能包含脚本字符") + @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + @Xss(message = "用户账号不能包含脚本字符") + @NotBlank(message = "用户账号不能为空") + @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符") + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符") + public String getPhonenumber() { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) { + this.phonenumber = phonenumber; + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getDelFlag() { + return delFlag; + } + + public void setDelFlag(String delFlag) { + this.delFlag = delFlag; + } + + public String getLoginIp() { + return loginIp; + } + + public void setLoginIp(String loginIp) { + this.loginIp = loginIp; + } + + public Date getLoginDate() { + return loginDate; + } + + public void setLoginDate(Date loginDate) { + this.loginDate = loginDate; + } + + public SysDept getDept() { + return dept; + } + + public void setDept(SysDept dept) { + this.dept = dept; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public Long[] getRoleIds() { + return roleIds; + } + + public void setRoleIds(Long[] roleIds) { + this.roleIds = roleIds; + } + + public Long[] getPostIds() { + return postIds; + } + + public void setPostIds(Long[] postIds) { + this.postIds = postIds; + } + + public Long getRoleId() { + return roleId; + } + + public void setRoleId(Long roleId) { + this.roleId = roleId; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("deptId", getDeptId()) + .append("userName", getUserName()) + .append("nickName", getNickName()) + .append("email", getEmail()) + .append("phonenumber", getPhonenumber()) + .append("sex", getSex()) + .append("avatar", getAvatar()) + .append("password", getPassword()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("loginIp", getLoginIp()) + .append("loginDate", getLoginDate()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("dept", getDept()) + .toString(); + } + + public String getRoleNames() { + return roleNames; + } + + public void setRoleNames(String roleNames) { + this.roleNames = roleNames; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/entity/UserClient.java b/maibu-common/src/main/java/com/maibu/core/domain/entity/UserClient.java new file mode 100644 index 0000000..31d663c --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/entity/UserClient.java @@ -0,0 +1,24 @@ +package com.maibu.core.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("sys_user_client") +public class UserClient { + + private static final long serialVersionUID = 1L; + + private Long clientId; + + private Long userId; + + private String userName; + + private String clientName; + + private String deviceName; + + private int isAlive; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/model/BindLoginBody.java b/maibu-common/src/main/java/com/maibu/core/domain/model/BindLoginBody.java new file mode 100644 index 0000000..b464ccd --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/model/BindLoginBody.java @@ -0,0 +1,22 @@ +package com.maibu.core.domain.model; + +/** + * 用户登录对象 + * + * @author ruoyi + */ +public class BindLoginBody extends LoginBody +{ + /** + * 绑定id + */ + private String bindId; + + public String getBindId() { + return bindId; + } + + public void setBindId(String bindId) { + this.bindId = bindId; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/model/BindRegisterBody.java b/maibu-common/src/main/java/com/maibu/core/domain/model/BindRegisterBody.java new file mode 100644 index 0000000..e84b75a --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/model/BindRegisterBody.java @@ -0,0 +1,21 @@ +package com.maibu.core.domain.model; + +/** + * 用户注册对象 + * + * @author ruoyi + */ +public class BindRegisterBody extends RegisterBody { + /** + * 绑定id + */ + private String bindId; + + public String getBindId() { + return bindId; + } + + public void setBindId(String bindId) { + this.bindId = bindId; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/model/LoginBody.java b/maibu-common/src/main/java/com/maibu/core/domain/model/LoginBody.java new file mode 100644 index 0000000..58d5bb8 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/model/LoginBody.java @@ -0,0 +1,67 @@ +package com.maibu.core.domain.model; + +import lombok.Data; + +/** + * 用户登录对象 + * + * @author ruoyi + */ +@Data +public class LoginBody { + + /** + * 用户id + */ + private Long userId; + /** + * 用户名 + */ + private String username; + + /** + * 用户密码 + */ + private String password; + + /** + * 验证码 + */ + private String code; + + /** + * 唯一标识 app端是手机地址出厂id + */ + private String uuid; + + /** + * 手机号 三方 + */ + private String phonenumber; + /** + * 微信openId 三方 + */ + private String openId; + + /** + * 登录平台 1-web端;2-app 3-PC端 + */ + private Integer sourceType; + + /** + * 登录平台 1-账号密码 2-公众号;3-手机号;4-微信 不传或者是null 默认账号密码 + */ + private Integer loginPlatForm = 1; + + /** + * 短信验证码 + */ + private String smsCode; + + + /** + * sessionId + */ + private String sessionId; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/model/LoginUser.java b/maibu-common/src/main/java/com/maibu/core/domain/model/LoginUser.java new file mode 100644 index 0000000..d7257cf --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/model/LoginUser.java @@ -0,0 +1,278 @@ +package com.maibu.core.domain.model; + +import com.alibaba.fastjson2.annotation.JSONField; +import com.maibu.core.domain.entity.SysUser; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Set; + +/** + * 登录用户身份权限 + * + * @author ruoyi + */ +public class LoginUser implements UserDetails { + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户唯一标识 + */ + private String token; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 权限列表 + */ + private Set permissions; + + /** + * 用户信息 + */ + private SysUser user; + + private String language; + + private Long deptUserId; + + private Boolean neverExpire = Boolean.FALSE; + + /** + * 登录平台 1-web端;2-app 3-PC端 + */ + private Integer sourceType; + + public Integer getSourceType() { + return sourceType; + } + + public void setSourceType(Integer s) { + this.sourceType = s; + } + + public Boolean getNeverExpire() { + return neverExpire; + } + + public void setNeverExpire(Boolean neverExpire) { + this.neverExpire = neverExpire; + } + + public String getLanguage() { + return language; + } + + public Long getDeptUserId() { + return deptUserId; + } + + public void setDeptUserId(Long deptUserId) { + this.deptUserId = deptUserId; + } + + public void setLanguage(String language) { + this.language = language; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getDeptId() { + return deptId; + } + + public void setDeptId(Long deptId) { + this.deptId = deptId; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public LoginUser() { + } + + public LoginUser(SysUser user, Set permissions) { + this.user = user; + this.permissions = permissions; + } + + public LoginUser(Long userId, Long deptId, String language, SysUser user, Set permissions) { + this.userId = userId; + this.deptId = deptId; + this.user = user; + this.language = language; + this.permissions = permissions; + } + + @JSONField(serialize = false) + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getUserName(); + } + + /** + * 账户是否未过期,过期无法验证 + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonExpired() { + return true; + } + + /** + * 指定用户是否解锁,锁定的用户无法进行身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonLocked() { + return true; + } + + /** + * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + /** + * 是否可用 ,禁用的用户不能身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isEnabled() { + return true; + } + + public Long getLoginTime() { + return loginTime; + } + + public void setLoginTime(Long loginTime) { + this.loginTime = loginTime; + } + + public String getIpaddr() { + return ipaddr; + } + + public void setIpaddr(String ipaddr) { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) { + this.loginLocation = loginLocation; + } + + public String getBrowser() { + return browser; + } + + public void setBrowser(String browser) { + this.browser = browser; + } + + public String getOs() { + return os; + } + + public void setOs(String os) { + this.os = os; + } + + public Long getExpireTime() { + return expireTime; + } + + public void setExpireTime(Long expireTime) { + this.expireTime = expireTime; + } + + public Set getPermissions() { + return permissions; + } + + public void setPermissions(Set permissions) { + this.permissions = permissions; + } + + public SysUser getUser() { + return user; + } + + public void setUser(SysUser user) { + this.user = user; + } + + @Override + public Collection getAuthorities() { + return null; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/domain/model/RegisterBody.java b/maibu-common/src/main/java/com/maibu/core/domain/model/RegisterBody.java new file mode 100644 index 0000000..74e637a --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/domain/model/RegisterBody.java @@ -0,0 +1,19 @@ +package com.maibu.core.domain.model; + +/** + * 用户注册对象 + * + * @author ruoyi + */ +public class RegisterBody extends LoginBody +{ + private String email; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/iot/response/DashDeviceTotalDto.java b/maibu-common/src/main/java/com/maibu/core/iot/response/DashDeviceTotalDto.java new file mode 100644 index 0000000..e7cb5fe --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/iot/response/DashDeviceTotalDto.java @@ -0,0 +1,22 @@ +package com.maibu.core.iot.response; + +import lombok.Data; + +/** + * 大屏设备总览数据 + * @author bill + */ +@Data +public class DashDeviceTotalDto { + + /*设备总数*/ + private Integer total; + /*在线设备总数*/ + private Integer onlineCount; + /*离线设备总数*/ + private Integer OfflineCount; + /*未激活设备数*/ + private Integer unActiveCount; + + +} diff --git a/maibu-common/src/main/java/com/maibu/core/iot/response/DeCodeBo.java b/maibu-common/src/main/java/com/maibu/core/iot/response/DeCodeBo.java new file mode 100644 index 0000000..ed10181 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/iot/response/DeCodeBo.java @@ -0,0 +1,26 @@ +package com.maibu.core.iot.response; + +import lombok.Data; + +/** + * @author gsb + * @date 2023/4/8 15:43 + */ +@Data +public class DeCodeBo { + + /**原始报文*/ + private String payload; + /**从机编号*/ + private Integer slaveId; + /**寄存器地址*/ + private Integer address; + /**功能码*/ + private Integer code; + /**读取个数*/ + private Integer count; + /**写入值*/ + private Integer writeData; + /**读写类型 1-解析 2-读指令 3-写指令 */ + private Integer type; +} diff --git a/maibu-common/src/main/java/com/maibu/core/iot/response/IdentityAndName.java b/maibu-common/src/main/java/com/maibu/core/iot/response/IdentityAndName.java new file mode 100644 index 0000000..480bdcc --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/iot/response/IdentityAndName.java @@ -0,0 +1,70 @@ +package com.maibu.core.iot.response; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 物模型值的项 + * + * @author kerwincui + * @date 2021-12-16 + */ +@NoArgsConstructor +@AllArgsConstructor +@Data +public class IdentityAndName +{ + + public IdentityAndName(String id,String value){ + this.id=id; + this.value=value; + } + + public IdentityAndName(String id,Integer isHistory){ + this.id=id; + this.isHistory=isHistory; + } + + public IdentityAndName(String id, Integer isHistory, String specs, String name, Integer type){ + this.id = id; + this.isHistory = isHistory; + this.dataType = specs; + this.name = name; + this.type = type; + } + + /** 物模型唯一标识符 */ + private String id; + /** 物模型值 */ + private Object value; + + private Integer isChart; + + /**是否监控*/ + private Integer isHistory; + /** + * 数据定义 + */ + private String dataType; + /**物模型名称*/ + private String name; + /** + * 物模型类型 + */ + private Integer type; + /** + * 是否是参数 + */ + private Integer isParams; + + private String formula; + + private Integer slaveId; + + private Integer tempSlaveId; + + private Integer quantity; + + private String code; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/DeviceReplyBo.java b/maibu-common/src/main/java/com/maibu/core/mq/DeviceReplyBo.java new file mode 100644 index 0000000..8c836df --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/DeviceReplyBo.java @@ -0,0 +1,17 @@ +package com.maibu.core.mq; + +import lombok.Data; + +/** + * @author bill + */ +@Data +public class DeviceReplyBo { + + /*设备下发消息id*/ + private String messageId; + /*标识符*/ + private String id; + /**下发值*/ + private String value; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/DeviceReport.java b/maibu-common/src/main/java/com/maibu/core/mq/DeviceReport.java new file mode 100644 index 0000000..8e8b35d --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/DeviceReport.java @@ -0,0 +1,78 @@ +package com.maibu.core.mq; + +import com.maibu.core.protocol.Message; +import com.maibu.core.thingsModel.ThingsModelSimpleItem; +import com.maibu.enums.FunctionReplyStatus; +import com.maibu.enums.ServerType; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; +import java.util.List; + + +/** + * 设备上行数据model + * + * @author bill + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class DeviceReport extends Message { + + /** + * 设备编号 + */ + private String serialNumber; + /** + * 产品ID + */ + private Long productId; + /** + * 平台时间 + */ + private Date platformDate; + /** + * 消息id + */ + private String messageId; + + /** 设备物模型值的集合 **/ + private List thingsModelSimpleItem; + + /** + * 是否设备回复数据 + */ + private Boolean isReply = false; + /** + * 原数据报文 + */ + private String sources; + + /** + * 设备回复消息 + */ + private String replyMessage; + /** + * 设备回复状态 + */ + private FunctionReplyStatus status; + /** + * 从机编号 + */ + private Integer slaveId; + /** + * 服务器类型 + */ + private ServerType serverType; + + private String protocolCode; + + private Long userId; + private String userName; + private String deviceName; + + private SubDeviceBo subDeviceBo; + + private GwDeviceBo gwDeviceBo; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/DeviceReportBo.java b/maibu-common/src/main/java/com/maibu/core/mq/DeviceReportBo.java new file mode 100644 index 0000000..ef6ffce --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/DeviceReportBo.java @@ -0,0 +1,77 @@ +package com.maibu.core.mq; + +import com.maibu.core.mq.message.PropRead; +import com.maibu.core.thingsModel.ThingsModelSimpleItem; +import com.maibu.enums.FunctionReplyStatus; +import com.maibu.enums.ServerType; +import com.maibu.enums.ThingsModelType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; +import java.util.List; + +/** + * 设备上报 + * @author bill + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DeviceReportBo { + + /*设备编号或IMEI号*/ + private String serialNumber; + /*产品ID*/ + private Long productId; + /*4G物联网卡CCID*/ + private String ccId; + /*topic*/ + private String topicName; + /*mqtt消息中的packetId*/ + private Long packetId; + /*上报时间*/ + private Date platformDate; + /*物模型类型 1=-属性,2-功能,3-事件 */ + private ThingsModelType type; + /*上报数据*/ + private byte[] data; + /*1-设备数据上报 2- 下发指令给设备,设备应答数据*/ + private Integer reportType; + /*消息id*/ + private String messageId; + /* modbus协议消息回调,记录数据*/ + private PropRead prop; + + /** 设备物模型值的集合 **/ + private List thingsModelSimpleItem; + /*处理的消息服务类型*/ + private ServerType serverType; + private Integer slaveId; + + /** + * 是否设备回复数据 + */ + private Boolean isReply = false; + + /** + * 设备回复消息 + */ + private String replyMessage; + /** + * 设备回复状态 + */ + private FunctionReplyStatus status; + /** + * 寄存器地址 + */ + private int address; + + private String protocolCode; + + private String sources; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/DeviceStatusBo.java b/maibu-common/src/main/java/com/maibu/core/mq/DeviceStatusBo.java new file mode 100644 index 0000000..07f0f73 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/DeviceStatusBo.java @@ -0,0 +1,35 @@ +package com.maibu.core.mq; + +import com.maibu.enums.DeviceStatus; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * 设备状态 + * @author bill + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +@Builder +public class DeviceStatusBo { + /** + * 设备客户端id + */ + private String serialNumber; + /**是否活跃*/ + private DeviceStatus status; + /**消息时间*/ + private Date timestamp; + /*host*/ + private String hostName; + /*port*/ + private Integer port; + + private String ip; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/DeviceTestReportBo.java b/maibu-common/src/main/java/com/maibu/core/mq/DeviceTestReportBo.java new file mode 100644 index 0000000..5edc3fa --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/DeviceTestReportBo.java @@ -0,0 +1,32 @@ +package com.maibu.core.mq; + +import com.maibu.core.thingsModel.ThingsModelSimpleItem; +import lombok.Data; + +import java.util.List; + +/** + * @author bill + */ +@Data +public class DeviceTestReportBo { + + private Long productId; + /** + * 设备编号 + */ + private String serialNumber; + /** + * 是否是回复数据 + */ + private Boolean isReply; + + /** + * 设备物模型值的集合 + */ + private List thingsModelSimpleItem; + /** + * 原报文 + */ + private Object sources; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/GwDeviceBo.java b/maibu-common/src/main/java/com/maibu/core/mq/GwDeviceBo.java new file mode 100644 index 0000000..e7595fb --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/GwDeviceBo.java @@ -0,0 +1,16 @@ +package com.maibu.core.mq; + + +import lombok.Data; + +/** + * @author gsb + * @date 2024/9/4 10:49 + */ +@Data +public class GwDeviceBo { + + private Long productId; + + private String serialNumber; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/InvokeReqDto.java b/maibu-common/src/main/java/com/maibu/core/mq/InvokeReqDto.java new file mode 100644 index 0000000..1963ea9 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/InvokeReqDto.java @@ -0,0 +1,63 @@ +package com.maibu.core.mq; + +import com.alibaba.fastjson2.JSONObject; +import com.maibu.enums.ThingsModelType; +import com.maibu.utils.DateUtils; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.Date; +import java.util.Map; + +/** + * @author gsb + * @date 2022/12/5 11:26 + */ +@Data +public class InvokeReqDto { + + @ApiModelProperty(value = "设备编号") + private String serialNumber; + + @NotNull(message = "标识符不能为空") + @ApiModelProperty(value = "标识符") + private String identifier; + /**消息体*/ + @ApiModelProperty(value = "消息体") + private JSONObject params; + /**远程消息体*/ + @ApiModelProperty(value = "远程调用消息体") + private Map remoteCommand; + /**设备超时时间*/ + @ApiModelProperty(value = "设备超时响应时间,默认10s") + private Integer timeOut = 10; + + @ApiModelProperty(value = "下发物模型类型") + private Integer type = ThingsModelType.SERVICE.getCode(); + + @ApiModelProperty(value = "是否是影子模式") + private String isShadow ; + + @ApiModelProperty(value = "产品id") + private Long productId; + + /** + * 物模型名称 + */ + private String modelName; + + private Date timestamp = DateUtils.getNowDate(); + + /** + * 场景id + */ + private Long sceneModelId; + + /** + * 场景变量类型 + */ + private Integer variableType; + + private Long userId; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/MQSendMessageBo.java b/maibu-common/src/main/java/com/maibu/core/mq/MQSendMessageBo.java new file mode 100644 index 0000000..e56d8ee --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/MQSendMessageBo.java @@ -0,0 +1,61 @@ +package com.maibu.core.mq; + +import com.alibaba.fastjson2.JSONObject; +import com.maibu.core.device.DeviceAndProtocol; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 服务(指令)下发对象 + * + * @author bill + */ +@Data +@NoArgsConstructor +public class MQSendMessageBo { + + /** + * 设备编号 + */ + private String serialNumber; + /** + * 下发属性标识符 + */ + private String identifier; + /** + * 下发参数 + */ + private JSONObject params; + + /** + * 下发的值 + */ + private String value; + /** + * messageId生成放到调用接口的时候生成 + */ + private String messageId; + /** + * 协议产品相关信息 + */ + private DeviceAndProtocol dp; + + /** + * 物模型 + */ + private String thingsModel; + /** + * 主题 + */ + private String topicName; + + + public Boolean isShadow; + + public Long userId; + + public Long delay; + + private String parentSerialNumber; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/MessageReplyBo.java b/maibu-common/src/main/java/com/maibu/core/mq/MessageReplyBo.java new file mode 100644 index 0000000..3bc881a --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/MessageReplyBo.java @@ -0,0 +1,51 @@ +package com.maibu.core.mq; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * 设备消息回调或者下发指令值 + * + * @author gsb + * @date 2022/5/11 9:27 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MessageReplyBo { + + + private String id; + /** + * 消息回执的messageId,和下行消息呼应 + */ + private String messageId; + /** + * 设备处理消息的状态 + */ + private Integer status; + /** + * 抵达服务器时间 + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date timestamp; + /** + * 设备上报的时间 + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date deviceTimestamp; + /** + * 回执消息内容 + */ + private String body; + /*产品编号*/ + private Long productId; + /*设备编号*/ + private String serialNumber; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/SubDeviceBo.java b/maibu-common/src/main/java/com/maibu/core/mq/SubDeviceBo.java new file mode 100644 index 0000000..d7d2e6c --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/SubDeviceBo.java @@ -0,0 +1,35 @@ +package com.maibu.core.mq; + +import lombok.Data; + +/** + * @author bill + */ +@Data +public class SubDeviceBo { + /** + * 网关设备id + */ + + private Long gwDeviceId; + + /** + * 子设备id + */ + private Long subDeviceId; + + /** + * 从机地址 + */ + private Integer slaveId; + /** + * 子设备名称 + */ + private String subDeviceName; + /** + * 子设备编号 + */ + private String subDeviceNo; + + private Long subProductId; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/message/DeviceData.java b/maibu-common/src/main/java/com/maibu/core/mq/message/DeviceData.java new file mode 100644 index 0000000..f22beef --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/message/DeviceData.java @@ -0,0 +1,43 @@ +package com.maibu.core.mq.message; + +import com.maibu.core.ota.OtaPackageCode; +import com.maibu.core.protocol.Message; +import com.maibu.core.protocol.modbus.ModbusCode; +import io.netty.buffer.ByteBuf; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 消息解析model + * @author gsb + * @date 2022/10/10 15:53 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Builder +public class DeviceData extends Message { + + /*topic*/ + private String topicName; + + /*设备编号*/ + private String serialNumber; + + /*原数据*/ + private byte[] data; + + private ByteBuf buf; + + private Object body; + /*MQTT OR 其他*/ + private int type; + /*Modbus*/ + private ModbusCode code; + /**产品id*/ + private Long productId; + + private OtaPackageCode netModbusCode; + + private int bitCount; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/message/DeviceDownMessage.java b/maibu-common/src/main/java/com/maibu/core/mq/message/DeviceDownMessage.java new file mode 100644 index 0000000..eb0d646 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/message/DeviceDownMessage.java @@ -0,0 +1,59 @@ +package com.maibu.core.mq.message; + +import com.maibu.core.protocol.modbus.ModbusCode; +import com.maibu.enums.ServerType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 设备下发指令model + * + * @author gsb + * @date 2022/10/10 16:18 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DeviceDownMessage { + + private String messageId; + /** + * 时间戳,单位毫秒 + */ + private Long timestamp; + /** + * 消息体 + */ + private Object body; + /*下发的指令,服务调用的时候就是服务标识符*/ + private String identifier; + /*产品id*/ + private Long productId; + /** + * 设备编码 + */ + private String serialNumber; + /** + * 从机编号 + */ + private Integer slaveId; + private ModbusCode code; + private String protocolCode; + + private List values; + private String topic; + private String subCode; + private ServerType serverType; + + public DeviceDownMessage(List values, String topic, String subCode,String transport) { + this.values = values; + this.topic = topic; + this.subCode = subCode; + this.serverType = ServerType.explain(transport); + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/message/DeviceFunctionMessage.java b/maibu-common/src/main/java/com/maibu/core/mq/message/DeviceFunctionMessage.java new file mode 100644 index 0000000..b0344c7 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/message/DeviceFunctionMessage.java @@ -0,0 +1,33 @@ +package com.maibu.core.mq.message; + +import lombok.Data; + +/** + * 平台下发指令数据model + * @author bill + */ +@Data +public class DeviceFunctionMessage { + + /*流水号,兼容modbus标准协议没有消息流水号*/ + private String seqNo; + /*平台时间*/ + private Long pfTimestamp; + /*下发的消息体*/ + private Object body; + /*下发的指令物模型标识符*/ + private String identifier; + /*下发的数据寄存器地址*/ + private String hexAddress; + /*产品ID*/ + private Long productId; + /*设备编号*/ + private String serialNumber; + /*网关设备编号*/ + private String protocolCode; + + /*是否有子设备 0-否,1-是*/ + private Integer hasSub; + /*子设备从机编号 例如 02 编号从机。通过主机集控下发的指定从机编号*/ + private String subDeviceCode; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/message/DeviceMessage.java b/maibu-common/src/main/java/com/maibu/core/mq/message/DeviceMessage.java new file mode 100644 index 0000000..ea594af --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/message/DeviceMessage.java @@ -0,0 +1,31 @@ +package com.maibu.core.mq.message; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.Date; + +/** + * 设备消息 + * @author bill + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DeviceMessage { + + /** 下发的数据*/ + private Object message; + + /** 下发的topic*/ + private String topicName; + + /** 设备编号*/ + private String serialNumber; + + private String dataType; + + /** 时间 */ + @JsonFormat(pattern ="yyyy-MM-dd HH:mm:ss:SSS") + private Date time; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/message/FunctionCallBackBo.java b/maibu-common/src/main/java/com/maibu/core/mq/message/FunctionCallBackBo.java new file mode 100644 index 0000000..5e5be3e --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/message/FunctionCallBackBo.java @@ -0,0 +1,24 @@ +package com.maibu.core.mq.message; + +import lombok.Data; + +/** + * 指令下发组将的model + * @author bill + */ +@Data +public class FunctionCallBackBo { + + /*下发的数据*/ + private byte[] message; + + /*MQTt-下发的topic*/ + private String topicName; + + /*设备编号*/ + private String serialNumber; + /** + * 原数据包 + */ + private String sources; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/message/InstructionsMessage.java b/maibu-common/src/main/java/com/maibu/core/mq/message/InstructionsMessage.java new file mode 100644 index 0000000..d5e38fc --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/message/InstructionsMessage.java @@ -0,0 +1,20 @@ +package com.maibu.core.mq.message; + +import lombok.Data; + +/** + * 指令下发组将的model + * @author bill + */ +@Data +public class InstructionsMessage { + + /*下发的数据*/ + private byte[] message; + + /*MQTt-下发的topic*/ + private String topicName; + + /*设备编号*/ + private String serialNumber; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/message/ModbusPollMsg.java b/maibu-common/src/main/java/com/maibu/core/mq/message/ModbusPollMsg.java new file mode 100644 index 0000000..ad7c7f9 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/message/ModbusPollMsg.java @@ -0,0 +1,32 @@ +package com.maibu.core.mq.message; + +import lombok.Data; + +import java.util.List; + +/** + * @author gsb + * @date 2024/6/20 10:53 + */ +@Data +public class ModbusPollMsg { + + /** + * 下发指令 + */ + private List commandList; + /** + * 服务端类型 + */ + private Integer serverType; + /** + * 产品id + */ + private Long productId; + /** + * 设备编码 + */ + private String serialNumber; + + private String transport; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/message/MqttBo.java b/maibu-common/src/main/java/com/maibu/core/mq/message/MqttBo.java new file mode 100644 index 0000000..cfbe849 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/message/MqttBo.java @@ -0,0 +1,26 @@ +package com.maibu.core.mq.message; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +/** + * @author bill + */ +@Data +public class MqttBo { + + /*主题*/ + private String topic; + /*数据*/ + private String data; + /*消息质量*/ + private int qos = 1; + /*发送方向*/ + private String direction; + /*时间*/ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date ts; +} + diff --git a/maibu-common/src/main/java/com/maibu/core/mq/message/PropRead.java b/maibu-common/src/main/java/com/maibu/core/mq/message/PropRead.java new file mode 100644 index 0000000..e313202 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/message/PropRead.java @@ -0,0 +1,39 @@ +package com.maibu.core.mq.message; + +import com.maibu.core.protocol.modbus.ModbusCode; +import lombok.Data; + +/** + * @author gsb + * @date 2022/12/9 10:15 + */ +@Data +public class PropRead { + + /**设备编号*/ + private String serialNumber; + /**寄存器起始地址*/ + private int address; + /** + * 读取寄存器个数 + */ + private int count; + /**数据结果长度计算值*/ + private int length; + /** + * 从机地址 + */ + private int slaveId; + /** + * 读取个数 + */ + private int quantity; + /** + * 数据 + */ + private String data; + /** + * 功能码 + */ + private ModbusCode code; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/message/ProtocolDto.java b/maibu-common/src/main/java/com/maibu/core/mq/message/ProtocolDto.java new file mode 100644 index 0000000..8194837 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/message/ProtocolDto.java @@ -0,0 +1,21 @@ +package com.maibu.core.mq.message; + +import lombok.Data; + +/** + * 协议bean + * @author gsb + * @date 2022/10/25 14:54 + */ +@Data +public class ProtocolDto { + + /**协议编号*/ + private String code; + private String name; + /*外部协议url*/ + private String protocolUrl; + private String description; + /**协议类型 协议类型 0:系统协议 1:jar,2.js,3.c*/ + private Integer protocolType; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/message/ReportDataBo.java b/maibu-common/src/main/java/com/maibu/core/mq/message/ReportDataBo.java new file mode 100644 index 0000000..d895d14 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/message/ReportDataBo.java @@ -0,0 +1,48 @@ +package com.maibu.core.mq.message; + +import com.maibu.core.mq.GwDeviceBo; +import com.maibu.core.thingsModel.ThingsModelSimpleItem; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +/** + * 上报数据模型bo + * @author bill + */ +@Data +@Accessors(chain = true) +public class ReportDataBo { + + /**产品id*/ + private Long productId; + /**设备编号*/ + private String serialNumber; + /**上报消息*/ + private String message; + /**上报的数据*/ + private List dataList; + /**设备影子*/ + private boolean isShadow; + /** + * 物模型类型 + * 1=属性,2=功能,3=事件,4=设备升级,5=设备上线,6=设备下线 + */ + private int type; + /**是否执行规则引擎*/ + private boolean isRuleEngine; + /**从机编号*/ + private Integer slaveId; + /** + * 上报原数据包 + */ + private Object sources; + + private GwDeviceBo gwDeviceBo; + + private Long userId; + private String userName; + private String deviceName; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/message/SubDeviceMessage.java b/maibu-common/src/main/java/com/maibu/core/mq/message/SubDeviceMessage.java new file mode 100644 index 0000000..b4b59da --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/message/SubDeviceMessage.java @@ -0,0 +1,18 @@ +package com.maibu.core.mq.message; + +import lombok.Data; + +/** + * 网关子设备model + * @author gsb + * @date 2022/10/10 10:18 + */ +@Data +public class SubDeviceMessage { + /*子设备编号或编码*/ + private String serialNumber; + /*数据*/ + private byte[] data; + /*消息id*/ + private String messageId; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/ota/OtaReplyMessage.java b/maibu-common/src/main/java/com/maibu/core/mq/ota/OtaReplyMessage.java new file mode 100644 index 0000000..8f5cd6d --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/ota/OtaReplyMessage.java @@ -0,0 +1,21 @@ +package com.maibu.core.mq.ota; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * OTA升级回复model + * @author gsb + * @date 2022/10/24 17:20 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class OtaReplyMessage { + + private Long taskId; + private BigDecimal version; + private int status; + private int progress; +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/ota/OtaUpgradeBo.java b/maibu-common/src/main/java/com/maibu/core/mq/ota/OtaUpgradeBo.java new file mode 100644 index 0000000..d6f1a38 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/ota/OtaUpgradeBo.java @@ -0,0 +1,40 @@ +package com.maibu.core.mq.ota; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * OTA远程升级 + * @author gsb + * @date 2022/10/10 10:22 + */ +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class OtaUpgradeBo { + + // 设备编码 + private String serialNumber; + // 升级任务ID + private Long taskId; + // 消息内容 + private byte[] msg; + // 设备状态 + private int status; + // topic + private String topicName; + // 固件包版本 + private BigDecimal version; + // 固件包http地址 + private String url; + // 固件包分包传输大小 + private int packageSize; + // 固件包传输偏移量 + private int offset; + // 升级进度 + private int progress; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/mq/ota/OtaUpgradeDelayTask.java b/maibu-common/src/main/java/com/maibu/core/mq/ota/OtaUpgradeDelayTask.java new file mode 100644 index 0000000..0e83aa3 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/mq/ota/OtaUpgradeDelayTask.java @@ -0,0 +1,63 @@ +package com.maibu.core.mq.ota; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.maibu.utils.DateUtils; +import lombok.Data; + +import java.util.Date; +import java.util.List; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; + +/** + * ota升级发送,实现Delayed延时接口 + * + * @author bill + */ +@Data +public class OtaUpgradeDelayTask implements Delayed { + + /** + * 固件id + */ + private Long firmwareId; + /** + * 1:指定产品 2:指定设备 + */ + private Long upgradeType; + + private List devices; + /** + * 任务id + */ + private Long taskId; + /** + * 开始升级时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date startTime; + + + /** + * 设置延迟执行时间 开始升级时间 -当前时间 + * + * @param unit + * @return + */ + @Override + public long getDelay(TimeUnit unit) { + return startTime.getTime() - DateUtils.getTimestamp(); + } + + @Override + public int compareTo(Delayed o) { + OtaUpgradeDelayTask delayTask = (OtaUpgradeDelayTask) o; + //比较 + long diff = this.startTime.getTime() - delayTask.startTime.getTime(); + if (diff <= 0) { + return -1; + } else { + return 1; + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/AlertPushParams.java b/maibu-common/src/main/java/com/maibu/core/notify/AlertPushParams.java new file mode 100644 index 0000000..8ffca71 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/AlertPushParams.java @@ -0,0 +1,48 @@ +package com.maibu.core.notify; + +import lombok.Data; + +import java.util.Set; + +/** + * @author fastb + * @version 1.0 + * @description: TODO + * @date 2023-12-26 11:03 + */ +@Data +public class AlertPushParams { + + /** + * 通知模版id + */ + private Long notifyTemplateId; + /** + * 告警时间 + */ + private String alertTime; + /** + * 设备名称 + */ + private String deviceName; + /** + * 设备编号 + */ + private String serialNumber; + /** + * 告警发生地点 + */ + private String address; + /** + * 告警名称 + */ + private String alertName; + /** + * 告警推送手机号 + */ + private Set userPhoneSet; + /** + * 告警推送用户id + */ + private Set userIdSet; +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/AppGeTuiParams.java b/maibu-common/src/main/java/com/maibu/core/notify/AppGeTuiParams.java new file mode 100644 index 0000000..5d71aaa --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/AppGeTuiParams.java @@ -0,0 +1,33 @@ +package com.maibu.core.notify; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.sms4j.provider.config.BaseConfig; + +/** + * 个推参数配置 + * @author gsb + * @date 2023/12/11 17:14 + */ +@Data +@Accessors(chain = true) +public class AppGeTuiParams extends BaseConfig { + + @ApiModelProperty("appId") + private String appId; + + @ApiModelProperty("appKey") + private String appKey; + + @ApiModelProperty("秘钥") + private String masterSecret; + + @ApiModelProperty("模板参数") + private String params; + + @Override + public String getSupplier() { + return null; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/EnterpriseWeChatAPPParams.java b/maibu-common/src/main/java/com/maibu/core/notify/EnterpriseWeChatAPPParams.java new file mode 100644 index 0000000..bf0b1e1 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/EnterpriseWeChatAPPParams.java @@ -0,0 +1,35 @@ +package com.maibu.core.notify; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; +import org.dromara.sms4j.provider.config.BaseConfig; + +/** + * 企业微信(应用消息) + * @author gsb + * @date 2023/12/11 17:25 + */ +@Data +@Accessors(chain = true) +public class EnterpriseWeChatAPPParams extends BaseConfig { + + @ApiModelProperty("企业ID") + private String corpId; + @ApiModelProperty("企业应用私钥OA") + private String corpSecret; + @ApiModelProperty("企业应用的id") + private Integer agentId; + //@ApiModelProperty("token") + //private String token; + //@ApiModelProperty("aes秘钥") + //private String aesKey; + + @ApiModelProperty("模板参数") + private String params; + + @Override + public String getSupplier() { + return null; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/NotifyConfigVO.java b/maibu-common/src/main/java/com/maibu/core/notify/NotifyConfigVO.java new file mode 100644 index 0000000..242bc82 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/NotifyConfigVO.java @@ -0,0 +1,37 @@ +package com.maibu.core.notify; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author fastb + * @version 1.0 + * @description: 通知配置参数属性VO类 + * @date 2024-01-09 14:01 + */ +@AllArgsConstructor +@NoArgsConstructor +@Data +public class NotifyConfigVO { + + /** + * 配置属性 + */ + private String attribute; + + /** + * 配置属性名称 + */ + private String name; + + /** + * 配置属性样式 string-字符串;text-富文本;file-文件;boolean-启用;int-整数 + */ + private String type; + + /** + * 值 + */ + private String value; +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/NotifySendResponse.java b/maibu-common/src/main/java/com/maibu/core/notify/NotifySendResponse.java new file mode 100644 index 0000000..c8d9f8e --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/NotifySendResponse.java @@ -0,0 +1,33 @@ +package com.maibu.core.notify; + +import lombok.Data; + +/** + * @author fastb + * @version 1.0 + * @description: 通知发送响应类 + * @date 2024-01-11 16:06 + */ +@Data +public class NotifySendResponse { + + /** + * 发送结果 1-成功;0-失败 + */ + private Integer status = 0; + + /** + * 返回结果内容 + */ + private String resultContent = ""; + + /** + * 发送内容,变量替换后的 + */ + private String sendContent = ""; + + /** + * 不是使用sendAccount账号发送,而是像钉钉这种,发送所有人或部门,记录这个 + */ + private String otherSendAccount = ""; +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/WeChatServerParams.java b/maibu-common/src/main/java/com/maibu/core/notify/WeChatServerParams.java new file mode 100644 index 0000000..16aecbb --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/WeChatServerParams.java @@ -0,0 +1,31 @@ +package com.maibu.core.notify; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 微信服务号推送参数 + * @author gsb + * @date 2023/12/11 17:11 + */ +@Data +@Accessors(chain = true) +public class WeChatServerParams { + + @ApiModelProperty("appId") + private String appId; + + @ApiModelProperty("app秘钥") + private String secret; + + @ApiModelProperty("模板ID") + private String templateId; + + @ApiModelProperty("跳转地址") + private String page; + + @ApiModelProperty("模板参数") + private String params; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/alertPush/AlertPushItem.java b/maibu-common/src/main/java/com/maibu/core/notify/alertPush/AlertPushItem.java new file mode 100644 index 0000000..6b43ec8 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/alertPush/AlertPushItem.java @@ -0,0 +1,28 @@ +package com.maibu.core.notify.alertPush; + +import lombok.Data; + +/** + * 推送项item + * @author bill + */ +@Data +public class AlertPushItem { + + /** + * 告警时间 + */ + private String alertTime; + /** + * 设备编号 + */ + private String serialNumber; + /** + * 告警发生地点 + */ + private String address; + /** + * 告警名称 + */ + private String alertName; +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/alertPush/PushMsg.java b/maibu-common/src/main/java/com/maibu/core/notify/alertPush/PushMsg.java new file mode 100644 index 0000000..033f944 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/alertPush/PushMsg.java @@ -0,0 +1,32 @@ +package com.maibu.core.notify.alertPush; + +import lombok.Data; + +import java.util.List; + +/** + * 推送配置信息 + * @author bill + */ +@Data +public class PushMsg { + + /** + * 用户Id + */ + private Long userId; + /** + * 设备id + */ + private Long deviceId; + /** + * 告警id + */ + private Long alertId; + /** + * 推送内容值 + */ + private AlertPushItem item; + + private List values; +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/config/DingTalkConfigParams.java b/maibu-common/src/main/java/com/maibu/core/notify/config/DingTalkConfigParams.java new file mode 100644 index 0000000..0282eb7 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/config/DingTalkConfigParams.java @@ -0,0 +1,21 @@ +package com.maibu.core.notify.config; + +import lombok.Data; + +/** + * @author fastb + * @version 1.0 + * @description: 钉钉渠道应用配置类 + * @date 2024-01-12 17:50 + */ +@Data +public class DingTalkConfigParams { + + private String appKey; + + private String appSecret; + + private String agentId; + + private String webHook; +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/config/EmailConfigParams.java b/maibu-common/src/main/java/com/maibu/core/notify/config/EmailConfigParams.java new file mode 100644 index 0000000..d14addd --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/config/EmailConfigParams.java @@ -0,0 +1,48 @@ +package com.maibu.core.notify.config; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 邮箱配置参数 + * @author gsb + * @date 2023/12/11 17:17 + */ +@Data +public class EmailConfigParams { + + @ApiModelProperty("服务器地址") + private String smtpServer; + + @ApiModelProperty("端口号") + private String port; + + @ApiModelProperty("账号(发件人地址)") + private String username; + + @ApiModelProperty("密码") + private String password; + + /** + * 是否开启ssl 默认开启 QQ之类的邮箱默认都需要ssl + */ + @ApiModelProperty("是否启动ssl") + private Boolean sslEnable; + + /** + * 是否开启验证 默认开启 + */ + @ApiModelProperty("启动ttl") + private Boolean authEnable; + + /** + * 重试间隔(单位:秒),默认为5秒 + */ + private Integer retryInterval = 5; + + /** + * 重试次数,默认为1次 + */ + private Integer maxRetries = 1; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/config/VoiceConfigParams.java b/maibu-common/src/main/java/com/maibu/core/notify/config/VoiceConfigParams.java new file mode 100644 index 0000000..54424ab --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/config/VoiceConfigParams.java @@ -0,0 +1,23 @@ +package com.maibu.core.notify.config; + +import lombok.Data; + +/** + * 语音配置 + * @author fastb + * @date 2023-12-09 17:32 + */ +@Data +public class VoiceConfigParams { + + /** + * 您的AccessKey ID + */ + private String accessKeyId; + + /** + * 您的AccessKey Secret + */ + private String accessKeySecret; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/config/WeChatConfigParams.java b/maibu-common/src/main/java/com/maibu/core/notify/config/WeChatConfigParams.java new file mode 100644 index 0000000..9e91c29 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/config/WeChatConfigParams.java @@ -0,0 +1,25 @@ +package com.maibu.core.notify.config; + +import lombok.Data; + +/** + * 微信推送配置参数 + * @author gsb + * @date 2023/12/11 17:11 + */ +@Data +public class WeChatConfigParams { + + private String appId; + + private String appSecret; + + private String corpId; + + private String corpSecret; + + private String agentId; + + private String webHook; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/msg/DingTalkMsgParams.java b/maibu-common/src/main/java/com/maibu/core/notify/msg/DingTalkMsgParams.java new file mode 100644 index 0000000..a4d2582 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/msg/DingTalkMsgParams.java @@ -0,0 +1,53 @@ +package com.maibu.core.notify.msg; + +import lombok.Data; + +/** + * @author fastb + * @version 1.0 + * @description: 钉钉模版配置参数类 + * @date 2024-01-12 17:51 + */ +@Data +public class DingTalkMsgParams { + + /** + * 发送账号 + */ + private String sendAccount; + + /** + * 是否发送所有人 + */ + private String sendAllEnable; + + /** + * 发送什么类型的文本 + */ + private String msgType; + + /** + * 消息内容 + */ + private String content; + + /** + * 消息标题 + */ + private String title; + + /** + * 消息链接 + */ + private String messageUrl; + + /** + * 图片链接 + */ + private String picUrl; + + /** + * 所属部门id + */ + private String deptId; +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/msg/EmailMsgParams.java b/maibu-common/src/main/java/com/maibu/core/notify/msg/EmailMsgParams.java new file mode 100644 index 0000000..7add3e6 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/msg/EmailMsgParams.java @@ -0,0 +1,33 @@ +package com.maibu.core.notify.msg; + +import lombok.Data; + +/** + * @author fastb + * @version 1.0 + * @description: 邮箱模板消息参数 + * @date 2023-12-22 10:47 + */ +@Data +public class EmailMsgParams { + + /** + * 发送账号 + */ + private String sendAccount; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 附件 + */ + private String attachment; +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/msg/VoiceMsgParams.java b/maibu-common/src/main/java/com/maibu/core/notify/msg/VoiceMsgParams.java new file mode 100644 index 0000000..6c2f0bf --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/msg/VoiceMsgParams.java @@ -0,0 +1,49 @@ +package com.maibu.core.notify.msg; + +import lombok.Data; + +/** + * @author fastb + * @version 1.0 + * @description: 语音消息模板参数 + * @date 2023-12-22 10:54 + */ +@Data +public class VoiceMsgParams { + + /** + * 发送账号 + */ + private String sendAccount; + + /** + * 模板ID + */ + private String templateId; + + /** + * 内容 + */ + private String content; + + /** + * 应用ID + */ + private String sdkAppId; + + /** + * 播放次数 1~3 + */ + private String playTimes = "1"; + + /** + * 播放音量 0-100 + */ + private String volume = "50"; + + /** + * 语速控制 -500-500 + */ + private String speed = "0"; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/msg/WeComMsgParams.java b/maibu-common/src/main/java/com/maibu/core/notify/msg/WeComMsgParams.java new file mode 100644 index 0000000..0dbd5b9 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/msg/WeComMsgParams.java @@ -0,0 +1,43 @@ +package com.maibu.core.notify.msg; + +import lombok.Data; + +/** + * @author fastb + * @version 1.0 + * @description: 企业微信消息模板参数 + * @date 2023-12-22 10:57 + */ +@Data +public class WeComMsgParams { + + /** + * 发送账号 + */ + private String sendAccount; + + /** + * 消息类型 + */ + private String msgType; + /** + * 消息内容 + */ + private String content; + /** + * 消息标题 + */ + private String title; + /** + * 消息描述 + */ + private String description; + /** + * 跳转链接 + */ + private String url; + /** + * 图片链接 + */ + private String picUrl; +} diff --git a/maibu-common/src/main/java/com/maibu/core/notify/msg/WechatMsgParams.java b/maibu-common/src/main/java/com/maibu/core/notify/msg/WechatMsgParams.java new file mode 100644 index 0000000..98e6e76 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/notify/msg/WechatMsgParams.java @@ -0,0 +1,43 @@ +package com.maibu.core.notify.msg; + +import lombok.Data; + +/** + * @author fastb + * @version 1.0 + * @description: 微信消息模板参数 + * @date 2023-12-22 10:57 + */ +@Data +public class WechatMsgParams { + + /** + * 发送账号 + */ + private String sendAccount; + + /** + * 模版id + */ + private String templateId; + + /** + * 内容 + */ + private String content; + + /** + * 跳转链接 + */ + private String redirectUrl; + + /** + * 跳转小程序appid + */ + private String appid; + + /** + * 小程序跳转路径 + */ + private String pagePath; +} diff --git a/maibu-common/src/main/java/com/maibu/core/ota/OtaPackageCode.java b/maibu-common/src/main/java/com/maibu/core/ota/OtaPackageCode.java new file mode 100644 index 0000000..ea9ff87 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/ota/OtaPackageCode.java @@ -0,0 +1,45 @@ +package com.maibu.core.ota; + +import com.maibu.exception.ServiceException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum OtaPackageCode { + + OTA_01("查询产品工装号",(byte) 0x01), + OTA_0A("OTA升级启动",(byte) 0x0A), + OTA_0B("OTA升级包传输",(byte) 0x0B) + ; + + private String desc; + private byte code; + + public static OtaPackageCode getInstance(int code) { + switch ((byte)code) { + case 0x01: + return OTA_01; + case 0x0A: + return OTA_0A; + case 0x0B: + return OTA_0B; + default: + throw new ServiceException("功能码[" + code + "],未定义"); + } + } + + public static String getDes(int code){ + switch ((byte)code) { + case 0x01: + return OTA_01.desc; + case 0x0A: + return OTA_0A.desc; + case 0x0B: + return OTA_0B.desc; + default: + return "UNKOWN"; + } + } + +} diff --git a/maibu-common/src/main/java/com/maibu/core/page/PageDomain.java b/maibu-common/src/main/java/com/maibu/core/page/PageDomain.java new file mode 100644 index 0000000..06aadb7 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/page/PageDomain.java @@ -0,0 +1,102 @@ +package com.maibu.core.page; + + +import com.maibu.utils.StringUtils; + +/** + * 分页数据 + * + * @author ruoyi + */ +public class PageDomain +{ + /** 当前记录起始索引 */ + private Integer pageNum; + + /** 每页显示记录数 */ + private Integer pageSize; + + /** 排序列 */ + private String orderByColumn; + + /** 排序的方向desc或者asc */ + private String isAsc = "asc"; + + /** 分页参数合理化 */ + private Boolean reasonable = true; + + public String getOrderBy() + { + if (StringUtils.isEmpty(orderByColumn)) + { + return ""; + } + return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc; + } + + public Integer getPageNum() + { + return pageNum; + } + + public void setPageNum(Integer pageNum) + { + this.pageNum = pageNum; + } + + public Integer getPageSize() + { + return pageSize; + } + + public void setPageSize(Integer pageSize) + { + this.pageSize = pageSize; + } + + public String getOrderByColumn() + { + return orderByColumn; + } + + public void setOrderByColumn(String orderByColumn) + { + this.orderByColumn = orderByColumn; + } + + public String getIsAsc() + { + return isAsc; + } + + public void setIsAsc(String isAsc) + { + if (StringUtils.isNotEmpty(isAsc)) + { + // 兼容前端排序类型 + if ("ascending".equals(isAsc)) + { + isAsc = "asc"; + } + else if ("descending".equals(isAsc)) + { + isAsc = "desc"; + } + this.isAsc = isAsc; + } + } + + public Boolean getReasonable() + { + if (StringUtils.isNull(reasonable)) + { + return Boolean.TRUE; + } + return reasonable; + } + + public void setReasonable(Boolean reasonable) + { + this.reasonable = reasonable; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/page/TableDataExtendInfo.java b/maibu-common/src/main/java/com/maibu/core/page/TableDataExtendInfo.java new file mode 100644 index 0000000..c16bae1 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/page/TableDataExtendInfo.java @@ -0,0 +1,99 @@ +package com.maibu.core.page; + +import java.io.Serializable; +import java.util.List; + +/** + * 表格分页数据对象 + * 可添加扩展参数 + * + * @author ruoyi + */ +public class TableDataExtendInfo implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 总记录数 */ + private long total; + + /** 列表数据 */ + private List rows; + + /** 消息状态码 */ + private int code; + + /** 消息内容 */ + private String msg; + + /** + * 全部启用 + */ + private Integer allEnable; + + public Integer getAllEnable() { + return allEnable; + } + + public void setAllEnable(Integer allEnable) { + this.allEnable = allEnable; + } + + /** + * 表格数据对象 + */ + public TableDataExtendInfo() + { + } + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableDataExtendInfo(List list, int total) + { + this.rows = list; + this.total = total; + } + + public long getTotal() + { + return total; + } + + public void setTotal(long total) + { + this.total = total; + } + + public List getRows() + { + return rows; + } + + public void setRows(List rows) + { + this.rows = rows; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/page/TableDataInfo.java b/maibu-common/src/main/java/com/maibu/core/page/TableDataInfo.java new file mode 100644 index 0000000..4e327aa --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/page/TableDataInfo.java @@ -0,0 +1,85 @@ +package com.maibu.core.page; + +import java.io.Serializable; +import java.util.List; + +/** + * 表格分页数据对象 + * + * @author ruoyi + */ +public class TableDataInfo implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 总记录数 */ + private long total; + + /** 列表数据 */ + private List rows; + + /** 消息状态码 */ + private int code; + + /** 消息内容 */ + private String msg; + + /** + * 表格数据对象 + */ + public TableDataInfo() + { + } + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableDataInfo(List list, int total) + { + this.rows = list; + this.total = total; + } + + public long getTotal() + { + return total; + } + + public void setTotal(long total) + { + this.total = total; + } + + public List getRows() + { + return rows; + } + + public void setRows(List rows) + { + this.rows = rows; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/page/TableSupport.java b/maibu-common/src/main/java/com/maibu/core/page/TableSupport.java new file mode 100644 index 0000000..8fff726 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/page/TableSupport.java @@ -0,0 +1,57 @@ +package com.maibu.core.page; + + +import com.maibu.core.text.Convert; +import com.maibu.utils.ServletUtils; + +/** + * 表格数据处理 + * + * @author ruoyi + */ +public class TableSupport +{ + /** + * 当前记录起始索引 + */ + public static final String PAGE_NUM = "pageNum"; + + /** + * 每页显示记录数 + */ + public static final String PAGE_SIZE = "pageSize"; + + /** + * 排序列 + */ + public static final String ORDER_BY_COLUMN = "orderByColumn"; + + /** + * 排序的方向 "desc" 或者 "asc". + */ + public static final String IS_ASC = "isAsc"; + + /** + * 分页参数合理化 + */ + public static final String REASONABLE = "reasonable"; + + /** + * 封装分页对象 + */ + public static PageDomain getPageDomain() + { + PageDomain pageDomain = new PageDomain(); + pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1)); + pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10)); + pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN)); + pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC)); + pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE)); + return pageDomain; + } + + public static PageDomain buildPageRequest() + { + return getPageDomain(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/protocol/Message.java b/maibu-common/src/main/java/com/maibu/core/protocol/Message.java new file mode 100644 index 0000000..cd38421 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/protocol/Message.java @@ -0,0 +1,33 @@ +package com.maibu.core.protocol; + +import io.netty.buffer.ByteBuf; +import lombok.Data; + +import java.io.Serializable; + +/** + * 基础消息 + * + * @author bill + */ +@Data +public class Message implements Serializable { + + /*获取客户端id*/ + public String clientId; + /*消息类型*/ + public String messageId; + /*消息流水号*/ + public String serNo; + /**消息通道id*/ + public String channelId; + + public ByteBuf payload; + + /** + * 是否数据和注册包都封装到一起 + */ + private Boolean isPackage = false; + + private Object body; +} diff --git a/maibu-common/src/main/java/com/maibu/core/protocol/modbus/ModbusCode.java b/maibu-common/src/main/java/com/maibu/core/protocol/modbus/ModbusCode.java new file mode 100644 index 0000000..d571d42 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/protocol/modbus/ModbusCode.java @@ -0,0 +1,88 @@ +package com.maibu.core.protocol.modbus; + +import com.maibu.exception.ServiceException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Modbus功能码 + * @author bill + * + * {bit 位操作} + * 线圈寄存器: bit对应一个信号的开关状态。功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。对应上面的功能码也就是:0x01 0x05 0x0f + * 离散输入寄存器:离散输入寄存器就 是 只读线圈寄存器,每个bit表示一个开关量,是不能够写的。 功能码: 0x02 + * + * {byte 字节操作} + * 保持寄存器: 两个byte,可读写的 写也分为单个写和多个写对应的三个:0x03 0x06 0x10 + * 输入寄存器: 和保持寄存器类似,只支持读而不能写,一般是读取各种实时数据。一个寄存器也是占据两个byte的空间。对应的功能码: 0x04 + * + */ +@Getter +@AllArgsConstructor +public enum ModbusCode { + + Read01("读线圈",(byte) 0x01,"1"), // 读线圈(读写位模式) + Read02("读离散量输入",(byte) 0x02,"2"), // 读离散量输入(位只读模式) + Read03("读保持寄存器",(byte) 0x03,"3"), // 读保持寄存器(字节读写模式) + Read04("读输入寄存器",(byte) 0x04,"4"), // 读输入寄存器(字节只读模式) + + Write05("写单个线圈(读写位模式)",(byte) 0x05,"5"), // 写单个线圈(读写位模式) + Write06("写多个线圈",(byte) 0x06,"6"), // 写单个保持寄存器 + Write0F("写多个线圈",(byte) 0x0F,"15"), // 写多个线圈 + Write10("写多个保持寄存器",(byte) 0x10,"16"), // 写多个保持寄存器 + UnKnow("未知功能码",(byte)0x0000,"100"); + + private String desc; + private byte code; + private String hex; + + public static ModbusCode getInstance(int code) { + switch ((byte)code) { + case 0x01: + return Read01; + case 0x02: + return Read02; + case 0x03: + return Read03; + case 0x04: + return Read04; + + case 0x05: + return Write05; + case 0x06: + return Write06; + case 0x0F: + return Write0F; + case 0x10: + return Write10; + + default: + throw new ServiceException("功能码[" + code + "],未定义"); + } + } + + public static String getDes(int code){ + switch ((byte)code) { + case 0x01: + return Read01.desc; + case 0x02: + return Read02.desc; + case 0x03: + return Read03.desc; + case 0x04: + return Read04.desc; + case 0x05: + return Write05.desc; + case 0x06: + return Write06.desc; + case 0x0F: + return Write0F.desc; + case 0x10: + return Write10.desc; + + default: + return "UNKOWN"; + } + } + +} diff --git a/maibu-common/src/main/java/com/maibu/core/redis/RedisCache.java b/maibu-common/src/main/java/com/maibu/core/redis/RedisCache.java new file mode 100644 index 0000000..ce1ab7c --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/redis/RedisCache.java @@ -0,0 +1,914 @@ +package com.maibu.core.redis; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.maibu.utils.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.BooleanUtils; +import org.springframework.data.redis.core.*; +import org.springframework.data.redis.support.atomic.RedisAtomicLong; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.maibu.core.redis.RedisKeyBuilder.buildModbusTcpCacheKey; +import static com.maibu.core.redis.RedisKeyBuilder.buildModbusTcpRuntimeCacheKey; +import static java.util.regex.Pattern.compile; + +/** + * spring redis 工具类 + * + * @author ruoyi + **/ +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +@Component +@Slf4j +public class RedisCache { + @Resource + public RedisTemplate redisTemplate; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public boolean deleteObject(final Collection collection) { + return redisTemplate.delete(collection) > 0; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return 是否成功 + */ + public boolean deleteCacheMapValue(final String key, final String hKey) { + return redisTemplate.opsForHash().delete(key, hKey) > 0; + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) { + return redisTemplate.keys(pattern); + } + + /** + * 是否存在key + * + * @param key 缓存key + * @return true:存在key ;false:key不存在或者已过期 + */ + public boolean containsKey(String key) { + return redisTemplate.hasKey(key); + } + + + /** + * 递增 + * + * @param key 键 + * @param delta 要增加几(大于0) + * @return + */ + public long incr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("递增因子必须大于0"); + } + return redisTemplate.opsForValue().increment(key, delta); + } + + /** + * redis 计数器自增 + * + * @param key key + * @param liveTime 过期时间,null不设置过期时间 + * @return 自增数 + */ + public Long incr2(String key, long liveTime) { + RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()); + Long increment = entityIdCounter.getAndIncrement(); + + if (increment == 0 && liveTime > 0) {//初始设置过期时间 + entityIdCounter.expire(liveTime, TimeUnit.HOURS); + } + + return increment; + } + + /** + * 将数据放入set缓存 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sAdd(String key, Object... values) { + try { + return redisTemplate.opsForSet().add(key, values); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 将set数据放入缓存 + * + * @param key 键 + * @param time 时间(秒) + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSetAndTime(String key, long time, Object... values) { + try { + Long count = redisTemplate.opsForSet().add(key, values); + if (time > 0) expire(key, time); + return count; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 移除set集合值为value的 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 移除的个数 + */ + public long setRemove(String key, Object... values) { + try { + Long count = redisTemplate.opsForSet().remove(key, values); + return count; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 添加一个元素, zset与set最大的区别就是每个元素都有一个score,因此有个排序的辅助功能; zadd + * + * @param key 键 + * @param value 值 + * @param score 分数 + */ + public boolean zSetAdd(String key, String value, double score) { + try { + Boolean aBoolean = stringRedisTemplate.opsForZSet().add(key, value, score); + return BooleanUtils.isTrue(aBoolean); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 移除一个zset有序集合的key的一个或者多个值 + * zrem key member [member ...] :移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。当 key 存在但不是有序集类型时,返回一个错误。 + * + * @param key 集合的键key + * @param values 需要移除的value + * @return + */ + public boolean zRem(String key, Object... values) { + try { + Long aLong = stringRedisTemplate.opsForZSet().remove(key, values); + return aLong != null ? true : false; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 移除有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。 + * + * @param key String + * @param start double 最小score + * @param end double 最大score + */ + public Long zRemBySocre(String key, double start, double end) { + try { + return stringRedisTemplate.opsForZSet().removeRangeByScore(key, start, end); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 判断value在zset中的排名 zrank命令 + * + * @param key 键 + * @param value 值 + * @return score 越小排名越高; + */ + public Long zRank(String key, String value) { + try { + return stringRedisTemplate.opsForZSet().rank(key, value); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 查询zSet集合中指定顺序的值, 0 -1 表示获取全部的集合内容 zrange + * + * @param key 键 + * @param start 开始 + * @param end 结束 + * @return 返回有序的集合,score小的在前面 + */ + public Set zRange(String key, int start, int end) { + try { + return stringRedisTemplate.opsForZSet().range(key, start, end); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。 + * 有序集成员按 score 值递增(从小到大)次序排列。 + * + * @param key String + * @param start double 最小score + * @param end double 最大score + */ + public Set zRangeByScore(String key, double start, double end) { + try { + return stringRedisTemplate.opsForZSet().rangeByScore(key, start, end); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 获取集合的元素, 从小到大排序 + * + * @param key 键 + * @param start 开始位置 + * @param end 结束位置, -1查询所有 + * @return + */ + public Set zRange(String key, long start, long end) { + try { + return stringRedisTemplate.opsForZSet().range(key, start, end); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + + /** + * 返回set集合的长度 + * + * @param key + * @return + */ + public Long zSize(String key) { + try { + return stringRedisTemplate.opsForZSet().zCard(key); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 根据前缀获取所有的key + * 例如:pro_* + */ + public Set getListKeyByPrefix(String prefix) { + Set keys = redisTemplate.keys(prefix.concat("*")); + return keys; + } + + /** + * 匹配获取键值对,ScanOptions.NONE为获取全部键对 + * + * @param key + * @param options + * @return + */ + public Cursor> hashScan(String key, ScanOptions options) { + return redisTemplate.opsForHash().scan(key, options); + } + + /** + * 获取所有键值对集合 + * + * @param key + */ + public Map hashEntity(String key) { + return redisTemplate.boundHashOps(key).entries(); + } + + /** + * 以map集合的形式添加键值对 + * + * @param key + * @param maps + */ + public void hashPutAll(String key, Map maps) { + redisTemplate.opsForHash().putAll(key, maps); + } + + /** + * 以map集合的形式添加键值对 + * + * @param key + * @param maps + */ + public void hashPutAllObj(String key, Map maps) { + redisTemplate.opsForHash().putAll(key, maps); + } + + /** + * 批量获取设备物模型值 + * + * @param keys 键的集合 + * @param hkeyCondition 筛选字段 + * @return + */ + public Map hashGetAllByKeys(Set keys, String hkeyCondition) { + return (Map) redisTemplate.execute((RedisCallback) con -> { + Iterator it = keys.iterator(); + Map mapList = new HashMap<>(); + while (it.hasNext()) { + String key = it.next(); + Map result = con.hGetAll(key.getBytes()); + Map ans; + if (CollectionUtils.isEmpty(result)) { + return new HashMap<>(0); + } + ans = new HashMap<>(result.size()); + for (Map.Entry entry : result.entrySet()) { + String field = new String((byte[]) entry.getKey()); + if (!"".equals(hkeyCondition)) { + if (field.endsWith(hkeyCondition)) { + ans.put(new String((byte[]) entry.getKey()), new String((byte[]) entry.getValue())); + } + } else { + ans.put(new String((byte[]) entry.getKey()), new String((byte[]) entry.getValue())); + } + } + mapList.put(key, ans); + } + return mapList; + }); + } + + /** + * 批量获取匹配触发器的物模型值(定时告警使用) + * + * @param keys 键的集合 + * @param operator 操作符 + * @param triggerValue 触发的值 + * @return + */ + public Map hashGetAllMatchByKeys(Set keys, String operator, String id, String triggerValue, String modelIndex) { + // 数组或数组对象拼接id和获取值索引 + String matchId; + int indexValue; + if (id.startsWith("array_")) { + int index = id.indexOf("_", id.indexOf("_") + 1); + matchId = id.substring(index + 1); + List list = StringUtils.str2List(id, "_", true, true); + indexValue = Integer.parseInt(list.get(1)); + } else { + indexValue = -1; + matchId = id; + } + return (Map) redisTemplate.execute((RedisCallback) con -> { + Iterator it = keys.iterator(); + Map mapList = new HashMap<>(); + while (it.hasNext()) { + String key = it.next(); + Map result = con.hGetAll(key.getBytes()); + if (CollectionUtils.isEmpty(result)) { + return new HashMap<>(0); + } + for (Map.Entry entry : result.entrySet()) { + String field = new String((byte[]) entry.getKey()); + // 获取物模型值并且匹配规则,获取值的类型和匹配规则后续还要仔细测了然后优化 + if (field.equals(matchId) || field.equals(matchId + "#V")) { + String valueStr = new String((byte[]) entry.getValue()); + JSONObject jsonObject = JSONObject.parseObject((String) JSON.parse(valueStr)); + String value = (String) jsonObject.get("value"); + // 数组或数组对象元素索引 + if (indexValue >= 0) { + List list = StringUtils.str2List(value, ",", true, true); + value = org.apache.commons.collections4.CollectionUtils.isEmpty(list) ? "" : list.get(indexValue); + } + if (ruleResult(operator, value, triggerValue)) { + mapList.put(key, value); + } + } + } + } + return mapList; + }); + } + + /** + * 批量获取匹配触发器的物模型值 + * + * @param productId productId + * @param operator 操作符 + * @param triggerValue 触发的值 + * @return + */ + public Map CheckMatchByProductId(Long productId, String operator, String id, String triggerValue) { + Set keys = getListKeyByPrefix("TSLV:" + productId); + return (Map) redisTemplate.execute((RedisCallback) con -> { + Iterator it = keys.iterator(); + Map mapList = new HashMap<>(); + while (it.hasNext()) { + String key = it.next(); + String value = CheckMatchByCacheKey(key, operator, id, triggerValue); + if (!Objects.equals(value, "")) { + mapList.put(key, value); + } + } + return mapList; + }); + } + + /** + * 获取匹配触发器的物模型值 + * + * @param cacheKey 设备key + * @param operator 操作符 + * @param triggerValue 触发的值 + * @return + */ + public String CheckMatchByCacheKey(String cacheKey, String operator, String id, String triggerValue) { + String cacheValue = getCacheMapValue(cacheKey, id); + Map result = JSON.parseObject(cacheValue, Map.class); + if (CollectionUtils.isEmpty(result)) { + return ""; + } + for (Map.Entry entry : result.entrySet()) { + String field = (String) entry.getKey(); + if (field.equals("value")) { + String value = (String) entry.getValue(); + value = value.replace("\"", ""); + if (ruleResult(operator, value, triggerValue)) { + return value; + } + } + } + return ""; + } + + /** + * 根据key集合获取字符串 + * + * @param keys 键的集合 + * @return + */ + public Map getStringAllByKeys(Set keys) { + return (Map) redisTemplate.execute((RedisCallback) con -> { + Iterator it = keys.iterator(); + Map mapList = new HashMap<>(); + while (it.hasNext()) { + String key = it.next(); + byte[] result = con.get(key.getBytes()); + if (result == null) { + return new HashMap<>(0); + } + String ans = new String((byte[]) result); + mapList.put(key, ans); + } + return mapList; + }); + } + + /** + * 根据条件返回所有键 + * + * @param query + * @return + */ + public List scan(String query) { + Set keys = (Set) redisTemplate.execute((RedisCallback>) connection -> { + Set keysTmp = new HashSet<>(); + Cursor cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match("*" + query + "*").count(1000).build()); + while (cursor.hasNext()) { + keysTmp.add(new String(cursor.next())); + } + return keysTmp; + }); + return new ArrayList<>(keys); + } + + /** + * 规则匹配结果 + * + * @param operator 操作符 + * @param value 上报的值 + * @param triggerValue 触发器的值 + * @return + */ + private boolean ruleResult(String operator, String value, String triggerValue) { + boolean result = false; + if ("".equals(value)) { + return result; + } + // 操作符比较 + switch (operator) { + case "=": + result = value.equals(triggerValue); + break; + case "!=": + result = !value.equals(triggerValue); + break; + case ">": + if (isNumeric(value) && isNumeric(triggerValue)) { + result = Double.parseDouble(value) > Double.parseDouble(triggerValue); + } + break; + case "<": + if (isNumeric(value) && isNumeric(triggerValue)) { + result = Double.parseDouble(value) < Double.parseDouble(triggerValue); + } + break; + case ">=": + if (isNumeric(value) && isNumeric(triggerValue)) { + result = Double.parseDouble(value) >= Double.parseDouble(triggerValue); + } + break; + case "<=": + if (isNumeric(value) && isNumeric(triggerValue)) { + result = Double.parseDouble(value) <= Double.parseDouble(triggerValue); + } + break; + case "contain": + result = value.contains(triggerValue); + break; + case "notcontain": + result = !value.contains(triggerValue); + break; + default: + break; + } + return result; + } + + + /** + * 根据 key 前缀扫描 Redis,返回所有匹配的 key-value 对 + * + * @param prefix key 前缀,例如 "stream:client:" + * @return Map 所有匹配的 key -> value + */ + public Map scanKeysByPrefix(String prefix) { + if (prefix == null || prefix.isEmpty()) { + return Collections.emptyMap(); + } + + String pattern = prefix.endsWith("*") ? prefix : prefix + "*"; + Map resultMap = new HashMap<>(); + + redisTemplate.execute((RedisCallback) connection -> { + Cursor cursor = connection.scan( + ScanOptions.scanOptions() + .match(pattern) + .count(100) // 每次扫描 100 个,可按需调整 + .build() + ); + + while (cursor.hasNext()) { + String key = new String(cursor.next()); + Object rawValue = redisTemplate.opsForValue().get(key); + String value = (rawValue != null) ? rawValue.toString() : null; + if (value != null) { + resultMap.put(key, value); + } + } + return null; + }); + + return resultMap; + } + + /** + * 判断字符串是否为整数或小数 + */ + private boolean isNumeric(String str) { + Pattern pattern = compile("[0-9]*\\.?[0-9]+"); + Matcher isNum = pattern.matcher(str); + if (!isNum.matches()) { + return false; + } + return true; + } + + public void publish(Object message, String channel) { + try { + redisTemplate.convertAndSend(channel, message); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setHashValue(final String key, final String hKey, final T value) { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public String getStrCacheObject(final String key) { + return stringRedisTemplate.opsForValue().get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public void deleteStrObject(final String key) { + stringRedisTemplate.delete(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public void deleteStrHash(final String key) { + stringRedisTemplate.opsForHash().getOperations().delete(key); + } + + /** + * 删除Hash中的数据 + * + * @param key + * @param hkey + */ + public void delHashValue(final String key, final String hkey) { + HashOperations hashOperations = redisTemplate.opsForHash(); + hashOperations.delete(key, hkey); + } + + public Object getStringHashValue(final String key, final String hKey) { + return stringRedisTemplate.opsForHash().get(key, hKey); + } + + public void delStringHashValue(final String key, final String hKey) { + stringRedisTemplate.opsForHash().delete(key, hKey); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class) + public synchronized String getCacheModbusTcpId(String serialNumber) { + //Redis 获取Key自增次数 每次+1 + String key = buildModbusTcpCacheKey(serialNumber); + String oldId = stringRedisTemplate.opsForValue().get(key); + long id = 0; + if (StringUtils.isBlank(oldId)) { + stringRedisTemplate.opsForValue().set(key, String.valueOf(0)); + } else { + int oldI = Integer.parseInt(oldId); + if (oldI < 65535) { + Long increment = stringRedisTemplate.opsForValue().increment(key); + if (null != increment) { + id = increment; + } + } else { + stringRedisTemplate.opsForValue().set(key, String.valueOf(0)); + } + } + return String.valueOf(id); + } + + public synchronized void cacheModbusTcpData(String serialNumber, String id, String data) { + String dataKey = buildModbusTcpRuntimeCacheKey(serialNumber); + stringRedisTemplate.opsForHash().put(dataKey, id, data); + stringRedisTemplate.expire(dataKey, 30, TimeUnit.SECONDS); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/core/redis/RedisKeyBuilder.java b/maibu-common/src/main/java/com/maibu/core/redis/RedisKeyBuilder.java new file mode 100644 index 0000000..86408e5 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/redis/RedisKeyBuilder.java @@ -0,0 +1,139 @@ +package com.maibu.core.redis; + + +import com.maibu.constant.CacheConstants; +import com.maibu.constant.FastBeeConstant; + +/** + * 缓存key生成器 + * + * @author bill + */ +public class RedisKeyBuilder { + + /**设备在线列表缓存key*/ + public static String buildDeviceOnlineListKey(){ + return FastBeeConstant.REDIS.DEVICE_ONLINE_LIST; + } + + /**设备实时数据key*/ + public static String buildDeviceRtCacheKey(String serialNumber){ + return FastBeeConstant.REDIS.DEVICE_RUNTIME_DATA + serialNumber; + } + + /** + * 设备通讯协议参数 + */ + public static String buildDeviceRtParamsKey(String serialNumber){ + return FastBeeConstant.REDIS.DEVICE_PROTOCOL_PARAM + serialNumber; + } + + /**固件版本缓存key*/ + public static String buildFirmwareCachedKey(Long firmwareId){ + return FastBeeConstant.REDIS.FIRMWARE_VERSION + firmwareId; + } + + /**属性读取回调缓存key*/ + public static String buildPropReadCacheKey(String serialNumber){ + return FastBeeConstant.REDIS.PROP_READ_STORE + serialNumber; + } + + /** + * 物模型值命名缓存key + * Key:TSLV:{productId}_{deviceNumber} HKey:{identity#V/identity#S/identity#M/identity#N} + */ + public static String buildTSLVCacheKey(Long productId,String serialNumber){ + return FastBeeConstant.REDIS.DEVICE_PRE_KEY + productId + "_" + serialNumber.toUpperCase(); + } + + /** + * 物模型缓存key + * 物模型命名空间:Key:TSL:{productId} hkey: identity value: thingsModel + */ + public static String buildTSLCacheKey(Long productId){ + return FastBeeConstant.REDIS.TSL_PRE_KEY + productId; + } + + public static String buildModbusKey(Long productId){ + return FastBeeConstant.REDIS.MODBUS_PRE_KEY + productId; + } + + /**录像缓存key*/ + public static String buildSipRecordinfoCacheKey(String recordKey){ + return FastBeeConstant.REDIS.RECORDINFO_KEY + recordKey; + } + + /**设备id缓存key*/ + public static String buildSipDeviceidCacheKey(String id){ + return FastBeeConstant.REDIS.DEVICEID_KEY + id; + } + /**ipCSEQ缓存key*/ + public static String buildStreamCacheKey(String steamId){ + return FastBeeConstant.REDIS.STREAM_KEY + steamId; + } + + public static String buildStreamCacheKey(String deviceId, String channelId, String stream, String ssrc){ + return FastBeeConstant.REDIS.STREAM_KEY + deviceId + ":" + channelId + ":" + stream + ":" + ssrc; + } + + public static String buildInviteCacheKey(String type, String deviceId, String channelId, String stream, String ssrc){ + return FastBeeConstant.REDIS.INVITE_KEY + type + ":"+ deviceId + ":" + channelId + ":" + stream + ":" + ssrc; + } + + /**ipCSEQ缓存key*/ + public static String buildSipCSEQCacheKey(String CSEQ){ + return FastBeeConstant.REDIS.SIP_CSEQ_PREFIX + CSEQ; + } + + /**rule静默时间缓存key*/ + public static String buildSilentTimeacheKey(String key){ + return FastBeeConstant.REDIS.RULE_SILENT_TIME + key; + } + + /**modbus指令缓存key*/ + public static String buildModbusPollCacheKey(String serialNumebr){ + return FastBeeConstant.REDIS.POLL_MODBUS_KEY + serialNumebr; + } + /*缓存设备下发指令消息ID*/ + public static String buildDownMessageIdCacheKey(String serialNumber){ + return FastBeeConstant.REDIS.DEVICE_MESSAGE_ID + serialNumber; + } + + /** + * 缓存产品id,设备编号,协议编号 + */ + public static String buildDeviceMsgCacheKey(String serialNumber){ + return FastBeeConstant.REDIS.DEVICE_MSG + serialNumber; + } + + /** + * 缓存产品id,设备编号,协议编号 + */ + public static String buildSceneModelTagCacheKey(Long sceneModelId){ + return FastBeeConstant.REDIS.SCENE_MODEL_TAG_ID + sceneModelId; + } + + public static String buildModbusRuntimeCacheKey(String serialNumber){ + return FastBeeConstant.REDIS.MODBUS_RUNTIME + serialNumber; + } + + public static String buildModbusLockCacheKey(String serialNumber){ + return FastBeeConstant.REDIS.MODBUS_LOCK + serialNumber; + } + + public static String buildModbusTcpCacheKey(String serialNumber){ + return FastBeeConstant.REDIS.MODBUS_TCP + serialNumber; + } + + public static String buildModbusTcpRuntimeCacheKey(String serialNumber){ + return FastBeeConstant.REDIS.MODBUS_TCP_RUNTIME + serialNumber; + } + + + /**设备OTA升级实时数据*/ + public static String buildDeviceOtaKey(String serialNumber){ + return CacheConstants.DEVICE_OTA_DATA + serialNumber; + } + + +} diff --git a/maibu-common/src/main/java/com/maibu/core/redis/RedisKeyDefine.java b/maibu-common/src/main/java/com/maibu/core/redis/RedisKeyDefine.java new file mode 100644 index 0000000..9861074 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/redis/RedisKeyDefine.java @@ -0,0 +1,113 @@ +package com.maibu.core.redis; + +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; + +import java.time.Duration; + +/** + * Redis Key 定义类 + * + * @author fastbee + */ +@Data +public class RedisKeyDefine { + + @Getter + @AllArgsConstructor + public enum KeyTypeEnum { + + STRING("String"), + LIST("List"), + HASH("Hash"), + SET("Set"), + ZSET("Sorted Set"), + STREAM("Stream"), + PUBSUB("Pub/Sub"); + + /** + * 类型 + */ + @JsonValue + private final String type; + + } + + @Getter + @AllArgsConstructor + public enum TimeoutTypeEnum { + + FOREVER(1), // 永不超时 + DYNAMIC(2), // 动态超时 + FIXED(3); // 固定超时 + + /** + * 类型 + */ + @JsonValue + private final Integer type; + + } + + /** + * Key 模板 + */ + private final String keyTemplate; + /** + * Key 类型的枚举 + */ + private final KeyTypeEnum keyType; + /** + * Value 类型 + * + * 如果是使用分布式锁,设置为 {@link java.util.concurrent.locks.Lock} 类型 + */ + private final Class valueType; + /** + * 超时类型 + */ + private final TimeoutTypeEnum timeoutType; + /** + * 过期时间 + */ + private final Duration timeout; + /** + * 备注 + */ + private final String memo; + + private RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class valueType, + TimeoutTypeEnum timeoutType, Duration timeout) { + this.memo = memo; + this.keyTemplate = keyTemplate; + this.keyType = keyType; + this.valueType = valueType; + this.timeout = timeout; + this.timeoutType = timeoutType; + // 添加注册表 + RedisKeyRegistry.add(this); + } + + public RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class valueType, Duration timeout) { + this(memo, keyTemplate, keyType, valueType, TimeoutTypeEnum.FIXED, timeout); + } + + public RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class valueType, TimeoutTypeEnum timeoutType) { + this(memo, keyTemplate, keyType, valueType, timeoutType, Duration.ZERO); + } + + /** + * 格式化 Key + * + * 注意,内部采用 {@link String#format(String, Object...)} 实现 + * + * @param args 格式化的参数 + * @return Key + */ + public String formatKey(Object... args) { + return String.format(keyTemplate, args); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/core/redis/RedisKeyRegistry.java b/maibu-common/src/main/java/com/maibu/core/redis/RedisKeyRegistry.java new file mode 100644 index 0000000..df2c4e4 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/redis/RedisKeyRegistry.java @@ -0,0 +1,28 @@ +package com.maibu.core.redis; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link RedisKeyDefine} 注册表 + */ +public class RedisKeyRegistry { + + /** + * Redis RedisKeyDefine 数组 + */ + private static final List DEFINES = new ArrayList<>(); + + public static void add(RedisKeyDefine define) { + DEFINES.add(define); + } + + public static List list() { + return DEFINES; + } + + public static int size() { + return DEFINES.size(); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/core/text/CharsetKit.java b/maibu-common/src/main/java/com/maibu/core/text/CharsetKit.java new file mode 100644 index 0000000..fbbab44 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/text/CharsetKit.java @@ -0,0 +1,88 @@ +package com.maibu.core.text; + + +import com.maibu.utils.StringUtils; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * 字符集工具类 + * + * @author ruoyi + */ +public class CharsetKit +{ + /** ISO-8859-1 */ + public static final String ISO_8859_1 = "ISO-8859-1"; + /** UTF-8 */ + public static final String UTF_8 = "UTF-8"; + /** GBK */ + public static final String GBK = "GBK"; + + /** ISO-8859-1 */ + public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1); + /** UTF-8 */ + public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8); + /** GBK */ + public static final Charset CHARSET_GBK = Charset.forName(GBK); + + /** + * 转换为Charset对象 + * + * @param charset 字符集,为空则返回默认字符集 + * @return Charset + */ + public static Charset charset(String charset) + { + return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, String srcCharset, String destCharset) + { + return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, Charset srcCharset, Charset destCharset) + { + if (null == srcCharset) + { + srcCharset = StandardCharsets.ISO_8859_1; + } + + if (null == destCharset) + { + destCharset = StandardCharsets.UTF_8; + } + + if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) + { + return source; + } + return new String(source.getBytes(srcCharset), destCharset); + } + + /** + * @return 系统字符集编码 + */ + public static String systemCharset() + { + return Charset.defaultCharset().name(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/text/Convert.java b/maibu-common/src/main/java/com/maibu/core/text/Convert.java new file mode 100644 index 0000000..c153732 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/text/Convert.java @@ -0,0 +1,1001 @@ +package com.maibu.core.text; + +import com.maibu.utils.StringUtils; +import org.apache.commons.lang3.ArrayUtils; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.text.NumberFormat; +import java.util.Set; + +/** + * 类型转换器 + * + * @author ruoyi + */ +public class Convert +{ + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static String toStr(Object value, String defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof String) + { + return (String) value; + } + return value.toString(); + } + + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static String toStr(Object value) + { + return toStr(value, null); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Character toChar(Object value, Character defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof Character) + { + return (Character) value; + } + + final String valueStr = toStr(value, null); + return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Character toChar(Object value) + { + return toChar(value, null); + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Byte toByte(Object value, Byte defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Byte) + { + return (Byte) value; + } + if (value instanceof Number) + { + return ((Number) value).byteValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Byte.parseByte(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Byte toByte(Object value) + { + return toByte(value, null); + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Short toShort(Object value, Short defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Short) + { + return (Short) value; + } + if (value instanceof Number) + { + return ((Number) value).shortValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Short.parseShort(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Short toShort(Object value) + { + return toShort(value, null); + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Number toNumber(Object value, Number defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Number) + { + return (Number) value; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return NumberFormat.getInstance().parse(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Number toNumber(Object value) + { + return toNumber(value, null); + } + + /** + * 转换为int
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Integer toInt(Object value, Integer defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Integer) + { + return (Integer) value; + } + if (value instanceof Number) + { + return ((Number) value).intValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Integer.parseInt(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为int
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Integer toInt(Object value) + { + return toInt(value, null); + } + + /** + * 转换为Integer数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String str) + { + return toIntArray(",", str); + } + + /** + * 转换为Long数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String str) + { + return toLongArray(",", str); + } + + /** + * 转换为Integer数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Integer[] {}; + } + String[] arr = str.split(split); + final Integer[] ints = new Integer[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Integer v = toInt(arr[i], 0); + ints[i] = v; + } + return ints; + } + + /** + * 转换为Long数组
+ * + * @param split 分隔符 + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Long[] {}; + } + String[] arr = str.split(split); + final Long[] longs = new Long[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Long v = toLong(arr[i], null); + longs[i] = v; + } + return longs; + } + + /** + * 转换为String数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String str) + { + return toStrArray(",", str); + } + + /** + * 转换为String数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String split, String str) + { + return str.split(split); + } + + /** + * 转换为long
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Long toLong(Object value, Long defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Long) + { + return (Long) value; + } + if (value instanceof Number) + { + return ((Number) value).longValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).longValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为long
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Long toLong(Object value) + { + return toLong(value, null); + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Double toDouble(Object value, Double defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Double) + { + return (Double) value; + } + if (value instanceof Number) + { + return ((Number) value).doubleValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).doubleValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Double toDouble(Object value) + { + return toDouble(value, null); + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Float toFloat(Object value, Float defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Float) + { + return (Float) value; + } + if (value instanceof Number) + { + return ((Number) value).floatValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Float.parseFloat(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Float toFloat(Object value) + { + return toFloat(value, null); + } + + /** + * 转换为boolean
+ * String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Boolean toBool(Object value, Boolean defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Boolean) + { + return (Boolean) value; + } + String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + valueStr = valueStr.trim().toLowerCase(); + switch (valueStr) + { + case "true": + case "yes": + case "ok": + case "1": + return true; + case "false": + case "no": + case "0": + return false; + default: + return defaultValue; + } + } + + /** + * 转换为boolean
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Boolean toBool(Object value) + { + return toBool(value, null); + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * + * @param clazz Enum的Class + * @param value 值 + * @param defaultValue 默认值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value, E defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (clazz.isAssignableFrom(value.getClass())) + { + @SuppressWarnings("unchecked") + E myE = (E) value; + return myE; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Enum.valueOf(clazz, valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * + * @param clazz Enum的Class + * @param value 值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value) + { + return toEnum(clazz, value, null); + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value, BigInteger defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigInteger) + { + return (BigInteger) value; + } + if (value instanceof Long) + { + return BigInteger.valueOf((Long) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigInteger(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value) + { + return toBigInteger(value, null); + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigDecimal) + { + return (BigDecimal) value; + } + if (value instanceof Long) + { + return new BigDecimal((Long) value); + } + if (value instanceof Double) + { + return BigDecimal.valueOf((Double) value); + } + if (value instanceof Integer) + { + return new BigDecimal((Integer) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigDecimal(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value) + { + return toBigDecimal(value, null); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @return 字符串 + */ + public static String utf8Str(Object obj) + { + return str(obj, CharsetKit.CHARSET_UTF_8); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charsetName 字符集 + * @return 字符串 + */ + public static String str(Object obj, String charsetName) + { + return str(obj, Charset.forName(charsetName)); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(Object obj, Charset charset) + { + if (null == obj) + { + return null; + } + + if (obj instanceof String) + { + return (String) obj; + } + else if (obj instanceof byte[]) + { + return str((byte[]) obj, charset); + } + else if (obj instanceof Byte[]) + { + byte[] bytes = ArrayUtils.toPrimitive((Byte[]) obj); + return str(bytes, charset); + } + else if (obj instanceof ByteBuffer) + { + return str((ByteBuffer) obj, charset); + } + return obj.toString(); + } + + /** + * 将byte数组转为字符串 + * + * @param bytes byte数组 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(byte[] bytes, String charset) + { + return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset)); + } + + /** + * 解码字节码 + * + * @param data 字符串 + * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 + * @return 解码后的字符串 + */ + public static String str(byte[] data, Charset charset) + { + if (data == null) + { + return null; + } + + if (null == charset) + { + return new String(data); + } + return new String(data, charset); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, String charset) + { + if (data == null) + { + return null; + } + + return str(data, Charset.forName(charset)); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, Charset charset) + { + if (null == charset) + { + charset = Charset.defaultCharset(); + } + return charset.decode(data).toString(); + } + + // ----------------------------------------------------------------------- 全角半角转换 + /** + * 半角转全角 + * + * @param input String. + * @return 全角字符串. + */ + public static String toSBC(String input) + { + return toSBC(input, null); + } + + /** + * 半角转全角 + * + * @param input String + * @param notConvertSet 不替换的字符集合 + * @return 全角字符串. + */ + public static String toSBC(String input, Set notConvertSet) + { + char[] c = input.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == ' ') + { + c[i] = '\u3000'; + } + else if (c[i] < '\177') + { + c[i] = (char) (c[i] + 65248); + + } + } + return new String(c); + } + + /** + * 全角转半角 + * + * @param input String. + * @return 半角字符串 + */ + public static String toDBC(String input) + { + return toDBC(input, null); + } + + /** + * 替换全角为半角 + * + * @param text 文本 + * @param notConvertSet 不替换的字符集合 + * @return 替换后的字符 + */ + public static String toDBC(String text, Set notConvertSet) + { + char[] c = text.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == '\u3000') + { + c[i] = ' '; + } + else if (c[i] > '\uFF00' && c[i] < '\uFF5F') + { + c[i] = (char) (c[i] - 65248); + } + } + String returnString = new String(c); + + return returnString; + } + + /** + * 数字金额大写转换 先写个完整的然后将如零拾替换成零 + * + * @param n 数字 + * @return 中文大写数字 + */ + public static String digitUppercase(double n) + { + String[] fraction = { "角", "分" }; + String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" }; + String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } }; + + String head = n < 0 ? "负" : ""; + n = Math.abs(n); + + String s = ""; + for (int i = 0; i < fraction.length; i++) + { + s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); + } + if (s.length() < 1) + { + s = "整"; + } + int integerPart = (int) Math.floor(n); + + for (int i = 0; i < unit[0].length && integerPart > 0; i++) + { + String p = ""; + for (int j = 0; j < unit[1].length && n > 0; j++) + { + p = digit[integerPart % 10] + unit[1][j] + p; + integerPart = integerPart / 10; + } + s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s; + } + return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整"); + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/text/IntArrayValuable.java b/maibu-common/src/main/java/com/maibu/core/text/IntArrayValuable.java new file mode 100644 index 0000000..dbf0df6 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/text/IntArrayValuable.java @@ -0,0 +1,13 @@ +package com.maibu.core.text; + +/** + * 可生成 Int 数组的接口 + */ +public interface IntArrayValuable { + + /** + * @return int 数组 + */ + int[] array(); + +} diff --git a/maibu-common/src/main/java/com/maibu/core/text/KeyValue.java b/maibu-common/src/main/java/com/maibu/core/text/KeyValue.java new file mode 100644 index 0000000..9a319f4 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/text/KeyValue.java @@ -0,0 +1,20 @@ +package com.maibu.core.text; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Key Value 的键值对 + * + * @author 芋道源码 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class KeyValue { + + private K key; + private V value; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/text/StrFormatter.java b/maibu-common/src/main/java/com/maibu/core/text/StrFormatter.java new file mode 100644 index 0000000..023feda --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/text/StrFormatter.java @@ -0,0 +1,93 @@ +package com.maibu.core.text; + + +import com.maibu.utils.StringUtils; + +/** + * 字符串格式化 + * + * @author ruoyi + */ +public class StrFormatter +{ + public static final String EMPTY_JSON = "{}"; + public static final char C_BACKSLASH = '\\'; + public static final char C_DELIM_START = '{'; + public static final char C_DELIM_END = '}'; + + /** + * 格式化字符串
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param strPattern 字符串模板 + * @param argArray 参数列表 + * @return 结果 + */ + public static String format(final String strPattern, final Object... argArray) + { + if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) + { + return strPattern; + } + final int strPatternLength = strPattern.length(); + + // 初始化定义好的长度以获得更好的性能 + StringBuilder sbuf = new StringBuilder(strPatternLength + 50); + + int handledPosition = 0; + int delimIndex;// 占位符所在位置 + for (int argIndex = 0; argIndex < argArray.length; argIndex++) + { + delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition); + if (delimIndex == -1) + { + if (handledPosition == 0) + { + return strPattern; + } + else + { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 + sbuf.append(strPattern, handledPosition, strPatternLength); + return sbuf.toString(); + } + } + else + { + if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) + { + if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) + { + // 转义符之前还有一个转义符,占位符依旧有效 + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + else + { + // 占位符被转义 + argIndex--; + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(C_DELIM_START); + handledPosition = delimIndex + 1; + } + } + else + { + // 正常占位符 + sbuf.append(strPattern, handledPosition, delimIndex); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + } + } + // 加入最后一个占位符后所有的字符 + sbuf.append(strPattern, handledPosition, strPattern.length()); + + return sbuf.toString(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/thingsModel/NeuronModel.java b/maibu-common/src/main/java/com/maibu/core/thingsModel/NeuronModel.java new file mode 100644 index 0000000..8909860 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/thingsModel/NeuronModel.java @@ -0,0 +1,44 @@ +package com.maibu.core.thingsModel; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + * Neuron-JSON格式协议 + * @author gsb + * @date 2023/5/31 16:36 + */ +@Data +public class NeuronModel { + + /** + * 产品节点 + */ + private String node; + + /** + * 网关编号 + */ + private String group; + /** + * 上报时间 + */ + private Date timestamp; + + /** + * 上报JSON + */ + private JSONObject values; + /** + * 错误集合 + */ + private JSONObject errors; + /** + * 上报属性值集合 + */ + private List items; + +} diff --git a/maibu-common/src/main/java/com/maibu/core/thingsModel/SceneThingsModelItem.java b/maibu-common/src/main/java/com/maibu/core/thingsModel/SceneThingsModelItem.java new file mode 100644 index 0000000..ec7e354 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/thingsModel/SceneThingsModelItem.java @@ -0,0 +1,38 @@ +package com.maibu.core.thingsModel; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +/** + * 物模型值的项 + * + * @author kerwincui + * @date 2021-12-16 + */ +@AllArgsConstructor +@Builder +@Data +public class SceneThingsModelItem +{ + /** 物模型唯一标识符 */ + private String id; + + /** 物模型值 */ + private String value; + + /** 类型:1=属性, 2=功能,3=事件, 4=设备升级,5=设备上线,6=设备下线 ,*/ + private int type; + + /** 脚本ID */ + private String stripId; + + /** 场景ID*/ + private Long sceneId; + + /** 产品ID */ + private Long productId; + + /** 设备编号 */ + private String DeviceNumber; +} diff --git a/maibu-common/src/main/java/com/maibu/core/thingsModel/ThingsModelRuleItem.java b/maibu-common/src/main/java/com/maibu/core/thingsModel/ThingsModelRuleItem.java new file mode 100644 index 0000000..0a597ba --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/thingsModel/ThingsModelRuleItem.java @@ -0,0 +1,29 @@ +package com.maibu.core.thingsModel; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Builder; +import lombok.Data; + +import java.util.Date; +@Data +@Builder +public class ThingsModelRuleItem { + /** 物模型唯一标识符 */ + private String id; + + /** 物模型值 */ + private String value; + private String operator; + private String triggerValue; + + /** + * 更新时间 + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date ts; + + /** 备注 **/ + private String remark; + + private String timestamp; +} diff --git a/maibu-common/src/main/java/com/maibu/core/thingsModel/ThingsModelSimpleItem.java b/maibu-common/src/main/java/com/maibu/core/thingsModel/ThingsModelSimpleItem.java new file mode 100644 index 0000000..3979f6c --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/thingsModel/ThingsModelSimpleItem.java @@ -0,0 +1,85 @@ +package com.maibu.core.thingsModel; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.maibu.utils.DateUtils; +import lombok.AllArgsConstructor; +import lombok.Builder; + +import java.util.Date; + +/** + * 物模型值的项 + * + * @author kerwincui + * @date 2021-12-16 + */ +@AllArgsConstructor +@Builder +public class ThingsModelSimpleItem +{ + /** 物模型唯一标识符 */ + private String id; + + /** 物模型值 */ + private String value; + + private String name; + + /** + * 更新时间 + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date ts; + + /** 备注 **/ + private String remark; + + + public ThingsModelSimpleItem(String id, String value , String remark){ + this.id=id; + this.value=value; + this.remark=remark; + } + + public Date getTs() { + return ts; + } + + public void setTs(Date ts) { + this.ts = ts != null ? ts : DateUtils.getNowDate(); + } + + public ThingsModelSimpleItem(){} + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/maibu-common/src/main/java/com/maibu/core/thingsModel/ThingsModelValuesInput.java b/maibu-common/src/main/java/com/maibu/core/thingsModel/ThingsModelValuesInput.java new file mode 100644 index 0000000..436549a --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/core/thingsModel/ThingsModelValuesInput.java @@ -0,0 +1,56 @@ +package com.maibu.core.thingsModel; + +import java.util.List; + +/** + * 设备输入物模型值参数 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ThingsModelValuesInput +{ + /** 产品ID **/ + private Long productId; + + private Long deviceId; + /** 设备ID **/ + private String deviceNumber; + + private Integer deviceType; + + /** 设备物模型值的集合 **/ + private List thingsModelSimpleItem; + + public Long getDeviceId() { + return deviceId; + } + + public void setDeviceId(Long deviceId) { + this.deviceId = deviceId; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getDeviceNumber() { + return deviceNumber; + } + + public void setDeviceNumber(String deviceNumber) { + this.deviceNumber = deviceNumber; + } + + public List getThingsModelValueRemarkItem() { + return thingsModelSimpleItem; + } + + public void setThingsModelValueRemarkItem(List thingsModelSimpleItem) { + this.thingsModelSimpleItem = thingsModelSimpleItem; + } +} diff --git a/maibu-common/src/main/java/com/maibu/enums/BusinessStatus.java b/maibu-common/src/main/java/com/maibu/enums/BusinessStatus.java new file mode 100644 index 0000000..966dab8 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/BusinessStatus.java @@ -0,0 +1,20 @@ +package com.maibu.enums; + +/** + * 操作状态 + * + * @author ruoyi + * + */ +public enum BusinessStatus +{ + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/maibu-common/src/main/java/com/maibu/enums/BusinessType.java b/maibu-common/src/main/java/com/maibu/enums/BusinessType.java new file mode 100644 index 0000000..39ea3fd --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/BusinessType.java @@ -0,0 +1,59 @@ +package com.maibu.enums; + +/** + * 业务操作类型 + * + * @author ruoyi + */ +public enum BusinessType +{ + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空数据 + */ + CLEAN, +} diff --git a/maibu-common/src/main/java/com/maibu/enums/CommonStatusEnum.java b/maibu-common/src/main/java/com/maibu/enums/CommonStatusEnum.java new file mode 100644 index 0000000..37ace7a --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/CommonStatusEnum.java @@ -0,0 +1,36 @@ +package com.maibu.enums; + +import com.maibu.core.text.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 通用状态枚举 + + */ +@Getter +@AllArgsConstructor +public enum CommonStatusEnum implements IntArrayValuable { + + ENABLE(0, "开启"), + DISABLE(1, "关闭"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CommonStatusEnum::getStatus).toArray(); + + /** + * 状态值 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/DataEnum.java b/maibu-common/src/main/java/com/maibu/enums/DataEnum.java new file mode 100644 index 0000000..7825388 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/DataEnum.java @@ -0,0 +1,37 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * @author gsb + * @date 2023/6/3 14:09 + */ +@Getter +@AllArgsConstructor +public enum DataEnum { + + DECIMAL("decimal", "十进制"), + DOUBLE("double", "双精度"), + ENUM("enum","枚举"), + BOOLEAN("bool","布尔类型"), + INTEGER("integer","整形"), + OBJECT("object", "对象"), + STRING("string","字符串"), + ARRAY("array","数组"); + + String type; + String msg; + + public static DataEnum convert(String type){ + for (DataEnum value : DataEnum.values()) { + if (Objects.equals(value.type, type)){ + return value; + } + } + return DataEnum.STRING; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/DataSourceType.java b/maibu-common/src/main/java/com/maibu/enums/DataSourceType.java new file mode 100644 index 0000000..c63509d --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/DataSourceType.java @@ -0,0 +1,21 @@ +package com.maibu.enums; + +/** + * 数据源 + * + * @author ruoyi + */ +public enum DataSourceType +{ + /** + * 主库 + */ + master, + + /** + * 从库 + */ + slave, + + sharding +} diff --git a/maibu-common/src/main/java/com/maibu/enums/DeviceDistributeTypeEnum.java b/maibu-common/src/main/java/com/maibu/enums/DeviceDistributeTypeEnum.java new file mode 100644 index 0000000..a1ebb0f --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/DeviceDistributeTypeEnum.java @@ -0,0 +1,41 @@ +package com.maibu.enums; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @description: + * @author admin + * @date 2024-07-18 14:52 + * @version 1.0 + */ +@Getter +@AllArgsConstructor +public enum DeviceDistributeTypeEnum { + + /** + * 确保唯一,不能重复 + */ + SELECT(1, "选择分配"), + IMPORT(2,"导入分配"); + + /** + * 渠道类型 + */ + private Integer type; + + /** + * 描述 + */ + private String desc; + + public static String getDesc(Integer type) { + for (DeviceDistributeTypeEnum item : DeviceDistributeTypeEnum.values()) { + if (item.getType().equals(type)) { + return item.getDesc(); + } + } + return ""; + } +} diff --git a/maibu-common/src/main/java/com/maibu/enums/DeviceLogTypeEnum.java b/maibu-common/src/main/java/com/maibu/enums/DeviceLogTypeEnum.java new file mode 100644 index 0000000..01594a3 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/DeviceLogTypeEnum.java @@ -0,0 +1,30 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 场景管理物模型、变量类型枚举 + * 注意:以下4张表下的variable_type相关字段统一用该枚举,保持一致 + * scene_model_tag表、scene_tag_points表、scene_model_device表、scene_model_data表 + * @author fastb + * @date 2024-05-22 10:01 + * @version 1.0 + */ +@Getter +@AllArgsConstructor +public enum DeviceLogTypeEnum { + + ATTRIBUTE_REPORT(1, "属性上报"), + INVOKE_FUNCTION(2, "调用功能"), + EVENT_REPORT(3, "事件上报"), + DEVICE_UPDATE(4, "设备升级"), + DEVICE_ONLINE(5, "设备上线"), + DEVICE_OFFLINE(6, "设备离线"), + SCENE_VARIABLE_REPORT(7, "场景录入、运算变量上报下发"); + + private final Integer type; + + private final String desc; + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/DeviceRecordTypeEnum.java b/maibu-common/src/main/java/com/maibu/enums/DeviceRecordTypeEnum.java new file mode 100644 index 0000000..2c093c7 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/DeviceRecordTypeEnum.java @@ -0,0 +1,35 @@ +package com.maibu.enums; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @description: + * @author admin + * @date 2024-07-18 14:52 + * @version 1.0 + */ +@Getter +@AllArgsConstructor +public enum DeviceRecordTypeEnum { + + /** + * 确保唯一,不能重复 + */ + IMPORT(1, "导入记录"), + RECOVERY(2,"回收记录"), + ASSIGNMENT(3,"分配记录"), + ASSIGNMENT_DETAIL(4,"分配详细记录"); + + + /** + * 渠道类型 + */ + private Integer type; + + /** + * 描述 + */ + private String desc; +} diff --git a/maibu-common/src/main/java/com/maibu/enums/DeviceStatus.java b/maibu-common/src/main/java/com/maibu/enums/DeviceStatus.java new file mode 100644 index 0000000..0abcba3 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/DeviceStatus.java @@ -0,0 +1,36 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum DeviceStatus { + + UNACTIVATED(1,"NOTACTIVE","未激活"), + FORBIDDEN(2,"DISABLE","禁用"), + ONLINE(3,"ONLINE","在线"), + OFFLINE(4,"OFFLINE","离线"); + + private int type; + private String code; + private String description; + + public static DeviceStatus convert(int type){ + for (DeviceStatus value : DeviceStatus.values()) { + if (value.type == type){ + return value; + } + } + return null; + } + + public static DeviceStatus convert(String code){ + for (DeviceStatus value : DeviceStatus.values()) { + if (value.code.equals(code)){ + return value; + } + } + return null; + } +} diff --git a/maibu-common/src/main/java/com/maibu/enums/ExceptionCode.java b/maibu-common/src/main/java/com/maibu/enums/ExceptionCode.java new file mode 100644 index 0000000..923e303 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/ExceptionCode.java @@ -0,0 +1,22 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author gsb + * @date 2022/11/3 11:05 + */ +@Getter +@AllArgsConstructor +public enum ExceptionCode { + + SUCCESS(200,"成功"), + TIMEOUT(400,"超时"), + OFFLINE(404,"设备断线"), + FAIL(500,"失败"); + ; + + public int code; + public String desc; +} diff --git a/maibu-common/src/main/java/com/maibu/enums/FunctionReplyStatus.java b/maibu-common/src/main/java/com/maibu/enums/FunctionReplyStatus.java new file mode 100644 index 0000000..5d0edb2 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/FunctionReplyStatus.java @@ -0,0 +1,21 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 设备回调状态 + * @author bill + */ +@Getter +@AllArgsConstructor +public enum FunctionReplyStatus { + SUCCESS(200,"设备执行成功"), + FAIl(201,"指令执行失败"), + UNKNOWN(204,"设备超时未回复"), + NORELY(203, "指令下发成功"); + + int code; + String message; + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/GlobalErrorCodeConstants.java b/maibu-common/src/main/java/com/maibu/enums/GlobalErrorCodeConstants.java new file mode 100644 index 0000000..7e3d9ce --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/GlobalErrorCodeConstants.java @@ -0,0 +1,52 @@ +package com.maibu.enums; + + +import com.maibu.exception.ErrorCode; + +/** + * 全局错误码枚举 + * 0-999 系统异常编码保留 + * + * 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status + * 虽然说,HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的 + * 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。 + * + * @author fastbee + */ +public interface GlobalErrorCodeConstants { + + ErrorCode SUCCESS = new ErrorCode(0, "成功"); + + // ========== 客户端错误段 ========== + + ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确"); + ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录"); + ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限"); + ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到"); + ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确"); + ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试"); // 并发请求,不允许 + ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试"); + + // ========== 服务端错误段 ========== + + ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常"); + ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启"); + + // ========== 自定义错误段 ========== + ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求 + ErrorCode DEMO_DENY = new ErrorCode(901, "演示模式,禁止写操作"); + + ErrorCode UNKNOWN = new ErrorCode(999, "未知错误"); + + /** + * 是否为服务端错误,参考 HTTP 5XX 错误码段 + * + * @param code 错误码 + * @return 是否 + */ + static boolean isServerErrorCode(Integer code) { + return code != null + && code >= INTERNAL_SERVER_ERROR.getCode() && code <= INTERNAL_SERVER_ERROR.getCode() + 99; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/HttpMethod.java b/maibu-common/src/main/java/com/maibu/enums/HttpMethod.java new file mode 100644 index 0000000..cbe6012 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/HttpMethod.java @@ -0,0 +1,37 @@ +package com.maibu.enums; + +import org.springframework.lang.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * 请求方式 + * + * @author ruoyi + */ +public enum HttpMethod +{ + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + + private static final Map mappings = new HashMap<>(16); + + static + { + for (HttpMethod httpMethod : values()) + { + mappings.put(httpMethod.name(), httpMethod); + } + } + + @Nullable + public static HttpMethod resolve(@Nullable String method) + { + return (method != null ? mappings.get(method) : null); + } + + public boolean matches(String method) + { + return (this == resolve(method)); + } +} diff --git a/maibu-common/src/main/java/com/maibu/enums/IErrorCode.java b/maibu-common/src/main/java/com/maibu/enums/IErrorCode.java new file mode 100644 index 0000000..62474da --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/IErrorCode.java @@ -0,0 +1,14 @@ +package com.maibu.enums; + +/** + * 常用API返回对象接口 + */ +public interface IErrorCode { + + /**返回码*/ + int getCode(); + + /**返回信息*/ + String getMessage(); + +} \ No newline at end of file diff --git a/maibu-common/src/main/java/com/maibu/enums/JobType.java b/maibu-common/src/main/java/com/maibu/enums/JobType.java new file mode 100644 index 0000000..2e6f0f4 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/JobType.java @@ -0,0 +1,35 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +@Getter +@AllArgsConstructor +public enum JobType { + //1==设备定时,2=设备告警,3=场景联动 4=规则引擎 + Device(1), + DeviceAlert(2), + Scene(3), + RuleEngine(4); + private final Integer value; + + public static JobType fromValue(Integer value) { + for (JobType type : JobType.values()) { + if (Objects.equals(type.getValue(), value)) { + return type; + } + } + return null; + } + + public static String getName(Integer value) { + for (JobType type : JobType.values()) { + if (Objects.equals(type.getValue(), value)) { + return type.name(); + } + } + return null; + } +} diff --git a/maibu-common/src/main/java/com/maibu/enums/LimitType.java b/maibu-common/src/main/java/com/maibu/enums/LimitType.java new file mode 100644 index 0000000..32acddc --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/LimitType.java @@ -0,0 +1,20 @@ +package com.maibu.enums; + +/** + * 限流类型 + * + * @author ruoyi + */ + +public enum LimitType +{ + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP +} diff --git a/maibu-common/src/main/java/com/maibu/enums/ModbusDataType.java b/maibu-common/src/main/java/com/maibu/enums/ModbusDataType.java new file mode 100644 index 0000000..75aead0 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/ModbusDataType.java @@ -0,0 +1,38 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * @author gsb + * @date 2023/9/4 14:46 + */ +@Getter +@AllArgsConstructor +public enum ModbusDataType { + + + U_SHORT("ushort","16位 无符号"), + SHORT("short","16位 有符号"), + LONG_ABCD("long-ABCD","32位 有符号(ABCD)"), + LONG_CDAB("long-CDAB","32位 有符号(CDAB)"), + U_LONG_ABCD("ulong-ABCD","32位 无符号(ABCD)"), + U_LONG_CDAB("ulong-CDAB","32位 无符号(CDAB)"), + FLOAT_ABCD("float-ABCD","32位 浮点数(ABCD)"), + FLOAT_CDAB("float-CDAB","32位 浮点数(CDAB)"), + BIT("bit","位"); + + String type; + String msg; + + public static ModbusDataType convert(String type){ + for (ModbusDataType value : ModbusDataType.values()) { + if (Objects.equals(value.type,type)){ + return value; + } + } + return ModbusDataType.U_SHORT; + } +} diff --git a/maibu-common/src/main/java/com/maibu/enums/NotifyChannelEnum.java b/maibu-common/src/main/java/com/maibu/enums/NotifyChannelEnum.java new file mode 100644 index 0000000..c36d2bc --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/NotifyChannelEnum.java @@ -0,0 +1,46 @@ +package com.maibu.enums; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @description: 通知渠道枚举 + * @author fastb + * @date 2023-12-16 17:00 + * @version 1.0 + */ +@Getter +@AllArgsConstructor +public enum NotifyChannelEnum { + + /** + * 确保唯一,不能重复 + */ + SMS("sms", "短信"), + VOICE("voice","语音"), + WECHAT("wechat","微信"), + DING_TALK("dingtalk","钉钉"), + EMAIL("email", "邮箱"); + + + /** + * 渠道类型 + */ + private String type; + + /** + * 描述 + */ + private String desc; + + public static NotifyChannelEnum getNotifyChannelEnum(String type) { + for (NotifyChannelEnum notifyChannelEnum : NotifyChannelEnum.values()) { + if (type.equals(notifyChannelEnum.type)) { + return notifyChannelEnum; + } + } + return null; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/NotifyChannelProviderEnum.java b/maibu-common/src/main/java/com/maibu/enums/NotifyChannelProviderEnum.java new file mode 100644 index 0000000..119431d --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/NotifyChannelProviderEnum.java @@ -0,0 +1,318 @@ +package com.maibu.enums; + +import com.maibu.core.notify.NotifyConfigVO; +import com.maibu.core.notify.config.DingTalkConfigParams; +import com.maibu.core.notify.config.EmailConfigParams; +import com.maibu.core.notify.config.VoiceConfigParams; +import com.maibu.core.notify.config.WeChatConfigParams; +import com.maibu.core.notify.msg.DingTalkMsgParams; +import com.maibu.core.notify.msg.EmailMsgParams; +import com.maibu.core.notify.msg.VoiceMsgParams; +import com.maibu.core.notify.msg.WechatMsgParams; +import com.maibu.utils.StringUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.dromara.sms4j.aliyun.config.AlibabaConfig; +import org.dromara.sms4j.cloopen.config.CloopenConfig; +import org.dromara.sms4j.ctyun.config.CtyunConfig; +import org.dromara.sms4j.emay.config.EmayConfig; +import org.dromara.sms4j.huawei.config.HuaweiConfig; +import org.dromara.sms4j.jdcloud.config.JdCloudConfig; +import org.dromara.sms4j.netease.config.NeteaseConfig; +import org.dromara.sms4j.tencent.config.TencentConfig; +import org.dromara.sms4j.yunpian.config.YunpianConfig; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author fastb + * @version 1.0 + * @description: 通知渠道枚举 + * @date 2023-12-18 11:52 + */ +@Getter +@AllArgsConstructor +@NoArgsConstructor +public enum NotifyChannelProviderEnum { + + /**** 短信 ******/ + SMS_ALIBABA("sms", "alibaba", "阿里云短信", AlibabaConfig.class, AlibabaConfig.class), + SMS_TENCENT("sms", "tencent", "腾讯云短信", TencentConfig.class, TencentConfig.class), + SMS_CTYUN("sms", "ctyun","天翼云短信", CtyunConfig.class, CtyunConfig.class), + SMS_HUAWEI("sms", "huawei", "华为云短信", HuaweiConfig.class, HuaweiConfig.class), + SMS_YUNPIAN("sms", "yunpian", "云片短信", YunpianConfig.class, YunpianConfig.class), + SMS_EMAY("sms", "emay","亿美软通短信", EmayConfig.class, EmayConfig.class), + SMS_CLOOPEN("sms", "cloopen","容连云短信", CloopenConfig.class, CloopenConfig.class), + SMS_JDCLOUD("sms", "jdcloud", "京东云短信", JdCloudConfig.class, JdCloudConfig.class), + SMS_NETEASE("sms", "netease", "网易云短信", NeteaseConfig.class, NeteaseConfig.class), + + /****** 语音 ******/ + VOICE_ALIBABA("voice", "alibaba", "阿里云语音", VoiceConfigParams.class, VoiceMsgParams.class), + VOICE_TENCENT("voice", "tencent", "腾讯云语音", VoiceMsgParams.class, VoiceMsgParams.class), + + /****** 邮箱 ******/ + EMAIL_QQ("email", "qq", "QQ邮箱", EmailConfigParams.class, EmailMsgParams.class), + EMAIL_163("email", "163", "163邮箱", EmailConfigParams.class, EmailMsgParams.class), + + /****** 微信 *****/ + WECHAT_MINI_PROGRAM("wechat", "mini_program", "微信小程序(订阅消息)", WeChatConfigParams.class, WechatMsgParams.class), + WECHAT_WECOM_APPLY("wechat", "wecom_apply", "企业微信应用消息", WeChatConfigParams.class, WechatMsgParams.class), + WECHAT_WECOM_ROBOT("wechat", "wecom_robot", "企业微信群机器人", WeChatConfigParams.class, WechatMsgParams.class), + WECHAT_PUBLIC_ACCOUNT("wechat", "public_account", "微信公众号", WeChatConfigParams.class, WechatMsgParams.class), + + /****** 钉钉 *****/ + DING_TALK_WORK("dingtalk", "work", "钉钉工作消息", DingTalkConfigParams.class, DingTalkMsgParams.class), + DING_TALK_GROUP_ROBOT("dingtalk", "group_robot", "钉钉群机器人", DingTalkConfigParams.class, DingTalkMsgParams.class); + + /** + * 渠道编码 + */ + private String channelType; + + /** + * 渠道编码 + */ + private String provider; + + /** + * 描述 + */ + private String desc; + + /** + * 渠道配置类 + */ + private Class configContentClass; + + /** + * 模板配置类 + */ + private Class msgParamsClass; + + + public static NotifyChannelProviderEnum getNotifyChannelCodeEnum(String channelCode) { + for (NotifyChannelProviderEnum channelCodeEnum : NotifyChannelProviderEnum.values()) { + if (channelCode.equals(channelCodeEnum.channelType)) { + return channelCodeEnum; + } + } + return null; + } + + public static NotifyChannelProviderEnum getByChannelTypeAndProvider(String channelType, String provider) { + for (NotifyChannelProviderEnum channelCodeEnum : NotifyChannelProviderEnum.values()) { + if (channelType.equals(channelCodeEnum.channelType) && provider.equals(channelCodeEnum.getProvider())) { + return channelCodeEnum; + } + } + return null; + } + + /** + * @description: 获取通知渠道配置信息 + * @param: type + * @return: java.lang.Object + */ + public static List getConfigContent(NotifyChannelProviderEnum type) { + List configVOList = new ArrayList<>(); + // 务必保证属性(attribute)参数名和各渠道对应配置类configContentClass里的属性名一致 + switch (type) { + case SMS_ALIBABA: + configVOList.add(new NotifyConfigVO("accessKeyId", "accessKeyId", "string", "")); + configVOList.add(new NotifyConfigVO("accessKeySecret", "accessKeySecret", "string", "")); + break; +// return new SmsAliConfigParams(); + case SMS_TENCENT: + configVOList.add(new NotifyConfigVO("accessKeyId", "accessKeyId", "string", "")); + configVOList.add(new NotifyConfigVO("accessKeySecret", "accessKeySecret", "string", "")); + break; +// return new SmsAliConfigParams(); +// case SMS_CTYUN: +// return new SmsAliConfigParams(); +// case SMS_HUAWEI: +// return new SmsAliConfigParams(); +// case SMS_YUNPIAN: +// return new SmsAliConfigParams(); +// case SMS_EMAY: +// return new SmsAliConfigParams(); +// case SMS_CLOOPEN: +// return new SmsAliConfigParams(); +// case SMS_JDCLOUD: +// return new SmsAliConfigParams(); +// case SMS_NETEASE: +// return new SmsAliConfigParams(); + case VOICE_ALIBABA: + case VOICE_TENCENT: + configVOList.add(new NotifyConfigVO("accessKeyId", "accessKeyId", "string", "")); + configVOList.add(new NotifyConfigVO("accessKeySecret", "accessKeySecret", "string", "")); + break; + case EMAIL_QQ: + case EMAIL_163: + if (EMAIL_QQ.equals(type)) { + configVOList.add(new NotifyConfigVO("smtpServer", "服务器地址", "string","smtp.qq.com")); + } + if (EMAIL_163.equals(type)) { + configVOList.add(new NotifyConfigVO("smtpServer", "服务器地址", "string","smtp.163.com")); + } + configVOList.add(new NotifyConfigVO("port", "端口号", "string", "465")); + configVOList.add(new NotifyConfigVO("username", "发件人账号", "string", "")); + configVOList.add(new NotifyConfigVO("password", "发件秘钥", "string", "")); + configVOList.add(new NotifyConfigVO("sslEnable", "是否启动ssl", "boolean", "true")); + configVOList.add(new NotifyConfigVO("authEnable", "开启验证", "boolean", "true")); + configVOList.add(new NotifyConfigVO("retryInterval", "重试间隔(秒)", "int", "5")); + configVOList.add(new NotifyConfigVO("maxRetries", "重试次数", "int","1")); + break; + case WECHAT_MINI_PROGRAM: + case WECHAT_PUBLIC_ACCOUNT: + configVOList.add(new NotifyConfigVO("appId", "appId", "string","")); + configVOList.add(new NotifyConfigVO("appSecret", "appSecret", "string","")); + break; + case WECHAT_WECOM_APPLY: + configVOList.add(new NotifyConfigVO("corpId", "企业ID", "string","")); + configVOList.add(new NotifyConfigVO("corpSecret", "应用Secret", "string","")); + configVOList.add(new NotifyConfigVO("agentId", "应用agentId", "string","")); + break; + case WECHAT_WECOM_ROBOT: + configVOList.add(new NotifyConfigVO("webHook", "webHook", "string","")); + break; + case DING_TALK_WORK: + configVOList.add(new NotifyConfigVO("appKey", "appKey", "string","")); + configVOList.add(new NotifyConfigVO("appSecret", "appSecret", "string","")); + configVOList.add(new NotifyConfigVO("agentId", "agentId", "string","")); + break; + case DING_TALK_GROUP_ROBOT: + configVOList.add(new NotifyConfigVO("webHook", "webHook", "string","")); + break; + default: + return configVOList; + } + return configVOList; + } + + /** + * @description: 获取通知模板配置信息 + * @param: type + * @return: java.lang.Object + */ + public static List getMsgParams(NotifyChannelProviderEnum type, String msgType) { + List configVOList = new ArrayList<>(); + switch (type) { + // 短信:配置参数来源于sms4j,务必属性字段名和sms4j配置参数字段名一致 + case SMS_ALIBABA: + configVOList.add(new NotifyConfigVO("sendAccount", "发送电话号", "string","")); + configVOList.add(new NotifyConfigVO("templateId", "模板CODE", "string","")); + configVOList.add(new NotifyConfigVO("signature", "签名", "string","")); + configVOList.add(new NotifyConfigVO("content", "模板内容", "string","")); + break; + case SMS_TENCENT: + configVOList.add(new NotifyConfigVO("sendAccount", "发送电话号", "string","")); + configVOList.add(new NotifyConfigVO("templateId", "模板ID", "string","")); + configVOList.add(new NotifyConfigVO("signature", "签名", "string","")); + configVOList.add(new NotifyConfigVO("sdkAppId", "应用SDKAppID", "string","")); + configVOList.add(new NotifyConfigVO("content", "模板内容", "string","")); + break; + // 邮箱 + case EMAIL_QQ: + case EMAIL_163: + configVOList.add(new NotifyConfigVO("sendAccount", "发送邮箱号", "string","")); + configVOList.add(new NotifyConfigVO("title", "标题", "string","")); + configVOList.add(new NotifyConfigVO("attachment", "附件", "file","")); + configVOList.add(new NotifyConfigVO("content", "邮箱正文", "text","")); + break; + case WECHAT_MINI_PROGRAM: + configVOList.add(new NotifyConfigVO("sendAccount", "发送用户ID", "string","")); + configVOList.add(new NotifyConfigVO("templateId", "模板ID", "string","")); + configVOList.add(new NotifyConfigVO("redirectUrl", "跳转链接", "string","")); + configVOList.add(new NotifyConfigVO("content", "模板内容", "string","")); + break; + case WECHAT_PUBLIC_ACCOUNT: + configVOList.add(new NotifyConfigVO("templateId", "模板ID", "string","")); + configVOList.add(new NotifyConfigVO("redirectUrl", "跳转链接", "string","")); + configVOList.add(new NotifyConfigVO("appid", "跳转小程序appid", "string","")); + configVOList.add(new NotifyConfigVO("pagePath", "跳转小程序路径", "string","")); + configVOList.add(new NotifyConfigVO("content", "模板内容", "string","")); + case WECHAT_WECOM_APPLY: + case WECHAT_WECOM_ROBOT: + if (StringUtils.isEmpty(msgType)) { + return configVOList; + } + if (type.equals(WECHAT_WECOM_APPLY)) { + configVOList.add(new NotifyConfigVO("sendAccount", "发送成员账号", "string","")); + } + switch (msgType) { + case "text": + case "markdown": + configVOList.add(new NotifyConfigVO("content", "消息内容", "string","")); + break; + case "news": + configVOList.add(new NotifyConfigVO("title", "消息标题", "string","")); + configVOList.add(new NotifyConfigVO("content", "消息内容", "string","")); + configVOList.add(new NotifyConfigVO("url", "跳转链接", "string","")); + configVOList.add(new NotifyConfigVO("picUrl", "图片链接", "file","")); + break; + default: + break; + } + break; + // 语音 + case VOICE_ALIBABA: + configVOList.add(new NotifyConfigVO("sendAccount", "发送电话号", "string","")); + configVOList.add(new NotifyConfigVO("templateId", "模板ID", "string","")); + configVOList.add(new NotifyConfigVO("content", "模板内容", "string","")); + configVOList.add(new NotifyConfigVO("playTimes", "播放次数 (1~3)", "int","2")); + configVOList.add(new NotifyConfigVO("volume", "播放音量 (0-100)", "string","50")); + configVOList.add(new NotifyConfigVO("speed", "语速控制 (-500-500)", "string","0")); + break; + case VOICE_TENCENT: + configVOList.add(new NotifyConfigVO("sendAccount", "发送电话号", "string","")); + configVOList.add(new NotifyConfigVO("sdkAppId", "应用SDKAppID", "string","")); + configVOList.add(new NotifyConfigVO("templateId", "模板ID", "string","")); + configVOList.add(new NotifyConfigVO("content", "模板内容", "string","")); + break; + // 钉钉 + case DING_TALK_WORK: + case DING_TALK_GROUP_ROBOT: + if (StringUtils.isEmpty(msgType)) { + return configVOList; + } + switch (msgType) { + case "text": + if (NotifyChannelProviderEnum.DING_TALK_WORK.equals(type)) { + configVOList.add(new NotifyConfigVO("deptId", "部门id", "string","")); + configVOList.add(new NotifyConfigVO("sendAllEnable", "发送所有人", "boolean","false")); + configVOList.add(new NotifyConfigVO("sendAccount", "员工UserID", "string","")); + } + configVOList.add(new NotifyConfigVO("content", "消息内容", "string","")); + break; + case "link": + if (NotifyChannelProviderEnum.DING_TALK_WORK.equals(type)) { + configVOList.add(new NotifyConfigVO("deptId", "部门id", "string","")); + configVOList.add(new NotifyConfigVO("sendAllEnable", "发送所有人", "boolean","false")); + configVOList.add(new NotifyConfigVO("sendAccount", "员工UserID", "string","")); + } + configVOList.add(new NotifyConfigVO("title", "消息标题", "string","")); + configVOList.add(new NotifyConfigVO("content", "消息内容", "string","")); + configVOList.add(new NotifyConfigVO("messageUrl", "消息链接", "string","")); + configVOList.add(new NotifyConfigVO("picUrl", "图片链接", "file","")); + break; + case "markdown": + if (NotifyChannelProviderEnum.DING_TALK_WORK.equals(type)) { + configVOList.add(new NotifyConfigVO("deptId", "部门id", "string","")); + configVOList.add(new NotifyConfigVO("sendAllEnable", "发送所有人", "boolean","false")); + configVOList.add(new NotifyConfigVO("sendAccount", "员工UserID", "string","")); + } + configVOList.add(new NotifyConfigVO("title", "消息标题", "string","")); + configVOList.add(new NotifyConfigVO("content", "消息内容", "string","")); + break; + default: + break; + } + break; + default: + return configVOList; + } + return configVOList; + } +} diff --git a/maibu-common/src/main/java/com/maibu/enums/NotifyServiceCodeEnum.java b/maibu-common/src/main/java/com/maibu/enums/NotifyServiceCodeEnum.java new file mode 100644 index 0000000..c1d0827 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/NotifyServiceCodeEnum.java @@ -0,0 +1,44 @@ +package com.maibu.enums; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @description: 通知业务编码枚举 + * @author fastb + * @date 2023-12-16 17:00 + * @version 1.0 + */ +@Getter +@AllArgsConstructor +public enum NotifyServiceCodeEnum { + + /** + * 确保唯一,不能重复 + */ + ALERT("alert", "设备告警"), + CAPTCHA("captcha","验证码"), + MARKETING("marketing", "营销通知"); + + + /** + * 业务编码 + */ + private String serviceCode; + + /** + * 描述 + */ + private String desc; + + public static NotifyServiceCodeEnum getNotifyServiceCodeEnum(String serviceCode) { + for (NotifyServiceCodeEnum notifyServiceCodeEnum : NotifyServiceCodeEnum.values()) { + if (serviceCode.equals(notifyServiceCodeEnum.serviceCode)) { + return notifyServiceCodeEnum; + } + } + return null; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/OTAUpgrade.java b/maibu-common/src/main/java/com/maibu/enums/OTAUpgrade.java new file mode 100644 index 0000000..969d2bf --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/OTAUpgrade.java @@ -0,0 +1,41 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * OTA升级状态 + * @author gsb + * @date 2022/10/24 17:29 + */ +@AllArgsConstructor +@Getter +public enum OTAUpgrade { + + + AWAIT(0, "等待升级","等待升级"), + SEND(1, "发送中","发送中"), + REPLY(2, "升级中","升级中"), + SUCCESS(3, "成功","升级成功"), + FAILED(4, "失败","升级失败"), + FAILED_PULL(4, "失败","拉取固件包失败"), + FAILED_PACKAGE_SIZE(4, "失败","获取固件包分包大小失败"), + FAILED_PUSH(4, "失败","推送固件包失败"), + STOP(5, "停止","设备重复升级停止推送"), + STOP_OFFLINE(5, "停止","设备离线停止推送"), + UNKNOWN(404, "未知","未知错误码"); + Integer status; + String subMsg; + String des; + + public static OTAUpgrade parse(Integer code){ + for (OTAUpgrade item: OTAUpgrade.values()){ + if(item.status.equals(code)){ + return item; + } + } + + return UNKNOWN; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/OperatorType.java b/maibu-common/src/main/java/com/maibu/enums/OperatorType.java new file mode 100644 index 0000000..c32002f --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/OperatorType.java @@ -0,0 +1,24 @@ +package com.maibu.enums; + +/** + * 操作人类别 + * + * @author ruoyi + */ +public enum OperatorType +{ + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/maibu-common/src/main/java/com/maibu/enums/PushType.java b/maibu-common/src/main/java/com/maibu/enums/PushType.java new file mode 100644 index 0000000..3c6d629 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/PushType.java @@ -0,0 +1,23 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 推送类型 + * @author bill + */ +@Getter +@AllArgsConstructor +public enum PushType { + + WECHAT_SERVER_PUSH("wechat_server_push","微信小程序服务号推送"); + + + /** + * 业务编号 + */ + private String serviceCode; + + private String desc; +} diff --git a/maibu-common/src/main/java/com/maibu/enums/ResultCode.java b/maibu-common/src/main/java/com/maibu/enums/ResultCode.java new file mode 100644 index 0000000..d770929 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/ResultCode.java @@ -0,0 +1,37 @@ +package com.maibu.enums; + +import com.maibu.constant.HttpStatus; +import lombok.AllArgsConstructor; + +/** + * API返回对象 + */ +@AllArgsConstructor +public enum ResultCode implements IErrorCode { + + SUCCESS(HttpStatus.SUCCESS,"请求成功"), + FAILED(HttpStatus.ERROR,"系统内部错误"), + ACCEPTED(HttpStatus.ACCEPTED,"请求已接收"), + REDIRECT(HttpStatus.SEE_OTHER,"重定向"), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"暂未登录或token过期"), + FORBIDDEN(HttpStatus.FORBIDDEN,"没有相关权限或授权过期"), + NOT_FOUND(HttpStatus.NOT_FOUND,"资源未找到"), + PARSE_MSG_EXCEPTION(4018, "解析协议异常"), + TIMEOUT(502, "响应超时!"), + FIRMWARE_VERSION_UNIQUE_ERROR(4022, "产品下已存在该版本固件"), + FIRMWARE_SEQ_UNIQUE_ERROR(4023, "产品下已存在该升级序列号"), + FIRMWARE_TASK_UNIQUE_ERROR(4024, "任务名已存在"), + REPLY_TIMEOUT(4001, "超时未回执"), + INVALID_USER_APP(4002, "用户信息不存在"), + INVALID_MQTT_USER(1003, "内部mqtt服务用户异常"), + DECODE_PROTOCOL_EXCEPTION(1000, "解析协议异常"), + MQTT_TOPIC_INVALID(1001, "MQTT订阅topic格式非法"); + + private int code; + private String message; + + public int getCode(){return code;} + + public String getMessage(){return message;} + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/ServerType.java b/maibu-common/src/main/java/com/maibu/enums/ServerType.java new file mode 100644 index 0000000..2b77456 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/ServerType.java @@ -0,0 +1,45 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author gsb + * @date 2022/9/15 9:10 + */ +@Getter +@AllArgsConstructor +public enum ServerType { + + MQTT(1, "MQTT","MQTT-BROKER"), + COAP(2, "COAP","COAP-SERVER"), + TCP(3, "TCP","TCP-SERVER"), + UDP(4, "UDP","UDP-SERVER"), + WEBSOCKET(5,"WEBSOCKET","WEBSOCKET-SERVER"), + GB28181(6,"GB28181","SIP-SERVER"), + HTTP(6,"HTTP","HTTP-SERVER"), + OTHER(999,"WEBSOCKET","MQTT-BROKER"); + + private int type; + private String code; + private String des; + + + + public static ServerType explain(String code) { + for (ServerType value : ServerType.values()) { + if (value.code.equals(code)) { + return value; + } + } + return ServerType.MQTT; + } + public static ServerType explainByType(int type) { + for (ServerType value : ServerType.values()) { + if (value.type == type) { + return value; + } + } + return ServerType.MQTT; + } +} diff --git a/maibu-common/src/main/java/com/maibu/enums/SocialPlatformType.java b/maibu-common/src/main/java/com/maibu/enums/SocialPlatformType.java new file mode 100644 index 0000000..6d49973 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/SocialPlatformType.java @@ -0,0 +1,69 @@ +package com.maibu.enums; + +import java.util.Arrays; +import java.util.List; + +/** + * 第三方登录平台枚举 + * + * @author json + */ +public enum SocialPlatformType { + WECHAT_OPEN_WEB("wechat_open_web", "微信开放平台网站应用"), + WECHAT_OPEN_WEB_BIND("wechat_open_web_bind", "微信开放平台网站应用个人中心绑定"), + WECHAT_OPEN_MOBILE("wechat_open_mobile", "微信开放平台移动应用"), + WECHAT_OPEN_MINI_PROGRAM("wechat_open_mini_program", "微信开放平台小程序"), + WECHAT_OPEN_PUBLIC_ACCOUNT("wechat_open_public_account", "微信开放平台公众号"), + QQ_OPEN_WEB("qq_open_web", "QQ互联网站应用"), + QQ_OPEN_APP("qq_open_app", "QQ互联移动应用"), + QQ_OPEN_MINI_PROGRAM("qq_open_mini_program", "QQ互联小程序"); +// ALIPAY_OPEN_WEB("alipay_open_web", ""), +// ALIPAY_OPEN_APP("alipay_open_app", ""), +// ALIPAY_OPEN_MINI_PROGRAM("alipay_open_mini_program", ""); + + public String sourceClient; + + public String desc; + + SocialPlatformType(String sourceClient, String desc) { + this.sourceClient = sourceClient; + this.desc = desc; + } + + // 查询微信绑定来源集合 + public static final List listWechatPlatform = Arrays.asList(WECHAT_OPEN_WEB.sourceClient, WECHAT_OPEN_MOBILE.sourceClient, WECHAT_OPEN_MINI_PROGRAM.sourceClient); + + public static String getDesc(String sourceClient) { + for (SocialPlatformType socialPlatformType : SocialPlatformType.values()) { + if (socialPlatformType.getSourceClient().equals(sourceClient)) { + return socialPlatformType.getDesc(); + } + } + return null; + } + + public static SocialPlatformType getSocialPlatformType(String sourceClient) { + for (SocialPlatformType socialPlatformType : SocialPlatformType.values()) { + if (socialPlatformType.getSourceClient().equals(sourceClient)) { + return socialPlatformType; + } + } + return null; + } + + public String getSourceClient() { + return sourceClient; + } + + public void setSourceClient(String sourceClient) { + this.sourceClient = sourceClient; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } +} diff --git a/maibu-common/src/main/java/com/maibu/enums/SourceType.java b/maibu-common/src/main/java/com/maibu/enums/SourceType.java new file mode 100644 index 0000000..c3afc87 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/SourceType.java @@ -0,0 +1,35 @@ +package com.maibu.enums; + + +import lombok.Getter; + +@Getter +public enum SourceType { + web(1, "web"), + app(2, "app"), + mini(3, "mini"), + pc(4, "pc"); + + private int type; + private String code; + + SourceType(int type, String code) { + this.type = type; + this.code = code; + } + + /** + * 根据type获取对应的code + * @param type 类型值 + * @return 对应的code + */ + public static String switchCode(int type) { + for (SourceType sourceType : SourceType.values()) { + if (sourceType.getType() == type) { + return sourceType.getCode(); + } + } + return null; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/StatusEnum.java b/maibu-common/src/main/java/com/maibu/enums/StatusEnum.java new file mode 100644 index 0000000..186cb44 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/StatusEnum.java @@ -0,0 +1,27 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 通用状态枚举 + + */ +@Getter +@AllArgsConstructor +public enum StatusEnum { + + SUCCESS(1, "成功"), + FAIL(0, "失败"); + + + /** + * 状态值 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/ThingsModelType.java b/maibu-common/src/main/java/com/maibu/enums/ThingsModelType.java new file mode 100644 index 0000000..d0b96fd --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/ThingsModelType.java @@ -0,0 +1,50 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 物模型类型 + * + * @author bill + */ +@Getter +@AllArgsConstructor +public enum ThingsModelType { + + PROP(1, "PROPERTY", "属性","properties"), + SERVICE(2, "FUNCTION", "服务","functions"), + EVENT(3, "EVENT", "事件","events"),; + + int code; + String type; + String name; + String list; + + public static ThingsModelType getType(int code) { + for (ThingsModelType value : ThingsModelType.values()) { + if (value.code == code) { + return value; + } + } + return ThingsModelType.PROP; + } + + public static ThingsModelType getType(String type) { + for (ThingsModelType value : ThingsModelType.values()) { + if (value.type.equals(type)) { + return value; + } + } + return ThingsModelType.PROP; + } + + public static String getName(int code) { + for (ThingsModelType value : ThingsModelType.values()) { + if (value.code == code) { + return value.list; + } + } + return ThingsModelType.PROP.list; + } +} diff --git a/maibu-common/src/main/java/com/maibu/enums/TopicType.java b/maibu-common/src/main/java/com/maibu/enums/TopicType.java new file mode 100644 index 0000000..47487cf --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/TopicType.java @@ -0,0 +1,72 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * topic类型 + * @author gsb + */ +@Getter +@AllArgsConstructor +public enum TopicType { + + /** + * @param type 0:标记是订阅主题 1:标记是发布属性 + * @param order 排序 + * @param topicSuffix topic后缀 + * @param msg 描述信息 + */ + + /*** 通用设备上报主题(平台订阅) ***/ + PROPERTY_POST(0,1,"/property/post", "订阅属性"), + EVENT_POST(0,2,"/event/post", "订阅事件"), + FUNCTION_POST(0,3,"/function/post", "订阅功能"), + INFO_POST(0,4,"/info/post","订阅设备信息"), + NTP_POST(0,5,"/ntp/post","订阅时钟同步"), + SERVICE_INVOKE_REPLY(0,8,"/service/reply", "订阅功能调用返回结果"), + MESSAGE_POST(0,26,"/message/post","订阅设备上报消息"), + + /*** 通用设备订阅主题(平台下发)***/ + FUNCTION_GET(1,17,"/function/get", "发布功能"), + PROPERTY_GET(1,12,"/property/get" ,"发布设备属性读取"), + PROPERTY_SET(1,15,"/property/set" ,"设置设备属性读取"), + STATUS_POST(1,11,"/status/post","发布状态"), + NTP_GET(1,15,"/ntp/get","发布时钟同步"), + INFO_GET(1,18,"/info/get","发布设备信息"), + + + /*** 视频监控设备转协议发布 ***/ + DEV_INFO_POST(3,19,"/info/post","设备端发布设备信息"), + DEV_EVENT_POST(3,20,"/event/post","设备端发布事件"), + DEV_PROPERTY_POST(3,22,"/property/post", "设备端发布属性"), + + + /*** webSocket转发前端使用 ***/ + WS_SERVICE_INVOKE(2,16,"/ws/service", "WS服务调用"), + + /** + * OTA升级 + */ + WS_OTA_STATUS(2,18,"/ws/ota/status","OTA升级状态"), + HTTP_FIRMWARE_SET(1,14, "/http/upgrade/set","http推送发布OTA升级"), + FETCH_FIRMWARE_SET(1,19, "/fetch/upgrade/set","分包拉取方式发布OTA升级"), + FETCH_UPGRADE_REPLY(0,9,"/fetch/upgrade/reply", "分包拉取方式OTA升级回调"), + HTTP_UPGRADE_REPLY(0,9,"/http/upgrade/reply", "http推送方式OTA升级回调") + ; + + Integer type; + Integer order; + String topicSuffix; + String msg; + + public static TopicType getType(String topicSuffix) { + for (TopicType value : TopicType.values()) { + if (value.topicSuffix.equals(topicSuffix)) { + return value; + } + } + return TopicType.PROPERTY_POST; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/TranslateType.java b/maibu-common/src/main/java/com/maibu/enums/TranslateType.java new file mode 100644 index 0000000..552fe0b --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/TranslateType.java @@ -0,0 +1,24 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import static com.maibu.constant.Constants.*; + +@Getter +@AllArgsConstructor +public enum TranslateType { + + MENU_TYPE(MENU, "sys_menu_translate", "sys_menu", "menu_id", "menu_name"), + DICT_DATA_TYPE(DICT_DATA, "sys_dict_data_translate", "sys_dict_data", "dict_code", "dict_label"), + DICT_TYPE_TYPE(DICT_TYPE, "sys_dict_type_translate", "sys_dict_type", "dict_id", "dict_name"), + THINGS_MODEL_TYPE(THINGS_MODEL, "iot_things_model_translate", "iot_things_model", "model_id", "model_name"), + THINGS_MODEL_TEMPLATE_TYPE(THINGS_MODEL_TEMPLATE, "iot_things_model_template_translate", "iot_things_model_template", "template_id", "template_name"); + + String value; + String translateTable; + String sourceTable; + String idColumn; + String nameColumn; + +} diff --git a/maibu-common/src/main/java/com/maibu/enums/UserStatus.java b/maibu-common/src/main/java/com/maibu/enums/UserStatus.java new file mode 100644 index 0000000..fddfa76 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/UserStatus.java @@ -0,0 +1,30 @@ +package com.maibu.enums; + +/** + * 用户状态 + * + * @author ruoyi + */ +public enum UserStatus +{ + OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除"); + + private final String code; + private final String info; + + UserStatus(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } +} diff --git a/maibu-common/src/main/java/com/maibu/enums/VerifyTypeEnum.java b/maibu-common/src/main/java/com/maibu/enums/VerifyTypeEnum.java new file mode 100644 index 0000000..9ba80ae --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/VerifyTypeEnum.java @@ -0,0 +1,23 @@ +package com.maibu.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * 验证类型枚举 + * @author fastb + * @date 2023-08-30 15:04 + */ +@Getter +@NoArgsConstructor +@AllArgsConstructor +public enum VerifyTypeEnum { + + PASSWORD(1, "账号密码验证"), + SMS(2, "短信验证"); + + private Integer verifyType; + + private String desc; +} diff --git a/maibu-common/src/main/java/com/maibu/enums/scenemodel/SceneModelTagOpreationEnum.java b/maibu-common/src/main/java/com/maibu/enums/scenemodel/SceneModelTagOpreationEnum.java new file mode 100644 index 0000000..72fcea5 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/scenemodel/SceneModelTagOpreationEnum.java @@ -0,0 +1,47 @@ +package com.maibu.enums.scenemodel; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 场景变量统计方式 + * @author fastb + * @date 2024-06-05 10:42 + * @version 1.0 + */ +@AllArgsConstructor +@Getter +public enum SceneModelTagOpreationEnum { + /** + * 原值 + */ + ORIGINAL_VALUE(1, "原值"), + /** + * 累计值 + */ + CUMULATIVE(2, "累计值"), + /** + * 平均值 + */ + AVERAGE_VALUE(3, "平均值"), + /** + * 最大值 + */ + MAX_VALUE(4, "最大值"), + /** + * 最小值 + */ + MIN_VALUE(5,"最小值"); + + private final Integer code; + private final String desc; + + public static SceneModelTagOpreationEnum getByCode(Integer code) { + for (SceneModelTagOpreationEnum opreationEnum : SceneModelTagOpreationEnum.values()) { + if (opreationEnum.getCode().equals(code)) { + return opreationEnum; + } + } + return null; + } +} diff --git a/maibu-common/src/main/java/com/maibu/enums/scenemodel/SceneModelVariableTypeEnum.java b/maibu-common/src/main/java/com/maibu/enums/scenemodel/SceneModelVariableTypeEnum.java new file mode 100644 index 0000000..06090cb --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/enums/scenemodel/SceneModelVariableTypeEnum.java @@ -0,0 +1,32 @@ +package com.maibu.enums.scenemodel; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; + +/** + * 场景管理物模型、变量类型枚举 + * 注意:以下4张表下的variable_type相关字段统一用该枚举,保持一致 + * scene_model_tag表、scene_tag_points表、scene_model_device表、scene_model_data表 + * @author fastb + * @date 2024-05-22 10:01 + * @version 1.0 + */ +@Getter +@AllArgsConstructor +public enum SceneModelVariableTypeEnum { + //1==设备物模型(直采变量),2=录入型变量,3=运算型变量 + THINGS_MODEL(1, "设备配置"), + INPUT_VARIABLE(2, "录入型变量"), + OPERATION_VARIABLE(3, "运算型变量"); + + public final static List ADD_LIST = Arrays.asList(INPUT_VARIABLE, OPERATION_VARIABLE); + + + private final Integer type; + + private final String name; + +} diff --git a/maibu-common/src/main/java/com/maibu/exception/DemoModeException.java b/maibu-common/src/main/java/com/maibu/exception/DemoModeException.java new file mode 100644 index 0000000..d0f1e7b --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/DemoModeException.java @@ -0,0 +1,15 @@ +package com.maibu.exception; + +/** + * 演示模式异常 + * + * @author ruoyi + */ +public class DemoModeException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public DemoModeException() + { + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/ErrorCode.java b/maibu-common/src/main/java/com/maibu/exception/ErrorCode.java new file mode 100644 index 0000000..e712a51 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/ErrorCode.java @@ -0,0 +1,28 @@ +package com.maibu.exception; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 错误码对象 + * + */ +@Data +@Accessors(chain = true) +public class ErrorCode { + + /** + * 错误码 + */ + private final Integer code; + /** + * 错误提示 + */ + private final String msg; + + public ErrorCode(Integer code, String message) { + this.code = code; + this.msg = message; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/exception/GlobalException.java b/maibu-common/src/main/java/com/maibu/exception/GlobalException.java new file mode 100644 index 0000000..2823f25 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/GlobalException.java @@ -0,0 +1,58 @@ +package com.maibu.exception; + +/** + * 全局异常 + * + * @author ruoyi + */ +public class GlobalException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public GlobalException() + { + } + + public GlobalException(String message) + { + this.message = message; + } + + public String getDetailMessage() + { + return detailMessage; + } + + public GlobalException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } + + @Override + public String getMessage() + { + return message; + } + + public GlobalException setMessage(String message) + { + this.message = message; + return this; + } +} \ No newline at end of file diff --git a/maibu-common/src/main/java/com/maibu/exception/ServerException.java b/maibu-common/src/main/java/com/maibu/exception/ServerException.java new file mode 100644 index 0000000..13a3a84 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/ServerException.java @@ -0,0 +1,60 @@ +package com.maibu.exception; + +import com.maibu.enums.GlobalErrorCodeConstants; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 服务器异常 Exception + */ +@Data +@EqualsAndHashCode(callSuper = true) +public final class ServerException extends RuntimeException { + + /** + * 全局错误码 + * + * @see GlobalErrorCodeConstants + */ + private Integer code; + /** + * 错误提示 + */ + private String message; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServerException() { + } + + public ServerException(ErrorCode errorCode) { + this.code = errorCode.getCode(); + this.message = errorCode.getMsg(); + } + + public ServerException(Integer code, String message) { + this.code = code; + this.message = message; + } + + public Integer getCode() { + return code; + } + + public ServerException setCode(Integer code) { + this.code = code; + return this; + } + + @Override + public String getMessage() { + return message; + } + + public ServerException setMessage(String message) { + this.message = message; + return this; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/exception/ServiceException.java b/maibu-common/src/main/java/com/maibu/exception/ServiceException.java new file mode 100644 index 0000000..c782159 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/ServiceException.java @@ -0,0 +1,80 @@ +package com.maibu.exception; + +/** + * 业务异常 + * + * @author ruoyi + */ +public final class ServiceException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() + { + } + + public ServiceException(String message) + { + this.message = message; + } + + public ServiceException(String message, Integer code) + { + this.message = message; + this.code = code; + } + + public ServiceException(Integer code, String message) + { + this.code = code; + this.message = message; + } + + public String getDetailMessage() + { + return detailMessage; + } + + @Override + public String getMessage() + { + return message; + } + + public Integer getCode() + { + return code; + } + + public ServiceException setMessage(String message) + { + this.message = message; + return this; + } + + public ServiceException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/ServiceExceptionUtil.java b/maibu-common/src/main/java/com/maibu/exception/ServiceExceptionUtil.java new file mode 100644 index 0000000..4e3fc73 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/ServiceExceptionUtil.java @@ -0,0 +1,125 @@ +package com.maibu.exception; + +import com.google.common.annotations.VisibleForTesting; +import com.maibu.enums.GlobalErrorCodeConstants; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * {@link ServiceException} 工具类 + * + * 目的在于,格式化异常信息提示。 + * 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化 + * + * 因为 {@link #MESSAGES} 里面默认是没有异常信息提示的模板的,所以需要使用方自己初始化进去。目前想到的有几种方式: + * + * 1. 异常提示信息,写在枚举类中,例如说,cn.iocoder.oceans.user.api.constants.ErrorCodeEnum 类 + ServiceExceptionConfiguration + * 2. 异常提示信息,写在 .properties 等等配置文件 + * 3. 异常提示信息,写在 Apollo 等等配置中心中,从而实现可动态刷新 + * 4. 异常提示信息,存储在 db 等等数据库中,从而实现可动态刷新 + */ +@Slf4j +public class ServiceExceptionUtil { + + /** + * 错误码提示模板 + */ + private static final ConcurrentMap MESSAGES = new ConcurrentHashMap<>(); + + public static void putAll(Map messages) { + ServiceExceptionUtil.MESSAGES.putAll(messages); + } + + public static void put(Integer code, String message) { + ServiceExceptionUtil.MESSAGES.put(code, message); + } + + public static void delete(Integer code, String message) { + ServiceExceptionUtil.MESSAGES.remove(code, message); + } + + // ========== 和 ServiceException 的集成 ========== + + public static ServiceException exception(ErrorCode errorCode) { + String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMsg()); + return exception0(errorCode.getCode(), messagePattern); + } + + public static ServiceException exception(ErrorCode errorCode, Object... params) { + String messagePattern = MESSAGES.getOrDefault(errorCode.getCode(), errorCode.getMsg()); + return exception0(errorCode.getCode(), messagePattern, params); + } + + /** + * 创建指定编号的 ServiceException 的异常 + * + * @param code 编号 + * @return 异常 + */ + public static ServiceException exception(Integer code) { + return exception0(code, MESSAGES.get(code)); + } + + /** + * 创建指定编号的 ServiceException 的异常 + * + * @param code 编号 + * @param params 消息提示的占位符对应的参数 + * @return 异常 + */ + public static ServiceException exception(Integer code, Object... params) { + return exception0(code, MESSAGES.get(code), params); + } + + public static ServiceException exception0(Integer code, String messagePattern, Object... params) { + String message = doFormat(code, messagePattern, params); + return new ServiceException(code, message); + } + + public static ServiceException invalidParamException(String messagePattern, Object... params) { + return exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), messagePattern, params); + } + + // ========== 格式化方法 ========== + + /** + * 将错误编号对应的消息使用 params 进行格式化。 + * + * @param code 错误编号 + * @param messagePattern 消息模版 + * @param params 参数 + * @return 格式化后的提示 + */ + @VisibleForTesting + public static String doFormat(int code, String messagePattern, Object... params) { + StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); + int i = 0; + int j; + int l; + for (l = 0; l < params.length; l++) { + j = messagePattern.indexOf("{}", i); + if (j == -1) { + log.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params); + if (i == 0) { + return messagePattern; + } else { + sbuf.append(messagePattern.substring(i)); + return sbuf.toString(); + } + } else { + sbuf.append(messagePattern, i, j); + sbuf.append(params[l]); + i = j + 2; + } + } + if (messagePattern.indexOf("{}", i) != -1) { + log.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params); + } + sbuf.append(messagePattern.substring(i)); + return sbuf.toString(); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/exception/UtilException.java b/maibu-common/src/main/java/com/maibu/exception/UtilException.java new file mode 100644 index 0000000..c135c9d --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/UtilException.java @@ -0,0 +1,26 @@ +package com.maibu.exception; + +/** + * 工具类异常 + * + * @author ruoyi + */ +public class UtilException extends RuntimeException +{ + private static final long serialVersionUID = 8247610319171014183L; + + public UtilException(Throwable e) + { + super(e.getMessage(), e); + } + + public UtilException(String message) + { + super(message); + } + + public UtilException(String message, Throwable throwable) + { + super(message, throwable); + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/base/BaseException.java b/maibu-common/src/main/java/com/maibu/exception/base/BaseException.java new file mode 100644 index 0000000..3c6a1c2 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/base/BaseException.java @@ -0,0 +1,97 @@ +package com.maibu.exception.base; + +import com.maibu.utils.MessageUtils; +import com.maibu.utils.StringUtils; + +/** + * 基础异常 + * + * @author ruoyi + */ +public class BaseException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 所属模块 + */ + private String module; + + /** + * 错误码 + */ + private String code; + + /** + * 错误码对应的参数 + */ + private Object[] args; + + /** + * 错误消息 + */ + private String defaultMessage; + + public BaseException(String module, String code, Object[] args, String defaultMessage) + { + this.module = module; + this.code = code; + this.args = args; + this.defaultMessage = defaultMessage; + } + + public BaseException(String module, String code, Object[] args) + { + this(module, code, args, null); + } + + public BaseException(String module, String defaultMessage) + { + this(module, null, null, defaultMessage); + } + + public BaseException(String code, Object[] args) + { + this(null, code, args, null); + } + + public BaseException(String defaultMessage) + { + this(null, null, null, defaultMessage); + } + + @Override + public String getMessage() + { + String message = null; + if (!StringUtils.isEmpty(code)) + { + message = MessageUtils.message(code, args); + } + if (message == null) + { + message = defaultMessage; + } + return message; + } + + public String getModule() + { + return module; + } + + public String getCode() + { + return code; + } + + public Object[] getArgs() + { + return args; + } + + public String getDefaultMessage() + { + return defaultMessage; + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/file/FileException.java b/maibu-common/src/main/java/com/maibu/exception/file/FileException.java new file mode 100644 index 0000000..16aae5d --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/file/FileException.java @@ -0,0 +1,20 @@ +package com.maibu.exception.file; + + +import com.maibu.exception.base.BaseException; + +/** + * 文件信息异常类 + * + * @author ruoyi + */ +public class FileException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public FileException(String code, Object[] args) + { + super("file", code, args, null); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/exception/file/FileNameLengthLimitExceededException.java b/maibu-common/src/main/java/com/maibu/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 0000000..46e781f --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,16 @@ +package com.maibu.exception.file; + +/** + * 文件名称超长限制异常类 + * + * @author ruoyi + */ +public class FileNameLengthLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileNameLengthLimitExceededException(int defaultFileNameLength) + { + super("upload.filename.exceed.length", new Object[] { defaultFileNameLength }); + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/file/FileSizeLimitExceededException.java b/maibu-common/src/main/java/com/maibu/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 0000000..0f39686 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,16 @@ +package com.maibu.exception.file; + +/** + * 文件名大小限制异常类 + * + * @author ruoyi + */ +public class FileSizeLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileSizeLimitExceededException(long defaultMaxSize) + { + super("upload.exceed.maxSize", new Object[] { defaultMaxSize }); + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/file/InvalidExtensionException.java b/maibu-common/src/main/java/com/maibu/exception/file/InvalidExtensionException.java new file mode 100644 index 0000000..e97de9f --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/file/InvalidExtensionException.java @@ -0,0 +1,82 @@ +package com.maibu.exception.file; + +import org.apache.commons.fileupload.FileUploadException; + +import java.util.Arrays; + +/** + * 文件上传 误异常类 + * + * @author ruoyi + */ +public class InvalidExtensionException extends FileUploadException +{ + private static final long serialVersionUID = 1L; + + private String[] allowedExtension; + private String extension; + private String filename; + + public InvalidExtensionException(String[] allowedExtension, String extension, String filename) + { + super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式"); + this.allowedExtension = allowedExtension; + this.extension = extension; + this.filename = filename; + } + + public String[] getAllowedExtension() + { + return allowedExtension; + } + + public String getExtension() + { + return extension; + } + + public String getFilename() + { + return filename; + } + + public static class InvalidImageExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidFlashExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidMediaExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidVideoExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/iot/MqttAuthorizationException.java b/maibu-common/src/main/java/com/maibu/exception/iot/MqttAuthorizationException.java new file mode 100644 index 0000000..e16d2d8 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/iot/MqttAuthorizationException.java @@ -0,0 +1,17 @@ +package com.maibu.exception.iot; + +import com.maibu.exception.GlobalException; +import lombok.NoArgsConstructor; + +/** + * mqtt客户端权限校验异常 + * @author gsb + * @date 2022/10/8 14:11 + */ +@NoArgsConstructor +public class MqttAuthorizationException extends GlobalException { + + public MqttAuthorizationException(String messageId){ + super(messageId); + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/iot/MqttClientUserNameOrPassException.java b/maibu-common/src/main/java/com/maibu/exception/iot/MqttClientUserNameOrPassException.java new file mode 100644 index 0000000..c12aeb6 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/iot/MqttClientUserNameOrPassException.java @@ -0,0 +1,17 @@ +package com.maibu.exception.iot; + +import com.maibu.exception.GlobalException; +import lombok.NoArgsConstructor; + +/** + * mqtt客户端校验 用户名或密码错误 + * @author gsb + * @date 2022/10/8 14:15 + */ +@NoArgsConstructor +public class MqttClientUserNameOrPassException extends GlobalException { + + public MqttClientUserNameOrPassException(String message){ + super(message); + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/job/TaskException.java b/maibu-common/src/main/java/com/maibu/exception/job/TaskException.java new file mode 100644 index 0000000..04a5626 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/job/TaskException.java @@ -0,0 +1,34 @@ +package com.maibu.exception.job; + +/** + * 计划策略异常 + * + * @author ruoyi + */ +public class TaskException extends Exception +{ + private static final long serialVersionUID = 1L; + + private Code code; + + public TaskException(String msg, Code code) + { + this(msg, code, null); + } + + public TaskException(String msg, Code code, Exception nestedEx) + { + super(msg, nestedEx); + this.code = code; + } + + public Code getCode() + { + return code; + } + + public enum Code + { + TASK_EXISTS, NO_TASK_EXISTS, TASK_ALREADY_STARTED, UNKNOWN, CONFIG_ERROR, TASK_NODE_NOT_AVAILABLE + } +} \ No newline at end of file diff --git a/maibu-common/src/main/java/com/maibu/exception/user/CaptchaException.java b/maibu-common/src/main/java/com/maibu/exception/user/CaptchaException.java new file mode 100644 index 0000000..49440b9 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/user/CaptchaException.java @@ -0,0 +1,16 @@ +package com.maibu.exception.user; + +/** + * 验证码错误异常类 + * + * @author ruoyi + */ +public class CaptchaException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaException() + { + super("user.jcaptcha.error", null); + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/user/CaptchaExpireException.java b/maibu-common/src/main/java/com/maibu/exception/user/CaptchaExpireException.java new file mode 100644 index 0000000..1f8b305 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/user/CaptchaExpireException.java @@ -0,0 +1,16 @@ +package com.maibu.exception.user; + +/** + * 验证码失效异常类 + * + * @author ruoyi + */ +public class CaptchaExpireException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaExpireException() + { + super("user.jcaptcha.expire", null); + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/user/UserException.java b/maibu-common/src/main/java/com/maibu/exception/user/UserException.java new file mode 100644 index 0000000..907949c --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/user/UserException.java @@ -0,0 +1,19 @@ +package com.maibu.exception.user; + + +import com.maibu.exception.base.BaseException; + +/** + * 用户信息异常类 + * + * @author ruoyi + */ +public class UserException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public UserException(String code, Object[] args) + { + super("user", code, args, null); + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/user/UserPasswordNotMatchException.java b/maibu-common/src/main/java/com/maibu/exception/user/UserPasswordNotMatchException.java new file mode 100644 index 0000000..af93863 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/user/UserPasswordNotMatchException.java @@ -0,0 +1,16 @@ +package com.maibu.exception.user; + +/** + * 用户密码不正确或不符合规范异常类 + * + * @author ruoyi + */ +public class UserPasswordNotMatchException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordNotMatchException() + { + super("user.password.not.match", null); + } +} diff --git a/maibu-common/src/main/java/com/maibu/exception/user/UserPasswordRetryLimitExceedException.java b/maibu-common/src/main/java/com/maibu/exception/user/UserPasswordRetryLimitExceedException.java new file mode 100644 index 0000000..5c20cf6 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/exception/user/UserPasswordRetryLimitExceedException.java @@ -0,0 +1,16 @@ +package com.maibu.exception.user; + +/** + * 用户错误最大次数异常类 + * + * @author ruoyi + */ +public class UserPasswordRetryLimitExceedException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime) + { + super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime }); + } +} diff --git a/maibu-common/src/main/java/com/maibu/filter/PropertyPreExcludeFilter.java b/maibu-common/src/main/java/com/maibu/filter/PropertyPreExcludeFilter.java new file mode 100644 index 0000000..b9c2b3d --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/filter/PropertyPreExcludeFilter.java @@ -0,0 +1,24 @@ +package com.maibu.filter; + +import com.alibaba.fastjson2.filter.SimplePropertyPreFilter; + +/** + * 排除JSON敏感属性 + * + * @author ruoyi + */ +public class PropertyPreExcludeFilter extends SimplePropertyPreFilter +{ + public PropertyPreExcludeFilter() + { + } + + public PropertyPreExcludeFilter addExcludes(String... filters) + { + for (int i = 0; i < filters.length; i++) + { + this.getExcludes().add(filters[i]); + } + return this; + } +} diff --git a/maibu-common/src/main/java/com/maibu/filter/RepeatableFilter.java b/maibu-common/src/main/java/com/maibu/filter/RepeatableFilter.java new file mode 100644 index 0000000..807001c --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/filter/RepeatableFilter.java @@ -0,0 +1,48 @@ +package com.maibu.filter; + +import com.maibu.utils.StringUtils; +import org.springframework.http.MediaType; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * Repeatable 过滤器 + * + * @author ruoyi + */ +public class RepeatableFilter implements Filter +{ + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest + && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) + { + requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); + } + if (null == requestWrapper) + { + chain.doFilter(request, response); + } + else + { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() + { + + } +} diff --git a/maibu-common/src/main/java/com/maibu/filter/RepeatedlyRequestWrapper.java b/maibu-common/src/main/java/com/maibu/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 0000000..646f246 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,77 @@ +package com.maibu.filter; + +import com.maibu.constant.Constants; +import com.maibu.utils.http.HttpHelper; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * 构建可重复读取inputStream的request + * + * @author ruoyi + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper +{ + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException + { + super(request); + request.setCharacterEncoding(Constants.UTF8); + response.setCharacterEncoding(Constants.UTF8); + + body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8); + } + + @Override + public BufferedReader getReader() throws IOException + { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() + { + @Override + public int read() throws IOException + { + return bais.read(); + } + + @Override + public int available() throws IOException + { + return body.length; + } + + @Override + public boolean isFinished() + { + return false; + } + + @Override + public boolean isReady() + { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) + { + + } + }; + } +} diff --git a/maibu-common/src/main/java/com/maibu/filter/XssFilter.java b/maibu-common/src/main/java/com/maibu/filter/XssFilter.java new file mode 100644 index 0000000..d207c39 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/filter/XssFilter.java @@ -0,0 +1,72 @@ +package com.maibu.filter; + + +import com.maibu.enums.HttpMethod; +import com.maibu.utils.StringUtils; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * 防止XSS攻击的过滤器 + * + * @author ruoyi + */ +public class XssFilter implements Filter +{ + /** + * 排除链接 + */ + public List excludes = new ArrayList<>(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + String tempExcludes = filterConfig.getInitParameter("excludes"); + if (StringUtils.isNotEmpty(tempExcludes)) + { + String[] url = tempExcludes.split(","); + for (int i = 0; url != null && i < url.length; i++) + { + excludes.add(url[i]); + } + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + if (handleExcludeURL(req, resp)) + { + chain.doFilter(request, response); + return; + } + XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); + chain.doFilter(xssRequest, response); + } + + private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) + { + String url = request.getServletPath(); + String method = request.getMethod(); + // GET DELETE 不过滤 + if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) + { + return true; + } + return StringUtils.matches(url, excludes); + } + + @Override + public void destroy() + { + + } +} \ No newline at end of file diff --git a/maibu-common/src/main/java/com/maibu/filter/XssHttpServletRequestWrapper.java b/maibu-common/src/main/java/com/maibu/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..a9b270d --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,112 @@ +package com.maibu.filter; + +import com.maibu.utils.StringUtils; +import com.maibu.utils.html.EscapeUtil; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +/** + * XSS过滤处理 + * + * @author ruoyi + */ +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper +{ + /** + * @param request + */ + public XssHttpServletRequestWrapper(HttpServletRequest request) + { + super(request); + } + + @Override + public String[] getParameterValues(String name) + { + String[] values = super.getParameterValues(name); + if (values != null) + { + int length = values.length; + String[] escapesValues = new String[length]; + for (int i = 0; i < length; i++) + { + // 防xss攻击和过滤前后空格 + escapesValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapesValues; + } + return super.getParameterValues(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + // 非json类型,直接返回 + if (!isJsonRequest()) + { + return super.getInputStream(); + } + + // 为空,直接返回 + String json = IOUtils.toString(super.getInputStream(), "utf-8"); + if (StringUtils.isEmpty(json)) + { + return super.getInputStream(); + } + + // xss过滤 + json = EscapeUtil.clean(json).trim(); + byte[] jsonBytes = json.getBytes("utf-8"); + final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes); + return new ServletInputStream() + { + @Override + public boolean isFinished() + { + return true; + } + + @Override + public boolean isReady() + { + return true; + } + + @Override + public int available() throws IOException + { + return jsonBytes.length; + } + + @Override + public void setReadListener(ReadListener readListener) + { + } + + @Override + public int read() throws IOException + { + return bis.read(); + } + }; + } + + /** + * 是否是Json请求 + * + * @param request + */ + public boolean isJsonRequest() + { + String header = super.getHeader(HttpHeaders.CONTENT_TYPE); + return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); + } +} \ No newline at end of file diff --git a/maibu-common/src/main/java/com/maibu/utils/Arith.java b/maibu-common/src/main/java/com/maibu/utils/Arith.java new file mode 100644 index 0000000..5b2ffc3 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/Arith.java @@ -0,0 +1,114 @@ +package com.maibu.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 精确的浮点数运算 + * + * @author ruoyi + */ +public class Arith +{ + + /** 默认除法运算精度 */ + private static final int DEF_DIV_SCALE = 10; + + /** 这个类不能实例化 */ + private Arith() + { + } + + /** + * 提供精确的加法运算。 + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + public static double add(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.add(b2).doubleValue(); + } + + /** + * 提供精确的减法运算。 + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static double sub(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.subtract(b2).doubleValue(); + } + + /** + * 提供精确的乘法运算。 + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static double mul(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.multiply(b2).doubleValue(); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 + * 小数点以后10位,以后的数字四舍五入。 + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的商 + */ + public static double div(double v1, double v2) + { + return div(v1, v2, DEF_DIV_SCALE); + } + + /** + * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 + * 定精度,以后的数字四舍五入。 + * @param v1 被除数 + * @param v2 除数 + * @param scale 表示表示需要精确到小数点以后几位。 + * @return 两个参数的商 + */ + public static double div(double v1, double v2, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + if (b1.compareTo(BigDecimal.ZERO) == 0) + { + return BigDecimal.ZERO.doubleValue(); + } + return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); + } + + /** + * 提供精确的小数位四舍五入处理。 + * @param v 需要四舍五入的数字 + * @param scale 小数点后保留几位 + * @return 四舍五入后的结果 + */ + public static double round(double v, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b = new BigDecimal(Double.toString(v)); + BigDecimal one = BigDecimal.ONE; + return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/Base64ToMultipartFile.java b/maibu-common/src/main/java/com/maibu/utils/Base64ToMultipartFile.java new file mode 100644 index 0000000..cebe5fb --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/Base64ToMultipartFile.java @@ -0,0 +1,80 @@ +package com.maibu.utils; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * @author fastb + * @version 1.0 + * @description: TODO + * @date 2023-12-26 9:27 + */ +public class Base64ToMultipartFile implements MultipartFile { + private final byte[] fileContent; + + private final String extension; + private final String contentType; + + + /** + * @param base64 + * @param dataUri 格式类似于: data:image/png;base64 + */ + public Base64ToMultipartFile(String base64, String dataUri) { + this.fileContent = Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8)); + this.extension = dataUri.split(";")[0].split("/")[1]; + this.contentType = dataUri.split(";")[0].split(":")[1]; + } + + public Base64ToMultipartFile(String base64, String extension, String contentType) { + this.fileContent = Base64.getDecoder().decode(base64.getBytes(StandardCharsets.UTF_8)); + this.extension = extension; + this.contentType = contentType; + } + + @Override + public String getName() { + return "param_" + System.currentTimeMillis(); + } + + @Override + public String getOriginalFilename() { + return "file_" + System.currentTimeMillis() + "." + extension; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public boolean isEmpty() { + return fileContent == null || fileContent.length == 0; + } + + @Override + public long getSize() { + return fileContent.length; + } + + @Override + public byte[] getBytes() throws IOException { + return fileContent; + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(fileContent); + } + + @Override + public void transferTo(File file) throws IOException, IllegalStateException { + try (FileOutputStream fos = new FileOutputStream(file)) { + fos.write(fileContent); + } + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/BeanMapUtilByReflect.java b/maibu-common/src/main/java/com/maibu/utils/BeanMapUtilByReflect.java new file mode 100644 index 0000000..6ac13ba --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/BeanMapUtilByReflect.java @@ -0,0 +1,74 @@ +package com.maibu.utils; + + +import com.maibu.core.thingsModel.ThingsModelSimpleItem; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BeanMapUtilByReflect { + + /** + * 对象转Map + * @param object + * @return + * @throws IllegalAccessException + */ + public static Map beanToMap(Object object) throws IllegalAccessException { + Map map = new HashMap(); + Field[] fields = object.getClass().getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + map.put(field.getName(), field.get(object)); + } + return map; + } + + /** + * bean转item对象 + * @param object + * @return + * @throws IllegalAccessException + */ + public static List beanToItem(Object object) throws IllegalAccessException { + List result = new ArrayList<>(); + Field[] fields = object.getClass().getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + ThingsModelSimpleItem item = new ThingsModelSimpleItem(); + item.setId(field.getName()); + item.setValue(field.get(object)+""); + item.setTs(DateUtils.getNowDate()); + result.add(item); + } + return result; + } + + /** + * map转对象 + * @param map + * @param beanClass + * @param + * @return + * @throws Exception + */ + public static T mapToBean(Map map, Class beanClass) throws Exception { + T object = beanClass.newInstance(); + Field[] fields = object.getClass().getDeclaredFields(); + for (Field field : fields) { + int mod = field.getModifiers(); + if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) { + continue; + } + field.setAccessible(true); + if (map.containsKey(field.getName())) { + field.set(object, map.get(field.getName())); + } + } + return object; + } +} \ No newline at end of file diff --git a/maibu-common/src/main/java/com/maibu/utils/BitUtils.java b/maibu-common/src/main/java/com/maibu/utils/BitUtils.java new file mode 100644 index 0000000..7f55715 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/BitUtils.java @@ -0,0 +1,276 @@ +package com.maibu.utils; + +import java.util.Arrays; + +/** + * + * @description 位运算工具 + * 用途:将二进制数中的每位数字1或0代表着某种开关标记,1为是,0为否,则一个数字可以代表N位的开关标记值,可有效减少过多的变量定义 或 过多的表字段 + */ +public class BitUtils { + + /** + * 获取二进制数字中指定位数的结果,如:1011,指定第2位,则结果是0,第3位,则结果是1 + * + * @param num 二进制数(可以十进制数传入,也可使用0b开头的二进制数表示形式) + * @param bit 位数(第几位,从右往左,从0开始) + * @return + */ + public static int getBitFlag(long num, int bit) { + return (int) num >> bit & 1; + } + + /** + * 更新二进制数字中指定位的值 + * + * @param num 二进制数(可以十进制数传入,也可使用0b开头的二进制数表示形式) + * @param bit 位数(第几位,从右往左,从0开始) + * @param flagValue 位标记值(true=1,false=0) + * @return + */ + public static long updateBitValue(long num, int bit, boolean flagValue) { + if (flagValue) { + //将某位由0改为1 + return num | (1 << bit); + } else { + //将某位由1改为0 + return num ^ (getBitFlag(num, bit) << bit); + } + } + + /** + * 将数字转换为二制值形式字符串 + * + * @param num + * @return + */ + public static String toBinaryString(long num) { + return Long.toBinaryString(num); + } + + /** + * 判断10进制数,某位是0还是1 + * @param num + * @param i + * @return + */ + public static int deter(int num, int i) { + // 先将数字右移指定第i位,然后再用&与1运算 + return num >> (i-1) & 1; + } + + public static String bin2hex(String input) { + StringBuilder sb = new StringBuilder(); + int len = input.length(); + System.out.println("原数据长度:" + (len / 8) + "字节"); + + for (int i = 0; i < len / 4; i++){ + //每4个二进制位转换为1个十六进制位 + String temp = input.substring(i * 4, (i + 1) * 4); + int tempInt = Integer.parseInt(temp, 2); + String tempHex = Integer.toHexString(tempInt).toUpperCase(); + sb.append(tempHex); + } + + return sb.toString(); + } + public static int bin2Dec(String binaryString){ + int sum = 0; + for(int i = 0;i < binaryString.length();i++){ + char ch = binaryString.charAt(i); + if(ch > '2' || ch < '0') + throw new NumberFormatException(String.valueOf(i)); + sum = sum * 2 + (binaryString.charAt(i) - '0'); + } + return sum; + } + + public static int[] string2Ins(String input) { + StringBuilder in = new StringBuilder(input); + int remainder = in.length() % 8; + if (remainder > 0) + for (int i = 0; i < 8 - remainder; i++) + in.append("0"); + int[] result = new int[in.length() /8]; + + // Step 8 Apply compression + for (int i = 0; i < result.length; i++) + result[i] = Integer.parseInt(in.substring(i * 8, i * 8 + 8), 2); + + return result; + } + public static byte[] string2bytes(String input) { + StringBuilder in = new StringBuilder(input); + int remainder = in.length() % 8; + if (remainder > 0) + for (int i = 0; i < 8 - remainder; i++) + in.insert(0,"0"); + byte[] bts = new byte[in.length() / 8]; + + // Step 8 Apply compression + for (int i = 0; i < bts.length; i++) + bts[i] = (byte) Integer.parseInt(in.substring(i * 8, i * 8 + 8), 2); + + return bts; + } + + /** + * 取得十制数组的from~to位,并按照十六进制转化值 + * + * @param data + * @param from + * @param to + * @return + */ + private static String getOctFromHexBytes(byte[] data, Object from, Object... to) { + if (data != null && data.length > 0 && from != null) { + try { + byte[] value; + int fromIndex = Integer.parseInt(from.toString()); + if (to != null && to.length > 0) { + int toIndex = Integer.parseInt(to[0].toString()); + if (fromIndex >= toIndex || toIndex <= 0) { + value = Arrays.copyOfRange(data, fromIndex, fromIndex + 1); + } else { + value = Arrays.copyOfRange(data, fromIndex, toIndex + 1); + } + } else { + value = Arrays.copyOfRange(data, fromIndex, fromIndex + 1); + } + if (value != null && value.length > 0) { + long octValue = 0L; + int j = -1; + for (int i = value.length - 1; i >= 0; i--, j++) { + int d = value[i]; + if (d < 0) { + d += 256; + } + octValue += Math.round(d * Math.pow(16, 2 * j + 2)); + } + return new Long(octValue).toString(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return null; + } + + /** + * 十进制的字符串表示转成字节数组 + * + * @param octString + * 十进制格式的字符串 + * @param capacity + * 需要填充的容量(可选) + * @return 转换后的字节数组 + **/ + private static byte[] octInt2ByteArray(Integer oct, int... capacity) { + return hexString2ByteArray(Integer.toHexString(oct), capacity); + } + /** + * 16进制的字符串表示转成字节数组 + * + * @param hexString + * 16进制格式的字符串 + * @param capacity + * 需要填充的容量(可选) + * @return 转换后的字节数组 + **/ + private static byte[] hexString2ByteArray(String hexString, int... capacity) { + hexString = hexString.toLowerCase(); + if (hexString.length() % 2 != 0) { + hexString = "0" + hexString; + } + int length = hexString.length() / 2; + if (length < 1) { + length = 1; + } + int size = length; + if (capacity != null && capacity.length > 0 && capacity[0] >= length) { + size = capacity[0]; + } + final byte[] byteArray = new byte[size]; + int k = 0; + for (int i = 0; i < size; i++) { + if (i < size - length) { + byteArray[i] = 0; + } else { + byte high = (byte) (Character.digit(hexString.charAt(k), 16) & 0xff); + if (k + 1 < hexString.length()) { + byte low = (byte) (Character.digit(hexString.charAt(k + 1), 16) & 0xff); + byteArray[i] = (byte) (high << 4 | low); + } else { + byteArray[i] = (byte) (high); + } + k += 2; + } + } + return byteArray; + + } + + /** + * 连接字节流 + * + * @return + */ + private static byte[] append(byte[] datas, byte[] data) { + if (datas == null) { + return data; + } + if (data == null) { + return datas; + } else { + return concat(datas, data); + } + } + + /** + * 字节流拼接 + * + * @param data + * 字节流 + * @return 拼接后的字节数组 + **/ + private static byte[] concat(byte[]... data) { + if (data != null && data.length > 0) { + int size = 0; + for (int i = 0; i < data.length; i++) { + size += data[i].length; + } + byte[] byteArray = new byte[size]; + int pos = 0; + for (int i = 0; i < data.length; i++) { + byte[] b = data[i]; + for (int j = 0; j < b.length; j++) { + byteArray[pos++] = b[j]; + } + } + return byteArray; + } + return null; + } + + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } + + + + + public static void main(String[] args) { + String s = bin2hex("1111111000000000"); + int i = bin2Dec("1111111000000000"); + byte[] ints = string2bytes("111111000000000"); + System.out.println(s); + System.out.println(i); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/CaculateUtils.java b/maibu-common/src/main/java/com/maibu/utils/CaculateUtils.java new file mode 100644 index 0000000..9a75f4c --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/CaculateUtils.java @@ -0,0 +1,433 @@ +package com.maibu.utils; + +import com.maibu.enums.ModbusDataType; +import com.maibu.exception.ServiceException; +import io.netty.buffer.ByteBufUtil; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 字符串公式计算工具 + */ +public class CaculateUtils { + + /** + * /* + * 暂时只支持加减乘除及括号的应用 + */ + private static final String symbol = "+-,*/,(),%"; + + + /** + * 公式计算 字符串 + * + * @param exeStr + */ + public static BigDecimal execute(String exeStr, Map replaceMap) { + //替换掉占位符 + exeStr = caculateReplace(exeStr, replaceMap); + exeStr = exeStr.replaceAll("\\s*", ""); + List suffixList = suffixHandle(exeStr); + return caculateAnalyse(suffixList); + + } + + /** + * 公式计算 后序list + * + * @param suffixList + * @return + */ + public static BigDecimal caculateAnalyse(List suffixList) { + + BigDecimal a = BigDecimal.ZERO; + BigDecimal b = BigDecimal.ZERO; + // 构建一个操作数栈 每当获得操作符号时取出最上面两个数进行计算。 + Stack caculateStack = new Stack(); + if (suffixList.size() > 1) { + + for (int i = 0; i < suffixList.size(); i++) { + String temp = suffixList.get(i); + if (symbol.contains(temp)) { + b = caculateStack.pop(); + a = caculateStack.pop(); + a = caculate(a, b, temp.toCharArray()[0]); + caculateStack.push(a); + } else { + if (isNumber(suffixList.get(i))) { + caculateStack.push(new BigDecimal(suffixList.get(i))); + } else { + throw new RuntimeException("公式异常!"); + } + } + } + } else if (suffixList.size() == 1) { + String temp = suffixList.get(0); + if (isNumber(temp)) { + a = BigDecimal.valueOf(Double.parseDouble(temp)); + } else { + throw new RuntimeException("公式异常!"); + } + } + return a; + } + + + /** + * 计算 使用double 进行计算 如果需要可以在这里使用bigdecimal 进行计算 + * + * @param a + * @param b + * @param symbol + * @return + */ + public static BigDecimal caculate(BigDecimal a, BigDecimal b, char symbol) { + switch (symbol) { + case '+': { + return a.add(b).stripTrailingZeros(); + } + case '-': + return a.subtract(b).stripTrailingZeros(); + case '*': + return a.multiply(b); + case '/': + return a.divide(b); + case '%': + // 取余,如果不包含小数点,下面length处理会报错,这里做个处理 + if (!b.toString().contains(".0")) { + b = new BigDecimal(b + ".0"); + } + int length = b.toString().split("\\.")[1].length(); + return a.divide(b, length, BigDecimal.ROUND_HALF_UP); + default: + throw new RuntimeException("操作符号异常!"); + } + + } + + /** + * 字符串直接 转 后序 + */ + public static List suffixHandle(String exeStr) { + StringBuilder buf = new StringBuilder(); + Stack stack = new Stack(); + char[] exeChars = exeStr.toCharArray(); + List res = new ArrayList(); + for (char x : exeChars) { + // 判断是不是操作符号 + if (symbol.indexOf(x) > -1) { + // 不管怎样先将数据添加进列表 + if (buf.length() > 0) { + // 添加数据到res + String temp = buf.toString(); + // 验证是否为数 + if (!isNumber(temp)) throw new RuntimeException(buf.append(" 格式不对").toString()); + + // 添加到结果列表中 + res.add(temp); + // 清空临时buf + buf.delete(0, buf.length()); + } + if (stack.size() > 0) { + + //2.判断是不是开是括号 + if (x == '(') { + stack.push(x); + continue; + } + //3.判断是不是闭合括号 + if (x == ')') { + while (stack.size() > 0) { + char con = (char) stack.peek(); + if (con == '(') { + stack.pop(); + continue; + } else { + res.add(String.valueOf(stack.pop())); + } + } + continue; + } + // 取出最后最近的一个操作符 + char last = (char) stack.peek(); + if (compare(x, last) > 0) { + stack.push(x); + } else if (compare(x, last) <= 0) { + if (last != '(') { + res.add(String.valueOf(stack.pop())); + } + stack.push(x); + } + } else { + stack.push(x); + } + } else { + buf.append(x); + } + } + if (buf.length() > 0) res.add(buf.toString()); + while (stack.size() > 0) { + res.add(String.valueOf(stack.pop())); + } + return res; + + } + + + /** + * 比较两个操作符号的优先级 + * + * @param a + * @param b + * @return + */ + public static int compare(char a, char b) { + if (symbol.indexOf(a) - symbol.indexOf(b) > 1) { + return 1; + } else if (symbol.indexOf(a) - symbol.indexOf(b) < -1) { + return -1; + } else { + return 0; + } + } + + + /** + * 判断是否为数 字符串 + * + * @param str + * @return + */ + public static boolean isNumber(String str) { + Pattern pattern = Pattern.compile("[0-9]+\\.{0,1}[0-9]*"); + Matcher isNum = pattern.matcher(str); + return isNum.matches(); + } + + public static String caculateReplace(String str, Map map) { + for (Map.Entry entry : map.entrySet()) { + str = str.replaceAll(entry.getKey(), entry.getValue()==null ? "1" : entry.getValue()); + } + return str; + } + + public static String toFloat(byte[] bytes) throws IOException { + ByteArrayInputStream mByteArrayInputStream = new ByteArrayInputStream(bytes); + DataInputStream mDataInputStream = new DataInputStream(mByteArrayInputStream); + try { + float v = mDataInputStream.readFloat(); + return String.format("%.6f",v); + }catch (Exception e){ + throw new ServiceException("modbus16转浮点数错误"); + } + finally { + mDataInputStream.close(); + mByteArrayInputStream.close(); + } + } + + public static String handleToUnSign16(String value,String dataType){ + long l = Long.parseLong(value); + if (dataType.equals(ModbusDataType.U_SHORT.getType())){ + return toUnSign16(l); + }else { + return value; + } + } + + /** + * 转16位无符号整形 + * @param value + * @return + */ + public static String toUnSign16(long value) { + long unSigned = value & 0xFFFF; + return unSigned +""; // 将字节数组转换为十六进制字符串 + } + + /** + * 32位有符号CDAB数据类型 + * @param value + * @return + */ + public static String toSign32_CDAB(long value) { + byte[] bytes = intToBytes2((int) value); + return bytesToInt2(bytes)+""; + } + + /** + * 32位无符号ABCD数据类型 + * @param value + * @return + */ + public static String toUnSign32_ABCD(long value) { + return Integer.toUnsignedString((int) value); + } + + /** + * 32位无符号CDAB数据类型 + * @param value + * @return + */ + public static String toUnSign32_CDAB(long value) { + byte[] bytes = intToBytes2((int) value); + int val = bytesToInt2(bytes); + return Integer.toUnsignedString(val); + } + + /** + * 转32位浮点数 ABCD + * @param bytes + * @return + */ + public static float toFloat32_ABCD(byte[] bytes) { + int intValue = (bytes[0] << 24) | ((bytes[1] & 0xFF) << 16) | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); + return Float.intBitsToFloat(intValue); + } + + /** + * 转32位浮点数 CDAB + * @param bytes + * @return + */ + public static Float toFloat32_CDAB(byte[] bytes) { + int intValue = ((bytes[2] & 0xFF) << 24) | ((bytes[3] & 0xFF) << 16) | ((bytes[0] & 0xFF) << 8) | ((bytes[1] & 0xFF)) ; + return Float.intBitsToFloat(intValue); + } + + /** + * MODBUS数据类型转换 + * + * @param dataType 数据类型 + * @param hexString + * @return + */ + public static String parseValue(String dataType, String hexString) { + String value = ""; + Long val = Long.parseLong(hexString, 16); + byte[] bytes = ByteBufUtil.decodeHexDump(hexString); + if (StringUtils.isNotEmpty(dataType)) { + ModbusDataType type = ModbusDataType.convert(dataType); + switch (type) { + case U_SHORT: + value = CaculateUtils.toUnSign16(val); + break; + case SHORT: + case LONG_ABCD: + value = val + ""; + break; + case LONG_CDAB: + value = CaculateUtils.toSign32_CDAB(val); + break; + case U_LONG_ABCD: + value = CaculateUtils.toUnSign32_ABCD(val); + break; + case U_LONG_CDAB: + value = CaculateUtils.toUnSign32_CDAB(val); + break; + case FLOAT_ABCD: + value = CaculateUtils.toFloat32_ABCD(bytes) + ""; + break; + case FLOAT_CDAB: + value = CaculateUtils.toFloat32_CDAB(bytes) + ""; + break; + } + } + return value; + } + + + /** + * byte数组中取int数值,本方法适用于(低位在后,高位在前)的顺序。和intToBytes2()配套使用 + */ + public static int bytesToInt2(byte[] src) { + return (((src[2] & 0xFF) << 24) | ((src[3] & 0xFF) << 16) | ((src[0] & 0xFF) << 8) | (src[1] & 0xFF)); + } + + /** + * 将int数值转换为占四个字节的byte数组,本方法适用于(高位在前,低位在后)的顺序。 和bytesToInt2()配套使用 + */ + public static byte[] intToBytes2(int value) { + byte[] src = new byte[4]; + src[0] = (byte) ((value >> 24) & 0xFF); + src[1] = (byte) ((value >> 16) & 0xFF); + src[2] = (byte) ((value >> 8) & 0xFF); + src[3] = (byte) (value & 0xFF); + return src; + } + + public static String subHexValue(String hexString){ + //截取报文中的值 + String substring = hexString.substring(4, 6); + int index = Integer.parseInt(substring); + return hexString.substring(6, 6 + index*2); + } + + + public static void main(String[] args) throws IOException { + Map replaceMap = new HashMap<>(); + replaceMap.put("A", "1.5"); + replaceMap.put("B", "2.5"); + replaceMap.put("C", "3.5"); + replaceMap.put("D", "4.5"); + replaceMap.put("E", "10"); +// replaceMap.put("%s", "1"); +// replaceMap.put("%s1", "5"); + BigDecimal execute = execute("A + B * (C - D) % E", replaceMap); + System.out.println(execute); + + + Map map = new HashMap<>(); + map.put("%s", "10"); + String caculate = caculateReplace("%s*2", map); + System.out.println(caculate); + System.out.println(execute("%s%3.00",map)); + + String s4 = toUnSign16(-1); + System.out.println("转16位无符号:"+s4); + + String s1 = toSign32_CDAB(40100); + System.out.println("转32位有符号-CDAB序"+s1); + + String s2 = toUnSign32_ABCD(-10); + System.out.println("转32位无符号-ABCD序:"+s2); + + String s3 = toUnSign32_CDAB(123456789); + System.out.println("转32位无符号-CDAB序:"+s3); + + String hexToBytes = "3fea3d71"; + byte[] bytes = ByteBufUtil.decodeHexDump(hexToBytes); + + float v1 = toFloat32_ABCD(bytes); + System.out.println("转32位浮点型-ABCD序:"+v1); + + String hexToBytes1= "800041EE"; + long i = Long.parseLong(hexToBytes1, 16); + System.out.println(i); + byte[] bytes1 = ByteBufUtil.decodeHexDump(hexToBytes1); + + float v2 = toFloat32_CDAB(bytes1); + System.out.println("转32位浮点型-CDAB序:"+v2); + + int signedShort = -32627; // 16位有符号整形 + // 将有符号短整型转换为无符号短整型 + int unSignedInt = signedShort & 0xFFFF; + // 输出结果 + System.out.println(unSignedInt); // 输出: 0 + + long l = Long.parseLong("00501F40", 16); + System.out.println(l); + + int val1 = -6553510; + byte[] bytes2 = intToBytes2(val1); + int i1 = bytesToInt2(bytes2); + System.out.println(i1); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/CaculateVariableAndNumberUtils.java b/maibu-common/src/main/java/com/maibu/utils/CaculateVariableAndNumberUtils.java new file mode 100644 index 0000000..c59d3f2 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/CaculateVariableAndNumberUtils.java @@ -0,0 +1,413 @@ +package com.maibu.utils; + + +import com.maibu.exception.ServiceException; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 字符串公式计算工具 + */ +public class CaculateVariableAndNumberUtils { + + /** + * /* + * 暂时只支持加减乘除及括号的应用 + */ + private static final String symbol = "+-,*/,(),%"; + + private static final Map symbol_map = new HashMap(){{ + put("*", 1); + put("/", 1); + put("%", 1); + put("+", 2); + put("-", 2); + put("(", 3); + put(")", 3); + }}; + + + /** + * 公式计算 字符串 + * + * @param exeStr + */ + public static BigDecimal execute(String exeStr, Map replaceMap) { + List list = suffixHandle(exeStr); + System.out.println("计算结果: " + list); + List list1 = new ArrayList<>(); + for (String s : list) { + String o = replaceMap.get(s); + if (StringUtils.isNotEmpty(o)) { + list1.add(o); + } else { + list1.add(s); + } + } + return caculateAnalyse(list1); + + } + + /** + * 公式计算 后序list + * + * @param suffixList + * @return + */ + public static BigDecimal caculateAnalyse(List suffixList) { + + BigDecimal a = BigDecimal.ZERO; + BigDecimal b = BigDecimal.ZERO; + // 构建一个操作数栈 每当获得操作符号时取出最上面两个数进行计算。 + Stack caculateStack = new Stack(); + if (suffixList.size() > 1) { + + for (int i = 0; i < suffixList.size(); i++) { + String temp = suffixList.get(i); + if (symbol.contains(temp)) { + b = caculateStack.pop(); + a = caculateStack.pop(); + a = caculate(a, b, temp.toCharArray()[0]); + caculateStack.push(a); + } else { + if (isNumber(suffixList.get(i))) { + caculateStack.push(new BigDecimal(suffixList.get(i))); + } else { + throw new RuntimeException("公式异常!"); + } + } + } + } else if (suffixList.size() == 1) { + String temp = suffixList.get(0); + if (isNumber(temp)) { + a = BigDecimal.valueOf(Double.parseDouble(temp)); + } else { + throw new RuntimeException("公式异常!"); + } + } + return a; + } + + + /** + * 计算 使用double 进行计算 如果需要可以在这里使用bigdecimal 进行计算 + * + * @param a + * @param b + * @param symbol + * @return + */ + public static BigDecimal caculate(BigDecimal a, BigDecimal b, char symbol) { + switch (symbol) { + case '+': { + return a.add(b).stripTrailingZeros(); + } + case '-': + return a.subtract(b).stripTrailingZeros(); + case '*': + return a.multiply(b); + case '/': + case '%': + int length1 = getDivideLength(a, b); + return a.divide(b, length1, BigDecimal.ROUND_HALF_UP); + default: + throw new RuntimeException("操作符号异常!"); + } + + } + + private static int getDivideLength(BigDecimal a, BigDecimal b) { + String s1 = a.toString(); + String s2 = b.toString(); + int length1 = 0; + int length2 = 0; + if (s1.contains(".")) { + length1 = s1.split("\\.")[1].length(); + } + if (s2.contains(".")) { + length2 = s2.split("\\.")[1].length(); + } + if (length1 == 0 && length2 == 0) { + return 2; + } else { + return Math.max(length1, length2); + } + } + + /** + * 字符串直接 转 后序 + */ + public static List suffixHandle(String exeStr) { + StringBuilder buf = new StringBuilder(); + Stack stack = new Stack(); + char[] exeChars = exeStr.toCharArray(); + List res = new ArrayList<>(); + for (char x : exeChars) { + // 判断是不是操作符号 + if (symbol.indexOf(x) > -1) { + // 不管怎样先将数据添加进列表 + if (buf.length() > 0) { + // 添加数据到res + String temp = buf.toString(); + // 验证是否为变量或数字 + if (!isVariableAndNumber(temp)) { + throw new RuntimeException(buf.append(" 格式不对").toString()); + } + + // 添加到结果列表中 + res.add(temp); + // 清空临时buf + buf.delete(0, buf.length()); + } + if (!stack.isEmpty()) { + + //2.判断是不是开是括号 + if (x == '(') { + stack.push(x); + continue; + } + //3.判断是不是闭合括号 + if (x == ')') { + boolean a = false; + while (!stack.isEmpty()) { + char con = (char) stack.peek(); + if (con == '(' && !a) { + stack.pop(); + a = true; + } else if (!a) { + res.add(String.valueOf(stack.pop())); + } else { + break; + } + } + continue; + } + + // 遵循四则运算法则 + int size = stack.size(); + while (size > 0) { + char con = (char) stack.peek(); + if (compare(con, x) > 0) { + res.add(String.valueOf(stack.pop())); + } + size--; + } + stack.push(x); + + } else { + stack.push(x); + } + } else { + buf.append(x); + } + } + if (buf.length() > 0) { + res.add(buf.toString()); + } + while (!stack.isEmpty()) { + res.add(String.valueOf(stack.pop())); + } + return res; + + } + + /** + * 比较两个操作符号的优先级 + * + * @param a + * @param b + * @return + */ + public static int compare(char a, char b) { + String s1 = String.valueOf(a); + String s2 = String.valueOf(b); + Integer ai = symbol_map.get(s1); + Integer bi = symbol_map.get(s2); + if (null != ai && null != bi) { + if (ai <= bi) { + return 1; + } else { + return -1; + } + } else { + return 0; + } + } + + + /** + * 判断是否为数 字符串 + * + * @param str + * @return + */ + public static boolean isNumber(String str) { + Pattern pattern = Pattern.compile("[-+]?\\d+(?:\\.\\d+)?"); + Matcher isNum = pattern.matcher(str); + return isNum.matches(); + } + + /** + * 判断是否为数 字符串 + * + * @param str + * @return + */ + public static boolean isVariable(String str) { + Pattern pattern = Pattern.compile("^[A-Z]+$"); + Matcher isNum = pattern.matcher(str); + return isNum.matches(); + } + + /** + * 判断是否为数 字符串 + * + * @param str + * @return + */ + public static boolean isVariableAndNumber(String str) { + Pattern pattern = Pattern.compile("[A-Z]|-?\\d+(\\.\\d+)?"); + Matcher isNum = pattern.matcher(str); + return isNum.matches(); + } + + public static String caculateReplace(String str, Map map) { + for (Map.Entry entry : map.entrySet()) { + str = str.replaceAll(entry.getKey(), entry.getValue()==null ? "1" : entry.getValue()); + } + return str; + } + + public static String toFloat(byte[] bytes) throws IOException { + ByteArrayInputStream mByteArrayInputStream = new ByteArrayInputStream(bytes); + DataInputStream mDataInputStream = new DataInputStream(mByteArrayInputStream); + try { + float v = mDataInputStream.readFloat(); + return String.format("%.6f",v); + }catch (Exception e){ + throw new ServiceException("modbus16转浮点数错误"); + } + finally { + mDataInputStream.close(); + mByteArrayInputStream.close(); + } + } + + /** + * 转16位无符号整形 + * @param value + * @return + */ + public static String toUnSign16(long value) { + long unSigned = value & 0xFFFF; + return unSigned +""; // 将字节数组转换为十六进制字符串 + } + + /** + * 32位有符号CDAB数据类型 + * @param value + * @return + */ + public static String toSign32_CDAB(long value) { + byte[] bytes = intToBytes2((int) value); + return bytesToInt2(bytes)+""; + } + + /** + * 32位无符号ABCD数据类型 + * @param value + * @return + */ + public static String toUnSign32_ABCD(long value) { + return Integer.toUnsignedString((int) value); + } + + /** + * 32位无符号CDAB数据类型 + * @param value + * @return + */ + public static String toUnSign32_CDAB(long value) { + byte[] bytes = intToBytes2((int) value); + int val = bytesToInt2(bytes); + return Integer.toUnsignedString(val); + } + + /** + * 转32位浮点数 ABCD + * @param bytes + * @return + */ + public static float toFloat32_ABCD(byte[] bytes) { + int intValue = (bytes[0] << 24) | ((bytes[1] & 0xFF) << 16) | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); + return Float.intBitsToFloat(intValue); + } + + /** + * 转32位浮点数 CDAB + * @param bytes + * @return + */ + public static Float toFloat32_CDAB(byte[] bytes) { + int intValue = ((bytes[2] & 0xFF) << 24) | ((bytes[3] & 0xFF) << 16) | ((bytes[0] & 0xFF) << 8) | ((bytes[1] & 0xFF)) ; + return Float.intBitsToFloat(intValue); + } + + /** + * byte数组中取int数值,本方法适用于(低位在后,高位在前)的顺序。和intToBytes2()配套使用 + */ + public static int bytesToInt2(byte[] src) { + return (((src[2] & 0xFF) << 24) | ((src[3] & 0xFF) << 16) | ((src[0] & 0xFF) << 8) | (src[1] & 0xFF)); + } + + /** + * 将int数值转换为占四个字节的byte数组,本方法适用于(高位在前,低位在后)的顺序。 和bytesToInt2()配套使用 + */ + public static byte[] intToBytes2(int value) { + byte[] src = new byte[4]; + src[0] = (byte) ((value >> 24) & 0xFF); + src[1] = (byte) ((value >> 16) & 0xFF); + src[2] = (byte) ((value >> 8) & 0xFF); + src[3] = (byte) (value & 0xFF); + return src; + } + + public static String subHexValue(String hexString){ + //截取报文中的值 + String substring = hexString.substring(4, 6); + int index = Integer.parseInt(substring); + return hexString.substring(6, 6 + index*2); + } + + + public static void main(String[] args) throws IOException { + String s1 = "A/B*C"; // 1.5 + String s2 = "E-((A+B)-(C+D))%10"; // 10.4 + String s3 = "A-B-C*(D-E)+10*5"; // 67 + String s4 = "A-B-C*(D+E)-(A+B)+(2+3)"; // -41 + String s5 = "A-(A-(B-C)*(D+E))%10+B"; // 1.5 + String s6 = "A-(B+C)*D+10"; // -9 + String s7 = "1+2*3-2+2*(1-2+3*4+5-6/2+(2-1)+3*4-2)%10"; // 9.8 + + + boolean number = isNumber("-10"); + System.out.println(number); + + Map replaceMap = new HashMap<>(); + replaceMap.put("A", "1"); + replaceMap.put("B", "2"); + replaceMap.put("C", "3"); + replaceMap.put("D", "4"); + replaceMap.put("E", "10"); + BigDecimal execute = execute(s7, replaceMap); + System.out.println(execute); + + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/DateUtils.java b/maibu-common/src/main/java/com/maibu/utils/DateUtils.java new file mode 100644 index 0000000..55739f0 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/DateUtils.java @@ -0,0 +1,259 @@ +package com.maibu.utils; + +import org.apache.commons.lang3.time.DateFormatUtils; + +import java.lang.management.ManagementFactory; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.*; +import java.util.Date; +import java.util.Random; + +/** + * 时间工具类 + * + * @author ruoyi + */ +public class DateUtils extends org.apache.commons.lang3.time.DateUtils +{ + public static String YYYY = "yyyy"; + + public static String YYYY_MM = "yyyy-MM"; + + public static String YYYY_MM_DD = "yyyy-MM-dd"; + + public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; + + public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + public static String SS_MM_HH_DD_HH_YY = "ssmmHHddMMyy"; + + public static String YY_MM_DD_HH_MM_SS = "yy-MM-dd HH:mm:ss"; + + private static String[] parsePatterns = { + "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", + "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", + "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; + + public static String YYYY_MM_DD_HH_MM_SS_SSS = "yyyy-MM-dd HH:mm:ss.SSS"; + + /** + * 获取当前Date型日期 + * + * @return Date() 当前日期 + */ + public static Date getNowDate() + { + return new Date(); + } + + /** + * 获取当前日期, 默认格式为yyyy-MM-dd + * + * @return String + */ + public static String getDate() + { + return dateTimeNow(YYYY_MM_DD); + } + + public static final String getTime() + { + return dateTimeNow(YYYY_MM_DD_HH_MM_SS); + } + + public static final String dateTimeNow() + { + return dateTimeNow(YYYYMMDDHHMMSS); + } + + public static final String dateTimeNow(final String format) + { + return parseDateToStr(format, new Date()); + } + + public static final String dateTime(final Date date) + { + return parseDateToStr(YYYY_MM_DD, date); + } + + public static final String parseDateToStr(final String format, final Date date) + { + return new SimpleDateFormat(format).format(date); + } + + public static final Date dateTime(final String format, final String ts) + { + try + { + return new SimpleDateFormat(format).parse(ts); + } + catch (ParseException e) + { + throw new RuntimeException(e); + } + } + + /** + * 日期路径 即年/月/日 如2018/08/08 + */ + public static final String datePath() + { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyy/MM/dd"); + } + + /** + * 日期路径 即年/月/日 如20180808 + */ + public static final String dateTime() + { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyyMMdd"); + } + + + /** + * 日期路径 即年/月/日 如20180808 + */ + public static final String dateTimeYY(Date date) + { + return DateFormatUtils.format(date, YY_MM_DD_HH_MM_SS); + } + + /** + * 日期型字符串转化为日期 格式 + */ + public static Date parseDate(Object str) + { + if (str == null) + { + return null; + } + try + { + return parseDate(str.toString(), parsePatterns); + } + catch (ParseException e) + { + return null; + } + } + + /** + * 获取服务器启动时间 + */ + public static Date getServerStartDate() + { + long time = ManagementFactory.getRuntimeMXBean().getStartTime(); + return new Date(time); + } + + /** + * 计算相差天数 + */ + public static int differentDaysByMillisecond(Date date1, Date date2) + { + return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); + } + + /** + * 计算相差秒数 + */ + public static int differentSeconds(Date date1, Date date2) + { + return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000))); + } + + /** + * 计算两个时间差 + */ + public static String getDatePoor(Date endDate, Date nowDate) + { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + // long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - nowDate.getTime(); + // 计算差多少天 + long day = diff / nd; + // 计算差多少小时 + long hour = diff % nd / nh; + // 计算差多少分钟 + long min = diff % nd % nh / nm; + // 计算差多少秒//输出结果 + // long sec = diff % nd % nh % nm / ns; + return day + "天" + hour + "小时" + min + "分钟"; + } + + /** + * 增加 LocalDateTime ==> Date + */ + public static Date toDate(LocalDateTime temporalAccessor) + { + ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + /** + * 增加 LocalDate ==> Date + */ + public static Date toDate(LocalDate temporalAccessor) + { + LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); + ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + public static long getTimestamp(){ + return System.currentTimeMillis(); + } + + public static long getTimestampSeconds(){ + return System.currentTimeMillis()/1000; + } + + public static String generateRandomHex(int length) { + Random random = new Random(); + StringBuilder sb = new StringBuilder(length); + // 添加"D"作为开头 + sb.append("D"); + for (int i = 1; i < length; i++) { + int randomInt = random.nextInt(16); // 生成0到15的随机整数 + char hexChar = Character.toUpperCase(Character.forDigit(randomInt, 16)); // 将整数转换为十六进制字符并转为大写 + sb.append(hexChar); + } + return sb.toString(); + } + + public static void main(String[] args) { + Date date = DateUtils.dateTime(SS_MM_HH_DD_HH_YY, "434123181121"); + String s = DateUtils.dateTimeYY(date); + System.out.println(s); + + String s1 = generateRandomHex(12); + System.out.println(s1); + + } + + /** + * 字符串去除毫秒 + * @param time 时间字符串 + * @return java.lang.String + */ + public static String strRemoveMs(String time) { + Date date = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS_SSS, time); + return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, date); + } + + /** + * 日期去除毫秒 + * @param time 时间 + * @return java.util.Date + */ + public static Date dateRemoveMs(Date time) { + String s = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, time); + return DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, s); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/DictUtils.java b/maibu-common/src/main/java/com/maibu/utils/DictUtils.java new file mode 100644 index 0000000..39c731f --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/DictUtils.java @@ -0,0 +1,199 @@ +package com.maibu.utils; + +import com.alibaba.fastjson2.JSONArray; +import com.maibu.constant.CacheConstants; +import com.maibu.core.domain.entity.SysDictData; +import com.maibu.core.redis.RedisCache; +import com.maibu.utils.spring.SpringUtils; + +import java.util.Collection; +import java.util.List; + +import static com.maibu.constant.Constants.EN_US; +import static com.maibu.constant.Constants.ZH_CN; + + +/** + * 字典工具类 + * + * @author ruoyi + */ +public class DictUtils +{ + /** + * 分隔符 + */ + public static final String SEPARATOR = ","; + + /** + * 设置字典缓存 + * + * @param key 参数键 + * @param dictDatas 字典数据列表 + */ + public static void setDictCache(String key, List dictDatas) + { + SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas); + } + + /** + * 获取字典缓存 + * + * @param key 参数键 + * @return dictDatas 字典数据列表 + */ + public static List getDictCache(String key) + { + JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key)); + if (StringUtils.isNotNull(arrayCache)) + { + return arrayCache.toList(SysDictData.class); + } + return null; + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @param language 语言 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue, String language) + { + return getDictLabel(dictType, dictValue, SEPARATOR, language); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel) + { + return getDictValue(dictType, dictLabel, SEPARATOR); + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @param separator 分隔符 + * @param language 语言 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue, String separator, String language) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + + if (StringUtils.isNotNull(datas)) + { + if (StringUtils.containsAny(separator, dictValue)) + { + for (SysDictData dict : datas) + { + for (String value : dictValue.split(separator)) + { + if (value.equals(dict.getDictValue())) + { + propertyString.append(dict.getDictLabel()).append(separator); + break; + } + } + } + } + else + { + for (SysDictData dict : datas) + { + if (dictValue.equals(dict.getDictValue())) + { + if (ZH_CN.equals(language)) { + return dict.getDictLabel_zh_CN(); + } else if (EN_US.equals(language)) { + return dict.getDictLabel_en_US(); + } else { + return dict.getDictLabel(); + } + } + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @param separator 分隔符 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel, String separator) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + + if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas)) + { + for (SysDictData dict : datas) + { + for (String label : dictLabel.split(separator)) + { + if (label.equals(dict.getDictLabel())) + { + propertyString.append(dict.getDictValue()).append(separator); + break; + } + } + } + } + else + { + for (SysDictData dict : datas) + { + if (dictLabel.equals(dict.getDictLabel())) + { + return dict.getDictValue(); + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 删除指定字典缓存 + * + * @param key 字典键 + */ + public static void removeDictCache(String key) + { + SpringUtils.getBean(RedisCache.class).deleteObject(getCacheKey(key)); + } + + /** + * 清空字典缓存 + */ + public static void clearDictCache() + { + Collection keys = SpringUtils.getBean(RedisCache.class).keys(CacheConstants.SYS_DICT_KEY + "*"); + SpringUtils.getBean(RedisCache.class).deleteObject(keys); + } + + /** + * 设置cache key + * + * @param configKey 参数键 + * @return 缓存键key + */ + public static String getCacheKey(String configKey) + { + return CacheConstants.SYS_DICT_KEY + configKey; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/DigestUtils.java b/maibu-common/src/main/java/com/maibu/utils/DigestUtils.java new file mode 100644 index 0000000..6d1cbcd --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/DigestUtils.java @@ -0,0 +1,75 @@ +package com.maibu.utils; + +import com.maibu.utils.uuid.IdUtils; +import lombok.NoArgsConstructor; +import org.apache.commons.lang3.Validate; + +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.SecureRandom; + +@NoArgsConstructor +public class DigestUtils { + private static SecureRandom random = new SecureRandom(); + private static IdUtils idUtils = new IdUtils(0,0); + + public static String getId(){ + return String.valueOf(Math.abs(random.nextLong())); + } + + public static String nextId(){ + return String.valueOf(idUtils.nextId()); + } + + + public static byte[] genSalt(int numBytes) { + Validate.isTrue(numBytes > 0, "numBytes argument must be a positive integer (1 or larger)", (long)numBytes); + byte[] bytes = new byte[numBytes]; + random.nextBytes(bytes); + return bytes; + } + + public static byte[] digest(byte[] input, String algorithm, byte[] salt, int iterations) { + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + if(salt != null) { + digest.update(salt); + } + + byte[] result = digest.digest(input); + + for(int i = 1; i < iterations; ++i) { + digest.reset(); + result = digest.digest(result); + } + + return result; + } catch (GeneralSecurityException var7) { + throw ExceptionUtils.unchecked(var7); + } + } + + public static byte[] digest(InputStream input, String algorithm) throws IOException { + try { + MessageDigest messageDigest = MessageDigest.getInstance(algorithm); + int bufferLength = 8192; + byte[] buffer = new byte[bufferLength]; + + for(int read = input.read(buffer, 0, bufferLength); read > -1; read = input.read(buffer, 0, bufferLength)) { + messageDigest.update(buffer, 0, read); + } + + return messageDigest.digest(); + } catch (GeneralSecurityException var6) { + throw ExceptionUtils.unchecked(var6); + } + } + + public static void main(String[] args) { + for (int i = 0; i < 10; i++) { + System.out.println(nextId()); + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/EmqxUtils.java b/maibu-common/src/main/java/com/maibu/utils/EmqxUtils.java new file mode 100644 index 0000000..faae786 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/EmqxUtils.java @@ -0,0 +1,9 @@ +package com.maibu.utils; + +/** + * @author bill + */ +public class EmqxUtils { + + //获取 +} diff --git a/maibu-common/src/main/java/com/maibu/utils/EncodeUtils.java b/maibu-common/src/main/java/com/maibu/utils/EncodeUtils.java new file mode 100644 index 0000000..8738198 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/EncodeUtils.java @@ -0,0 +1,172 @@ +package com.maibu.utils; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.crypto.codec.Hex; +import org.springframework.web.multipart.MultipartFile; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.regex.Pattern; + +public class EncodeUtils { + + private static final Logger logger = LoggerFactory.getLogger(EncodeUtils.class); + private static final String DEFAULT_URL_ENCODING = "UTF-8"; + private static final char[] BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray(); + private static Pattern p1 = Pattern.compile("<\\s*(script|link|style|iframe)([\\s\\S]+?)<\\/\\s*\\1\\s*>", 2); + private static Pattern p2 = Pattern.compile("\\s*on[a-z]+\\s*=\\s*(\"[^\"]+\"|'[^']+'|[^\\s]+)\\s*(?=>)", 2); + private static Pattern p3 = Pattern.compile("\\s*(href|src)\\s*=\\s*(\"\\s*(javascript|vbscript):[^\"]+\"|'\\s*(javascript|vbscript):[^']+'|(javascript|vbscript):[^\\s]+)\\s*(?=>)", 2); + private static Pattern p4 = Pattern.compile("epression\\((.|\\n)*\\);?", 2); + private static Pattern p5 = Pattern.compile("(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|(\\b(select|update|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)\\b)", 2); + + public EncodeUtils() { + } + + public static String encodeHex(byte[] input) { + return new String(Hex.encode(input)); + } + + public static byte[] decodeHex(String input) { + try { + return Hex.decode(input); + } catch (Exception var2) { + throw ExceptionUtils.unchecked(var2); + } + } + + public static String encodeBase64(byte[] input) { + return new String(Base64.encodeBase64(input)); + } + + public static String encodeBase64(String input) { + try { + return new String(Base64.encodeBase64(input.getBytes("UTF-8"))); + } catch (UnsupportedEncodingException var2) { + return ""; + } + } + + public static byte[] decodeBase64(String input) { + return Base64.decodeBase64(input.getBytes()); + } + + public static String decodeBase64String(String input) { + try { + return new String(Base64.decodeBase64(input.getBytes()), "UTF-8"); + } catch (UnsupportedEncodingException var2) { + return ""; + } + } + + public static String encodeBase62(byte[] input) { + char[] chars = new char[input.length]; + + for(int i = 0; i < input.length; ++i) { + chars[i] = BASE62[(input[i] & 255) % BASE62.length]; + } + + return new String(chars); + } + + public static String encodeHtml(String html) { + return StringEscapeUtils.escapeHtml4(html); + } + + public static String decodeHtml(String htmlEscaped) { + return StringEscapeUtils.unescapeHtml4(htmlEscaped); + } + + public static String encodeXml(String xml) { + return StringEscapeUtils.escapeXml(xml); + } + + public static String decodeXml(String xmlEscaped) { + return StringEscapeUtils.unescapeXml(xmlEscaped); + } + + public static String encodeUrl(String part) { + return encodeUrl(part, "UTF-8"); + } + + public static String encodeUrl(String part, String encoding) { + if(part == null) { + return null; + } else { + try { + return URLEncoder.encode(part, encoding); + } catch (UnsupportedEncodingException var3) { + throw ExceptionUtils.unchecked(var3); + } + } + } + + public static String decodeUrl(String part) { + return decodeUrl(part, "UTF-8"); + } + + public static String decodeUrl(String part, String encoding) { + try { + return URLDecoder.decode(part, encoding); + } catch (UnsupportedEncodingException var3) { + throw ExceptionUtils.unchecked(var3); + } + } + + public static String decodeUrl2(String part) { + return decodeUrl(decodeUrl(part)); + } + + public static String xssFilter(String text) { + if(text == null) { + return null; + } else { + String oriValue = StringUtils.trim(text); + String value = p1.matcher(oriValue).replaceAll(""); + value = p2.matcher(value).replaceAll(""); + value = p3.matcher(value).replaceAll(""); + value = p4.matcher(value).replaceAll(""); + if(!StringUtils.startsWithIgnoreCase(value, "") && !StringUtils.startsWithIgnoreCase(value, "", ">"); + } + + if(logger.isInfoEnabled() && !value.equals(oriValue)) { + logger.info("xssFilter: {} to {}", text, value); + } + + return value; + } + } + + public static String sqlFilter(String text) { + if(text != null) { + String value = p5.matcher(text).replaceAll(""); + if(logger.isWarnEnabled() && !value.equals(text)) { + logger.warn("sqlFilter: {} to {}", text, value); + return ""; + } else { + return value; + } + } else { + return null; + } + } + + public static MultipartFile base64toMultipartFile(String base64) { + final String[] base64Array = base64.split(","); + String dataUir, data; + if (base64Array.length > 1) { + dataUir = base64Array[0]; + data = base64Array[1]; + } else { + //根据你base64代表的具体文件构建 + dataUir = "data:image/png;base64"; + data = base64Array[0]; + } + return new Base64ToMultipartFile(data, dataUir); + } +} + diff --git a/maibu-common/src/main/java/com/maibu/utils/ExceptionUtil.java b/maibu-common/src/main/java/com/maibu/utils/ExceptionUtil.java new file mode 100644 index 0000000..1e242bd --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/ExceptionUtil.java @@ -0,0 +1,40 @@ +package com.maibu.utils; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * 错误信息处理类。 + * + * @author ruoyi + */ +public class ExceptionUtil +{ + /** + * 获取exception的详细错误信息。 + */ + public static String getExceptionMessage(Throwable e) + { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw, true)); + return sw.toString(); + } + + public static String getRootErrorMessage(Exception e) + { + Throwable root = ExceptionUtils.getRootCause(e); + root = (root == null ? e : root); + if (root == null) + { + return ""; + } + String msg = root.getMessage(); + if (msg == null) + { + return "null"; + } + return StringUtils.defaultString(msg); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/ExceptionUtils.java b/maibu-common/src/main/java/com/maibu/utils/ExceptionUtils.java new file mode 100644 index 0000000..ce87513 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/ExceptionUtils.java @@ -0,0 +1,53 @@ +package com.maibu.utils; + +import lombok.NoArgsConstructor; + +import javax.servlet.http.HttpServletRequest; +import java.io.PrintWriter; +import java.io.StringWriter; + +@NoArgsConstructor +public class ExceptionUtils { + + + public static Throwable getThrowable(HttpServletRequest request) { + Throwable ex = null; + if(request.getAttribute("exception") != null) { + ex = (Throwable)request.getAttribute("exception"); + } else if(request.getAttribute("javax.servlet.error.exception") != null) { + ex = (Throwable)request.getAttribute("javax.servlet.error.exception"); + } + + return ex; + } + + public static String getStackTraceAsString(Throwable e) { + if(e == null) { + return ""; + } else { + StringWriter stringWriter = new StringWriter(); + e.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } + } + + public static boolean isCausedBy(Exception ex, Class... causeExceptionClasses) { + for(Throwable cause = ex.getCause(); cause != null; cause = cause.getCause()) { + Class[] var3 = causeExceptionClasses; + int var4 = causeExceptionClasses.length; + + for(int var5 = 0; var5 < var4; ++var5) { + Class causeClass = var3[var5]; + if(causeClass.isInstance(cause)) { + return true; + } + } + } + + return false; + } + + public static RuntimeException unchecked(Exception e) { + return e instanceof RuntimeException?(RuntimeException)e:new RuntimeException(e); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/LogUtils.java b/maibu-common/src/main/java/com/maibu/utils/LogUtils.java new file mode 100644 index 0000000..a2e89f3 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/LogUtils.java @@ -0,0 +1,18 @@ +package com.maibu.utils; + +/** + * 处理并记录日志文件 + * + * @author ruoyi + */ +public class LogUtils +{ + public static String getBlock(Object msg) + { + if (msg == null) + { + msg = ""; + } + return "[" + msg.toString() + "]"; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/MapUtils.java b/maibu-common/src/main/java/com/maibu/utils/MapUtils.java new file mode 100644 index 0000000..e895da0 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/MapUtils.java @@ -0,0 +1,66 @@ +package com.maibu.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.maibu.core.text.KeyValue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Map 工具类 + * + * @author 芋道源码 + */ +public class MapUtils { + + /** + * 从哈希表表中,获得 keys 对应的所有 value 数组 + * + * @param multimap 哈希表 + * @param keys keys + * @return value 数组 + */ + public static List getList(Multimap multimap, Collection keys) { + List result = new ArrayList<>(); + keys.forEach(k -> { + Collection values = multimap.get(k); + if (CollectionUtil.isEmpty(values)) { + return; + } + result.addAll(values); + }); + return result; + } + + /** + * 从哈希表查找到 key 对应的 value,然后进一步处理 + * 注意,如果查找到的 value 为 null 时,不进行处理 + * + * @param map 哈希表 + * @param key key + * @param consumer 进一步处理的逻辑 + */ + public static void findAndThen(Map map, K key, Consumer consumer) { + if (CollUtil.isEmpty(map)) { + return; + } + V value = map.get(key); + if (value == null) { + return; + } + consumer.accept(value); + } + + public static Map convertMap(List> keyValues) { + Map map = Maps.newLinkedHashMapWithExpectedSize(keyValues.size()); + keyValues.forEach(keyValue -> map.put(keyValue.getKey(), keyValue.getValue())); + return map; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/Md5Utils.java b/maibu-common/src/main/java/com/maibu/utils/Md5Utils.java new file mode 100644 index 0000000..6aeee17 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/Md5Utils.java @@ -0,0 +1,82 @@ +package com.maibu.utils; + +import lombok.NoArgsConstructor; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +@NoArgsConstructor +public class Md5Utils { + private static final String MD5 = "MD5"; + private static final String DEFAULT_ENCODING = "UTF-8"; + + + + public static String md5(String input) { + return md5((String) input, 1); + } + + public static String md5(String input, int iterations) { + try { + return EncodeUtils.encodeHex(DigestUtils.digest(input.getBytes("UTF-8"), "MD5", (byte[]) null, iterations)); + } catch (UnsupportedEncodingException var3) { + return ""; + } + } + + public static byte[] md5(byte[] input) { + return md5((byte[]) input, 1); + } + + public static byte[] md5(byte[] input, int iterations) { + return DigestUtils.digest(input, "MD5", (byte[]) null, iterations); + } + + public static byte[] md5(InputStream input) throws IOException { + return DigestUtils.digest(input, "MD5"); + } + + public static boolean isMd5(String str) { + int cnt = 0; + for (int i = 0; i < str.length(); ++i) { + switch (str.charAt(i)) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + ++cnt; + if (32 <= cnt) return true; + break; + case '/': + if ((i + 10) < str.length()) {// "/storage/" + char ch1 = str.charAt(i + 1); + char ch2 = str.charAt(i + 8); + if ('/' == ch2 && ('s' == ch1 || 'S' == ch1)) return true; + } + default: + cnt = 0; + break; + } + } + return false; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/MessageUtils.java b/maibu-common/src/main/java/com/maibu/utils/MessageUtils.java new file mode 100644 index 0000000..1e5e6d6 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/MessageUtils.java @@ -0,0 +1,26 @@ +package com.maibu.utils; + +import com.maibu.utils.spring.SpringUtils; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; + +/** + * 获取i18n资源文件 + * + * @author ruoyi + */ +public class MessageUtils +{ + /** + * 根据消息键和参数 获取消息 委托给spring messageSource + * + * @param code 消息键 + * @param args 参数 + * @return 获取国际化翻译值 + */ + public static String message(String code, Object... args) + { + MessageSource messageSource = SpringUtils.getBean(MessageSource.class); + return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/MinioUtil.java b/maibu-common/src/main/java/com/maibu/utils/MinioUtil.java new file mode 100644 index 0000000..9cbaf68 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/MinioUtil.java @@ -0,0 +1,50 @@ +package com.maibu.utils; + +import io.minio.BucketExistsArgs; +import io.minio.MakeBucketArgs; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.InputStream; + +@Service +public class MinioUtil { + + @Resource + private MinioClient minioClient; + + + private String defaultBucket = "mbzn"; + + + private String endpoint = "https://oss.satabot.com"; // http://ip:port + + // 上传文件 + public void uploadFile(String objectName, InputStream stream, String contentType) throws Exception { + // 检查桶是否存在 + boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(defaultBucket).build()); + if (!found) { + minioClient.makeBucket(MakeBucketArgs.builder().bucket(defaultBucket).build()); + } + + minioClient.putObject( + PutObjectArgs.builder() + .bucket(defaultBucket) + .object(objectName) + .stream(stream, -1, 10485760) + .contentType(contentType) + .build() + ); + } + + // 返回 public URL + public String getPublicUrl(String objectName) { + // 拼接 public URL + return endpoint + "/" + defaultBucket + "/" + objectName; + } +} + + + diff --git a/maibu-common/src/main/java/com/maibu/utils/PageUtils.java b/maibu-common/src/main/java/com/maibu/utils/PageUtils.java new file mode 100644 index 0000000..3078f11 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/PageUtils.java @@ -0,0 +1,35 @@ +package com.maibu.utils; + +import com.github.pagehelper.PageHelper; +import com.maibu.core.page.PageDomain; +import com.maibu.core.page.TableSupport; +import com.maibu.utils.sql.SqlUtil; + +/** + * 分页工具类 + * + * @author ruoyi + */ +public class PageUtils extends PageHelper +{ + /** + * 设置请求分页数据 + */ + public static void startPage() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + Integer pageNum = pageDomain.getPageNum(); + Integer pageSize = pageDomain.getPageSize(); + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + Boolean reasonable = pageDomain.getReasonable(); + PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable); + } + + /** + * 清理分页的线程变量 + */ + public static void clearPage() + { + PageHelper.clearPage(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/SecurityUtils.java b/maibu-common/src/main/java/com/maibu/utils/SecurityUtils.java new file mode 100644 index 0000000..5419890 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/SecurityUtils.java @@ -0,0 +1,139 @@ +package com.maibu.utils; + +import com.maibu.constant.HttpStatus; +import com.maibu.core.domain.model.LoginUser; +import com.maibu.exception.ServiceException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +/** + * 安全服务工具类 + * + * @author ruoyi + */ +public class SecurityUtils +{ + /** + * 用户ID + **/ + public static Long getUserId() + { + try + { + return getLoginUser().getUserId(); + } + catch (Exception e) + { + throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取部门ID + **/ + public static Long getDeptId() + { + try + { + return getLoginUser().getDeptId(); + } + catch (Exception e) + { + throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户账户 + **/ + public static String getUsername() + { + try + { + return getLoginUser().getUsername(); + } + catch (Exception e) + { + throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户 + **/ + public static LoginUser getLoginUser() + { + try + { + return (LoginUser) getAuthentication().getPrincipal(); + } + catch (Exception e) + { + return null; + } + } + + /** + * 获取Authentication + */ + public static Authentication getAuthentication() + { + return SecurityContextHolder.getContext().getAuthentication(); + } + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * @return 加密字符串 + */ + public static String encryptPassword(String password) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * @return 结果 + */ + public static boolean matchesPassword(String rawPassword, String encodedPassword) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 是否为管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + /** + * 获取语言 + * @return + */ + public static String getLanguage(){ + try + { + String language = getLoginUser().getLanguage(); + if (StringUtils.isEmpty(language)){ + return "en-US"; + } + return language; + } + catch (Exception e) + { + return "en-US"; + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/ServletUtils.java b/maibu-common/src/main/java/com/maibu/utils/ServletUtils.java new file mode 100644 index 0000000..93a4625 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/ServletUtils.java @@ -0,0 +1,228 @@ +package com.maibu.utils; + +import cn.hutool.extra.servlet.ServletUtil; +import com.maibu.constant.Constants; +import com.maibu.core.text.Convert; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * 客户端工具类 + * + * @author ruoyi + */ +public class ServletUtils +{ + /** + * 获取String参数 + */ + public static String getParameter(String name) + { + return getRequest().getParameter(name); + } + + /** + * 获取String参数 + */ + public static String getParameter(String name, String defaultValue) + { + return Convert.toStr(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name) + { + return Convert.toInt(getRequest().getParameter(name)); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name, Integer defaultValue) + { + return Convert.toInt(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name) + { + return Convert.toBool(getRequest().getParameter(name)); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name, Boolean defaultValue) + { + return Convert.toBool(getRequest().getParameter(name), defaultValue); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParams(ServletRequest request) + { + final Map map = request.getParameterMap(); + return Collections.unmodifiableMap(map); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParamMap(ServletRequest request) + { + Map params = new HashMap<>(); + for (Map.Entry entry : getParams(request).entrySet()) + { + params.put(entry.getKey(), StringUtils.join(entry.getValue(), ",")); + } + return params; + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() + { + return getRequestAttributes().getRequest(); + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() + { + return getRequestAttributes().getResponse(); + } + + /** + * 获取session + */ + public static HttpSession getSession() + { + return getRequest().getSession(); + } + + public static ServletRequestAttributes getRequestAttributes() + { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + */ + public static void renderString(HttpServletResponse response, String string) + { + try + { + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(string); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + public static boolean isAjaxRequest(HttpServletRequest request) + { + String accept = request.getHeader("accept"); + if (accept != null && accept.contains("application/json")) + { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) + { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) + { + return true; + } + + String ajax = request.getParameter("__ajax"); + return StringUtils.inStringIgnoreCase(ajax, "json", "xml"); + } + + /** + * 内容编码 + * + * @param str 内容 + * @return 编码后的内容 + */ + public static String urlEncode(String str) + { + try + { + return URLEncoder.encode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } + + /** + * 内容解码 + * + * @param str 内容 + * @return 解码后的内容 + */ + public static String urlDecode(String str) + { + try + { + return URLDecoder.decode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } + + public static String getClientIP() { + HttpServletRequest request = getRequest(); + if (request == null) { + return null; + } + return ServletUtil.getClientIP(request); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/StringUtils.java b/maibu-common/src/main/java/com/maibu/utils/StringUtils.java new file mode 100644 index 0000000..92fe135 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/StringUtils.java @@ -0,0 +1,846 @@ +package com.maibu.utils; + +import com.maibu.constant.Constants; +import com.maibu.core.text.StrFormatter; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import org.apache.commons.collections4.MapUtils; +import org.springframework.util.AntPathMatcher; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 字符串工具类 + * + * @author ruoyi + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils { + /** + * 空字符串 + */ + private static final String NULLSTR = ""; + + /** + * 下划线 + */ + private static final char SEPARATOR = '_'; + + /** + * 获取参数不为空值 + * + * @param value defaultValue 要判断的value + * @return value 返回值 + */ + public static T nvl(T value, T defaultValue) { + return value != null ? value : defaultValue; + } + + /** + * * 判断一个Collection是否为空, 包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Collection coll) { + return isNull(coll) || coll.isEmpty(); + } + + /** + * * 判断一个Collection是否非空,包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Collection coll) { + return !isEmpty(coll); + } + + /** + * * 判断一个对象数组是否为空 + * + * @param objects 要判断的对象数组 + * * @return true:为空 false:非空 + */ + public static boolean isEmpty(Object[] objects) { + return isNull(objects) || (objects.length == 0); + } + + /** + * * 判断一个对象数组是否非空 + * + * @param objects 要判断的对象数组 + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Object[] objects) { + return !isEmpty(objects); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Map map) { + return isNull(map) || map.isEmpty(); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Map map) { + return !isEmpty(map); + } + + /** + * * 判断一个字符串是否为空串 + * + * @param str String + * @return true:为空 false:非空 + */ + public static boolean isEmpty(String str) { + return isNull(str) || NULLSTR.equals(str.trim()); + } + + /** + * * 判断一个字符串是否为非空串 + * + * @param str String + * @return true:非空串 false:空串 + */ + public static boolean isNotEmpty(String str) { + return !isEmpty(str); + } + + /** + * * 判断一个对象是否为空 + * + * @param object Object + * @return true:为空 false:非空 + */ + public static boolean isNull(Object object) { + return object == null; + } + + /** + * * 判断一个对象是否非空 + * + * @param object Object + * @return true:非空 false:空 + */ + public static boolean isNotNull(Object object) { + return !isNull(object); + } + + /** + * * 判断一个对象是否是数组类型(Java基本型别的数组) + * + * @param object 对象 + * @return true:是数组 false:不是数组 + */ + public static boolean isArray(Object object) { + return isNotNull(object) && object.getClass().isArray(); + } + + /** + * 去空格 + */ + public static String trim(String str) { + return (str == null ? "" : str.trim()); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @return 结果 + */ + public static String substring(final String str, int start) { + if (str == null) { + return NULLSTR; + } + + if (start < 0) { + start = str.length() + start; + } + + if (start < 0) { + start = 0; + } + if (start > str.length()) { + return NULLSTR; + } + + return str.substring(start); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @param end 结束 + * @return 结果 + */ + public static String substring(final String str, int start, int end) { + if (str == null) { + return NULLSTR; + } + + if (end < 0) { + end = str.length() + end; + } + if (start < 0) { + start = str.length() + start; + } + + if (end > str.length()) { + end = str.length(); + } + + if (start > end) { + return NULLSTR; + } + + if (start < 0) { + start = 0; + } + if (end < 0) { + end = 0; + } + + return str.substring(start, end); + } + + /** + * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param params 参数值 + * @return 格式化后的文本 + */ + public static String format(String template, Object... params) { + if (isEmpty(params) || isEmpty(template)) { + return template; + } + return StrFormatter.format(template, params); + } + + /** + * 是否为http(s)://开头 + * + * @param link 链接 + * @return 结果 + */ + public static boolean ishttp(String link) { + return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); + } + + /** + * 字符串转set + * + * @param str 字符串 + * @param sep 分隔符 + * @return set集合 + */ + public static final Set str2Set(String str, String sep) { + return new HashSet(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) { + List list = new ArrayList(); + if (StringUtils.isEmpty(str)) { + return list; + } + + // 过滤空白字符串 + if (filterBlank && StringUtils.isBlank(str)) { + return list; + } + String[] split = str.split(sep); + for (String string : split) { + if (filterBlank && StringUtils.isBlank(string)) { + continue; + } + if (trim) { + string = string.trim(); + } + list.add(string); + } + + return list; + } + + /** + * 判断给定的set列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value + * + * @param set 给定的集合 + * @param array 给定的数组 + * @return boolean 结果 + */ + public static boolean containsAny(Collection collection, String... array) { + if (isEmpty(collection) || isEmpty(array)) { + return false; + } else { + for (String str : array) { + if (collection.contains(str)) { + return true; + } + } + return false; + } + } + + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + * + * @param cs 指定字符串 + * @param searchCharSequences 需要检查的字符串数组 + * @return 是否包含任意一个字符串 + */ + public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) { + if (isEmpty(cs) || isEmpty(searchCharSequences)) { + return false; + } + for (CharSequence testStr : searchCharSequences) { + if (containsIgnoreCase(cs, testStr)) { + return true; + } + } + return false; + } + + /** + * 驼峰转下划线命名 + */ + public static String toUnderScoreCase(String str) { + if (str == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + // 前置字符是否大写 + boolean preCharIsUpperCase = true; + // 当前字符是否大写 + boolean curreCharIsUpperCase = true; + // 下一字符是否大写 + boolean nexteCharIsUpperCase = true; + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (i > 0) { + preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); + } else { + preCharIsUpperCase = false; + } + + curreCharIsUpperCase = Character.isUpperCase(c); + + if (i < (str.length() - 1)) { + nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); + } + + if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) { + sb.append(SEPARATOR); + } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) { + sb.append(SEPARATOR); + } + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 是否包含字符串 + * + * @param str 验证字符串 + * @param strs 字符串组 + * @return 包含返回true + */ + public static boolean inStringIgnoreCase(String str, String... strs) { + if (str != null && strs != null) { + for (String s : strs) { + if (str.equalsIgnoreCase(trim(s))) { + return true; + } + } + } + return false; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) { + StringBuilder result = new StringBuilder(); + // 快速检查 + if (name == null || name.isEmpty()) { + // 没必要转换 + return ""; + } else if (!name.contains("_")) { + // 不含下划线,仅将首字母大写 + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + // 用下划线将原始字符串分割 + String[] camels = name.split("_"); + for (String camel : camels) { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) { + continue; + } + // 首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + return result.toString(); + } + + /** + * 驼峰式命名法 例如:user_name->userName + */ + public static String toCamelCase(String s) { + if (s == null) { + return null; + } + s = s.toLowerCase(); + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + if (c == SEPARATOR) { + upperCase = true; + } else if (upperCase) { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } else { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 + * + * @param str 指定字符串 + * @param strs 需要检查的字符串数组 + * @return 是否匹配 + */ + public static boolean matches(String str, List strs) { + if (isEmpty(str) || isEmpty(strs)) { + return false; + } + for (String pattern : strs) { + if (isMatch(pattern, str)) { + return true; + } + } + return false; + } + + /** + * 判断url是否与规则配置: + * ? 表示单个字符; + * * 表示一层路径内的任意字符串,不可跨层级; + * ** 表示任意层路径; + * + * @param pattern 匹配规则 + * @param url 需要匹配的url + * @return + */ + public static boolean isMatch(String pattern, String url) { + AntPathMatcher matcher = new AntPathMatcher(); + return matcher.match(pattern, url); + } + + @SuppressWarnings("unchecked") + public static T cast(Object obj) { + return (T) obj; + } + + /** + * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 + * + * @param num 数字对象 + * @param size 字符串指定长度 + * @return 返回数字的字符串格式,该字符串为指定长度。 + */ + public static final String padl(final Number num, final int size) { + return padl(num.toString(), size, '0'); + } + + /** + * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 + * + * @param s 原始字符串 + * @param size 字符串指定长度 + * @param c 用于补齐的字符 + * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 + */ + public static final String padl(final String s, final int size, final char c) { + final StringBuilder sb = new StringBuilder(size); + if (s != null) { + final int len = s.length(); + if (s.length() <= size) { + for (int i = size - len; i > 0; i--) { + sb.append(c); + } + sb.append(s); + } else { + return s.substring(len - size, len); + } + } else { + for (int i = size; i > 0; i--) { + sb.append(c); + } + } + return sb.toString(); + } + + /*将字符串转小写,首字母大写,其他小写*/ + public static String upperCase(String str) { + char[] ch = str.toLowerCase().toCharArray(); + if (ch[0] >= 'a' && ch[0] <= 'z') { + ch[0] = (char) (ch[0] - 32); + } + return new String(ch); + } + + public static String toString(Object value) { + if (value == null) { + return "null"; + } + if (value instanceof ByteBuf) { + return ByteBufUtil.hexDump((ByteBuf) value); + } + if (!value.getClass().isArray()) { + return value.toString(); + } + + StringBuilder root = new StringBuilder(32); + toString(value, root); + return root.toString(); + } + + public static StringBuilder toString(Object value, StringBuilder builder) { + if (value == null) { + return builder; + } + + builder.append('['); + int start = builder.length(); + + if (value instanceof long[]) { + long[] array = (long[]) value; + for (long t : array) { + builder.append(t).append(','); + } + + } else if (value instanceof int[]) { + int[] array = (int[]) value; + for (int t : array) { + builder.append(t).append(','); + } + + } else if (value instanceof short[]) { + short[] array = (short[]) value; + for (short t : array) { + builder.append(t).append(','); + } + + } else if (value instanceof byte[]) { + byte[] array = (byte[]) value; + for (byte t : array) { + builder.append(t).append(','); + } + + } else if (value instanceof char[]) { + char[] array = (char[]) value; + for (char t : array) { + builder.append(t).append(','); + } + + } else if (value instanceof double[]) { + double[] array = (double[]) value; + for (double t : array) { + builder.append(t).append(','); + } + + } else if (value instanceof float[]) { + float[] array = (float[]) value; + for (float t : array) { + builder.append(t).append(','); + } + + } else if (value instanceof boolean[]) { + boolean[] array = (boolean[]) value; + for (boolean t : array) { + builder.append(t).append(','); + } + + } else if (value instanceof String[]) { + String[] array = (String[]) value; + for (String t : array) { + builder.append(t).append(','); + } + + } else if (isArray1(value)) { + Object[] array = (Object[]) value; + for (Object t : array) { + toString(t, builder).append(','); + } + + } else if (value instanceof Object[]) { + Object[] array = (Object[]) value; + for (Object t : array) { + builder.append(t).append(','); + } + } + + int end = builder.length(); + if (end <= start) { + builder.append(']'); + } else { + builder.setCharAt(end - 1, ']'); + } + return builder; + } + + private static boolean isArray1(Object value) { + Class componentType = value.getClass().getComponentType(); + if (componentType == null) { + return false; + } + return componentType.isArray(); + } + + public static String leftPad(String str, int size, char ch) { + int length = str.length(); + int pads = size - length; + if (pads > 0) { + char[] result = new char[size]; + str.getChars(0, length, result, pads); + while (pads > 0) { + result[--pads] = ch; + } + return new String(result); + } + return str; + } + + /** + * 获取字符串中的数字 + * @param str + * @return + */ + public static Integer matcherNum(String str){ + Pattern pattern = Pattern.compile("\\d+"); + Matcher matcher = pattern.matcher(str); + while (matcher.find()){ + return Integer.parseInt(matcher.group()); + } + return 0; + } + + /** + * 获取字符串中的变量 + * @param variable 变量标识符合 + * @param: str 内容 + * @return java.util.List + */ + public static List getVariables(String variable, String str) { + List variables = new ArrayList<>(); + Pattern pattern = null; + switch (variable) { + case "${}": + pattern = Pattern.compile("\\$\\{([^}]+)}"); + break; + case "{{}}": + pattern = Pattern.compile("\\{\\{([^}]+)}}"); + break; + case "{}": + pattern = Pattern.compile("\\{([^}]+)}"); + break; + case "#{}": + pattern = Pattern.compile("#\\{([^}]+)}"); + break; + default: + break; + } + assert pattern != null; + Matcher matcher = pattern.matcher(str); + while (matcher.find()) { + variables.add(matcher.group(1)); + } + return variables; + } + + /** + * 获取微信小程序变量 + * @param content 内容 + * @return java.util.List + */ + public static List getWeChatMiniVariables(String content) { + List variables = new ArrayList<>(); + Pattern pattern = Pattern.compile("\\{\\{([^}]+)}}"); + Matcher matcher = pattern.matcher(content); + while (matcher.find()) { + variables.add(matcher.group(1).replace(".DATA", "")); + } + return variables; + } + + /** + * 将字符串text中由openToken和closeToken组成的占位符依次替换为args数组中的值 + * @param openToken + * @param closeToken + * @param text + * @param args + * @return + */ + public static String parse(String openToken, String closeToken, String text, Object... args) { + if (args == null || args.length <= 0) { + return text; + } + int argsIndex = 0; + + if (text == null || text.isEmpty()) { + return ""; + } + char[] src = text.toCharArray(); + int offset = 0; + // search open token + int start = text.indexOf(openToken, offset); + if (start == -1) { + return text; + } + final StringBuilder builder = new StringBuilder(); + StringBuilder expression = null; + while (start > -1) { + if (start > 0 && src[start - 1] == '\\') { + // this open token is escaped. remove the backslash and continue. + builder.append(src, offset, start - offset - 1).append(openToken); + offset = start + openToken.length(); + } else { + // found open token. let's search close token. + if (expression == null) { + expression = new StringBuilder(); + } else { + expression.setLength(0); + } + builder.append(src, offset, start - offset); + offset = start + openToken.length(); + int end = text.indexOf(closeToken, offset); + while (end > -1) { + if (end > offset && src[end - 1] == '\\') { + // this close token is escaped. remove the backslash and continue. + expression.append(src, offset, end - offset - 1).append(closeToken); + offset = end + closeToken.length(); + end = text.indexOf(closeToken, offset); + } else { + expression.append(src, offset, end - offset); + offset = end + closeToken.length(); + break; + } + } + if (end == -1) { + // close token was not found. + builder.append(src, start, src.length - start); + offset = src.length; + } else { + ///////////////////////////////////////仅仅修改了该else分支下的个别行代码//////////////////////// + + String value = (argsIndex <= args.length - 1) ? + (args[argsIndex] == null ? "" : args[argsIndex].toString()) : expression.toString(); + builder.append(value); + offset = end + closeToken.length(); + argsIndex++; + //////////////////////////////////////////////////////////////////////////////////////////////// + } + } + start = text.indexOf(openToken, offset); + } + if (offset < src.length) { + builder.append(src, offset, src.length - offset); + } + return builder.toString(); + } + + // + public static String reverse(String str) { + return new StringBuilder(str).reverse().toString(); + } + + /** + * @description: 替换 ${variable} + * @author fastb + * @date 2023-12-26 15:35 + * @version 1.0 + */ + public static String parseVariable(String text, Object... args) { + return parse("${", "}", text, args); + } + + public static String strReplaceVariable(String openIndex, String closeIndex, String content, LinkedHashMap map) { + if (StringUtils.isEmpty(content) || MapUtils.isEmpty(map)) { + return content; + } + StringBuilder sendContent = new StringBuilder(content); + for (Map.Entry m : map.entrySet()) { + sendContent = new StringBuilder(sendContent.toString().replace(openIndex + m.getKey() + closeIndex, m.getValue())); + } + return sendContent.toString(); + } + + public static List splitEvenly(String str, int size) { + List parts = new ArrayList<>(); + int length = str.length(); + if (size > length || size <= 0) { + throw new IllegalArgumentException("Size is too large or too small."); + } + + for (int i = 0; i < length; i += size) { + parts.add(str.substring(i, Math.min(length, i + size))); + } + + return parts; + } + + public static String underlineToHump(String param) { + StringBuilder result = new StringBuilder(); + String[] strs = param.split("_"); + for (String str : strs) { + result.append(str.substring(0, 1).toUpperCase()).append(str.substring(1)); + } + return result.toString(); + } + + public static String toGet(String param){ + String s = underlineToHump(param); + return "get" + s + "()"; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/Threads.java b/maibu-common/src/main/java/com/maibu/utils/Threads.java new file mode 100644 index 0000000..a2dec82 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/Threads.java @@ -0,0 +1,96 @@ +package com.maibu.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.*; + +/** + * 线程相关工具类. + * + * @author ruoyi + */ +public class Threads +{ + private static final Logger logger = LoggerFactory.getLogger(Threads.class); + + /** + * sleep等待,单位为毫秒 + */ + public static void sleep(long milliseconds) + { + try + { + Thread.sleep(milliseconds); + } + catch (InterruptedException e) + { + return; + } + } + + /** + * 停止线程池 + * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. + * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. + * 如果仍然超時,則強制退出. + * 另对在shutdown时线程本身被调用中断做了处理. + */ + public static void shutdownAndAwaitTermination(ExecutorService pool) + { + if (pool != null && !pool.isShutdown()) + { + pool.shutdown(); + try + { + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) + { + pool.shutdownNow(); + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) + { + logger.info("Pool did not terminate"); + } + } + } + catch (InterruptedException ie) + { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + + /** + * 打印线程异常信息 + */ + public static void printException(Runnable r, Throwable t) + { + if (t == null && r instanceof Future) + { + try + { + Future future = (Future) r; + if (future.isDone()) + { + future.get(); + } + } + catch (CancellationException ce) + { + t = ce; + } + catch (ExecutionException ee) + { + t = ee.getCause(); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + } + } + if (t != null) + { + logger.error(t.getMessage(), t); + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/ValidationUtils.java b/maibu-common/src/main/java/com/maibu/utils/ValidationUtils.java new file mode 100644 index 0000000..28ebeb4 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/ValidationUtils.java @@ -0,0 +1,62 @@ +package com.maibu.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import org.springframework.util.StringUtils; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validation; +import javax.validation.Validator; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * 校验工具类 + * + * @author fastbee + */ +public class ValidationUtils { + + private static final Pattern PATTERN_MOBILE = Pattern.compile("^(?:(?:\\+|00)86)?1(?:(?:3[\\d])|(?:4[0,1,4-9])|(?:5[0-3,5-9])|(?:6[2,5-7])|(?:7[0-8])|(?:8[\\d])|(?:9[0-3,5-9]))\\d{8}$"); + + private static final Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"); + + private static final Pattern PATTERN_XML_NCNAME = Pattern.compile("[a-zA-Z_][\\-_.0-9_a-zA-Z$]*"); + + private static final Pattern PATTERN_EMAIL = Pattern.compile("^(\\w+([-.][A-Za-z0-9]+)*){3,18}@\\w+([-.][A-Za-z0-9]+)*\\.\\w+([-.][A-Za-z0-9]+)*$"); + + public static boolean isMobile(String mobile) { + return StringUtils.hasText(mobile) + && PATTERN_MOBILE.matcher(mobile).matches(); + } + + public static boolean isURL(String url) { + return StringUtils.hasText(url) + && PATTERN_URL.matcher(url).matches(); + } + + public static boolean isXmlNCName(String str) { + return StringUtils.hasText(str) + && PATTERN_XML_NCNAME.matcher(str).matches(); + } + + public static boolean isEmail(String email) { + return StringUtils.hasText(email) + && PATTERN_EMAIL.matcher(email).matches(); + } + + public static void validate(Object object, Class... groups) { + Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + Assert.notNull(validator); + validate(validator, object, groups); + } + + public static void validate(Validator validator, Object object, Class... groups) { + Set> constraintViolations = validator.validate(object, groups); + if (CollUtil.isNotEmpty(constraintViolations)) { + throw new ConstraintViolationException(constraintViolations); + } + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/VerifyCodeUtils.java b/maibu-common/src/main/java/com/maibu/utils/VerifyCodeUtils.java new file mode 100644 index 0000000..956b9ac --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/VerifyCodeUtils.java @@ -0,0 +1,224 @@ +package com.maibu.utils; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Random; + +/** + * 验证码工具类 + * + * @author ruoyi + */ +public class VerifyCodeUtils +{ + // 使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符 + public static final String VERIFY_CODES = "123456789ABCDEFGHJKLMNPQRSTUVWXYZ"; + + private static Random random = new SecureRandom(); + + /** + * 使用系统默认字符源生成验证码 + * + * @param verifySize 验证码长度 + * @return + */ + public static String generateVerifyCode(int verifySize) + { + return generateVerifyCode(verifySize, VERIFY_CODES); + } + + /** + * 使用指定源生成验证码 + * + * @param verifySize 验证码长度 + * @param sources 验证码字符源 + * @return + */ + public static String generateVerifyCode(int verifySize, String sources) + { + if (sources == null || sources.length() == 0) + { + sources = VERIFY_CODES; + } + int codesLen = sources.length(); + Random rand = new Random(System.currentTimeMillis()); + StringBuilder verifyCode = new StringBuilder(verifySize); + for (int i = 0; i < verifySize; i++) + { + verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1))); + } + return verifyCode.toString(); + } + + /** + * 输出指定验证码图片流 + * + * @param w + * @param h + * @param os + * @param code + * @throws IOException + */ + public static void outputImage(int w, int h, OutputStream os, String code) throws IOException + { + int verifySize = code.length(); + BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + Random rand = new Random(); + Graphics2D g2 = image.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + Color[] colors = new Color[5]; + Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN, Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, + Color.ORANGE, Color.PINK, Color.YELLOW }; + float[] fractions = new float[colors.length]; + for (int i = 0; i < colors.length; i++) + { + colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)]; + fractions[i] = rand.nextFloat(); + } + Arrays.sort(fractions); + + g2.setColor(Color.GRAY);// 设置边框色 + g2.fillRect(0, 0, w, h); + + Color c = getRandColor(200, 250); + g2.setColor(c);// 设置背景色 + g2.fillRect(0, 2, w, h - 4); + + // 绘制干扰线 + Random random = new Random(); + g2.setColor(getRandColor(160, 200));// 设置线条的颜色 + for (int i = 0; i < 20; i++) + { + int x = random.nextInt(w - 1); + int y = random.nextInt(h - 1); + int xl = random.nextInt(6) + 1; + int yl = random.nextInt(12) + 1; + g2.drawLine(x, y, x + xl + 40, y + yl + 20); + } + + // 添加噪点 + float yawpRate = 0.05f;// 噪声率 + int area = (int) (yawpRate * w * h); + for (int i = 0; i < area; i++) + { + int x = random.nextInt(w); + int y = random.nextInt(h); + int rgb = getRandomIntColor(); + image.setRGB(x, y, rgb); + } + + shear(g2, w, h, c);// 使图片扭曲 + + g2.setColor(getRandColor(100, 160)); + int fontSize = h - 4; + Font font = new Font("Algerian", Font.ITALIC, fontSize); + g2.setFont(font); + char[] chars = code.toCharArray(); + for (int i = 0; i < verifySize; i++) + { + AffineTransform affine = new AffineTransform(); + affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), + (w / verifySize) * i + fontSize / 2, h / 2); + g2.setTransform(affine); + g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10); + } + + g2.dispose(); + ImageIO.write(image, "jpg", os); + } + + private static Color getRandColor(int fc, int bc) + { + if (fc > 255) { + fc = 255; + } + if (bc > 255) { + bc = 255; + } + int r = fc + random.nextInt(bc - fc); + int g = fc + random.nextInt(bc - fc); + int b = fc + random.nextInt(bc - fc); + return new Color(r, g, b); + } + + private static int getRandomIntColor() + { + int[] rgb = getRandomRgb(); + int color = 0; + for (int c : rgb) + { + color = color << 8; + color = color | c; + } + return color; + } + + private static int[] getRandomRgb() + { + int[] rgb = new int[3]; + for (int i = 0; i < 3; i++) + { + rgb[i] = random.nextInt(255); + } + return rgb; + } + + private static void shear(Graphics g, int w1, int h1, Color color) + { + shearX(g, w1, h1, color); + shearY(g, w1, h1, color); + } + + private static void shearX(Graphics g, int w1, int h1, Color color) + { + + int period = random.nextInt(2); + + boolean borderGap = true; + int frames = 1; + int phase = random.nextInt(2); + + for (int i = 0; i < h1; i++) + { + double d = (double) (period >> 1) + * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames); + g.copyArea(0, i, w1, 1, (int) d, 0); + if (borderGap) + { + g.setColor(color); + g.drawLine((int) d, i, 0, i); + g.drawLine((int) d + w1, i, w1, i); + } + } + + } + + private static void shearY(Graphics g, int w1, int h1, Color color) + { + + int period = random.nextInt(40) + 10; // 50; + + boolean borderGap = true; + int frames = 20; + int phase = 7; + for (int i = 0; i < w1; i++) + { + double d = (double) (period >> 1) + * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames); + g.copyArea(i, 0, 1, h1, 0, (int) d); + if (borderGap) + { + g.setColor(color); + g.drawLine(i, (int) d, i, 0); + g.drawLine(i, (int) d + h1, i, h1); + } + + } + } +} \ No newline at end of file diff --git a/maibu-common/src/main/java/com/maibu/utils/bean/BeanUtils.java b/maibu-common/src/main/java/com/maibu/utils/bean/BeanUtils.java new file mode 100644 index 0000000..24be819 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/bean/BeanUtils.java @@ -0,0 +1,110 @@ +package com.maibu.utils.bean; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Bean 工具类 + * + * @author ruoyi + */ +public class BeanUtils extends org.springframework.beans.BeanUtils +{ + /** Bean方法名中属性名开始的下标 */ + private static final int BEAN_METHOD_PROP_INDEX = 3; + + /** * 匹配getter方法的正则表达式 */ + private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)"); + + /** * 匹配setter方法的正则表达式 */ + private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)"); + + /** + * Bean属性复制工具方法。 + * + * @param dest 目标对象 + * @param src 源对象 + */ + public static void copyBeanProp(Object dest, Object src) + { + try + { + copyProperties(src, dest); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * 获取对象的setter方法。 + * + * @param obj 对象 + * @return 对象的setter方法列表 + */ + public static List getSetterMethods(Object obj) + { + // setter方法列表 + List setterMethods = new ArrayList(); + + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + + // 查找setter方法 + + for (Method method : methods) + { + Matcher m = SET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 1)) + { + setterMethods.add(method); + } + } + // 返回setter方法列表 + return setterMethods; + } + + /** + * 获取对象的getter方法。 + * + * @param obj 对象 + * @return 对象的getter方法列表 + */ + + public static List getGetterMethods(Object obj) + { + // getter方法列表 + List getterMethods = new ArrayList(); + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + // 查找getter方法 + for (Method method : methods) + { + Matcher m = GET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 0)) + { + getterMethods.add(method); + } + } + // 返回getter方法列表 + return getterMethods; + } + + /** + * 检查Bean方法名中的属性名是否相等。
+ * 如getName()和setName()属性名一样,getName()和setAge()属性名不一样。 + * + * @param m1 方法名1 + * @param m2 方法名2 + * @return 属性名一样返回true,否则返回false + */ + + public static boolean isMethodPropEquals(String m1, String m2) + { + return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX)); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/bean/BeanValidators.java b/maibu-common/src/main/java/com/maibu/utils/bean/BeanValidators.java new file mode 100644 index 0000000..d9d3645 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/bean/BeanValidators.java @@ -0,0 +1,24 @@ +package com.maibu.utils.bean; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; +import java.util.Set; + +/** + * bean对象属性验证 + * + * @author ruoyi + */ +public class BeanValidators +{ + public static void validateWithException(Validator validator, Object object, Class... groups) + throws ConstraintViolationException + { + Set> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) + { + throw new ConstraintViolationException(constraintViolations); + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/collection/CollectionUtils.java b/maibu-common/src/main/java/com/maibu/utils/collection/CollectionUtils.java new file mode 100644 index 0000000..a85cf39 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/collection/CollectionUtils.java @@ -0,0 +1,268 @@ +package com.maibu.utils.collection; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; + +import java.util.*; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * @author gsb + * @date 2022/9/15 16:52 + */ +public class CollectionUtils { + + /*数组复制*/ + public static String[] copy(String[] source){ + if(isEmpty(source)){ + return null; + } + int len = source.length; + String[] arr = new String[len]; + for(int i=0; i < len; i ++){ + arr[i] = source[i]; + } + return arr; + } + + + /*数组连接*/ + public static String concat(String[] source, String split){ + if(isEmpty(source)){ + return null; + } + String result = ""; + for(int i=0; i < source.length; i ++){ + result = result.concat(source[i]); + if(i != source.length - 1){ + result = result.concat(split); + } + } + return result; + } + + public static boolean isEmpty(String[] source){ + if(null == source){ + return true; + } + if(0 == source.length){ + return true; + } + return false; + } + + public static boolean containsAny(Object source, Object... targets) { + return Arrays.asList(targets).contains(source); + } + + public static boolean isAnyEmpty(Collection... collections) { + return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty); + } + + public static List filterList(Collection from, Predicate predicate) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return from.stream().filter(predicate).collect(Collectors.toList()); + } + + public static List distinct(Collection from, Function keyMapper) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return distinct(from, keyMapper, (t1, t2) -> t1); + } + + public static List distinct(Collection from, Function keyMapper, BinaryOperator cover) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values()); + } + + public static List convertList(Collection from, Function func) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList()); + } + + public static List convertList(Collection from, Function func, Predicate filter) { + if (CollUtil.isEmpty(from)) { + return new ArrayList<>(); + } + return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList()); + } + + public static Set convertSet(Collection from, Function func) { + if (CollUtil.isEmpty(from)) { + return new HashSet<>(); + } + return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet()); + } + + public static Set convertSet(Collection from, Function func, Predicate filter) { + if (CollUtil.isEmpty(from)) { + return new HashSet<>(); + } + return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet()); + } + + public static Map convertMap(Collection from, Function keyFunc) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return convertMap(from, keyFunc, Function.identity()); + } + + public static Map convertMap(Collection from, Function keyFunc, Supplier> supplier) { + if (CollUtil.isEmpty(from)) { + return supplier.get(); + } + return convertMap(from, keyFunc, Function.identity(), supplier); + } + + public static Map convertMap(Collection from, Function keyFunc, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1); + } + + public static Map convertMap(Collection from, Function keyFunc, Function valueFunc, BinaryOperator mergeFunction) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return convertMap(from, keyFunc, valueFunc, mergeFunction, HashMap::new); + } + + public static Map convertMap(Collection from, Function keyFunc, Function valueFunc, Supplier> supplier) { + if (CollUtil.isEmpty(from)) { + return supplier.get(); + } + return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1, supplier); + } + + public static Map convertMap(Collection from, Function keyFunc, Function valueFunc, BinaryOperator mergeFunction, Supplier> supplier) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return from.stream().collect(Collectors.toMap(keyFunc, valueFunc, mergeFunction, supplier)); + } + + public static Map> convertMultiMap(Collection from, Function keyFunc) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(t -> t, Collectors.toList()))); + } + + public static Map> convertMultiMap(Collection from, Function keyFunc, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return from.stream() + .collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList()))); + } + + // 暂时没想好名字,先以 2 结尾噶 + public static Map> convertMultiMap2(Collection from, Function keyFunc, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return new HashMap<>(); + } + return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet()))); + } + + + public static boolean containsAny(Collection source, Collection candidates) { + return org.springframework.util.CollectionUtils.containsAny(source, candidates); + } + + public static T getFirst(List from) { + return !CollectionUtil.isEmpty(from) ? from.get(0) : null; + } + + public static T findFirst(List from, Predicate predicate) { + if (CollUtil.isEmpty(from)) { + return null; + } + return from.stream().filter(predicate).findFirst().orElse(null); + } + + public static > V getMaxValue(List from, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return null; + } + assert from.size() > 0; // 断言,避免告警 + T t = from.stream().max(Comparator.comparing(valueFunc)).get(); + return valueFunc.apply(t); + } + + public static > V getMinValue(List from, Function valueFunc) { + if (CollUtil.isEmpty(from)) { + return null; + } + assert from.size() > 0; // 断言,避免告警 + T t = from.stream().min(Comparator.comparing(valueFunc)).get(); + return valueFunc.apply(t); + } + + public static > V getSumValue(List from, Function valueFunc, BinaryOperator accumulator) { + if (CollUtil.isEmpty(from)) { + return null; + } + assert from.size() > 0; // 断言,避免告警 + return from.stream().map(valueFunc).reduce(accumulator).get(); + } + + public static void addIfNotNull(Collection coll, T item) { + if (item == null) { + return; + } + coll.add(item); + } + + public static Collection singleton(T deptId) { + return deptId == null ? Collections.emptyList() : Collections.singleton(deptId); + } + + /** + * 开始分页 + * + * @param list 传入的list集合 + * @param pageNum 页码 + * @param pageSize 每页多少条数据 + * @return + */ + public static List startPage(List list, Integer pageNum, + Integer pageSize) { + if (list == null) { + return null; + } + if (list.size() == 0) { + return null; + } + Integer count = list.size(); // 记录总数 + Integer pageCount = 0; // 页数 + if (count % pageSize == 0) { + pageCount = count / pageSize; + } else { + pageCount = count / pageSize + 1; + } + int fromIndex = 0; // 开始索引 + int toIndex = 0; // 结束索引 + if (!pageNum.equals(pageCount)) { + fromIndex = (pageNum - 1) * pageSize; + toIndex = fromIndex + pageSize; + } else { + fromIndex = (pageNum - 1) * pageSize; + toIndex = count; + } + List pageList = list.subList(fromIndex, toIndex); + return pageList; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/date/DateUtils.java b/maibu-common/src/main/java/com/maibu/utils/date/DateUtils.java new file mode 100644 index 0000000..bb281c9 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/date/DateUtils.java @@ -0,0 +1,173 @@ +package com.maibu.utils.date; + +import cn.hutool.core.date.LocalDateTimeUtil; + +import java.time.*; +import java.util.Calendar; +import java.util.Date; + +/** + * 时间工具类 + * + * @author fastbee + */ +public class DateUtils { + + /** + * 时区 - 默认 + */ + public static final String TIME_ZONE_DEFAULT = "GMT+8"; + + /** + * 秒转换成毫秒 + */ + public static final long SECOND_MILLIS = 1000; + + public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss"; + + public static final String FORMAT_HOUR_MINUTE_SECOND = "HH:mm:ss"; + + /** + * 将 LocalDateTime 转换成 Date + * + * @param date LocalDateTime + * @return LocalDateTime + */ + public static Date of(LocalDateTime date) { + // 将此日期时间与时区相结合以创建 ZonedDateTime + ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault()); + // 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳 + Instant instant = zonedDateTime.toInstant(); + // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间 + return Date.from(instant); + } + + /** + * 将 Date 转换成 LocalDateTime + * + * @param date Date + * @return LocalDateTime + */ + public static LocalDateTime of(Date date) { + // 转为时间戳 + Instant instant = date.toInstant(); + // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间 + return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + } + + @Deprecated + public static Date addTime(Duration duration) { + return new Date(System.currentTimeMillis() + duration.toMillis()); + } + + public static boolean isExpired(Date time) { + return System.currentTimeMillis() > time.getTime(); + } + + public static boolean isExpired(LocalDateTime time) { + LocalDateTime now = LocalDateTime.now(); + return now.isAfter(time); + } + + public static long diff(Date endTime, Date startTime) { + return endTime.getTime() - startTime.getTime(); + } + + /** + * 创建指定时间 + * + * @param year 年 + * @param mouth 月 + * @param day 日 + * @return 指定时间 + */ + public static Date buildTime(int year, int mouth, int day) { + return buildTime(year, mouth, day, 0, 0, 0); + } + + /** + * 创建指定时间 + * + * @param year 年 + * @param mouth 月 + * @param day 日 + * @param hour 小时 + * @param minute 分钟 + * @param second 秒 + * @return 指定时间 + */ + public static Date buildTime(int year, int mouth, int day, + int hour, int minute, int second) { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, mouth - 1); + calendar.set(Calendar.DAY_OF_MONTH, day); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, minute); + calendar.set(Calendar.SECOND, second); + calendar.set(Calendar.MILLISECOND, 0); // 一般情况下,都是 0 毫秒 + return calendar.getTime(); + } + + public static Date max(Date a, Date b) { + if (a == null) { + return b; + } + if (b == null) { + return a; + } + return a.compareTo(b) > 0 ? a : b; + } + + public static LocalDateTime max(LocalDateTime a, LocalDateTime b) { + if (a == null) { + return b; + } + if (b == null) { + return a; + } + return a.isAfter(b) ? a : b; + } + + /** + * 计算当期时间相差的日期 + * + * @param field 日历字段.
eg:Calendar.MONTH,Calendar.DAY_OF_MONTH,
Calendar.HOUR_OF_DAY等. + * @param amount 相差的数值 + * @return 计算后的日志 + */ + public static Date addDate(int field, int amount) { + return addDate(null, field, amount); + } + + /** + * 计算当期时间相差的日期 + * + * @param date 设置时间 + * @param field 日历字段 例如说,{@link Calendar#DAY_OF_MONTH} 等 + * @param amount 相差的数值 + * @return 计算后的日志 + */ + public static Date addDate(Date date, int field, int amount) { + if (amount == 0) { + return date; + } + Calendar c = Calendar.getInstance(); + if (date != null) { + c.setTime(date); + } + c.add(field, amount); + return c.getTime(); + } + + /** + * 是否今天 + * + * @param date 日期 + * @return 是否 + */ + public static boolean isToday(LocalDateTime date) { + return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now()); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/date/LocalDateTimeUtils.java b/maibu-common/src/main/java/com/maibu/utils/date/LocalDateTimeUtils.java new file mode 100644 index 0000000..00404ff --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/date/LocalDateTimeUtils.java @@ -0,0 +1,76 @@ +package com.maibu.utils.date; + +import cn.hutool.core.date.LocalDateTimeUtil; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * 时间工具类,用于 {@link LocalDateTime} + * + * @author fastbee + */ +public class LocalDateTimeUtils { + + public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + /** + * 空的 LocalDateTime 对象,主要用于 DB 唯一索引的默认值 + */ + public static LocalDateTime EMPTY = buildTime(1970, 1, 1); + + public static LocalDateTime addTime(Duration duration) { + return LocalDateTime.now().plus(duration); + } + + public static boolean beforeNow(LocalDateTime date) { + return date.isBefore(LocalDateTime.now()); + } + + public static boolean afterNow(LocalDateTime date) { + return date.isAfter(LocalDateTime.now()); + } + + /** + * 创建指定时间 + * + * @param year 年 + * @param mouth 月 + * @param day 日 + * @return 指定时间 + */ + public static LocalDateTime buildTime(int year, int mouth, int day) { + return LocalDateTime.of(year, mouth, day, 0, 0, 0); + } + + public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1, + int year2, int mouth2, int day2) { + return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)}; + } + + /** + * 判断当前时间是否在该时间范围内 + * + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 是否 + */ + public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime) { + if (startTime == null || endTime == null) { + return false; + } + return LocalDateTimeUtil.isIn(LocalDateTime.now(), startTime, endTime); + } + + /** + * 时间转字符串 + * @param localDateTime 时间 + * @param: format 格式 + * @return java.lang.String + */ + public static String localDateTimeToStr(LocalDateTime localDateTime, String format) { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format); + return localDateTime.format(dateTimeFormatter); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/file/FileTypeUtils.java b/maibu-common/src/main/java/com/maibu/utils/file/FileTypeUtils.java new file mode 100644 index 0000000..8998a09 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/file/FileTypeUtils.java @@ -0,0 +1,77 @@ +package com.maibu.utils.file; + +import org.apache.commons.lang3.StringUtils; + +import java.io.File; + +/** + * 文件类型工具类 + * + * @author ruoyi + */ +public class FileTypeUtils +{ + /** + * 获取文件类型 + *

+ * 例如: fastbee.txt, 返回: txt + * + * @param file 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(File file) + { + if (null == file) + { + return StringUtils.EMPTY; + } + return getFileType(file.getName()); + } + + /** + * 获取文件类型 + *

+ * 例如: fastbee.txt, 返回: txt + * + * @param fileName 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(String fileName) + { + int separatorIndex = fileName.lastIndexOf("."); + if (separatorIndex < 0) + { + return ""; + } + return fileName.substring(separatorIndex + 1).toLowerCase(); + } + + /** + * 获取文件类型 + * + * @param photoByte 文件字节码 + * @return 后缀(不含".") + */ + public static String getFileExtendName(byte[] photoByte) + { + String strFileExtendName = "JPG"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) + { + strFileExtendName = "GIF"; + } + else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) + { + strFileExtendName = "JPG"; + } + else if ((photoByte[0] == 66) && (photoByte[1] == 77)) + { + strFileExtendName = "BMP"; + } + else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) + { + strFileExtendName = "PNG"; + } + return strFileExtendName; + } +} \ No newline at end of file diff --git a/maibu-common/src/main/java/com/maibu/utils/file/FileUploadUtils.java b/maibu-common/src/main/java/com/maibu/utils/file/FileUploadUtils.java new file mode 100644 index 0000000..4186abc --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/file/FileUploadUtils.java @@ -0,0 +1,233 @@ +package com.maibu.utils.file; + +import com.maibu.config.RuoYiConfig; +import com.maibu.constant.Constants; +import com.maibu.exception.file.FileNameLengthLimitExceededException; +import com.maibu.exception.file.FileSizeLimitExceededException; +import com.maibu.exception.file.InvalidExtensionException; +import com.maibu.utils.DateUtils; +import com.maibu.utils.StringUtils; +import com.maibu.utils.uuid.Seq; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Objects; + +/** + * 文件上传工具类 + * + * @author ruoyi + */ +public class FileUploadUtils +{ + /** + * 默认大小 50M + */ + public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; + + /** + * 默认的文件名最大长度 100 + */ + public static final int DEFAULT_FILE_NAME_LENGTH = 100; + + /** + * 默认上传的地址 + */ + private static String defaultBaseDir = RuoYiConfig.getProfile(); + + public static void setDefaultBaseDir(String defaultBaseDir) + { + FileUploadUtils.defaultBaseDir = defaultBaseDir; + } + + public static String getDefaultBaseDir() + { + return defaultBaseDir; + } + + /** + * 以默认配置进行文件上传 + * + * @param file 上传的文件 + * @return 文件名称 + * @throws Exception + */ + public static final String upload(MultipartFile file) throws IOException + { + try + { + return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + catch (Exception e) + { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 根据文件路径上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @return 文件名称 + * @throws IOException + */ + public static final String upload(String baseDir, MultipartFile file) throws IOException + { + try + { + return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + catch (Exception e) + { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 文件上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param allowedExtension 上传文件类型 + * @return 返回上传成功的文件名 + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws FileNameLengthLimitExceededException 文件名太长 + * @throws IOException 比如读写文件出错时 + * @throws InvalidExtensionException 文件校验异常 + */ + public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, + InvalidExtensionException + { + int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) + { + throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); + } + + assertAllowed(file, allowedExtension); + + String fileName = extractFilename(file); + + String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); + file.transferTo(Paths.get(absPath)); + return getPathFileName(baseDir, fileName); + } + + /** + * 编码文件名 + */ + public static final String extractFilename(MultipartFile file) + { + return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), + FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); + } + + public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException + { + File desc = new File(uploadDir + File.separator + fileName); + + if (!desc.exists()) + { + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + } + return desc; + } + + public static final String getPathFileName(String uploadDir, String fileName) throws IOException + { + int dirLastIndex = RuoYiConfig.getProfile().length() + 1; + String currentDir = StringUtils.substring(uploadDir, dirLastIndex); + return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; + } + + /** + * 文件大小校验 + * + * @param file 上传的文件 + * @return + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws InvalidExtensionException + */ + public static final void assertAllowed(MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, InvalidExtensionException + { + long size = file.getSize(); + if (size > DEFAULT_MAX_SIZE) + { + throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); + } + + String fileName = file.getOriginalFilename(); + String extension = getExtension(file); + if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) + { + if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) + { + throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) + { + throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) + { + throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) + { + throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, + fileName); + } + else + { + throw new InvalidExtensionException(allowedExtension, extension, fileName); + } + } + } + + /** + * 判断MIME类型是否是允许的MIME类型 + * + * @param extension + * @param allowedExtension + * @return + */ + public static final boolean isAllowedExtension(String extension, String[] allowedExtension) + { + for (String str : allowedExtension) + { + if (str.equalsIgnoreCase(extension)) + { + return true; + } + } + return false; + } + + /** + * 获取文件名的后缀 + * + * @param file 表单文件 + * @return 后缀名 + */ + public static final String getExtension(MultipartFile file) + { + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + if (StringUtils.isEmpty(extension)) + { + extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); + } + return extension; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/file/FileUtils.java b/maibu-common/src/main/java/com/maibu/utils/file/FileUtils.java new file mode 100644 index 0000000..baf0c43 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/file/FileUtils.java @@ -0,0 +1,334 @@ +package com.maibu.utils.file; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.IdUtil; +import com.maibu.config.RuoYiConfig; +import com.maibu.utils.DateUtils; +import com.maibu.utils.StringUtils; +import com.maibu.utils.uuid.IdUtils; +import lombok.SneakyThrows; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * 文件处理工具类 + * + * @author ruoyi + */ +public class FileUtils +{ + public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; + + /** + * 输出指定文件的byte数组 + * + * @param filePath 文件路径 + * @param os 输出流 + * @return + */ + public static void writeBytes(String filePath, OutputStream os) throws IOException + { + FileInputStream fis = null; + try + { + File file = new File(filePath); + if (!file.exists()) + { + throw new FileNotFoundException(filePath); + } + fis = new FileInputStream(file); + byte[] b = new byte[1024]; + int length; + while ((length = fis.read(b)) > 0) + { + os.write(b, 0, length); + } + } + catch (IOException e) + { + throw e; + } + finally + { + IOUtils.close(os); + IOUtils.close(fis); + } + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeImportBytes(byte[] data) throws IOException + { + return writeBytes(data, RuoYiConfig.getImportPath()); + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @param uploadDir 目标文件 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeBytes(byte[] data, String uploadDir) throws IOException + { + FileOutputStream fos = null; + String pathName = ""; + try + { + String extension = getFileExtendName(data); + pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; + File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName); + fos = new FileOutputStream(file); + fos.write(data); + } + finally + { + IOUtils.close(fos); + } + return FileUploadUtils.getPathFileName(uploadDir, pathName); + } + + /** + * 删除文件 + * + * @param filePath 文件 + * @return + */ + public static boolean deleteFile(String filePath) + { + boolean flag = false; + File file = new File(filePath); + // 路径为文件且不为空则进行删除 + if (file.isFile() && file.exists()) + { + flag = file.delete(); + } + return flag; + } + + /** + * 文件名称验证 + * + * @param filename 文件名称 + * @return true 正常 false 非法 + */ + public static boolean isValidFilename(String filename) + { + return filename.matches(FILENAME_PATTERN); + } + + /** + * 检查文件是否可下载 + * + * @param resource 需要下载的文件 + * @return true 正常 false 非法 + */ + public static boolean checkAllowDownload(String resource) + { + // 禁止目录上跳级别 + if (StringUtils.contains(resource, "..")) + { + return false; + } + + // 检查允许下载的文件规则 + if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) + { + return true; + } + + // 不在允许下载的文件规则 + return false; + } + + /** + * 下载文件名重新编码 + * + * @param request 请求对象 + * @param fileName 文件名 + * @return 编码后的文件名 + */ + public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException + { + final String agent = request.getHeader("USER-AGENT"); + String filename = fileName; + if (agent.contains("MSIE")) + { + // IE浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + filename = filename.replace("+", " "); + } + else if (agent.contains("Firefox")) + { + // 火狐浏览器 + filename = new String(fileName.getBytes(), "ISO8859-1"); + } + else if (agent.contains("Chrome")) + { + // google浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + else + { + // 其它浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + return filename; + } + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException + { + String percentEncodedFileName = percentEncode(realFileName); + + StringBuilder contentDispositionValue = new StringBuilder(); + contentDispositionValue.append("attachment; filename=") + .append(percentEncodedFileName) + .append(";") + .append("filename*=") + .append("utf-8''") + .append(percentEncodedFileName); + + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); + response.setHeader("Content-disposition", contentDispositionValue.toString()); + response.setHeader("download-filename", percentEncodedFileName); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public static String percentEncode(String s) throws UnsupportedEncodingException + { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); + return encode.replaceAll("\\+", "%20"); + } + + /** + * 获取图像后缀 + * + * @param photoByte 图像数据 + * @return 后缀名 + */ + public static String getFileExtendName(byte[] photoByte) + { + String strFileExtendName = "jpg"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) + { + strFileExtendName = "gif"; + } + else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) + { + strFileExtendName = "jpg"; + } + else if ((photoByte[0] == 66) && (photoByte[1] == 77)) + { + strFileExtendName = "bmp"; + } + else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) + { + strFileExtendName = "png"; + } + return strFileExtendName; + } + + /** + * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png + * + * @param fileName 路径名称 + * @return 没有文件路径的名称 + */ + public static String getName(String fileName) + { + if (fileName == null) + { + return null; + } + int lastUnixPos = fileName.lastIndexOf('/'); + int lastWindowsPos = fileName.lastIndexOf('\\'); + int index = Math.max(lastUnixPos, lastWindowsPos); + return fileName.substring(index + 1); + } + + /** + * 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi + * + * @param fileName 路径名称 + * @return 没有文件路径和后缀的名称 + */ + public static String getNameNotSuffix(String fileName) + { + if (fileName == null) + { + return null; + } + String baseName = FilenameUtils.getBaseName(fileName); + return baseName; + } + + /** + * 创建临时文件 + * 该文件会在 JVM 退出时,进行删除 + * + * @param data 文件内容 + * @return 文件 + */ + @SneakyThrows + public static File createTempFile(String data) { + File file = createTempFile(); + // 写入内容 + FileUtil.writeUtf8String(data, file); + return file; + } + + /** + * 创建临时文件 + * 该文件会在 JVM 退出时,进行删除 + * + * @param data 文件内容 + * @return 文件 + */ + @SneakyThrows + public static File createTempFile(byte[] data) { + File file = createTempFile(); + // 写入内容 + FileUtil.writeBytes(data, file); + return file; + } + + /** + * 创建临时文件,无内容 + * 该文件会在 JVM 退出时,进行删除 + * + * @return 文件 + */ + @SneakyThrows + public static File createTempFile() { + // 创建文件,通过 UUID 保证唯一 + File file = File.createTempFile(IdUtil.simpleUUID(), null); + // 标记 JVM 退出时,自动删除 + file.deleteOnExit(); + return file; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/file/ImageUtils.java b/maibu-common/src/main/java/com/maibu/utils/file/ImageUtils.java new file mode 100644 index 0000000..f5ea6dd --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/file/ImageUtils.java @@ -0,0 +1,99 @@ +package com.maibu.utils.file; + +import com.maibu.config.RuoYiConfig; +import com.maibu.constant.Constants; +import com.maibu.utils.StringUtils; +import org.apache.poi.util.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; + +/** + * 图片处理工具类 + * + * @author ruoyi + */ +public class ImageUtils +{ + private static final Logger log = LoggerFactory.getLogger(ImageUtils.class); + + public static byte[] getImage(String imagePath) + { + InputStream is = getFile(imagePath); + try + { + return IOUtils.toByteArray(is); + } + catch (Exception e) + { + log.error("图片加载异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(is); + } + } + + public static InputStream getFile(String imagePath) + { + try + { + byte[] result = readFile(imagePath); + result = Arrays.copyOf(result, result.length); + return new ByteArrayInputStream(result); + } + catch (Exception e) + { + log.error("获取图片异常 {}", e); + } + return null; + } + + /** + * 读取文件为字节数据 + * + * @param url 地址 + * @return 字节数据 + */ + public static byte[] readFile(String url) + { + InputStream in = null; + try + { + if (url.startsWith("http")) + { + // 网络地址 + URL urlObj = new URL(url); + URLConnection urlConnection = urlObj.openConnection(); + urlConnection.setConnectTimeout(30 * 1000); + urlConnection.setReadTimeout(60 * 1000); + urlConnection.setDoInput(true); + in = urlConnection.getInputStream(); + } + else + { + // 本机地址 + String localPath = RuoYiConfig.getProfile(); + String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX); + in = new FileInputStream(downloadPath); + } + return IOUtils.toByteArray(in); + } + catch (Exception e) + { + log.error("获取文件路径异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(in); + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/file/MimeTypeUtils.java b/maibu-common/src/main/java/com/maibu/utils/file/MimeTypeUtils.java new file mode 100644 index 0000000..7a4dd6b --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/file/MimeTypeUtils.java @@ -0,0 +1,59 @@ +package com.maibu.utils.file; + +/** + * 媒体类型工具类 + * + * @author ruoyi + */ +public class MimeTypeUtils +{ + public static final String IMAGE_PNG = "image/png"; + + public static final String IMAGE_JPG = "image/jpg"; + + public static final String IMAGE_JPEG = "image/jpeg"; + + public static final String IMAGE_BMP = "image/bmp"; + + public static final String IMAGE_GIF = "image/gif"; + + public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" }; + + public static final String[] FLASH_EXTENSION = { "swf", "flv" }; + + public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", + "asf", "rm", "rmvb" }; + + public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" }; + + public static final String[] DEFAULT_ALLOWED_EXTENSION = { + // 图片 + "bmp", "gif", "jpg", "jpeg", "png", "svg", + // word excel powerpoint + "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", + // 压缩文件 + "rar", "zip", "gz", "bz2", + // 视频格式 + "mp4", "avi", "rmvb", + // pdf + "pdf" }; + + public static String getExtension(String prefix) + { + switch (prefix) + { + case IMAGE_PNG: + return "png"; + case IMAGE_JPG: + return "jpg"; + case IMAGE_JPEG: + return "jpeg"; + case IMAGE_BMP: + return "bmp"; + case IMAGE_GIF: + return "gif"; + default: + return ""; + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/file/QRCodeUtils.java b/maibu-common/src/main/java/com/maibu/utils/file/QRCodeUtils.java new file mode 100644 index 0000000..27058bc --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/file/QRCodeUtils.java @@ -0,0 +1,68 @@ +package com.maibu.utils.file; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class QRCodeUtils { + + /** + * 生成二维码 + * @param content 二维码的内容 + * @return BitMatrix对象 + * */ + public static BitMatrix createCode(String content) throws IOException { + //二维码的宽高 + int width = 200; + int height = 200; + + //其他参数,如字符集编码 + Map hints = new HashMap(); + hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); + //容错级别为H + hints.put(EncodeHintType.ERROR_CORRECTION , ErrorCorrectionLevel.H); + //白边的宽度,可取0~4 + hints.put(EncodeHintType.MARGIN , 0); + + BitMatrix bitMatrix = null; + try { + //生成矩阵,因为我的业务场景传来的是编码之后的URL,所以先解码 + bitMatrix = new MultiFormatWriter().encode(content, + BarcodeFormat.QR_CODE, width, height, hints); + + //bitMatrix = deleteWhite(bitMatrix); + } catch (WriterException e) { + e.printStackTrace(); + } + + return bitMatrix; + } + + /** + * 删除生成的二维码周围的白边,根据审美决定是否删除 + * @param matrix BitMatrix对象 + * @return BitMatrix对象 + * */ + private static BitMatrix deleteWhite(BitMatrix matrix) { + int[] rec = matrix.getEnclosingRectangle(); + int resWidth = rec[2] + 1; + int resHeight = rec[3] + 1; + + BitMatrix resMatrix = new BitMatrix(resWidth, resHeight); + resMatrix.clear(); + for (int i = 0; i < resWidth; i++) { + for (int j = 0; j < resHeight; j++) { + if (matrix.get(i + rec[0], j + rec[1])) + resMatrix.set(i, j); + } + } + return resMatrix; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/gateway/CRC16Utils.java b/maibu-common/src/main/java/com/maibu/utils/gateway/CRC16Utils.java new file mode 100644 index 0000000..2505017 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/gateway/CRC16Utils.java @@ -0,0 +1,149 @@ +package com.maibu.utils.gateway; + + +import com.maibu.utils.CaculateUtils; +import com.maibu.utils.gateway.protocol.ByteUtils; +import org.apache.commons.lang3.ArrayUtils; + +public class CRC16Utils { + + //ff + private static int CRC_FF = 0x000000ff; + //01 + private static int CRC_01 = 0x00000001; + //04 + private static final int LENGTH_04 = 4; + //16进制 + private static final int OXFF = 0xff; + + /** + * 低位在前,高位在后 + * + * @param bytes + * @return + */ + public static String getCRC(byte[] bytes) { + return getCRC(bytes, true); + } + + /** + * @param bytes + * @param lb 是否低位在前, 高位在后 + * @return + */ + public static String getCRC(byte[] bytes, boolean lb) { + int CRC = 0x0000ffff; + int POLYNOMIAL = 0x0000a001; + + int i, j; + for (i = 0; i < bytes.length; i++) { + CRC ^= ((int) bytes[i] & 0x000000ff); + for (j = 0; j < 8; j++) { + if ((CRC & 0x00000001) != 0) { + CRC >>= 1; + CRC ^= POLYNOMIAL; + } else { + CRC >>= 1; + } + } + } + + //结果转换为16进制 + String result = Integer.toHexString(CRC).toUpperCase(); + if (result.length() != 4) { + StringBuffer sb = new StringBuffer("0000"); + result = sb.replace(4 - result.length(), 4, result).toString(); + } + + if (lb) { // 低位在前, 高位在后 + result = result.substring(2, 4) + result.substring(0, 2); + } + + return result; + } + + /** + * 计算CRC校验和 + * + * @param bytes + * @return 返回 byte[] + */ + public static byte[] getCrc16Byte(byte[] bytes) { + + //寄存器全为1 + int CRC_16 = 0x0000ffff; + // 多项式校验值 + int POLYNOMIAL = 0x0000a001; + for (byte aByte : bytes) { + CRC_16 ^= ((int) aByte & CRC_FF); + for (int j = 0; j < 8; j++) { + if ((CRC_16 & CRC_01) != 0) { + CRC_16 >>= 1; + CRC_16 ^= POLYNOMIAL; + } else { + CRC_16 >>= 1; + } + } + } + // 低8位 ,高8位 + return new byte[]{(byte) (CRC_16 & OXFF), (byte) (CRC_16 >> 8 & OXFF)}; + } + + public static byte[] AddCRC(byte[] source) { + byte[] result = new byte[source.length + 2]; + byte[] crc16Byte = CRC16Utils.getCrc16Byte(source); + System.arraycopy(source, 0, result, 0, source.length); + System.arraycopy(crc16Byte, 0, result, result.length - 2, 2); + return result; + } + + public static byte[] AddPakCRC(byte[] source) { + byte[] subarray = ArrayUtils.subarray(source, 11, source.length); + byte[] result = new byte[source.length + 2]; + byte[] crc16Byte = CRC16Utils.getCrc16Byte(subarray); + System.arraycopy(source, 0, result, 0, source.length); + System.arraycopy(crc16Byte, 0, result, result.length - 2, 2); + return result; + } + + public static byte[] CRC(byte[] source) { + source[2] = (byte) ((int) source[2] * 2); + byte[] result = new byte[source.length + 2]; + byte[] crc16Byte = CRC16Utils.getCrc16Byte(source); + System.arraycopy(source, 0, result, 0, source.length); + System.arraycopy(crc16Byte, 0, result, result.length - 2, 2); + return result; + } + + public static byte CRC8(byte[] buffer) { + int crci = 0xFF; //起始字节FF + for (int j = 0; j < buffer.length; j++) { + crci ^= buffer[j] & 0xFF; + for (int i = 0; i < 8; i++) { + if ((crci & 1) != 0) { + crci >>= 1; + crci ^= 0xB8; //多项式当中的那个啥的,不同多项式不一样 + } else { + crci >>= 1; + } + } + } + return (byte) crci; + } + + + public static void main(String[] args)throws Exception { + String hex = "0103028000"; + byte[] bytes = ByteUtils.hexToByte(hex); + String crc = getCRC(bytes); + System.out.println(crc); + String crc8 = "680868333701120008C100"; + byte[] byte8 = ByteUtils.hexToByte(crc8); + int b = CRC8(byte8); + System.out.println((int) b); + float v = CaculateUtils.toFloat32_CDAB(bytes); + System.out.println(v); + } + + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/gateway/CRC8Utils.java b/maibu-common/src/main/java/com/maibu/utils/gateway/CRC8Utils.java new file mode 100644 index 0000000..b8ce799 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/gateway/CRC8Utils.java @@ -0,0 +1,94 @@ +package com.maibu.utils.gateway; + + +import com.maibu.utils.gateway.protocol.ByteUtils; + +/** + * @author gsb + * @date 2023/5/19 11:33 + */ +public class CRC8Utils { + + + //TODO:-----------------根据C写法转译-------------------------------------- + /* CRC-8, poly = x^8 + x^2 + x^1 + x^0, init = 0 */ + + /** + * CRC8 校验 多项式 x8+x2+x+1 + * @param data + * @return 校验和 + */ + public static byte calcCrc8_E5(byte[] data){ + byte crc = 0; + for (int j = 0; j < data.length; j++) { + crc ^= data[j]; + for (int i = 0; i < 8; i++) { + if ((crc & 0x80) != 0) { + crc = (byte) ((crc)<< 1); + crc ^= 0xE5; + } else { + crc = (byte) ((crc)<< 1); + } + } + } + return crc; + } + + + + + + static byte[] crc8_tab = {(byte) 0, (byte) 94, (byte) 188, (byte) 226, (byte) 97, (byte) 63, (byte) 221, (byte) 131, (byte) 194, (byte) 156, (byte) 126, (byte) 32, (byte) 163, (byte) 253, (byte) 31, (byte) 65, (byte) 157, (byte) 195, (byte) 33, (byte) 127, (byte) 252, (byte) 162, (byte) 64, (byte) 30, (byte) 95, (byte) 1, (byte) 227, (byte) 189, (byte) 62, (byte) 96, (byte) 130, (byte) 220, (byte) 35, (byte) 125, (byte) 159, (byte) 193, (byte) 66, (byte) 28, (byte) 254, (byte) 160, (byte) 225, (byte) 191, (byte) 93, (byte) 3, (byte) 128, (byte) 222, (byte) 60, (byte) 98, (byte) 190, (byte) 224, (byte) 2, (byte) 92, (byte) 223, (byte) 129, (byte) 99, (byte) 61, (byte) 124, (byte) 34, (byte) 192, (byte) 158, (byte) 29, (byte) 67, (byte) 161, (byte) 255, (byte) 70, (byte) 24, + (byte) 250, (byte) 164, (byte) 39, (byte) 121, (byte) 155, (byte) 197, (byte) 132, (byte) 218, (byte) 56, (byte) 102, (byte) 229, (byte) 187, (byte) 89, (byte) 7, (byte) 219, (byte) 133, (byte) 103, (byte) 57, (byte) 186, (byte) 228, (byte) 6, (byte) 88, (byte) 25, (byte) 71, (byte) 165, (byte) 251, (byte) 120, (byte) 38, (byte) 196, (byte) 154, (byte) 101, (byte) 59, (byte) 217, (byte) 135, (byte) 4, (byte) 90, (byte) 184, (byte) 230, (byte) 167, (byte) 249, (byte) 27, (byte) 69, (byte) 198, (byte) 152, (byte) 122, (byte) 36, (byte) 248, (byte) 166, (byte) 68, (byte) 26, (byte) 153, (byte) 199, (byte) 37, (byte) 123, (byte) 58, (byte) 100, (byte) 134, (byte) 216, (byte) 91, (byte) 5, (byte) 231, (byte) 185, (byte) 140, (byte) 210, (byte) 48, (byte) 110, (byte) 237, + (byte) 179, (byte) 81, (byte) 15, (byte) 78, (byte) 16, (byte) 242, (byte) 172, (byte) 47, (byte) 113, (byte) 147, (byte) 205, (byte) 17, (byte) 79, (byte) 173, (byte) 243, (byte) 112, (byte) 46, (byte) 204, (byte) 146, (byte) 211, (byte) 141, (byte) 111, (byte) 49, (byte) 178, (byte) 236, (byte) 14, (byte) 80, (byte) 175, (byte) 241, (byte) 19, (byte) 77, (byte) 206, (byte) 144, (byte) 114, (byte) 44, (byte) 109, (byte) 51, (byte) 209, (byte) 143, (byte) 12, (byte) 82, (byte) 176, (byte) 238, (byte) 50, (byte) 108, (byte) 142, (byte) 208, (byte) 83, (byte) 13, (byte) 239, (byte) 177, (byte) 240, (byte) 174, (byte) 76, (byte) 18, (byte) 145, (byte) 207, (byte) 45, (byte) 115, (byte) 202, (byte) 148, (byte) 118, (byte) 40, (byte) 171, (byte) 245, (byte) 23, (byte) 73, (byte) 8, + (byte) 86, (byte) 180, (byte) 234, (byte) 105, (byte) 55, (byte) 213, (byte) 139, (byte) 87, (byte) 9, (byte) 235, (byte) 181, (byte) 54, (byte) 104, (byte) 138, (byte) 212, (byte) 149, (byte) 203, (byte) 41, (byte) 119, (byte) 244, (byte) 170, (byte) 72, (byte) 22, (byte) 233, (byte) 183, (byte) 85, (byte) 11, (byte) 136, (byte) 214, (byte) 52, (byte) 106, (byte) 43, (byte) 117, (byte) 151, (byte) 201, (byte) 74, (byte) 20, (byte) 246, (byte) 168, (byte) 116, (byte) 42, (byte) 200, (byte) 150, (byte) 21, (byte) 75, (byte) 169, (byte) 247, (byte) 182, (byte) 232, (byte) 10, (byte) 84, (byte) 215, (byte) 137, (byte) 107, 53}; + + /** + * 计算数组的CRC8校验值 + * + * @param data 需要计算的数组 + * @return CRC8校验值 + */ + public static byte calcCrc8(byte[] data) { + return calcCrc8(data, 0, data.length, (byte) 0); + } + + /** + * 计算CRC8校验值 + * + * @param data 数据 + * @param offset 起始位置 + * @param len 长度 + * @return 校验值 + */ + public static byte calcCrc8(byte[] data, int offset, int len) { + return calcCrc8(data, offset, len, (byte) 0); + } + + /** + * 计算CRC8校验值 + * + * @param data 数据 + * @param offset 起始位置 + * @param len 长度 + * @param preval 之前的校验值 + * @return 校验值 + */ + public static byte calcCrc8(byte[] data, int offset, int len, byte preval) { + byte ret = preval; + for (int i = offset; i < (offset + len); ++i) { + ret = crc8_tab[(0x00ff & (ret ^ data[i]))]; + } + return ret; + } + + // 测试 + public static void main(String[] args) { + String hex = "333701120008C100"; + byte[] bytes = ByteUtils.hexToByte(hex); + byte crc = CRC8Utils.calcCrc8_E5(bytes); + System.out.println("" + Integer.toHexString(0x00ff & crc)); + } + + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/gateway/mq/Topics.java b/maibu-common/src/main/java/com/maibu/utils/gateway/mq/Topics.java new file mode 100644 index 0000000..81c35cc --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/gateway/mq/Topics.java @@ -0,0 +1,16 @@ +package com.maibu.utils.gateway.mq; + +import lombok.Data; + +/** + * @author bill + */ +@Data +public class Topics { + + + private String topicName; + private Integer qos =0; + private String desc; + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/gateway/mq/TopicsPost.java b/maibu-common/src/main/java/com/maibu/utils/gateway/mq/TopicsPost.java new file mode 100644 index 0000000..b7df747 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/gateway/mq/TopicsPost.java @@ -0,0 +1,14 @@ +package com.maibu.utils.gateway.mq; + +import lombok.Data; + +/** + * @author gsb + * @date 2023/2/27 13:41 + */ +@Data +public class TopicsPost { + + private String[] topics; + private int[] qos; +} diff --git a/maibu-common/src/main/java/com/maibu/utils/gateway/mq/TopicsUtils.java b/maibu-common/src/main/java/com/maibu/utils/gateway/mq/TopicsUtils.java new file mode 100644 index 0000000..8134e4f --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/gateway/mq/TopicsUtils.java @@ -0,0 +1,339 @@ +package com.maibu.utils.gateway.mq; + +import com.maibu.constant.FastBeeConstant; +import com.maibu.enums.TopicType; +import org.springframework.util.StringUtils; +import com.maibu.utils.collection.CollectionUtils; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.*; + +/** + * topic工具类 + * + * @author gsb + * @date 2022/9/15 16:49 + */ +@Slf4j +@Component +public class TopicsUtils { + + @Value("${server.broker.enabled}") + private Boolean enabled; + + /** + * 拼接topic + * + * @param productId 产品id + * @param serialNumber 设备编号 + * @param type 主题类型 + * @return topic + */ + public String buildTopic(Long productId, String serialNumber, TopicType type) { + /* + * 订阅属性: + * 如果启动emq 则为 /+/+/property/post + * 如果启动netty的mqttBroker 则为 /{productId}/{serialNumber}/property/post + * + * 发布都为:/{productId}/{serialNumber}/property/get + */ + String product = String.valueOf(productId); + if (null == productId || productId == -1L || productId == 0L) { + product = "+"; + } + if (StringUtils.isEmpty(serialNumber)) { + serialNumber = "+"; + } + if (type.getType() == 0) { + return enabled ? "/" + product + "/" + serialNumber + type.getTopicSuffix() : FastBeeConstant.MQTT.PREDIX + type.getTopicSuffix(); + } else { + return "/" + product + "/" + serialNumber + type.getTopicSuffix(); + } + } + + public String buildTopic(String serialNumber, TopicType type) { + /* + * 订阅属性: + * 如果启动emq 则为 /+/+/property/post + * 如果启动netty的mqttBroker 则为 /{productId}/{serialNumber}/property/post + * + * 发布都为:/{productId}/{serialNumber}/property/get + */ + + if (StringUtils.isEmpty(serialNumber)) { + serialNumber = "+"; + } + if (type.getType() == 0) { + return enabled ? "/" + serialNumber + type.getTopicSuffix() : FastBeeConstant.MQTT.PREDIX + type.getTopicSuffix(); + } else { + return "/" + serialNumber + type.getTopicSuffix(); + } + } + /** + * 获取所有可订阅的主题 + * + * @return 订阅主题列表 + */ + public TopicsPost getAllPost() { + List qos = new ArrayList<>(); + List topics = new ArrayList<>(); + TopicsPost post = new TopicsPost(); + for (TopicType topicType : TopicType.values()) { + if (topicType.getType() == 0) { + String topic = this.buildTopic(0L, null, topicType); + topics.add(topic); + qos.add(1); + } + } + post.setTopics(topics.toArray(new String[0])); + int[] ints = Arrays.stream(qos.toArray(new Integer[0])).mapToInt(Integer::valueOf).toArray(); + post.setQos(ints); + return post; + } + + /** + * 获取所有get topic + * + * @param isSimulate 是否是设备模拟 + * @return list + */ + public static List getAllGet(boolean isSimulate) { + List result = new ArrayList<>(); + for (TopicType type : TopicType.values()) { + if (type.getType() == 4) { + Topics topics = new Topics(); + topics.setTopicName(type.getTopicSuffix()); + topics.setDesc(type.getMsg()); + topics.setQos(1); + result.add(topics); + if (isSimulate && type == TopicType.PROPERTY_GET) { + result.remove(topics); + } + } + } + return result; + } + + + /** + * 替换topic中的产品编码和设备编码,唯一作用是在系统收到来自网关设备上报子设备消息时将topic进行替换 + * + * @param orgTopic String 原始topic + * @param productId String 目标产品编码 + * @param serialNumber String 目标设备编码 + * @return 替换产品编码和设备编码后的新topic + */ + public String topicSubDevice(String orgTopic, Long productId, String serialNumber) { + if (StringUtils.isEmpty(orgTopic)) { + return orgTopic; + } + String[] splits = orgTopic.split("/"); + StringBuilder sb = new StringBuilder(splits[0]) + .append("/") + .append(productId) + .append("/") + .append(serialNumber); + for (int index = 3; index < splits.length; index++) { + sb.append("/").append(splits[index]); + } + return sb.toString(); + } + + /** + * 从topic中获取IMEI号 IMEI即是设备编号 + * + * @param topic /{productId}/{serialNumber}/property/post + * @return serialNumber + */ + @SneakyThrows + public Long parseProductId(String topic) { + String[] values = topic.split("/"); + return Long.parseLong(values[1]); + } + + + /** + * 从topic中获取IMEI号 IMEI即是设备编号 + * + * @param topic /{productId}/{serialNumber}/property/post + * @return serialNumber + */ + @SneakyThrows + public String parseSerialNumber(String topic) { + String[] split = topic.split("/"); + String sn = Arrays.stream(split).filter(imei -> imei.length() > 9).findFirst().get(); + return sn; + } + + /** + * 获取topic 判断字段 name + **/ + public String parseTopicName(String topic) { + String[] values = topic.split("/"); + if (values.length >2){ + return "/"+ values[3] + "/" + values[4]; + }else { + return null; + } + } + + /** + * 获取topic 判断字段 name + **/ + public String parseTopicName4(String topic) { + String[] values = topic.split("/"); + return values[4]; + } + + /** + * 从topic解析物模型类型 + * + * @param topic /{productId}/{serialNumber}/property/post + * @return 物模型类型 + */ + @SneakyThrows + public String getThingsModel(String topic) { + String[] split = topic.split("/"); + return split[2].toUpperCase(); + } + + /** + * 检查topic的合法性 + * + * @param topicNameList 主题list + * @return 验证结果 + */ + public static boolean validTopicFilter(List topicNameList) { + for (String topicName : topicNameList) { + if (StringUtils.isEmpty(topicName)) { + return false; + } + /*以#或+符号开头的、以/符号结尾的及不存在/符号的订阅按非法订阅处理*/ + if (StringUtils.startsWithIgnoreCase(topicName, "#") || StringUtils.startsWithIgnoreCase(topicName, "+") || StringUtils.endsWithIgnoreCase(topicName, "/") || !topicName.contains("/")) { + return false; + } + if (topicName.contains("#")) { + /*不是以/#字符串结尾的订阅按非法订阅处理*/ + if (!StringUtils.endsWithIgnoreCase(topicName, "/#")) { + return false; + } + /*如果出现多个#符号的订阅按非法订阅处理*/ + if (StringUtils.countOccurrencesOf(topicName, "#") > 1) { + return false; + } + } + if (topicName.contains("+")) { + /*如果+符号和/+字符串出现的次数不等的情况按非法订阅处理*/ + if (StringUtils.countOccurrencesOf(topicName, "+") != StringUtils.countOccurrencesOf(topicName, "/+")) { + return false; + } + } + } + return true; + } + + /** + * 判断topic与topicFilter是否匹配,topic与topicFilter需要符合协议规范 + * + * @param topic: 主题 + * @param topicFilter: 主题过滤器 + * @return boolean + * @author ZhangJun + * @date 23:57 2021/2/27 + */ + public static boolean matchTopic(String topic, String topicFilter) { + if (topic.contains("+") || topic.contains("#")) { + + String[] topicSpilts = topic.split("/"); + String[] filterSpilts = topicFilter.split("/"); + + if (!topic.contains("#") && topicSpilts.length < filterSpilts.length) { + return false; + } + + String level; + for (int i = 0; i < topicSpilts.length; i++) { + level = topicSpilts[i]; + if (!level.equals(filterSpilts[i]) && !level.equals("+") && !level.equals("#")) { + return false; + } + } + } else { + return topic.equals(topicFilter); + } + return true; + } + + /** + * 根据指定topic搜索所有订阅的topic + * 指定的topic没有通配符,但是订阅的时候可能会存在通配符,所以有个查找的过程 + * + * @param topic 主题 + * @return 返回的所有主题 + */ + public static List searchTopic(String topic) { + try { + List topicList = new ArrayList<>(); + topicList.add(topic); + /*先处理#通配符*/ + String[] filterDup = topic.split("/"); + int[] source = new int[filterDup.length]; + String itemTopic = ""; + for (int i = 0; i < filterDup.length; i++) { + String item = itemTopic.concat("#"); + topicList.add(item); + itemTopic = itemTopic.concat(filterDup[i]).concat("/"); + source[i] = i; + continue; + } + /*处理+通配符*/ + Map, Boolean> map = TopicsUtils.handle(source); + for (List key : map.keySet()) { + String[] arr = CollectionUtils.copy(filterDup); + for (Integer index : key) { + arr[index] = "+"; + } + String newTopic = CollectionUtils.concat(arr, "/"); + topicList.add(newTopic); + } + return topicList; + } catch (Exception e) { + log.error("=>查询topic异常", e); + return null; + } + } + + + public static Map, Boolean> handle(int[] src) { + int nCnt = src.length; + int nBit = (0xFFFFFFFF >>> (32 - nCnt)); + Map, Boolean> map = new HashMap<>(); + for (int i = 1; i <= nBit; i++) { + List list = new ArrayList<>(); + for (int j = 0; j < nCnt; j++) { + if ((i << (31 - j)) >> 31 == -1) { + list.add(j); + } + } + map.put(list, true); + } + return map; + } + + /** + * 构建场景变量上报主题 + * @param sceneModelId 场景id + * @param: sceneModelDeviceId 场景来源id + * @return java.lang.String + */ + public static String buildSceneReportTopic(Long sceneModelId, Long sceneModelDeviceId) { + return "/" + sceneModelId + "/" + sceneModelDeviceId + "/scene/report"; + } + + public static String buildRuleEngineTopic(String requestId) { + return "/" + requestId + "/ruleengine/test"; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/gateway/protocol/ByteUtils.java b/maibu-common/src/main/java/com/maibu/utils/gateway/protocol/ByteUtils.java new file mode 100644 index 0000000..e458218 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/gateway/protocol/ByteUtils.java @@ -0,0 +1,958 @@ +package com.maibu.utils.gateway.protocol; + + +import com.maibu.exception.ServiceException; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + + +public class ByteUtils { + + + // public static Payload resolvePayload(byte[] content, short start, ModbusCode code) { + // Payload payload; + // switch (code) { + // case Read01: + // case Read02: + // payload = new RealCoilPayload(start, content); break; + // case Read03: + // case Read04: + // payload = new ReadPayload(content, start); break; + // default: + // payload = WritePayload.getInstance(); + // } + // + // return payload; + // } + + public static Write10Build write10Build(Object... args) { + int num = 0; List bytes = new ArrayList<>(); + for(Object arg : args) { + if(arg instanceof Integer) { + num += 2; + bytes.add(getBytes((Integer) arg)); + } else if(arg instanceof Long) { + num += 4; + bytes.add(getBytes((Long) arg)); + } else if(arg instanceof Float) { + num += 2; + bytes.add(getBytes((Float) arg)); + } else if(arg instanceof Double) { + num += 4; + bytes.add(getBytes((Double) arg)); + } else if(arg instanceof Short) { + num += 1; + bytes.add(getBytesOfReverse((Short) arg)); + } else if(arg instanceof Byte) { + num += 1; + bytes.add(new byte[]{0x00, (byte) arg}); + } else if(arg instanceof String) { + byte[] bytes1 = arg.toString().getBytes(StandardCharsets.UTF_8); + if(bytes1.length % 2 != 0) { + num += bytes1.length / 2 + 1; + byte[] addMessage = new byte[bytes1.length + 1]; + addBytes(addMessage, bytes1, 0); + bytes.add(addMessage); + } else { + num += bytes1.length / 2; + bytes.add(bytes1); + } + } else { + throw new ServiceException("不支持的数据类型"); + } + } + + Integer length = bytes.stream().map(item -> item.length).reduce((a, b) -> a + b).get(); + byte[] write = new byte[length]; + + int index = 0; + for(int i=0; i> 8); + return bytes; + } + + /** + * 将short数据类型转化成Byte数组 + * @see ByteOrder#BIG_ENDIAN + * @param data short值 + * @return byte[]数组 + */ + public static byte[] getBytesOfReverse(short data) { + byte[] bytes = new byte[2]; + bytes[1] = (byte) (data & 0xff); + bytes[0] = (byte) ((data & 0xff00) >> 8); + return bytes; + } + + /** + * @see ByteOrder#LITTLE_ENDIAN + * @param bytes + * @param offset + * @return + */ + public static short bytesToShort(byte[] bytes, int offset) { + return (short) ((0xff & bytes[0 + offset]) | (0xff00 & (bytes[1 + offset] << 8))); + } + + /** + * 将字节数组转换成short数据 + * @see ByteOrder#BIG_ENDIAN + * @param bytes 字节数组 + * @return short值 + */ + public static short bytesToShortOfReverse(byte[] bytes) { + return ByteUtils.bytesToShortOfReverse(bytes, 0); + } + + /** + * 将字节数组转换成short数据,采用倒序的表达方式 + * @param bytes 字节数组 + * @param offset 起始位置 + * @return short值 + */ + public static short bytesToShortOfReverse(byte[] bytes, int offset) { + return (short) ((0xff & bytes[1 + offset]) | (0xff00 & (bytes[0 + offset] << 8))); + } + + /** + * 将int数据类型转化成Byte数组 + * @see ByteOrder#LITTLE_ENDIAN + * @param data int值 + * @return byte[]数组 + */ + public static byte[] getBytes(int data) { + byte[] bytes = new byte[4]; + bytes[0] = (byte) (data & 0xff); + bytes[1] = (byte) ((data >> 8) & 0xff); + bytes[2] = (byte) ((data >> 16) & 0xff); + bytes[3] = (byte) ((data >> 24) & 0xff); + return bytes; + } + + /** + * 将int数据类型转化成Byte数组 倒序 + * @see ByteOrder#BIG_ENDIAN + * @param data int值 + * @return byte[]数组 + */ + public static byte[] getBytesOfReverse(int data) { + byte[] src = new byte[4]; + src[0] = (byte) ((data >> 24) & 0xFF); + src[1] = (byte) ((data >> 16) & 0xFF); + src[2] = (byte) ((data >> 8) & 0xFF); + src[3] = (byte) (data & 0xFF); + return src; + } + + /** + * 将long数据类型转化成Byte数组 + * @see ByteOrder#LITTLE_ENDIAN + * @param data long值 + * @return byte[]数组 + */ + public static byte[] getBytes(long data) { + byte[] bytes = new byte[8]; + bytes[0] = (byte) (data & 0xff); + bytes[1] = (byte) ((data >> 8) & 0xff); + bytes[2] = (byte) ((data >> 16) & 0xff); + bytes[3] = (byte) ((data >> 24) & 0xff); + bytes[4] = (byte) ((data >> 32) & 0xff); + bytes[5] = (byte) ((data >> 40) & 0xff); + bytes[6] = (byte) ((data >> 48) & 0xff); + bytes[7] = (byte) ((data >> 56) & 0xff); + return bytes; + } + + /** + * 将long数据类型转化成Byte数组 倒序 + * @see ByteOrder#BIG_ENDIAN + * @param data long值 + * @return byte[]数组 + */ + public static byte[] getBytesOfReverse(long data) { + byte[] bytes = new byte[8]; + bytes[7] = (byte) (data & 0xff); + bytes[6] = (byte) ((data >> 8) & 0xff); + bytes[5] = (byte) ((data >> 16) & 0xff); + bytes[4] = (byte) ((data >> 24) & 0xff); + bytes[3] = (byte) ((data >> 32) & 0xff); + bytes[2] = (byte) ((data >> 40) & 0xff); + bytes[1] = (byte) ((data >> 48) & 0xff); + bytes[0] = (byte) ((data >> 56) & 0xff); + return bytes; + } + + /** + * 将float数据类型转化成Byte数组 + * @see ByteOrder#LITTLE_ENDIAN + * @param data float值 + * @return byte[]数组 + */ + public static byte[] getBytes(float data) { + int intBits = Float.floatToIntBits(data); + return getBytes(intBits); + } + + /** + * 将float数据类型转化成Byte数组 倒序 + * @see ByteOrder#BIG_ENDIAN + * @param data float值 + * @return byte[]数组 + */ + public static byte[] getBytesOfReverse(float data) { + int intBits = Float.floatToIntBits(data); + return getBytesOfReverse(intBits); + } + + /** + * 将double数据类型转化成Byte数组 + * @see ByteOrder#LITTLE_ENDIAN + * @param data double值 + * @return byte[]数组 + */ + public static byte[] getBytes(double data) { + long intBits = Double.doubleToLongBits(data); + return getBytes(intBits); + } + + /** + * 将double数据类型转化成Byte数组 倒序 + * @see ByteOrder#BIG_ENDIAN + * @param data double值 + * @return byte[]数组 + */ + public static byte[] getBytesOfReverse(double data) { + long intBits = Double.doubleToLongBits(data); + return getBytesOfReverse(intBits); + } + + /** + * 字符串转字节数组(UTF-8) + * @param data + * @return + */ + public static byte[] getBytes(String data) { + return data.getBytes(StandardCharsets.UTF_8); + } + + /** + * 将字符串转换成byte[]数组 + * @param data 字符串值 + * @param charsetName 编码方式 + * @return 字节数组 + */ + public static byte[] getBytes(String data, String charsetName) { + Charset charset = Charset.forName(charsetName); + return data.getBytes(charset); + } + + /* + * 把16进制字符串转换成字节数组 + * @param hex + * @return + */ + public static byte[] hexToByte(String hexStr) { + if(StringUtils.isBlank(hexStr)) { + return null; + } + if(hexStr.length()%2 != 0) {//长度为单数 + hexStr = "0" + hexStr;//前面补0 + } + + char[] chars = hexStr.toCharArray(); + int len = chars.length/2; + byte[] bytes = new byte[len]; + for (int i = 0; i < len; i++) { + int x = i*2; + bytes[i] = (byte)Integer.parseInt(String.valueOf(new char[]{chars[x], chars[x+1]}), 16); + } + return bytes; + + } + + public static byte getByte(byte[] src, int offset) { + return src[offset]; + } + + /** + * 字节数组转16进制 + * @param bArray + * @return + */ + public static final String bytesToHex(byte[] bArray) { + StringBuffer sb = new StringBuffer(bArray.length); + String sTemp; + for (int i = 0; i < bArray.length; i++) { + sTemp = Integer.toHexString(0xFF & bArray[i]); + if (sTemp.length() < 2) sb.append(0); + sb.append(sTemp.toUpperCase()); + } + return sb.toString(); + } + + /** + * 字节数组转16进制且格式化十六进制 + * @param bArray + * @return + */ + public static final String bytesToHexByFormat(byte[] bArray) { + StringBuffer sb = new StringBuffer(bArray.length); + String sTemp; + for (int i = 0; i < bArray.length; i++) { + sTemp = Integer.toHexString(0xFF & bArray[i]); + if (sTemp.length() < 2) sb.append(0); + sb.append(sTemp.toUpperCase()).append(' '); + } + return sb.toString(); + } + + /** + * 字节数组转16进制 + * @param src + * @return + */ + public static final String bytesToHex(byte[] src, int offset, int length) { + byte[] bArray = ArrayUtils.subarray(src, offset, offset + length); + return bytesToHex(bArray); + } + + public static final String byteToHex(byte value) { + String s = Integer.toHexString(0xff & value); + if(s.length() == 1) return "0"+s; + return s; + } + + public static final String shortToHex(short value) { + String s = Integer.toHexString(value); + switch (s.length()) { + case 1: return "000" + s; + case 2: return "00" + s; + case 3: return "0" + s; + default: return s; + } + } + + public static final String intToHex(int value) { + StringBuilder s = new StringBuilder(Integer.toHexString(value)); + String v1 = s.toString().replace("f",""); + if (v1.length() <4){ + for (int i = 0; i < 4 - v1.length(); i++) { + s.insert(0, "0"); + } + return s.toString().replace("f",""); + } + else return s.toString().replace("f",""); + } + + public static final String hexTo8Bit(int value,int index){ + String s = Integer.toBinaryString(value); + StringBuilder result = new StringBuilder(s); + if (s.length() < index){ + for (int i = 0; i < index - s.length(); i++) { + result.insert(0,"0"); + } + } + return result.toString(); + } + + /** + * @函数功能: BCD码转为10进制串(阿拉伯数据) + * @输入参数: BCD码 + * @输出结果: 10进制串 + */ + public static String bcdToStr(byte[] bytes){ + StringBuffer temp=new StringBuffer(bytes.length*2); + + for(int i=0;i>>4)); + temp.append((byte)(bytes[i] & 0x0f)); + } + + return temp.toString(); + } + + /** + * + * @param src 原报文 + * @param offset 起始位置 + * @param length 长度 + * @return + */ + public static String bcdToStr(byte[] src, int offset, int length){ + byte[] bArray = ArrayUtils.subarray(src, offset, offset + length); + return bcdToStr(bArray); + } + + public static byte[] str2Bcd(String asc) { + int len = asc.length(); + int mod = len % 2; + + if (mod != 0) { + asc = "0" + asc; + len = asc.length(); + } + + byte abt[]; + if (len >= 2) { + len = len / 2; + } + + byte bbt[] = new byte[len]; + abt = asc.getBytes(); + int j, k; + + for (int p = 0; p < asc.length()/2; p++) { + if ( (abt[2 * p] >= '0') && (abt[2 * p] <= '9')) { + j = abt[2 * p] - '0'; + } else if ( (abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) { + j = abt[2 * p] - 'a' + 0x0a; + } else { + j = abt[2 * p] - 'A' + 0x0a; + } + + if ( (abt[2 * p + 1] >= '0') && (abt[2 * p + 1] <= '9')) { + k = abt[2 * p + 1] - '0'; + } else if ( (abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) { + k = abt[2 * p + 1] - 'a' + 0x0a; + }else { + k = abt[2 * p + 1] - 'A' + 0x0a; + } + + int a = (j << 4) + k; + byte b = (byte) a; + bbt[p] = b; + } + return bbt; + } + + private static byte toByte(char c) { + return (byte) c; + } + + /** + * 将字节数组转换成 ushort 数据 + * @param bytes 字节数组 + * @param offset 起始位置 + * @return short值 + */ + public static int bytesToUShort(byte[] bytes, int offset) { + return ((0xff & bytes[0 + offset]) | (0xff00 & (bytes[1 + offset] << 8))); + } + + /** + * 将字节数组转换成 ushort 数据,采用倒序的方式 + * @param bytes 字节数组 + * @return short值 + */ + public static int bytesToUShortOfReverse(byte[] bytes) { + return ByteUtils.bytesToShortOfReverse(bytes, 0); + } + + /** + * 将字节数组转换成 ushort 数据,采用倒序的方式 + * @param bytes 字节数组 + * @param offset 起始位置 + * @return short值 + */ + public static int bytesToUShortOfReverse(byte[] bytes, int offset) { + return ((0xff & bytes[1 + offset]) | (0xff00 & (bytes[0 + offset] << 8))); + } + + /** + * byte数组中取int数值,本方法适用于(低位在前,高位在后)的顺序,和intToBytes配套使用 + * + * @param src + * byte数组 + * @param offset + * 从数组的第offset位开始 + * @return int数值 + */ + public static int bytesToInt(byte[] src, int offset) { + return ((src[offset] & 0xFF) | ((src[offset + 1] & 0xFF) << 8) + | ((src[offset + 2] & 0xFF) << 16) | ((src[offset + 3] & 0xFF) << 24)); + } + + /** + * byte数组中取int数值,本方法适用于(低位在前,高位在后)的顺序,和intToBytes配套使用 + * @param src byte数组 + * @return int数值 + */ + public static int bytesToInt(byte[] src) { + return ByteUtils.bytesToInt(src, 0); + } + + /** + * 将字节数组转换成int数据,采用倒序的方式 + * @param bytes 字节数组 + * @param offset 起始位置 + * @return int值 + */ + public static int bytesToIntOfReverse(byte[] bytes, int offset) { + return (0xff & bytes[3 + offset]) | + (0xff00 & (bytes[2 + offset] << 8)) | + (0xff0000 & (bytes[1 + offset] << 16)) | + (0xff000000 & (bytes[0 + offset] << 24)); + } + + /** + * 将字节数组转换成int数据,采用倒序的方式 + * @param bytes 字节数组 + * @return int值 + */ + public static int bytesToIntOfReverse(byte[] bytes) { + return ByteUtils.bytesToIntOfReverse(bytes, 0); + } + + /** + * 将字节数组转换成uint数据 + * @param bytes 字节数组 + * @param offset 起始位置 + * @return int值 + */ + public static long bytesToUInt(byte[] bytes, int offset) { + int value = bytesToInt(bytes, offset); + if (value >= 0) return value; + return 65536L * 65536L + value; + } + + /** + * 将字节数组转换成uint数据 + * @param bytes 字节数组 + * @return int值 + */ + public static long bytesToUInt(byte[] bytes) { + return ByteUtils.bytesToUInt(bytes, 0); + } + + /** + * 将字节数组转换成uint数据 + * @param bytes 字节数组 + * @param offset 起始位置 + * @return int值 + */ + public static long bytesToUIntOfReverse(byte[] bytes, int offset) { + int value = bytesToIntOfReverse(bytes, offset); + if (value >= 0) return value; + return 65536L * 65536L + value; + } + + /** + * 将字节数组转换成uint数据 倒序 + * @param bytes 字节数组 + * @return int值 + */ + public static long bytesToUIntOfReverse(byte[] bytes) { + return ByteUtils.bytesToUIntOfReverse(bytes, 0); + } + + /** + * 将字节数组转换成float数据 + * @param bytes 字节数组 + * @return float值 + */ + public static float bytesToFloat(byte[] bytes) { + return Float.intBitsToFloat(bytesToInt(bytes, 0)); + } + + /** + * 将字节数组转换成float数据 + * @param bytes 字节数组 + * @param offset 起始位置 + * @return float值 + */ + public static float bytesToFloat(byte[] bytes, int offset) { + return Float.intBitsToFloat(bytesToInt(bytes,offset)); + } + + /** + * 将字节数组转换成float数据 + * @param bytes 字节数组 + * @return float值 + */ + public static float bytesToFloatOfReverse(byte[] bytes) { + return bytesToFloatOfReverse(bytes, 0); + } + + /** + * 将字节数组转换成float数据 + * @param bytes 字节数组 + * @param offset 偏移量 + * @return float值 + */ + public static float bytesToFloatOfReverse(byte[] bytes, int offset) { + return Float.intBitsToFloat(bytesToIntOfReverse(bytes, offset)); + } + + + /** + * byte数组中取double数值 + * @param src byte数组 + * @return double数值 + */ + public static double bytesToDouble(byte[] src) { + return Double.longBitsToDouble(bytesToLong(src)); + } + + /** + * byte数组中取double数值 + * @param src byte数组 + * @param offset 从数组的第offset位开始 + * @return double数值 + */ + public static double bytesToDouble(byte[] src, int offset) { + return Double.longBitsToDouble(bytesToLong(src, offset)); + } + + /** + * byte数组中取double数值 + * @param src byte数组 + * @return double数值 + */ + public static double bytesToDoubleOfReverse(byte[] src) { + return bytesToDoubleOfReverse(src, 0); + } + + /** + * byte数组中取double数值 + * @param src byte数组 + * @param offset 从数组的第offset位开始 + * @return double数值 + */ + public static double bytesToDoubleOfReverse(byte[] src, int offset) { + return Double.longBitsToDouble(bytesToLongOfReverse(src, offset)); + } + + /** + * 去掉字节数组尾数为零的字节,并将其转成字符串 + * @param src + * @param charset + * @return + */ + public static String bytesToString(byte[] src, Charset charset){ + int search = Arrays.binarySearch(src, (byte) 0); + return new String(Arrays.copyOf(src, search), charset); + } + + /** + * 去掉字节数组尾数为零的字节,并将其转成字符串 + * @param src + * @return + */ + public static String bytesToString(byte[] src){ + return new String(wipeLastZero(src)); + } + + /** + * 去掉字节数组尾数为零的字节,并将其转成字符串 + * @param src + * @return + */ + public static String bytesToString(byte[] src, int startIndex, int endIndex){ + return new String(wipeLastZero(subBytes(src, startIndex, endIndex))); + } + + /** + * 去掉字节数组尾数为零的字节,并将其转成字符串 + * @param src + * @return + */ + public static String bytesToString(byte[] src, int startIndex, int endIndex, Charset charset){ + return new String(wipeLastZero(subBytes(src, startIndex, endIndex)), charset); + } + + /** + * 将byte[]数组的数据进行翻转 + * @param reverse 等待反转的字符串 + */ + public static void bytesReverse(byte[] reverse) { + if (reverse != null) { + byte tmp = 0; + for (int i = 0; i < reverse.length / 2; i++) { + tmp = reverse[i]; + reverse[i] = reverse[reverse.length - 1 - i]; + reverse[reverse.length - 1 - i] = tmp; + } + } + } + + /** + * 去除包含0的字节 + * @param src + * @return + */ + private static byte[] wipeLastZero(byte[] src){ + int index = 0; + for(int i=0; i 0 ? 1 : 0); + } + + /** + *将bool数组转换到byte数组
+ * @param array bool数组 + * @return 字节数组 + */ + public static byte[] boolArrayToByte(boolean[] array) { + if (array == null) return null; + + int length = array.length % 8 == 0 ? array.length / 8 : array.length / 8 + 1; + byte[] buffer = new byte[length]; + + for (int i = 0; i < array.length; i++) { + if (array[i]) { + buffer[i / 8] += (1 << i % 8); + } + } + + return buffer; + } + + public static Integer cutMessageHexTo(byte[] source, int startIndex, int endIndex){ + byte[] subarray = ArrayUtils.subarray(source, startIndex, endIndex); + String s = bytesToHexString(subarray); + return Integer.parseInt(s,16); + } + + /** + * byte数组转换炒年糕十六进制字符串 + * + * @param bArray byte数组 + * @return hex字符串 + */ + public static String bytesToHexString(byte[] bArray) { + StringBuilder sb = new StringBuilder(bArray.length); + for (int i = 0; i < bArray.length; i++) { + String hexStr = Integer.toHexString(0xFF & bArray[i]); + if (hexStr.length() < 2) { + sb.append(0); + } + sb.append(hexStr.toUpperCase()); + } + return sb.toString(); + } + + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/gateway/protocol/NettyUtils.java b/maibu-common/src/main/java/com/maibu/utils/gateway/protocol/NettyUtils.java new file mode 100644 index 0000000..ee79f71 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/gateway/protocol/NettyUtils.java @@ -0,0 +1,22 @@ +package com.maibu.utils.gateway.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; + +/** + * @author gsb + * @date 2022/9/15 14:44 + */ +public class NettyUtils { + + /** + * ByteBuf转 byte[] + * @param buf buffer + * @return byte[] + */ + public static byte[] readBytesFromByteBuf(ByteBuf buf){ + return ByteBufUtil.getBytes(buf); + } + + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/html/EscapeUtil.java b/maibu-common/src/main/java/com/maibu/utils/html/EscapeUtil.java new file mode 100644 index 0000000..b3f90d9 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/html/EscapeUtil.java @@ -0,0 +1,168 @@ +package com.maibu.utils.html; + + +import com.maibu.utils.StringUtils; + +/** + * 转义和反转义工具类 + * + * @author ruoyi + */ +public class EscapeUtil +{ + public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; + + private static final char[][] TEXT = new char[64][]; + + static + { + for (int i = 0; i < 64; i++) + { + TEXT[i] = new char[] { (char) i }; + } + + // special HTML characters + TEXT['\''] = "'".toCharArray(); // 单引号 + TEXT['"'] = """.toCharArray(); // 双引号 + TEXT['&'] = "&".toCharArray(); // &符 + TEXT['<'] = "<".toCharArray(); // 小于号 + TEXT['>'] = ">".toCharArray(); // 大于号 + } + + /** + * 转义文本中的HTML字符为安全的字符 + * + * @param text 被转义的文本 + * @return 转义后的文本 + */ + public static String escape(String text) + { + return encode(text); + } + + /** + * 还原被转义的HTML特殊字符 + * + * @param content 包含转义符的HTML内容 + * @return 转换后的字符串 + */ + public static String unescape(String content) + { + return decode(content); + } + + /** + * 清除所有HTML标签,但是不删除标签内的内容 + * + * @param content 文本 + * @return 清除标签后的文本 + */ + public static String clean(String content) + { + return new HTMLFilter().filter(content); + } + + /** + * Escape编码 + * + * @param text 被编码的文本 + * @return 编码后的字符 + */ + private static String encode(String text) + { + if (StringUtils.isEmpty(text)) + { + return StringUtils.EMPTY; + } + + final StringBuilder tmp = new StringBuilder(text.length() * 6); + char c; + for (int i = 0; i < text.length(); i++) + { + c = text.charAt(i); + if (c < 256) + { + tmp.append("%"); + if (c < 16) + { + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + else + { + tmp.append("%u"); + if (c <= 0xfff) + { + // issue#I49JU8@Gitee + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + } + return tmp.toString(); + } + + /** + * Escape解码 + * + * @param content 被转义的内容 + * @return 解码后的字符串 + */ + public static String decode(String content) + { + if (StringUtils.isEmpty(content)) + { + return content; + } + + StringBuilder tmp = new StringBuilder(content.length()); + int lastPos = 0, pos = 0; + char ch; + while (lastPos < content.length()) + { + pos = content.indexOf("%", lastPos); + if (pos == lastPos) + { + if (content.charAt(pos + 1) == 'u') + { + ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16); + tmp.append(ch); + lastPos = pos + 6; + } + else + { + ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16); + tmp.append(ch); + lastPos = pos + 3; + } + } + else + { + if (pos == -1) + { + tmp.append(content.substring(lastPos)); + lastPos = content.length(); + } + else + { + tmp.append(content.substring(lastPos, pos)); + lastPos = pos; + } + } + } + return tmp.toString(); + } + + public static void main(String[] args) + { + String html = ""; + String escape = EscapeUtil.escape(html); + // String html = "ipt>alert(\"XSS\")ipt>"; + // String html = "<123"; + // String html = "123>"; + System.out.println("clean: " + EscapeUtil.clean(html)); + System.out.println("escape: " + escape); + System.out.println("unescape: " + EscapeUtil.unescape(escape)); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/html/HTMLFilter.java b/maibu-common/src/main/java/com/maibu/utils/html/HTMLFilter.java new file mode 100644 index 0000000..5b98bed --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/html/HTMLFilter.java @@ -0,0 +1,566 @@ +package com.maibu.utils.html; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * HTML过滤器,用于去除XSS漏洞隐患。 + * + * @author ruoyi + */ +public final class HTMLFilter +{ + /** + * regex flag union representing /si modifiers in php + **/ + private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; + private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL); + private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI); + private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL); + private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI); + private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI); + private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI); + private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI); + private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI); + private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?"); + private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?"); + private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?"); + private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))"); + private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL); + private static final Pattern P_END_ARROW = Pattern.compile("^>"); + private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_AMP = Pattern.compile("&"); + private static final Pattern P_QUOTE = Pattern.compile("\""); + private static final Pattern P_LEFT_ARROW = Pattern.compile("<"); + private static final Pattern P_RIGHT_ARROW = Pattern.compile(">"); + private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>"); + + // @xxx could grow large... maybe use sesat's ReferenceMap + private static final ConcurrentMap P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map vTagCounts = new HashMap<>(); + + /** + * html elements which must always be self-closing (e.g. "") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "") + **/ + private final String[] vNeedClosingTags; + /** + * set of disallowed html elements + **/ + private final String[] vDisallowed; + /** + * attributes which should be checked for valid protocols + **/ + private final String[] vProtocolAtts; + /** + * allowed protocols + **/ + private final String[] vAllowedProtocols; + /** + * tags which should be removed if they contain no content (e.g. "" or "") + **/ + private final String[] vRemoveBlanks; + /** + * entities allowed within html markup + **/ + private final String[] vAllowedEntities; + /** + * flag determining whether comments are allowed in input String. + */ + private final boolean stripComment; + private final boolean encodeQuotes; + /** + * flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "" + * becomes " text "). If set to false, unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLFilter() + { + vAllowed = new HashMap<>(); + + final ArrayList a_atts = new ArrayList<>(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList img_atts = new ArrayList<>(); + img_atts.add("src"); + img_atts.add("width"); + img_atts.add("height"); + img_atts.add("alt"); + vAllowed.put("img", img_atts); + + final ArrayList no_atts = new ArrayList<>(); + vAllowed.put("b", no_atts); + vAllowed.put("strong", no_atts); + vAllowed.put("i", no_atts); + vAllowed.put("em", no_atts); + + vSelfClosingTags = new String[] { "img" }; + vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" }; + vDisallowed = new String[] {}; + vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp. + vProtocolAtts = new String[] { "src", "href" }; + vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" }; + vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" }; + stripComment = true; + encodeQuotes = true; + alwaysMakeTags = false; + } + + /** + * Map-parameter configurable constructor. + * + * @param conf map containing configuration. keys match field names. + */ + @SuppressWarnings("unchecked") + public HTMLFilter(final Map conf) + { + + assert conf.containsKey("vAllowed") : "configuration requires vAllowed"; + assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags"; + assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags"; + assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed"; + assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols"; + assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts"; + assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks"; + assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities"; + + vAllowed = Collections.unmodifiableMap((HashMap>) conf.get("vAllowed")); + vSelfClosingTags = (String[]) conf.get("vSelfClosingTags"); + vNeedClosingTags = (String[]) conf.get("vNeedClosingTags"); + vDisallowed = (String[]) conf.get("vDisallowed"); + vAllowedProtocols = (String[]) conf.get("vAllowedProtocols"); + vProtocolAtts = (String[]) conf.get("vProtocolAtts"); + vRemoveBlanks = (String[]) conf.get("vRemoveBlanks"); + vAllowedEntities = (String[]) conf.get("vAllowedEntities"); + stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true; + encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true; + alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true; + } + + private void reset() + { + vTagCounts.clear(); + } + + // --------------------------------------------------------------- + // my versions of some PHP library functions + public static String chr(final int decimal) + { + return String.valueOf((char) decimal); + } + + public static String htmlSpecialChars(final String s) + { + String result = s; + result = regexReplace(P_AMP, "&", result); + result = regexReplace(P_QUOTE, """, result); + result = regexReplace(P_LEFT_ARROW, "<", result); + result = regexReplace(P_RIGHT_ARROW, ">", result); + return result; + } + + // --------------------------------------------------------------- + + /** + * given a user submitted input String, filter out any invalid or restricted html. + * + * @param input text (i.e. submitted by a user) than may contain html + * @return "clean" version of input, with only valid, whitelisted html elements allowed + */ + public String filter(final String input) + { + reset(); + String s = input; + + s = escapeComments(s); + + s = balanceHTML(s); + + s = checkTags(s); + + s = processRemoveBlanks(s); + + // s = validateEntities(s); + + return s; + } + + public boolean isAlwaysMakeTags() + { + return alwaysMakeTags; + } + + public boolean isStripComments() + { + return stripComment; + } + + private String escapeComments(final String s) + { + final Matcher m = P_COMMENTS.matcher(s); + final StringBuffer buf = new StringBuffer(); + if (m.find()) + { + final String match = m.group(1); // (.*?) + m.appendReplacement(buf, Matcher.quoteReplacement("")); + } + m.appendTail(buf); + + return buf.toString(); + } + + private String balanceHTML(String s) + { + if (alwaysMakeTags) + { + // + // try and form html + // + s = regexReplace(P_END_ARROW, "", s); + // 不追加结束标签 + s = regexReplace(P_BODY_TO_END, "<$1>", s); + s = regexReplace(P_XML_CONTENT, "$1<$2", s); + + } + else + { + // + // escape stray brackets + // + s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); + s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); + + // + // the last regexp causes '<>' entities to appear + // (we need to do a lookahead assertion so that the last bracket can + // be used in the next pass of the regexp) + // + s = regexReplace(P_BOTH_ARROWS, "", s); + } + + return s; + } + + private String checkTags(String s) + { + Matcher m = P_TAGS.matcher(s); + + final StringBuffer buf = new StringBuffer(); + while (m.find()) + { + String replaceStr = m.group(1); + replaceStr = processTag(replaceStr); + m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr)); + } + m.appendTail(buf); + + // these get tallied in processTag + // (remember to reset before subsequent calls to filter method) + final StringBuilder sBuilder = new StringBuilder(buf.toString()); + for (String key : vTagCounts.keySet()) + { + for (int ii = 0; ii < vTagCounts.get(key); ii++) + { + sBuilder.append(""); + } + } + s = sBuilder.toString(); + + return s; + } + + private String processRemoveBlanks(final String s) + { + String result = s; + for (String tag : vRemoveBlanks) + { + if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) + { + P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>")); + } + result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result); + if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) + { + P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>")); + } + result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result); + } + + return result; + } + + private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) + { + Matcher m = regex_pattern.matcher(s); + return m.replaceAll(replacement); + } + + private String processTag(final String s) + { + // ending tags + Matcher m = P_END_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + if (allowed(name)) + { + if (!inArray(name, vSelfClosingTags)) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) - 1); + return ""; + } + } + } + } + + // starting tags + m = P_START_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + final String body = m.group(2); + String ending = m.group(3); + + // debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" ); + if (allowed(name)) + { + final StringBuilder params = new StringBuilder(); + + final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body); + final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body); + final List paramNames = new ArrayList<>(); + final List paramValues = new ArrayList<>(); + while (m2.find()) + { + paramNames.add(m2.group(1)); // ([a-z0-9]+) + paramValues.add(m2.group(3)); // (.*?) + } + while (m3.find()) + { + paramNames.add(m3.group(1)); // ([a-z0-9]+) + paramValues.add(m3.group(3)); // ([^\"\\s']+) + } + + String paramName, paramValue; + for (int ii = 0; ii < paramNames.size(); ii++) + { + paramName = paramNames.get(ii).toLowerCase(); + paramValue = paramValues.get(ii); + + // debug( "paramName='" + paramName + "'" ); + // debug( "paramValue='" + paramValue + "'" ); + // debug( "allowed? " + vAllowed.get( name ).contains( paramName ) ); + + if (allowedAttribute(name, paramName)) + { + if (inArray(paramName, vProtocolAtts)) + { + paramValue = processParamProtocol(paramValue); + } + params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\\\""); + } + } + + if (inArray(name, vSelfClosingTags)) + { + ending = " /"; + } + + if (inArray(name, vNeedClosingTags)) + { + ending = ""; + } + + if (ending == null || ending.length() < 1) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) + 1); + } + else + { + vTagCounts.put(name, 1); + } + } + else + { + ending = " /"; + } + return "<" + name + params + ending + ">"; + } + else + { + return ""; + } + } + + // comments + m = P_COMMENT.matcher(s); + if (!stripComment && m.find()) + { + return "<" + m.group() + ">"; + } + + return ""; + } + + private String processParamProtocol(String s) + { + s = decodeEntities(s); + final Matcher m = P_PROTOCOL.matcher(s); + if (m.find()) + { + final String protocol = m.group(1); + if (!inArray(protocol, vAllowedProtocols)) + { + // bad protocol, turn into local anchor link instead + s = "#" + s.substring(protocol.length() + 1); + if (s.startsWith("#//")) + { + s = "#" + s.substring(3); + } + } + } + + return s; + } + + private String decodeEntities(String s) + { + StringBuffer buf = new StringBuffer(); + + Matcher m = P_ENTITY.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.decode(match).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENTITY_UNICODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENCODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + s = validateEntities(s); + return s; + } + + private String validateEntities(final String s) + { + StringBuffer buf = new StringBuffer(); + + // validate entities throughout the string + Matcher m = P_VALID_ENTITIES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // ([^&;]*) + final String two = m.group(2); // (?=(;|&|$)) + m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two))); + } + m.appendTail(buf); + + return encodeQuotes(buf.toString()); + } + + private String encodeQuotes(final String s) + { + if (encodeQuotes) + { + StringBuffer buf = new StringBuffer(); + Matcher m = P_VALID_QUOTES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // (>|^) + final String two = m.group(2); // ([^<]+?) + final String three = m.group(3); // (<|$) + // 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, two) + m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three)); + } + m.appendTail(buf); + return buf.toString(); + } + else + { + return s; + } + } + + private String checkEntity(final String preamble, final String term) + { + + return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble; + } + + private boolean isValidEntity(final String entity) + { + return inArray(entity, vAllowedEntities); + } + + private static boolean inArray(final String s, final String[] array) + { + for (String item : array) + { + if (item != null && item.equals(s)) + { + return true; + } + } + return false; + } + + private boolean allowed(final String name) + { + return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed); + } + + private boolean allowedAttribute(final String name, final String paramName) + { + return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName)); + } +} \ No newline at end of file diff --git a/maibu-common/src/main/java/com/maibu/utils/http/HttpHelper.java b/maibu-common/src/main/java/com/maibu/utils/http/HttpHelper.java new file mode 100644 index 0000000..e57e084 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/http/HttpHelper.java @@ -0,0 +1,56 @@ +package com.maibu.utils.http; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletRequest; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * 通用http工具封装 + * + * @author ruoyi + */ +public class HttpHelper +{ + private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); + + public static String getBodyString(ServletRequest request) + { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try (InputStream inputStream = request.getInputStream()) + { + reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String line = ""; + while ((line = reader.readLine()) != null) + { + sb.append(line); + } + } + catch (IOException e) + { + LOGGER.warn("getBodyString出现问题!"); + } + finally + { + if (reader != null) + { + try + { + reader.close(); + } + catch (IOException e) + { + LOGGER.error(ExceptionUtils.getMessage(e)); + } + } + } + return sb.toString(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/http/HttpUtils.java b/maibu-common/src/main/java/com/maibu/utils/http/HttpUtils.java new file mode 100644 index 0000000..1c44af0 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/http/HttpUtils.java @@ -0,0 +1,332 @@ +package com.maibu.utils.http; + +import com.maibu.constant.Constants; +import com.maibu.utils.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.*; +import java.io.*; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; + +/** + * 通用http发送方法 + * + * @author ruoyi + */ +public class HttpUtils +{ + private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url) + { + return sendGet(url, StringUtils.EMPTY); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param) + { + return sendGet(url, param, Constants.UTF8); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @param contentType 编码类型 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param, String contentType) + { + StringBuilder result = new StringBuilder(); + BufferedReader in = null; + try + { + String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url; + log.info("sendGet - {}", urlNameString); + URL realUrl = new URL(urlNameString); + URLConnection connection = realUrl.openConnection(); + connection.setRequestProperty("accept", "*/*"); + connection.setRequestProperty("connection", "Keep-Alive"); + connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + connection.setConnectTimeout(30000); + connection.setReadTimeout(30000); + connection.connect(); + in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); + String line; + while ((line = in.readLine()) != null) + { + result.append(line); + } + log.info("recv - {}", result); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); + } + finally + { + try + { + if (in != null) + { + in.close(); + } + } + catch (Exception ex) + { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 JSON String格式 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param) + { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try + { + log.info("sendPost - {}", url); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + out = new PrintWriter(conn.getOutputStream()); + out.print(param); + out.flush(); + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) + { + result.append(line); + } + log.info("recv - {}", result); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); + } + finally + { + try + { + if (out != null) + { + out.close(); + } + if (in != null) + { + in.close(); + } + } + catch (IOException ex) + { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + public static String sendSSLPost(String url, String param) + { + StringBuilder result = new StringBuilder(); + String urlNameString = url + "?" + param; + try + { + log.info("sendSSLPost - {}", urlNameString); + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom()); + URL console = new URL(urlNameString); + HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + + conn.setSSLSocketFactory(sc.getSocketFactory()); + conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); + conn.connect(); + InputStream is = conn.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String ret = ""; + while ((ret = br.readLine()) != null) + { + if (ret != null && !"".equals(ret.trim())) + { + result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); + } + } + log.info("recv - {}", result); + conn.disconnect(); + br.close(); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); + } + return result.toString(); + } + + public static String sendJsonPost(String url, String json) throws IOException { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try + { + log.info("sendPost - {}", url); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setDoOutput(true); + conn.setDoInput(true); + out = new PrintWriter(conn.getOutputStream()); + out.print(json); + out.flush(); + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) + { + result.append(line); + } + log.info("recv - {}", result); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + json, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + json, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + json, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + json, e); + } + finally + { + try + { + if (out != null) + { + out.close(); + } + if (in != null) + { + in.close(); + } + } + catch (IOException ex) + { + log.error("调用in.close Exception, url=" + url + ",param=" + json, ex); + } + } + return result.toString(); + } + + private static class TrustAnyTrustManager implements X509TrustManager + { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + { + } + + @Override + public X509Certificate[] getAcceptedIssuers() + { + return new X509Certificate[] {}; + } + } + + private static class TrustAnyHostnameVerifier implements HostnameVerifier + { + @Override + public boolean verify(String hostname, SSLSession session) + { + return true; + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/ip/AddressUtils.java b/maibu-common/src/main/java/com/maibu/utils/ip/AddressUtils.java new file mode 100644 index 0000000..de1e949 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/ip/AddressUtils.java @@ -0,0 +1,56 @@ +package com.maibu.utils.ip; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.maibu.config.RuoYiConfig; +import com.maibu.constant.Constants; +import com.maibu.utils.StringUtils; +import com.maibu.utils.http.HttpUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 获取地址类 + * + * @author ruoyi + */ +public class AddressUtils +{ + private static final Logger log = LoggerFactory.getLogger(AddressUtils.class); + + // IP地址查询 + public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp"; + + // 未知地址 + public static final String UNKNOWN = "XX XX"; + + public static String getRealAddressByIP(String ip) + { + // 内网不查询 + if (IpUtils.internalIp(ip)) + { + return "内网IP"; + } + if (RuoYiConfig.isAddressEnabled()) + { + try + { + String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK); + if (StringUtils.isEmpty(rspStr)) + { + log.error("获取地理位置异常 {}", ip); + return UNKNOWN; + } + JSONObject obj = JSON.parseObject(rspStr); + String region = obj.getString("pro"); + String city = obj.getString("city"); + return String.format("%s %s", region, city); + } + catch (Exception e) + { + log.error("获取地理位置异常 {}", ip); + } + } + return UNKNOWN; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/ip/IpUtils.java b/maibu-common/src/main/java/com/maibu/utils/ip/IpUtils.java new file mode 100644 index 0000000..fc80a88 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/ip/IpUtils.java @@ -0,0 +1,266 @@ +package com.maibu.utils.ip; + + +import com.maibu.utils.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * 获取IP方法 + * + * @author ruoyi + */ +public class IpUtils +{ + /** + * 获取客户端IP + * + * @param request 请求对象 + * @return IP地址 + */ + public static String getIpAddr(HttpServletRequest request) + { + if (request == null) + { + return "unknown"; + } + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Forwarded-For"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getRemoteAddr(); + } + + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param ip IP地址 + * @return 结果 + */ + public static boolean internalIp(String ip) + { + byte[] addr = textToNumericFormatV4(ip); + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param addr byte地址 + * @return 结果 + */ + private static boolean internalIp(byte[] addr) + { + if (StringUtils.isNull(addr) || addr.length < 2) + { + return true; + } + final byte b0 = addr[0]; + final byte b1 = addr[1]; + // 10.x.x.x/8 + final byte SECTION_1 = 0x0A; + // 172.16.x.x/12 + final byte SECTION_2 = (byte) 0xAC; + final byte SECTION_3 = (byte) 0x10; + final byte SECTION_4 = (byte) 0x1F; + // 192.168.x.x/16 + final byte SECTION_5 = (byte) 0xC0; + final byte SECTION_6 = (byte) 0xA8; + switch (b0) + { + case SECTION_1: + return true; + case SECTION_2: + if (b1 >= SECTION_3 && b1 <= SECTION_4) + { + return true; + } + case SECTION_5: + switch (b1) + { + case SECTION_6: + return true; + } + default: + return false; + } + } + + /** + * 将IPv4地址转换成字节 + * + * @param text IPv4地址 + * @return byte 字节 + */ + public static byte[] textToNumericFormatV4(String text) + { + if (text.length() == 0) + { + return null; + } + + byte[] bytes = new byte[4]; + String[] elements = text.split("\\.", -1); + try + { + long l; + int i; + switch (elements.length) + { + case 1: + l = Long.parseLong(elements[0]); + if ((l < 0L) || (l > 4294967295L)) + { + return null; + } + bytes[0] = (byte) (int) (l >> 24 & 0xFF); + bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 2: + l = Integer.parseInt(elements[0]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[0] = (byte) (int) (l & 0xFF); + l = Integer.parseInt(elements[1]); + if ((l < 0L) || (l > 16777215L)) + { + return null; + } + bytes[1] = (byte) (int) (l >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 3: + for (i = 0; i < 2; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + l = Integer.parseInt(elements[2]); + if ((l < 0L) || (l > 65535L)) + { + return null; + } + bytes[2] = (byte) (int) (l >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 4: + for (i = 0; i < 4; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + break; + default: + return null; + } + } + catch (NumberFormatException e) + { + return null; + } + return bytes; + } + + /** + * 获取IP地址 + * + * @return 本地IP地址 + */ + public static String getHostIp() + { + try + { + return InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException e) + { + } + return "127.0.0.1"; + } + + /** + * 获取主机名 + * + * @return 本地主机名 + */ + public static String getHostName() + { + try + { + return InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException e) + { + } + return "未知"; + } + + /** + * 从多级反向代理中获得第一个非unknown IP地址 + * + * @param ip 获得的IP地址 + * @return 第一个非unknown IP地址 + */ + public static String getMultistageReverseProxyIp(String ip) + { + // 多级反向代理检测 + if (ip != null && ip.indexOf(",") > 0) + { + final String[] ips = ip.trim().split(","); + for (String subIp : ips) + { + if (false == isUnknown(subIp)) + { + ip = subIp; + break; + } + } + } + return ip; + } + + /** + * 检测给定字符串是否为未知,多用于检测HTTP请求相关 + * + * @param checkString 被检测的字符串 + * @return 是否未知 + */ + public static boolean isUnknown(String checkString) + { + return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); + } +} \ No newline at end of file diff --git a/maibu-common/src/main/java/com/maibu/utils/json/JsonUtils.java b/maibu-common/src/main/java/com/maibu/utils/json/JsonUtils.java new file mode 100644 index 0000000..81ca0d5 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/json/JsonUtils.java @@ -0,0 +1,159 @@ +package com.maibu.utils.json; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +/** + * JSON 工具类 + * + * @author fastbee + */ +@UtilityClass +@Slf4j +public class JsonUtils { + + private static ObjectMapper objectMapper = new ObjectMapper(); + + static { + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化 + } + + /** + * 初始化 objectMapper 属性 + *

+ * 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean + * + * @param objectMapper ObjectMapper 对象 + */ + public static void init(ObjectMapper objectMapper) { + JsonUtils.objectMapper = objectMapper; + } + + @SneakyThrows + public static String toJsonString(Object object) { + return objectMapper.writeValueAsString(object); + } + + @SneakyThrows + public static byte[] toJsonByte(Object object) { + return objectMapper.writeValueAsBytes(object); + } + + @SneakyThrows + public static String toJsonPrettyString(Object object) { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object); + } + + public static T parseObject(String text, Class clazz) { + if (StrUtil.isEmpty(text)) { + return null; + } + try { + return objectMapper.readValue(text, clazz); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + public static T parseObject(String text, Type type) { + if (StrUtil.isEmpty(text)) { + return null; + } + try { + return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type)); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + /** + * 将字符串解析成指定类型的对象 + * 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下, + * 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。 + * + * @param text 字符串 + * @param clazz 类型 + * @return 对象 + */ + public static T parseObject2(String text, Class clazz) { + if (StrUtil.isEmpty(text)) { + return null; + } + return JSONUtil.toBean(text, clazz); + } + + public static T parseObject(byte[] bytes, Class clazz) { + if (ArrayUtil.isEmpty(bytes)) { + return null; + } + try { + return objectMapper.readValue(bytes, clazz); + } catch (IOException e) { + log.error("json parse err,json:{}", bytes, e); + throw new RuntimeException(e); + } + } + + public static T parseObject(String text, TypeReference typeReference) { + try { + return objectMapper.readValue(text, typeReference); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + public static List parseArray(String text, Class clazz) { + if (StrUtil.isEmpty(text)) { + return new ArrayList<>(); + } + try { + return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz)); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + public static JsonNode parseTree(String text) { + try { + return objectMapper.readTree(text); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + public static JsonNode parseTree(byte[] text) { + try { + return objectMapper.readTree(text); + } catch (IOException e) { + log.error("json parse err,json:{}", text, e); + throw new RuntimeException(e); + } + } + + public static boolean isJson(String text) { + return JSONUtil.isTypeJSON(text); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/modbus/BitUtils.java b/maibu-common/src/main/java/com/maibu/utils/modbus/BitUtils.java new file mode 100644 index 0000000..bfc0ebf --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/modbus/BitUtils.java @@ -0,0 +1,78 @@ +package com.maibu.utils.modbus; + +/** + * + * @description 位运算工具 + * 用途:将二进制数中的每位数字1或0代表着某种开关标记,1为是,0为否,则一个数字可以代表N位的开关标记值,可有效减少过多的变量定义 或 过多的表字段 + */ +public class BitUtils { + + /** + * 获取二进制数字中指定位数的结果,如:1011,指定第2位,则结果是0,第3位,则结果是1 + * + * @param num 二进制数(可以十进制数传入,也可使用0b开头的二进制数表示形式) + * @param bit 位数(第几位,从右往左,从0开始) + * @return + */ + public static int getBitFlag(long num, int bit) { + return (int) num >> bit & 1; + } + + /** + * 更新二进制数字中指定位的值 + * + * @param num 二进制数(可以十进制数传入,也可使用0b开头的二进制数表示形式) + * @param bit 位数(第几位,从右往左,从0开始) + * @param flagValue 位标记值(true=1,false=0) + * @return + */ + public static long updateBitValue(long num, int bit, boolean flagValue) { + if (flagValue) { + //将某位由0改为1 + return num | (1 << bit); + } else { + //将某位由1改为0 + return num ^ (getBitFlag(num, bit) << bit); + } + } + + /** + * 将数字转换为二制值形式字符串 + * + * @param num + * @return + */ + public static String toBinaryString(long num) { + return Long.toBinaryString(num); + } + + /** + * 判断10进制数,某位是0还是1 + * @param num + * @param i + * @return + */ + public static int deter(int num, int i) { + // 先将数字右移指定第i位,然后再用&与1运算 + i += 1; + return num >> (i-1) & 1; + } + + /** + * 判断hex数据,某位的值 + * @param hex + * @param i + * @return + */ + public static int deterHex(String hex,int i){ + return deter(Integer.parseInt(hex,16),i); + } + + public static void main(String[] args) { + int deter = deter(7, 0); + int deterHex = deterHex("10", 4); + System.out.println(deter); + System.out.println(deterHex); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/modbus/ModbusUtils.java b/maibu-common/src/main/java/com/maibu/utils/modbus/ModbusUtils.java new file mode 100644 index 0000000..e5c1832 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/modbus/ModbusUtils.java @@ -0,0 +1,76 @@ +package com.maibu.utils.modbus; + + +import com.maibu.core.protocol.modbus.ModbusCode; + +/** + * @author gsb + * @date 2024/6/15 11:21 + */ +public class ModbusUtils { + + /** + * 获取modbus功能码 + * isReadOnly: 0-读写 1-只读 + * type: 1-IO寄存器 2-数据寄存器 + * IO寄存器读写 05功能码 数据寄存器只读 06功能码 + * @param type modbus数据类型 + * @return modbus功能码 + */ + public static ModbusCode getModbusCode(int type){ + if (type == 1){ + return ModbusCode.Write05; + }else { + return ModbusCode.Write06; + } + } + + public static ModbusCode getReadModbusCode(int type,int isReadOnly){ + if (type == 1){ + return isReadOnly == 1 ? ModbusCode.Read01 : ModbusCode.Read02; + }else { + return isReadOnly == 1 ? ModbusCode.Read03 : ModbusCode.Read04; + } + } + + /** + * 获取modbus-hex字符串寄存器地址 + * @param hexString hex字符串 + * @return 寄存器地址-10进制 + */ + public static int getModbusAddress(String hexString){ + return Integer.parseInt(hexString.substring(4, 8),16); + } + + /** + * 获取从机地址 + * @param hexString + * @return + */ + public static int getModbusSlaveId(String hexString){ + return Integer.parseInt(hexString.substring(0,2),16); + } + + /** + * 获取功能码 + * @param hexString + * @return + */ + public static int getModbusCode(String hexString){ + return Integer.parseInt(hexString.substring(2,4),16); + } + + public static Mparams getModbusParams(String hexString){ + Mparams mparams = new Mparams(); + mparams.setSlaveId(Integer.parseInt(hexString.substring(0,2),16)); + mparams.setCode(Integer.parseInt(hexString.substring(2,4),16)); + mparams.setAddress(Integer.parseInt(hexString.substring(4, 8),16)); + return mparams; + } + + + public static void main(String[] args) { + int modbusAddress = getModbusAddress("0101000A0001FDCA"); + System.out.println(modbusAddress); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/modbus/Mparams.java b/maibu-common/src/main/java/com/maibu/utils/modbus/Mparams.java new file mode 100644 index 0000000..ebab520 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/modbus/Mparams.java @@ -0,0 +1,16 @@ +package com.maibu.utils.modbus; + +import lombok.Data; + +/** + * @author bill + */ +@Data +public class Mparams { + + private int slaveId; + + private int code; + + private int address; +} diff --git a/maibu-common/src/main/java/com/maibu/utils/object/ObjectUtils.java b/maibu-common/src/main/java/com/maibu/utils/object/ObjectUtils.java new file mode 100644 index 0000000..15716e2 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/object/ObjectUtils.java @@ -0,0 +1,63 @@ +package com.maibu.utils.object; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.function.Consumer; + +/** + * Object 工具类 + * + * @author fastbee + */ +public class ObjectUtils { + + /** + * 复制对象,并忽略 Id 编号 + * + * @param object 被复制对象 + * @param consumer 消费者,可以二次编辑被复制对象 + * @return 复制后的对象 + */ + public static T cloneIgnoreId(T object, Consumer consumer) { + T result = ObjectUtil.clone(object); + // 忽略 id 编号 + Field field = ReflectUtil.getField(object.getClass(), "id"); + if (field != null) { + ReflectUtil.setFieldValue(result, field, null); + } + // 二次编辑 + if (result != null) { + consumer.accept(result); + } + return result; + } + + public static > T max(T obj1, T obj2) { + if (obj1 == null) { + return obj2; + } + if (obj2 == null) { + return obj1; + } + return obj1.compareTo(obj2) > 0 ? obj1 : obj2; + } + + @SafeVarargs + public static T defaultIfNull(T... array) { + for (T item : array) { + if (item != null) { + return item; + } + } + return null; + } + + @SafeVarargs + public static boolean equalsAny(T obj, T... array) { + return Arrays.asList(array).contains(obj); + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/poi/ExcelHandlerAdapter.java b/maibu-common/src/main/java/com/maibu/utils/poi/ExcelHandlerAdapter.java new file mode 100644 index 0000000..0fa1344 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/poi/ExcelHandlerAdapter.java @@ -0,0 +1,19 @@ +package com.maibu.utils.poi; + +/** + * Excel数据格式处理适配器 + * + * @author ruoyi + */ +public interface ExcelHandlerAdapter +{ + /** + * 格式化 + * + * @param value 单元格数据值 + * @param args excel注解args参数组 + * + * @return 处理后的值 + */ + Object format(Object value, String[] args); +} diff --git a/maibu-common/src/main/java/com/maibu/utils/reflect/ReflectUtils.java b/maibu-common/src/main/java/com/maibu/utils/reflect/ReflectUtils.java new file mode 100644 index 0000000..6031379 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/reflect/ReflectUtils.java @@ -0,0 +1,406 @@ +package com.maibu.utils.reflect; + +import com.maibu.core.text.Convert; +import com.maibu.utils.DateUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.apache.poi.ss.usermodel.DateUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.*; +import java.util.Date; + +/** + * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * + * @author ruoyi + */ +@SuppressWarnings("rawtypes") +public class ReflectUtils +{ + private static final String SETTER_PREFIX = "set"; + + private static final String GETTER_PREFIX = "get"; + + private static final String CGLIB_CLASS_SEPARATOR = "$$"; + + private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); + + /** + * 调用Getter方法. + * 支持多级,如:对象名.对象名.方法 + */ + @SuppressWarnings("unchecked") + public static E invokeGetter(Object obj, String propertyName) + { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + return (E) object; + } + + /** + * 调用Setter方法, 仅匹配方法名。 + * 支持多级,如:对象名.对象名.方法 + */ + public static void invokeSetter(Object obj, String propertyName, E value) + { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i = 0; i < names.length; i++) + { + if (i < names.length - 1) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + else + { + String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + invokeMethodByName(object, setterMethodName, new Object[] { value }); + } + } + } + + /** + * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. + */ + @SuppressWarnings("unchecked") + public static E getFieldValue(final Object obj, final String fieldName) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return null; + } + E result = null; + try + { + result = (E) field.get(obj); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常{}", e.getMessage()); + } + return result; + } + + /** + * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. + */ + public static void setFieldValue(final Object obj, final String fieldName, final E value) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return; + } + try + { + field.set(obj, value); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常: {}", e.getMessage()); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符. + * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. + * 同时匹配方法名+参数类型, + */ + @SuppressWarnings("unchecked") + public static E invokeMethod(final Object obj, final String methodName, final Class[] parameterTypes, + final Object[] args) + { + if (obj == null || methodName == null) + { + return null; + } + Method method = getAccessibleMethod(obj, methodName, parameterTypes); + if (method == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符, + * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. + * 只匹配函数名,如果有多个同名函数调用第一个。 + */ + @SuppressWarnings("unchecked") + public static E invokeMethodByName(final Object obj, final String methodName, final Object[] args) + { + Method method = getAccessibleMethodByName(obj, methodName, args.length); + if (method == null) + { + // 如果为空不报错,直接返回空。 + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + // 类型转换(将参数数据类型转换为目标方法参数类型) + Class[] cs = method.getParameterTypes(); + for (int i = 0; i < cs.length; i++) + { + if (args[i] != null && !args[i].getClass().equals(cs[i])) + { + if (cs[i] == String.class) + { + args[i] = Convert.toStr(args[i]); + if (StringUtils.endsWith((String) args[i], ".0")) + { + args[i] = StringUtils.substringBefore((String) args[i], ".0"); + } + } + else if (cs[i] == Integer.class) + { + args[i] = Convert.toInt(args[i]); + } + else if (cs[i] == Long.class) + { + args[i] = Convert.toLong(args[i]); + } + else if (cs[i] == Double.class) + { + args[i] = Convert.toDouble(args[i]); + } + else if (cs[i] == Float.class) + { + args[i] = Convert.toFloat(args[i]); + } + else if (cs[i] == Date.class) + { + if (args[i] instanceof String) + { + args[i] = DateUtils.parseDate(args[i]); + } + else + { + args[i] = DateUtil.getJavaDate((Double) args[i]); + } + } + else if (cs[i] == boolean.class || cs[i] == Boolean.class) + { + args[i] = Convert.toBool(args[i]); + } + } + } + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + */ + public static Field getAccessibleField(final Object obj, final String fieldName) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(fieldName, "fieldName can't be blank"); + for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) + { + try + { + Field field = superClass.getDeclaredField(fieldName); + makeAccessible(field); + return field; + } + catch (NoSuchFieldException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 匹配函数名+参数类型。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethod(final Object obj, final String methodName, + final Class... parameterTypes) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + try + { + Method method = searchType.getDeclaredMethod(methodName, parameterTypes); + makeAccessible(method); + return method; + } + catch (NoSuchMethodException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 只匹配函数名。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + Method[] methods = searchType.getDeclaredMethods(); + for (Method method : methods) + { + if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) + { + makeAccessible(method); + return method; + } + } + } + return null; + } + + /** + * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Method method) + { + if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) + && !method.isAccessible()) + { + method.setAccessible(true); + } + } + + /** + * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Field field) + { + if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) + || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) + { + field.setAccessible(true); + } + } + + /** + * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 + * 如无法找到, 返回Object.class. + */ + @SuppressWarnings("unchecked") + public static Class getClassGenricType(final Class clazz) + { + return getClassGenricType(clazz, 0); + } + + /** + * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. + * 如无法找到, 返回Object.class. + */ + public static Class getClassGenricType(final Class clazz, final int index) + { + Type genType = clazz.getGenericSuperclass(); + + if (!(genType instanceof ParameterizedType)) + { + logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType"); + return Object.class; + } + + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + + if (index >= params.length || index < 0) + { + logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + + params.length); + return Object.class; + } + if (!(params[index] instanceof Class)) + { + logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); + return Object.class; + } + + return (Class) params[index]; + } + + public static Class getUserClass(Object instance) + { + if (instance == null) + { + throw new RuntimeException("Instance must not be null"); + } + Class clazz = instance.getClass(); + if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) + { + Class superClass = clazz.getSuperclass(); + if (superClass != null && !Object.class.equals(superClass)) + { + return superClass; + } + } + return clazz; + + } + + /** + * 将反射时的checked exception转换为unchecked exception. + */ + public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e) + { + if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException + || e instanceof NoSuchMethodException) + { + return new IllegalArgumentException(msg, e); + } + else if (e instanceof InvocationTargetException) + { + return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException()); + } + return new RuntimeException(msg, e); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/sign/Base64.java b/maibu-common/src/main/java/com/maibu/utils/sign/Base64.java new file mode 100644 index 0000000..de1c432 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/sign/Base64.java @@ -0,0 +1,291 @@ +package com.maibu.utils.sign; + +/** + * Base64工具类 + * + * @author ruoyi + */ +public final class Base64 +{ + static private final int BASELENGTH = 128; + static private final int LOOKUPLENGTH = 64; + static private final int TWENTYFOURBITGROUP = 24; + static private final int EIGHTBIT = 8; + static private final int SIXTEENBIT = 16; + static private final int FOURBYTE = 4; + static private final int SIGN = -128; + static private final char PAD = '='; + static final private byte[] base64Alphabet = new byte[BASELENGTH]; + static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; + + static + { + for (int i = 0; i < BASELENGTH; ++i) + { + base64Alphabet[i] = -1; + } + for (int i = 'Z'; i >= 'A'; i--) + { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) + { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + + for (int i = '9'; i >= '0'; i--) + { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) + { + lookUpBase64Alphabet[i] = (char) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('0' + j); + } + lookUpBase64Alphabet[62] = (char) '+'; + lookUpBase64Alphabet[63] = (char) '/'; + } + + private static boolean isWhiteSpace(char octect) + { + return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); + } + + private static boolean isPad(char octect) + { + return (octect == PAD); + } + + private static boolean isData(char octect) + { + return (octect < BASELENGTH && base64Alphabet[octect] != -1); + } + + /** + * Encodes hex octects into Base64 + * + * @param binaryData Array containing binaryData + * @return Encoded Base64 array + */ + public static String encode(byte[] binaryData) + { + if (binaryData == null) + { + return null; + } + + int lengthDataBits = binaryData.length * EIGHTBIT; + if (lengthDataBits == 0) + { + return ""; + } + + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; + char encodedData[] = null; + + encodedData = new char[numberQuartet * 4]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + + for (int i = 0; i < numberTriplets; i++) + { + b1 = binaryData[dataIndex++]; + b2 = binaryData[dataIndex++]; + b3 = binaryData[dataIndex++]; + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; + } + + // form integral number of 6-bit groups + if (fewerThan24bits == EIGHTBIT) + { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex++] = PAD; + encodedData[encodedIndex++] = PAD; + } + else if (fewerThan24bits == SIXTEENBIT) + { + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex++] = PAD; + } + return new String(encodedData); + } + + /** + * Decodes Base64 data into octects + * + * @param encoded string containing Base64 data + * @return Array containind decoded data. + */ + public static byte[] decode(String encoded) + { + if (encoded == null) + { + return null; + } + + char[] base64Data = encoded.toCharArray(); + // remove white spaces + int len = removeWhiteSpace(base64Data); + + if (len % FOURBYTE != 0) + { + return null;// should be divisible by four + } + + int numberQuadruple = (len / FOURBYTE); + + if (numberQuadruple == 0) + { + return new byte[0]; + } + + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; + char d1 = 0, d2 = 0, d3 = 0, d4 = 0; + + int i = 0; + int encodedIndex = 0; + int dataIndex = 0; + decodedData = new byte[(numberQuadruple) * 3]; + + for (; i < numberQuadruple - 1; i++) + { + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) + || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) + { + return null; + } // if found "no data" just return null + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + } + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) + { + return null;// if found "no data" just return null + } + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + + d3 = base64Data[dataIndex++]; + d4 = base64Data[dataIndex++]; + if (!isData((d3)) || !isData((d4))) + {// Check if they are PAD characters + if (isPad(d3) && isPad(d4)) + { + if ((b2 & 0xf) != 0)// last 4 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 1]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + return tmp; + } + else if (!isPad(d3) && isPad(d4)) + { + b3 = base64Alphabet[d3]; + if ((b3 & 0x3) != 0)// last 2 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 2]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + return tmp; + } + else + { + return null; + } + } + else + { // No PAD e.g 3cQl + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + + } + return decodedData; + } + + /** + * remove WhiteSpace from MIME containing encoded Base64 data. + * + * @param data the byte array of base64 data (with WS) + * @return the new length + */ + private static int removeWhiteSpace(char[] data) + { + if (data == null) + { + return 0; + } + + // count characters that's not whitespace + int newSize = 0; + int len = data.length; + for (int i = 0; i < len; i++) + { + if (!isWhiteSpace(data[i])) + { + data[newSize++] = data[i]; + } + } + return newSize; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/sign/Md5Utils.java b/maibu-common/src/main/java/com/maibu/utils/sign/Md5Utils.java new file mode 100644 index 0000000..d800842 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/sign/Md5Utils.java @@ -0,0 +1,68 @@ +package com.maibu.utils.sign; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +/** + * Md5加密方法 + * + * @author ruoyi + */ +public class Md5Utils +{ + private static final Logger log = LoggerFactory.getLogger(Md5Utils.class); + + private static byte[] md5(String s) + { + MessageDigest algorithm; + try + { + algorithm = MessageDigest.getInstance("MD5"); + algorithm.reset(); + algorithm.update(s.getBytes("UTF-8")); + byte[] messageDigest = algorithm.digest(); + return messageDigest; + } + catch (Exception e) + { + log.error("MD5 Error...", e); + } + return null; + } + + private static final String toHex(byte hash[]) + { + if (hash == null) + { + return null; + } + StringBuffer buf = new StringBuffer(hash.length * 2); + int i; + + for (i = 0; i < hash.length; i++) + { + if ((hash[i] & 0xff) < 0x10) + { + buf.append("0"); + } + buf.append(Long.toString(hash[i] & 0xff, 16)); + } + return buf.toString(); + } + + public static String hash(String s) + { + try + { + return new String(toHex(md5(s)).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + } + catch (Exception e) + { + log.error("not supported charset...{}", e); + return s; + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/sign/SignUtils.java b/maibu-common/src/main/java/com/maibu/utils/sign/SignUtils.java new file mode 100644 index 0000000..3f3022b --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/sign/SignUtils.java @@ -0,0 +1,66 @@ +package com.maibu.utils.sign; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +/** + * @author fastb + * @version 1.0 + * @description: 签名验证 + * @date 2024-03-12 15:54 + */ +public class SignUtils { + + /** + * 验证签名 + */ + public static boolean checkSignature(String token, String signature, String timestamp,String nonce) { + // 1.将token、timestamp、nonce三个参数进行字典序排序 + String[] arr = new String[] { token, timestamp, nonce }; + Arrays.sort(arr); + + // 2. 将三个参数字符串拼接成一个字符串进行sha1加密 + StringBuilder content = new StringBuilder(); + for (int i = 0; i < arr.length; i++) { + content.append(arr[i]); + } + MessageDigest md = null; + String tmpStr = null; + try { + md = MessageDigest.getInstance("SHA-1"); + // 将三个参数字符串拼接成一个字符串进行sha1加密 + byte[] digest = md.digest(content.toString().getBytes()); + tmpStr = byteToStr(digest); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + content = null; + // 3.将sha1加密后的字符串可与signature对比,标识该请求来源于微信 + return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false; + } + + /** + * 将字节数组转换为十六进制字符串 + */ + private static String byteToStr(byte[] byteArray) { + String strDigest = ""; + for (int i = 0; i < byteArray.length; i++) { + strDigest += byteToHexStr(byteArray[i]); + } + return strDigest; + } + + /** + * 将字节转换为十六进制字符串 + */ + private static String byteToHexStr(byte mByte) { + char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A','B', 'C', 'D', 'E', 'F' }; + char[] tempArr = new char[2]; + tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; + tempArr[1] = Digit[mByte & 0X0F]; + String s = new String(tempArr); + return s; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/spring/SpringUtils.java b/maibu-common/src/main/java/com/maibu/utils/spring/SpringUtils.java new file mode 100644 index 0000000..dc52f77 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/spring/SpringUtils.java @@ -0,0 +1,183 @@ +package com.maibu.utils.spring; + +import com.maibu.utils.StringUtils; +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.Map; + + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author ruoyi + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware +{ + /** Spring应用上下文环境 */ + private static ConfigurableListableBeanFactory beanFactory; + + private static ApplicationContext applicationContext; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException + { + SpringUtils.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + SpringUtils.applicationContext = applicationContext; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws org.springframework.beans.BeansException + * + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws org.springframework.beans.BeansException + * + */ + public static T getBean(Class clz) throws BeansException + { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) + { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) + { + return (T) AopContext.currentProxy(); + } + + /** + * 获取当前的环境配置,无配置返回null + * + * @return 当前的环境配置 + */ + public static String[] getActiveProfiles() + { + return applicationContext.getEnvironment().getActiveProfiles(); + } + + /** + * 获取当前的环境配置,当有多个环境配置时,只获取第一个 + * + * @return 当前的环境配置 + */ + public static String getActiveProfile() + { + final String[] activeProfiles = getActiveProfiles(); + return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; + } + + /** + * 获取配置文件中的值 + * + * @param key 配置文件的key + * @return 当前的配置文件的值 + * + */ + public static String getRequiredProperty(String key) + { + return applicationContext.getEnvironment().getRequiredProperty(key); + } + + /** + * 获取带有annotation注解的所有bean集合 + * @param annotation 注解 + * @param + * @return 集合 + */ + public static Map getBeanWithAnnotation(Class annotation){ + Map resultMap = new HashMap<>(); + Map beanMap = applicationContext.getBeansWithAnnotation(annotation); + if (CollectionUtils.isEmpty(beanMap)){ + return resultMap; + } + beanMap.forEach((key,value)->{ + resultMap.put(key,(T) value); + }); + return resultMap; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/sql/SqlUtil.java b/maibu-common/src/main/java/com/maibu/utils/sql/SqlUtil.java new file mode 100644 index 0000000..af64e5f --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/sql/SqlUtil.java @@ -0,0 +1,62 @@ +package com.maibu.utils.sql; + + +import com.maibu.exception.UtilException; +import com.maibu.utils.StringUtils; + +/** + * sql操作工具类 + * + * @author ruoyi + */ +public class SqlUtil +{ + /** + * 定义常用的 sql关键字 + */ + public static String SQL_REGEX = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |+|user()"; + + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; + + /** + * 检查字符,防止注入绕过 + */ + public static String escapeOrderBySql(String value) + { + if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) + { + throw new UtilException("参数不符合规范,不能进行查询"); + } + return value; + } + + /** + * 验证 order by 语法是否符合规范 + */ + public static boolean isValidOrderBySql(String value) + { + return value.matches(SQL_PATTERN); + } + + /** + * SQL关键字检查 + */ + public static void filterKeyword(String value) + { + if (StringUtils.isEmpty(value)) + { + return; + } + String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); + for (String sqlKeyword : sqlKeywords) + { + if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) + { + throw new UtilException("参数存在SQL注入风险"); + } + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/uuid/IdUtils.java b/maibu-common/src/main/java/com/maibu/utils/uuid/IdUtils.java new file mode 100644 index 0000000..2e7ec52 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/uuid/IdUtils.java @@ -0,0 +1,137 @@ +package com.maibu.utils.uuid; + +import com.maibu.utils.Md5Utils; +import lombok.extern.slf4j.Slf4j; + +import java.util.Random; + +/** + * ID生成器工具类 + * + * @author ruoyi + */ +@Slf4j +public class IdUtils { + private static long lastTimestamp = -1L; + private long sequence = 0L; + private final long workerId; + private final long datacenterId; + private static Integer startIndex = 0; + private static Integer endIndex = 6; + + public IdUtils(long workerId, long datacenterId) { + if (workerId <= 31L && workerId >= 0L) { + this.workerId = workerId; + } else { + if (workerId != -1L) { + throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0"); + } + + this.workerId = (long) (new Random()).nextInt(31); + } + + if (datacenterId <= 31L && datacenterId >= 0L) { + this.datacenterId = datacenterId; + } else { + if (datacenterId != -1L) { + throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0"); + } + + this.datacenterId = (long) (new Random()).nextInt(31); + } + + } + + public synchronized long nextId() { + long timestamp = this.timeGen(); + if (timestamp < lastTimestamp) { + try { + throw new Exception("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds"); + } catch (Exception e) { + log.warn("生成ID异常", e); + } + } + + if (lastTimestamp == timestamp) { + this.sequence = this.sequence + 1L & 4095L; + if (this.sequence == 0L) { + timestamp = this.tilNextMillis(lastTimestamp); + } + } else { + this.sequence = 0L; + } + + lastTimestamp = timestamp; + return timestamp - 1288834974657L << 22 | this.datacenterId << 17 | this.workerId << 12 | this.sequence; + } + + private long tilNextMillis(long lastTimestamp) { + long timestamp; + for (timestamp = this.timeGen(); timestamp <= lastTimestamp; timestamp = this.timeGen()) { + ; + } + return timestamp; + } + + private long timeGen() { + return System.currentTimeMillis(); + } + + public static String uuid() { + return java.util.UUID.randomUUID().toString().replaceAll("-", ""); + } + + public static String getNextCode() { + return Md5Utils.md5(IdUtils.uuid() + System.currentTimeMillis()).substring(startIndex, endIndex); + } + + + /** + * 获取随机UUID + * + * @return 随机UUID + */ + public static String randomUUID() { + return UUID.randomUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线 + * + * @return 简化的UUID,去掉了横线 + */ + public static String simpleUUID() { + return UUID.randomUUID().toString(true); + } + + /** + * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 随机UUID + */ + public static String fastUUID() { + return UUID.fastUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 简化的UUID,去掉了横线 + */ + public static String fastSimpleUUID() { + return UUID.fastUUID().toString(true); + } + + + // 随机生成16位字符串 + public static String getRandomStr(int length) { + String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + Random random = new Random(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + int number = random.nextInt(base.length()); + sb.append(base.charAt(number)); + } + return sb.toString(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/uuid/Seq.java b/maibu-common/src/main/java/com/maibu/utils/uuid/Seq.java new file mode 100644 index 0000000..0d913a8 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/uuid/Seq.java @@ -0,0 +1,88 @@ +package com.maibu.utils.uuid; + + +import com.maibu.utils.DateUtils; +import com.maibu.utils.StringUtils; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author ruoyi 序列生成类 + */ +public class Seq +{ + // 通用序列类型 + public static final String commSeqType = "COMMON"; + + // 上传序列类型 + public static final String uploadSeqType = "UPLOAD"; + + // 通用接口序列数 + private static AtomicInteger commSeq = new AtomicInteger(1); + + // 上传接口序列数 + private static AtomicInteger uploadSeq = new AtomicInteger(1); + + // 机器标识 + private static String machineCode = "A"; + + /** + * 获取通用序列号 + * + * @return 序列值 + */ + public static String getId() + { + return getId(commSeqType); + } + + /** + * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串 + * + * @return 序列值 + */ + public static String getId(String type) + { + AtomicInteger atomicInt = commSeq; + if (uploadSeqType.equals(type)) + { + atomicInt = uploadSeq; + } + return getId(atomicInt, 3); + } + + /** + * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串 + * + * @param atomicInt 序列数 + * @param length 数值长度 + * @return 序列值 + */ + public static String getId(AtomicInteger atomicInt, int length) + { + String result = DateUtils.dateTimeNow(); + result += machineCode; + result += getSeq(atomicInt, length); + return result; + } + + /** + * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数 + * + * @return 序列值 + */ + private synchronized static String getSeq(AtomicInteger atomicInt, int length) + { + // 先取值再+1 + int value = atomicInt.getAndIncrement(); + + // 如果更新后值>=10 的 (length)幂次方则重置为1 + int maxSeq = (int) Math.pow(10, length); + if (atomicInt.get() >= maxSeq) + { + atomicInt.set(1); + } + // 转字符串,用0左补齐 + return StringUtils.padl(value, length); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/uuid/UUID.java b/maibu-common/src/main/java/com/maibu/utils/uuid/UUID.java new file mode 100644 index 0000000..25ec6ce --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/uuid/UUID.java @@ -0,0 +1,486 @@ +package com.maibu.utils.uuid; + + +import com.maibu.exception.UtilException; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 提供通用唯一识别码(universally unique identifier)(UUID)实现 + * + * @author ruoyi + */ +public final class UUID implements java.io.Serializable, Comparable +{ + private static final long serialVersionUID = -1185015143654744140L; + + /** + * SecureRandom 的单例 + * + */ + private static class Holder + { + static final SecureRandom numberGenerator = getSecureRandom(); + } + + /** 此UUID的最高64有效位 */ + private final long mostSigBits; + + /** 此UUID的最低64有效位 */ + private final long leastSigBits; + + /** + * 私有构造 + * + * @param data 数据 + */ + private UUID(byte[] data) + { + long msb = 0; + long lsb = 0; + assert data.length == 16 : "data must be 16 bytes in length"; + for (int i = 0; i < 8; i++) + { + msb = (msb << 8) | (data[i] & 0xff); + } + for (int i = 8; i < 16; i++) + { + lsb = (lsb << 8) | (data[i] & 0xff); + } + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + /** + * 使用指定的数据构造新的 UUID。 + * + * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 + * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 + */ + public UUID(long mostSigBits, long leastSigBits) + { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID fastUUID() + { + return randomUUID(false); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID() + { + return randomUUID(true); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID(boolean isSecure) + { + final Random ng = isSecure ? Holder.numberGenerator : getRandom(); + + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(randomBytes); + } + + /** + * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 + * + * @param name 用于构造 UUID 的字节数组。 + * + * @return 根据指定数组生成的 {@code UUID} + */ + public static UUID nameUUIDFromBytes(byte[] name) + { + MessageDigest md; + try + { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException nsae) + { + throw new InternalError("MD5 not supported"); + } + byte[] md5Bytes = md.digest(name); + md5Bytes[6] &= 0x0f; /* clear version */ + md5Bytes[6] |= 0x30; /* set to version 3 */ + md5Bytes[8] &= 0x3f; /* clear variant */ + md5Bytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(md5Bytes); + } + + /** + * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 + * + * @param name 指定 {@code UUID} 字符串 + * @return 具有指定值的 {@code UUID} + * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 + * + */ + public static UUID fromString(String name) + { + String[] components = name.split("-"); + if (components.length != 5) + { + throw new IllegalArgumentException("Invalid UUID string: " + name); + } + for (int i = 0; i < 5; i++) + { + components[i] = "0x" + components[i]; + } + + long mostSigBits = Long.decode(components[0]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[2]).longValue(); + + long leastSigBits = Long.decode(components[3]).longValue(); + leastSigBits <<= 48; + leastSigBits |= Long.decode(components[4]).longValue(); + + return new UUID(mostSigBits, leastSigBits); + } + + /** + * 返回此 UUID 的 128 位值中的最低有效 64 位。 + * + * @return 此 UUID 的 128 位值中的最低有效 64 位。 + */ + public long getLeastSignificantBits() + { + return leastSigBits; + } + + /** + * 返回此 UUID 的 128 位值中的最高有效 64 位。 + * + * @return 此 UUID 的 128 位值中最高有效 64 位。 + */ + public long getMostSignificantBits() + { + return mostSigBits; + } + + /** + * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 + *

+ * 版本号具有以下含意: + *

    + *
  • 1 基于时间的 UUID + *
  • 2 DCE 安全 UUID + *
  • 3 基于名称的 UUID + *
  • 4 随机生成的 UUID + *
+ * + * @return 此 {@code UUID} 的版本号 + */ + public int version() + { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

+ * 变体号具有以下含意: + *

    + *
  • 0 为 NCS 向后兼容保留 + *
  • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
  • 6 保留,微软向后兼容 + *
  • 7 保留供以后定义使用 + *
+ * + * @return 此 {@code UUID} 相关联的变体号 + */ + public int variant() + { + // This field is composed of a varying number of bits. + // 0 - - Reserved for NCS backward compatibility + // 1 0 - The IETF aka Leach-Salz variant (used by this class) + // 1 1 0 Reserved, Microsoft backward compatibility + // 1 1 1 Reserved for future definition. + return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); + } + + /** + * 与此 UUID 相关联的时间戳值。 + * + *

+ * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
+ * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

+ * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 + */ + public long timestamp() throws UnsupportedOperationException + { + checkTimeBase(); + return (mostSigBits & 0x0FFFL) << 48// + | ((mostSigBits >> 16) & 0x0FFFFL) << 32// + | mostSigBits >>> 32; + } + + /** + * 与此 UUID 相关联的时钟序列值。 + * + *

+ * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

+ * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 + * UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的时钟序列 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public int clockSequence() throws UnsupportedOperationException + { + checkTimeBase(); + return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); + } + + /** + * 与此 UUID 相关的节点值。 + * + *

+ * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

+ * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException + { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() + { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 + * @return 此{@code UUID} 的字符串表现形式 + */ + public String toString(boolean isSimple) + { + final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); + // time_low + builder.append(digits(mostSigBits >> 32, 8)); + if (!isSimple) + { + builder.append('-'); + } + // time_mid + builder.append(digits(mostSigBits >> 16, 4)); + if (!isSimple) + { + builder.append('-'); + } + // time_high_and_version + builder.append(digits(mostSigBits, 4)); + if (!isSimple) + { + builder.append('-'); + } + // variant_and_sequence + builder.append(digits(leastSigBits >> 48, 4)); + if (!isSimple) + { + builder.append('-'); + } + // node + builder.append(digits(leastSigBits, 12)); + + return builder.toString(); + } + + /** + * 返回此 UUID 的哈希码。 + * + * @return UUID 的哈希码值。 + */ + @Override + public int hashCode() + { + long hilo = mostSigBits ^ leastSigBits; + return ((int) (hilo >> 32)) ^ (int) hilo; + } + + /** + * 将此对象与指定对象比较。 + *

+ * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 + * + * @param obj 要与之比较的对象 + * + * @return 如果对象相同,则返回 {@code true};否则返回 {@code false} + */ + @Override + public boolean equals(Object obj) + { + if ((null == obj) || (obj.getClass() != UUID.class)) + { + return false; + } + UUID id = (UUID) obj; + return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + // Comparison Operations + + /** + * 将此 UUID 与指定的 UUID 比较。 + * + *

+ * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 + * + * @param val 与此 UUID 比较的 UUID + * + * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 + * + */ + @Override + public int compareTo(UUID val) + { + // The ordering is intentionally set up so that the UUIDs + // can simply be numerically compared as two numbers + return (this.mostSigBits < val.mostSigBits ? -1 : // + (this.mostSigBits > val.mostSigBits ? 1 : // + (this.leastSigBits < val.leastSigBits ? -1 : // + (this.leastSigBits > val.leastSigBits ? 1 : // + 0)))); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Private method start + /** + * 返回指定数字对应的hex值 + * + * @param val 值 + * @param digits 位 + * @return 值 + */ + private static String digits(long val, int digits) + { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * 检查是否为time-based版本UUID + */ + private void checkTimeBase() + { + if (version() != 1) + { + throw new UnsupportedOperationException("Not a time-based UUID"); + } + } + + /** + * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) + * + * @return {@link SecureRandom} + */ + public static SecureRandom getSecureRandom() + { + try + { + return SecureRandom.getInstance("SHA1PRNG"); + } + catch (NoSuchAlgorithmException e) + { + throw new UtilException(e); + } + } + + /** + * 获取随机数生成器对象
+ * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() + { + return ThreadLocalRandom.current(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/wechat/AesException.java b/maibu-common/src/main/java/com/maibu/utils/wechat/AesException.java new file mode 100644 index 0000000..d0fd1af --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/wechat/AesException.java @@ -0,0 +1,56 @@ +package com.maibu.utils.wechat; + +/** + * 加解密异常类 + */ +public class AesException extends Exception { + public final static int OK = 0; + public final static int ValidateSignatureError = -40001; + public final static int ParseXmlError = -40002; + public final static int ComputeSignatureError = -40003; + public final static int IllegalAesKey = -40004; + public final static int ValidateCorpidError = -40005; + public final static int EncryptAESError = -40006; + public final static int DecryptAESError = -40007; + public final static int IllegalBuffer = -40008; + //public final static int EncodeBase64Error = -40009; +//public final static int DecodeBase64Error = -40010; +//public final static int GenReturnXmlError = -40011; + private int code; + + private static String getMessage(int code) { + switch (code) { + case ValidateSignatureError: + return "签名验证错误"; + case ParseXmlError: + return "xml解析失败"; + case ComputeSignatureError: + return "sha加密生成签名失败"; + case IllegalAesKey: + return "SymmetricKey非法"; + case ValidateCorpidError: + return "corpid校验失败"; + case EncryptAESError: + return "aes加密失败"; + case DecryptAESError: + return "aes解密失败"; + case IllegalBuffer: + return "解密后得到的buffer非法"; +// case EncodeBase64Error: +// return "base64加密错误"; +// case DecodeBase64Error: +// return "base64解密错误"; +// case GenReturnXmlError: +// return "xml生成失败"; + default: + return null; // cannot be + } + } + public int getCode() { + return code; + } + AesException(int code) { + super(getMessage(code)); + this.code = code; + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/wechat/ByteGroup.java b/maibu-common/src/main/java/com/maibu/utils/wechat/ByteGroup.java new file mode 100644 index 0000000..58a464d --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/wechat/ByteGroup.java @@ -0,0 +1,26 @@ +package com.maibu.utils.wechat; + +import java.util.ArrayList; + +class ByteGroup { + ArrayList byteContainer = new ArrayList(); + + public byte[] toBytes() { + byte[] bytes = new byte[byteContainer.size()]; + for (int i = 0; i < byteContainer.size(); i++) { + bytes[i] = byteContainer.get(i); + } + return bytes; + } + + public ByteGroup addBytes(byte[] bytes) { + for (byte b : bytes) { + byteContainer.add(b); + } + return this; + } + + public int size() { + return byteContainer.size(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/wechat/PKCS7Encoder.java b/maibu-common/src/main/java/com/maibu/utils/wechat/PKCS7Encoder.java new file mode 100644 index 0000000..ddb6fb6 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/wechat/PKCS7Encoder.java @@ -0,0 +1,59 @@ +package com.maibu.utils.wechat; + +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * 提供基于PKCS7算法的加解密接口. + */ +class PKCS7Encoder { + static Charset CHARSET = Charset.forName("utf-8"); + static int BLOCK_SIZE = 32; + + /** + * 获得对明文进行补位填充的字节. + * + * @param count 需要进行填充补位操作的明文字节个数 + * @return 补齐用的字节数组 + */ + static byte[] encode(int count) { + // 计算需要填充的位数 + int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); + if (amountToPad == 0) { + amountToPad = BLOCK_SIZE; + } + // 获得补位所用的字符 + char padChr = chr(amountToPad); + String tmp = new String(); + for (int index = 0; index < amountToPad; index++) { + tmp += padChr; + } + return tmp.getBytes(CHARSET); + } + + /** + * 删除解密后明文的补位字符 + * + * @param decrypted 解密后的明文 + * @return 删除补位字符后的明文 + */ + static byte[] decode(byte[] decrypted) { + int pad = (int) decrypted[decrypted.length - 1]; + if (pad < 1 || pad > 32) { + pad = 0; + } + return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); + } + + /** + * 将数字转化成ASCII码对应的字符,用于对明文进行补码 + * + * @param a 需要转化的数字 + * @return 转化得到的字符 + */ + static char chr(int a) { + byte target = (byte) (a & 0xFF); + return (char) target; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/wechat/SHA1.java b/maibu-common/src/main/java/com/maibu/utils/wechat/SHA1.java new file mode 100644 index 0000000..024ece7 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/wechat/SHA1.java @@ -0,0 +1,57 @@ +package com.maibu.utils.wechat; + +import java.security.MessageDigest; +import java.util.Arrays; +/** + * 对企业微信发送给企业后台的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ +/** + * SHA1 class + * + * 计算消息签名接口. + */ +public class SHA1 { + + /** + * 用SHA1算法生成安全签名 + * @param token 票据 + * @param timestamp 时间戳 + * @param nonce 随机字符串 + * @param encrypt 密文 + * @return 安全签名 + * @throws AesException + */ + public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException + { + try { + String[] array = new String[] { token, timestamp, nonce, encrypt }; + StringBuffer sb = new StringBuffer(); + // 字符串排序 + Arrays.sort(array); + for (int i = 0; i < 4; i++) { + sb.append(array[i]); + } + String str = sb.toString(); + // SHA1签名生成 + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(str.getBytes()); + byte[] digest = md.digest(); + + StringBuffer hexstr = new StringBuffer(); + String shaHex = ""; + for (int i = 0; i < digest.length; i++) { + shaHex = Integer.toHexString(digest[i] & 0xFF); + if (shaHex.length() < 2) { + hexstr.append(0); + } + hexstr.append(shaHex); + } + return hexstr.toString(); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.ComputeSignatureError); + } + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/wechat/WXBizMsgCrypt.java b/maibu-common/src/main/java/com/maibu/utils/wechat/WXBizMsgCrypt.java new file mode 100644 index 0000000..2123842 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/wechat/WXBizMsgCrypt.java @@ -0,0 +1,289 @@ +package com.maibu.utils.wechat; + +/** + * 对企业微信发送给企业后台的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +/** + * 针对org.apache.commons.codec.binary.Base64, + * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) + * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi + */ + +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Random; + +/** + * 提供接收和推送给企业微信消息的加解密接口(UTF8编码的字符串). + *

    + *
  1. 第三方回复加密消息给企业微信
  2. + *
  3. 第三方收到企业微信发送的消息,验证消息的安全性,并对消息进行解密。
  4. + *
+ * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案 + *
    + *
  1. 在官方网站下载JCE无限制权限策略文件(JDK7的下载地址: + * http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
  2. + *
  3. 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
  4. + *
  5. 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
  6. + *
  7. 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件
  8. + *
+ */ +public class WXBizMsgCrypt { + static Charset CHARSET = Charset.forName("utf-8"); + Base64 base64 = new Base64(); + byte[] aesKey; + String token; + String receiveid; + + /** + * 构造函数 + * @param token 企业微信后台,开发者设置的token + * @param encodingAesKey 企业微信后台,开发者设置的EncodingAESKey + * @param receiveid, 不同场景含义不同,详见文档 + * + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public WXBizMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException { + if (encodingAesKey.length() != 43) { + throw new AesException(AesException.IllegalAesKey); + } + + this.token = token; + this.receiveid = receiveid; + aesKey = Base64.decodeBase64(encodingAesKey + "="); + } + + // 生成4个字节的网络字节序 + byte[] getNetworkBytesOrder(int sourceNumber) { + byte[] orderBytes = new byte[4]; + orderBytes[3] = (byte) (sourceNumber & 0xFF); + orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF); + orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF); + orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF); + return orderBytes; + } + + // 还原4个字节的网络字节序 + int recoverNetworkBytesOrder(byte[] orderBytes) { + int sourceNumber = 0; + for (int i = 0; i < 4; i++) { + sourceNumber <<= 8; + sourceNumber |= orderBytes[i] & 0xff; + } + return sourceNumber; + } + + // 随机生成16位字符串 + String getRandomStr() { + String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 16; i++) { + int number = random.nextInt(base.length()); + sb.append(base.charAt(number)); + } + return sb.toString(); + } + + /** + * 对明文进行加密. + * + * @param text 需要加密的明文 + * @return 加密后base64编码的字符串 + * @throws AesException aes加密失败 + */ + String encrypt(String randomStr, String text) throws AesException { + ByteGroup byteCollector = new ByteGroup(); + byte[] randomStrBytes = randomStr.getBytes(CHARSET); + byte[] textBytes = text.getBytes(CHARSET); + byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length); + byte[] receiveidBytes = receiveid.getBytes(CHARSET); + + // randomStr + networkBytesOrder + text + receiveid + byteCollector.addBytes(randomStrBytes); + byteCollector.addBytes(networkBytesOrder); + byteCollector.addBytes(textBytes); + byteCollector.addBytes(receiveidBytes); + + // ... + pad: 使用自定义的填充方式对明文进行补位填充 + byte[] padBytes = PKCS7Encoder.encode(byteCollector.size()); + byteCollector.addBytes(padBytes); + + // 获得最终的字节流, 未加密 + byte[] unencrypted = byteCollector.toBytes(); + + try { + // 设置加密模式为AES的CBC模式 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); + + // 加密 + byte[] encrypted = cipher.doFinal(unencrypted); + + // 使用BASE64对加密后的字符串进行编码 + String base64Encrypted = base64.encodeToString(encrypted); + + return base64Encrypted; + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.EncryptAESError); + } + } + + /** + * 对密文进行解密. + * + * @param text 需要解密的密文 + * @return 解密得到的明文 + * @throws AesException aes解密失败 + */ + String decrypt(String text) throws AesException { + byte[] original; + try { + // 设置解密模式为AES的CBC模式 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); + cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); + + // 使用BASE64对密文进行解码 + byte[] encrypted = Base64.decodeBase64(text); + + // 解密 + original = cipher.doFinal(encrypted); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.DecryptAESError); + } + + String xmlContent, from_receiveid; + try { + // 去除补位字符 + byte[] bytes = PKCS7Encoder.decode(original); + + // 分离16位随机字符串,网络字节序和receiveid + byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); + + int xmlLength = recoverNetworkBytesOrder(networkOrder); + + xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); + from_receiveid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), + CHARSET); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.IllegalBuffer); + } + + // receiveid不相同的情况 + if (!from_receiveid.equals(receiveid)) { + throw new AesException(AesException.ValidateCorpidError); + } + return xmlContent; + + } + + /** + * 将企业微信回复用户的消息加密打包. + *
    + *
  1. 对要发送的消息进行AES-CBC加密
  2. + *
  3. 生成安全签名
  4. + *
  5. 将消息密文和安全签名打包成xml格式
  6. + *
+ * + * @param replyMsg 企业微信待回复用户的消息,xml格式的字符串 + * @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp + * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce + * + * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String EncryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException { + // 加密 + String encrypt = encrypt(getRandomStr(), replyMsg); + + // 生成安全签名 + if (timeStamp == "") { + timeStamp = Long.toString(System.currentTimeMillis()); + } + + String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt); + + // System.out.println("发送给平台的签名是: " + signature[1].toString()); + // 生成发送的xml + String result = XMLParse.generate(encrypt, signature, timeStamp, nonce); + return result; + } + + /** + * 检验消息的真实性,并且获取解密后的明文. + *
    + *
  1. 利用收到的密文生成安全签名,进行签名验证
  2. + *
  3. 若验证通过,则提取xml中的加密消息
  4. + *
  5. 对消息进行解密
  6. + *
+ * + * @param msgSignature 签名串,对应URL参数的msg_signature + * @param timeStamp 时间戳,对应URL参数的timestamp + * @param nonce 随机串,对应URL参数的nonce + * @param postData 密文,对应POST请求的数据 + * + * @return 解密后的原文 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String DecryptMsg(String msgSignature, String timeStamp, String nonce, String postData) + throws AesException { + + // 密钥,公众账号的app secret + // 提取密文 + Object[] encrypt = XMLParse.extract(postData); + + // 验证安全签名 + String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString()); + + // 和URL中的签名比较是否相等 + // System.out.println("第三方收到URL中的签名:" + msg_sign); + // System.out.println("第三方校验签名:" + signature); + if (!signature.equals(msgSignature)) { + throw new AesException(AesException.ValidateSignatureError); + } + + // 解密 + String result = decrypt(encrypt[1].toString()); + return result; + } + + /** + * 验证URL + * @param msgSignature 签名串,对应URL参数的msg_signature + * @param timeStamp 时间戳,对应URL参数的timestamp + * @param nonce 随机串,对应URL参数的nonce + * @param echoStr 随机串,对应URL参数的echostr + * + * @return 解密之后的echostr + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String VerifyURL(String msgSignature, String timeStamp, String nonce, String echoStr) + throws AesException { + String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr); + + if (!signature.equals(msgSignature)) { + throw new AesException(AesException.ValidateSignatureError); + } + + String result = decrypt(echoStr); + return result; + } + +} diff --git a/maibu-common/src/main/java/com/maibu/utils/wechat/WechatUtils.java b/maibu-common/src/main/java/com/maibu/utils/wechat/WechatUtils.java new file mode 100644 index 0000000..498d4ea --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/wechat/WechatUtils.java @@ -0,0 +1,123 @@ +package com.maibu.utils.wechat; + +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.maibu.constant.CacheConstants; +import com.maibu.constant.FastBeeConstant; +import com.maibu.core.redis.RedisCache; +import com.maibu.utils.StringUtils; +import com.maibu.utils.http.HttpUtils; +import com.maibu.utils.spring.SpringUtils; +import com.maibu.wechat.*; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.concurrent.TimeUnit; + +/** + * @author fastb + * @version 1.0 + * @description: 微信相关工具类 + * @date 2024-01-08 17:36 + */ +public class WechatUtils { + + /** + * 网站、移动应用获取微信用户accessToken + * @param code 用户登录code + * @param appId 微信平台appId + * @param secret 微信平台密钥 + * @return WeChatAppResult + */ + public static WeChatAppResult getAccessTokenOpenId(String code, String appId, String secret) { + String url = FastBeeConstant.URL.WX_GET_ACCESS_TOKEN_URL_PREFIX + "?appid=" + appId + "&secret=" + secret + "&code=" + code + "&grant_type=authorization_code"; + String s = HttpUtils.sendGet(url); + return JSON.parseObject(s, WeChatAppResult.class); + } + + /** + * 获取微信用户信息 + * @param accessToken 接口调用凭证 + * @param openId 用户唯一标识 + * @return WeChatUserInfo + */ + public static WeChatUserInfo getWeChatUserInfo(String accessToken, String openId) { + String url = FastBeeConstant.URL.WX_GET_USER_INFO_URL_PREFIX + "?access_token=" + accessToken + "&openid=" + openId; + String s = HttpUtils.sendGet(url); + return JSON.parseObject(s, WeChatUserInfo.class); + } + + /** + * 小程序获取微信用户登录信息 + * @param code 用户凭证 + * @param appId 微信平台appId + * @param secret 微信平台密钥 + * @return 结果 + */ + public static WeChatMiniProgramResult codeToSession(String code, String appId, String secret) { + String url = FastBeeConstant.URL.WX_MINI_PROGRAM_GET_USER_SESSION_URL_PREFIX + "?appid=" + appId + "&secret=" + secret + "&js_code=" + code + "&grant_type=authorization_code"; + String s = HttpUtils.sendGet(url); + return JSON.parseObject(s, WeChatMiniProgramResult.class); + } + + /** + * 小程序获取微信用户手机号 + * @param code 凭证 + * @param accessToken 微信用户token + * @return 手机号信息 + */ + public static WeChatPhoneInfo getWechatUserPhoneInfo(String code, String accessToken) { + String url = FastBeeConstant.URL.WX_GET_USER_PHONE_URL_PREFIX + accessToken; + HashMap map = new HashMap<>(); + map.put("code", code); + String s = HttpUtils.sendPost(url, JSONObject.toJSONString(map)); + return JSON.parseObject(s, WeChatPhoneInfo.class); + } + + /** + * 小程序获、公众号取微信accessToken + * @param appId 微信平台appId + * @param secret 微信平台密钥 + * @return WeChatAppResult + */ + public static WeChatAppResult getAccessToken(String appId, String secret) { + // 加个缓存 + WeChatAppResult wechatAppResultRedis = SpringUtils.getBean(RedisCache.class).getCacheObject(CacheConstants.WECHAT_GET_ACCESS_TOKEN_APPID + appId); + if (ObjectUtil.isNotNull(wechatAppResultRedis)) { + return wechatAppResultRedis; + } + String url = FastBeeConstant.URL.WX_MINI_PROGRAM_GET_ACCESS_TOKEN_URL_PREFIX + "&appid=" + appId + "&secret=" + secret; + String s = HttpUtils.sendGet(url); + WeChatAppResult weChatAppResult = JSON.parseObject(s, WeChatAppResult.class); + if (ObjectUtil.isNotNull(weChatAppResult) && StringUtils.isNotEmpty(weChatAppResult.getAccessToken())) { + SpringUtils.getBean(RedisCache.class).setCacheObject(CacheConstants.WECHAT_GET_ACCESS_TOKEN_APPID + appId, weChatAppResult, 1, TimeUnit.HOURS); + } + return weChatAppResult; + } + + /** + * 微信公众号获取微信用户信息 + * @param accessToken 接口调用凭证 + * @param openId 用户唯一标识 + * @return WeChatUserInfo + */ + public static WeChatUserInfo getWeChatPublicAccountUserInfo(String accessToken, String openId) { + String url = FastBeeConstant.URL.WX_PUBLIC_ACCOUNT_GET_USER_INFO_URL_PREFIX + "?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN"; + String s = HttpUtils.sendGet(url); + return JSON.parseObject(s, WeChatUserInfo.class); + } + + public static String responseText(WxCallBackXmlBO wxCallBackXmlBO, String content) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(""); + stringBuilder.append(""); + stringBuilder.append(""); + stringBuilder.append("" + (LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000) + ""); + stringBuilder.append(""); + stringBuilder.append(""); //替换空格,文本信息内容不能包含有空格 .Replace(" ", string.Empty) + stringBuilder.append(""); + return stringBuilder.toString(); + } +} diff --git a/maibu-common/src/main/java/com/maibu/utils/wechat/XMLParse.java b/maibu-common/src/main/java/com/maibu/utils/wechat/XMLParse.java new file mode 100644 index 0000000..8d4f1c3 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/utils/wechat/XMLParse.java @@ -0,0 +1,103 @@ +package com.maibu.utils.wechat; + +/** + * 对企业微信发送给企业后台的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.StringReader; + +/** + * XMLParse class + * + * 提供提取消息格式中的密文及生成回复消息格式的接口. + */ +class XMLParse { + + /** + * 提取出xml数据包中的加密消息 + * @param xmltext 待提取的xml字符串 + * @return 提取出的加密消息字符串 + * @throws AesException + */ + public static Object[] extract(String xmltext) throws AesException { + Object[] result = new Object[3]; + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + + String FEATURE = null; + // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented + // Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl + FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; + dbf.setFeature(FEATURE, true); + + // If you can't completely disable DTDs, then at least do the following: + // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities + // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities + // JDK7+ - http://xml.org/sax/features/external-general-entities + FEATURE = "http://xml.org/sax/features/external-general-entities"; + dbf.setFeature(FEATURE, false); + + // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities + // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities + // JDK7+ - http://xml.org/sax/features/external-parameter-entities + FEATURE = "http://xml.org/sax/features/external-parameter-entities"; + dbf.setFeature(FEATURE, false); + + // Disable external DTDs as well + FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; + dbf.setFeature(FEATURE, false); + + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + + // And, per Timothy Morgan: "If for some reason support for inline DOCTYPEs are a requirement, then + // ensure the entity settings are disabled (as shown above) and beware that SSRF attacks + // (http://cwe.mitre.org/data/definitions/918.html) and denial + // of service attacks (such as billion laughs or decompression bombs via "jar:") are a risk." + + // remaining parser logic + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(xmltext); + InputSource is = new InputSource(sr); + Document document = db.parse(is); + + Element root = document.getDocumentElement(); + NodeList nodelist1 = root.getElementsByTagName("Encrypt"); + result[0] = 0; + result[1] = nodelist1.item(0).getTextContent(); + return result; + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.ParseXmlError); + } + } + + /** + * 生成xml消息 + * @param encrypt 加密后的消息密文 + * @param signature 安全签名 + * @param timestamp 时间戳 + * @param nonce 随机字符串 + * @return 生成的xml字符串 + */ + public static String generate(String encrypt, String signature, String timestamp, String nonce) { + + String format = "\n" + "\n" + + "\n" + + "%3$s\n" + "\n" + ""; + return String.format(format, encrypt, signature, timestamp, nonce); + + } +} diff --git a/maibu-common/src/main/java/com/maibu/wechat/WeChatAppResult.java b/maibu-common/src/main/java/com/maibu/wechat/WeChatAppResult.java new file mode 100644 index 0000000..e51ac03 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/wechat/WeChatAppResult.java @@ -0,0 +1,72 @@ +package com.maibu.wechat; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * WeChat 调用api接口获取openid登信息后的返回类 + * @author fastb + * @date 2023-07-31 11:43 + */ +@Data +public class WeChatAppResult { + + /** + * 接口调用凭证 + */ + @JSONField(name = "access_token") + private String accessToken; + + /** + * access_token 接口调用凭证超时时间,单位(秒) + */ + @JSONField(name = "expires_in") + private Long expiresIn; + + /** + * 用户刷新 access_token + */ + @JSONField(name = "refresh_token") + private String refreshToken; + + /** + * 授权用户唯一标识 + */ + @JSONField(name = "openid") + private String openId; + + /** + * 用户授权的作用域(snsapi_userinfo) + */ + @JSONField(name = "scope") + private String scope; + + /** + * 当且仅当该移动应用已获得该用户的 userinfo 授权时,才会出现该字段 + */ + @JSONField(name = "unionid") + private String unionId; + + /** + * 错误码 + */ + @JSONField(name = "errcode") + private Integer errCode; + + /** + * 错误信息 + */ + @JSONField(name = "errmsg") + private String errMsg; + + /** + * 是否绑定手机号 + */ + private Boolean isBind; + + /** + * token 自定义登录状态 + */ + private String token; + +} diff --git a/maibu-common/src/main/java/com/maibu/wechat/WeChatLoginBody.java b/maibu-common/src/main/java/com/maibu/wechat/WeChatLoginBody.java new file mode 100644 index 0000000..a1d3dab --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/wechat/WeChatLoginBody.java @@ -0,0 +1,89 @@ +package com.maibu.wechat; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 微信端登录参数 + * @author fastb + * @date 2023-07-31 11:32 + */ +@Data +@Accessors(chain = true) +public class WeChatLoginBody { + + /** + * 传入参数:临时登录凭证 + */ + private String code; + + /** + * 临时获取用户手机号凭证 + */ + private String phoneCode; + + /** + * 传入参数 openid + */ + private String openId; + + /** + * 传入参数 session_key + */ + private String sessionKey; + + /** + * 传入参数 unionid + */ + private String unionId; + + /** + * 传入参数: 用户非敏感信息 + */ + private String rawData; + + /** + * 传入参数: 签名 + */ + private String signature; + + /** + * 传入参数: 用户敏感信息 + */ + private String encryptedData; + + /** + * 传入参数: 解密算法的向量 + */ + private String iv; + + /** + * 用户手机号 + */ + private String userPhone; + + /** + * 用户密码 + */ + private String userPwd; + + /** + * 接口调用凭证 + */ + private String accessToken; + + /** + * access_token 接口调用凭证超时时间,单位(秒) + */ + private Long expiresIn; + + /** + * 用户刷新 access_token + */ + private String refreshToken; + + /** + * 用户授权的作用域(snsapi_userinfo) + */ + private String scope; +} diff --git a/maibu-common/src/main/java/com/maibu/wechat/WeChatLoginResult.java b/maibu-common/src/main/java/com/maibu/wechat/WeChatLoginResult.java new file mode 100644 index 0000000..1c57ff1 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/wechat/WeChatLoginResult.java @@ -0,0 +1,22 @@ +package com.maibu.wechat; + +import lombok.Data; + +/** + * 微信登录返回结果 + * @author fastb + * @date 2023-08-15 16:43 + */ +@Data +public class WeChatLoginResult { + + /** + * 登录成功返回token + */ + private String token; + + /** + * 绑定账号跳转页面 + */ + private String bindId; +} diff --git a/maibu-common/src/main/java/com/maibu/wechat/WeChatMiniProgramResult.java b/maibu-common/src/main/java/com/maibu/wechat/WeChatMiniProgramResult.java new file mode 100644 index 0000000..c4d7958 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/wechat/WeChatMiniProgramResult.java @@ -0,0 +1,42 @@ +package com.maibu.wechat; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @author fastb + * @date 2023-08-14 10:07 + */ +@Data +public class WeChatMiniProgramResult { + + /** + * 会话密钥 + */ + @JSONField(name = "session_key") + private String sessionKey; + + /** + * 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台账号下会返回,详见 UnionID 机制说明 + */ + @JSONField(name = "unionid") + private String unionId; + + /** + * 错误信息 + */ + @JSONField(name = "errmsg") + private String errMsg; + + /** + * 用户唯一标识 + */ + @JSONField(name = "openid") + private String openId; + + /** + * 错误码 + */ + @JSONField(name = "errcode") + private String errCode; +} diff --git a/maibu-common/src/main/java/com/maibu/wechat/WeChatPhoneInfo.java b/maibu-common/src/main/java/com/maibu/wechat/WeChatPhoneInfo.java new file mode 100644 index 0000000..e643804 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/wechat/WeChatPhoneInfo.java @@ -0,0 +1,41 @@ +package com.maibu.wechat; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * @author fastb + * @date 2023-08-16 17:48 + */ +@Data +public class WeChatPhoneInfo { + + @JSONField(name = "errcode") + private String errCode; + + @JSONField(name = "errmsg") + private String errmsg; + + @JSONField(name = "phone_info") + private PhoneInfo phoneInfo; + + @Data + public class PhoneInfo { + + private String phoneNumber; + + private String purePhoneNumber; + + private String countryCode; + + private WaterMark watermark; + } + + @Data + class WaterMark { + + private String timestamp; + + private String appid; + } +} diff --git a/maibu-common/src/main/java/com/maibu/wechat/WeChatUserInfo.java b/maibu-common/src/main/java/com/maibu/wechat/WeChatUserInfo.java new file mode 100644 index 0000000..ea6dfe0 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/wechat/WeChatUserInfo.java @@ -0,0 +1,80 @@ +package com.maibu.wechat; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * 微信用户信息 + * @author fastb + * @date 2023-07-31 14:56 + */ +@Data +public class WeChatUserInfo { + + /** + * 普通用户的标识,对当前开发者账号唯一 + */ + @JSONField(name = "openid") + private String openId; + + /** + * 普通用户昵称 + */ + @JSONField(name = "nickname") + private String nickname; + + /** + * 普通用户性别,1 为男性,2 为女性 + */ + @JSONField(name = "sex") + private Integer sex; + + /** + * 普通用户个人资料填写的省份 + */ + @JSONField(name = "province") + private String province; + + /** + * 普通用户个人资料填写的城市 + */ + @JSONField(name = "city") + private String city; + + /** + * 国家,如中国为 CN + */ + @JSONField(name = "country") + private String country; + + /** + * 用户头像,最后一个数值代表正方形头像大小(有 0、46、64、96、132 数值可选,0 代表 640*640 正方形头像),用户没有头像时该项为空 + */ + @JSONField(name = "headimgurl") + private String headImgUrl; + + /** + * 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom) + */ + @JSONField(name = "privilege") + private String privilege; + + /** + * 用户统一标识。针对一个微信开放平台账号下的应用,同一用户的 unionid 是唯一的。 + */ + @JSONField(name = "unionid") + private String unionId; + + /** + * 错误码 + */ + @JSONField(name = "errcode") + private Integer errCode; + + /** + * 错误信息 + */ + @JSONField(name = "errmsg") + private String errMsg; + +} diff --git a/maibu-common/src/main/java/com/maibu/wechat/WxCallBackXmlBO.java b/maibu-common/src/main/java/com/maibu/wechat/WxCallBackXmlBO.java new file mode 100644 index 0000000..8ffabb5 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/wechat/WxCallBackXmlBO.java @@ -0,0 +1,67 @@ +package com.maibu.wechat; + +import com.alibaba.fastjson2.annotation.JSONField; +import lombok.Data; + +/** + * 微信回调的时候入参XML解析节点封装BO + * @author fastb + * @date 2024-03-12 15:24 + * @version 1.0 + */ +@Data +public class WxCallBackXmlBO { + + @JSONField(name = "MsgType") + private String msgType; + + @JSONField(name = "FromUserName") + private String fromUserName; + + @JSONField(name = "ToUserName") + private String toUserName; + + @JSONField(name = "CreateTime") + private String createTime; + + @JSONField(name = "Content") + private String content; + + @JSONField(name = "MsgId") + private String msgId; + + @JSONField(name = "Event") + private String event; + + @JSONField(name = "EventKey") + private String eventKey; + + @JSONField(name = "Ticket") + private String ticket; + + @JSONField(name = "UnionId") + private String unionId; + + @JSONField(name = "Recognition") + private String recognition; + + @JSONField(name = "PicUrl") + private String picUrl; + + @JSONField(name = "SuccessOrderId") + private String successOrderId; + + @JSONField(name = "CardId") + private String cardId; + + @JSONField(name = "UserCardCode") + private String userCardCode; + + @JSONField(name = "LocationX") + private String locationX; + + @JSONField(name = "LocationY") + private String locationY; + + +} diff --git a/maibu-common/src/main/java/com/maibu/xss/Xss.java b/maibu-common/src/main/java/com/maibu/xss/Xss.java new file mode 100644 index 0000000..2796872 --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/xss/Xss.java @@ -0,0 +1,27 @@ +package com.maibu.xss; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义xss校验注解 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) +@Constraint(validatedBy = { XssValidator.class }) +public @interface Xss +{ + String message() + + default "不允许任何脚本运行"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/maibu-common/src/main/java/com/maibu/xss/XssValidator.java b/maibu-common/src/main/java/com/maibu/xss/XssValidator.java new file mode 100644 index 0000000..ee7498d --- /dev/null +++ b/maibu-common/src/main/java/com/maibu/xss/XssValidator.java @@ -0,0 +1,36 @@ +package com.maibu.xss; + + +import com.maibu.utils.StringUtils; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 自定义xss校验注解实现 + * + * @author ruoyi + */ +public class XssValidator implements ConstraintValidator +{ + private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />"; + + @Override + public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) + { + if (StringUtils.isBlank(value)) + { + return true; + } + return !containsHtml(value); + } + + public static boolean containsHtml(String value) + { + Pattern pattern = Pattern.compile(HTML_PATTERN); + Matcher matcher = pattern.matcher(value); + return matcher.matches(); + } +} \ No newline at end of file diff --git a/maibu-external/pom.xml b/maibu-external/pom.xml new file mode 100644 index 0000000..6897311 --- /dev/null +++ b/maibu-external/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + + com.maibu + MiddlePlatform + 4.0.0 + + + maibu-external + + + + org.springframework.boot + spring-boot-starter + + + org.apache.logging.log4j + log4j-core + 2.20.0 + + + org.apache.logging.log4j + log4j-api + 2.20.0 + + + + org.projectlombok + lombok + 1.18.8 + + + + org.java-websocket + Java-WebSocket + 1.5.3 + + + io.netty + netty-all + 4.1.56.Final + compile + + + org.apache.commons + commons-lang3 + 3.12.0 + compile + + + io.vertx + vertx-core + 4.5.7 + + + cn.hutool + hutool-all + 5.8.26 + + + commons-codec + commons-codec + 1.17.0 + + + com.maibu + maibu-iot-service + ${maibu.version} + + + com.maibu + maibu-common + + + io.minio + minio + 8.5.7 + + + + + 8 + 8 + UTF-8 + + + \ No newline at end of file diff --git a/maibu-external/src/main/java/com/maibu/own/controller/CommonDeviceController.java b/maibu-external/src/main/java/com/maibu/own/controller/CommonDeviceController.java new file mode 100644 index 0000000..99727dd --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/controller/CommonDeviceController.java @@ -0,0 +1,49 @@ +package com.maibu.own.controller; + +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.iot.domain.IoTCommonDevice; +import com.fastbee.own.entity.WorkOrder; +import com.fastbee.own.service.CommonDeviceService; +import com.fastbee.own.service.WorkOrderService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/iot/commonDevice") +public class CommonDeviceController extends BaseController { + + @Autowired + private CommonDeviceService commonDeviceService; + + + @GetMapping("/unRegisterList") + public AjaxResult unRegisterList() { + return AjaxResult.success(commonDeviceService.unRegisterList()); + } + + @PostMapping("/register") + public AjaxResult register(@RequestBody IoTCommonDevice commonDevice) { + commonDeviceService.save(commonDevice); + return AjaxResult.success(); + } + + @GetMapping("/list") + public TableDataInfo list(String searchVale) { + startPage(); + // 限制当前用户机构 + return getDataTable(commonDeviceService.list(searchVale)); + } + + @GetMapping("/detail") + public AjaxResult detail(@RequestParam String id) { + return AjaxResult.success(commonDeviceService.detail(id)); + } + + @GetMapping("/delete") + public AjaxResult delete(@RequestParam String id) { + return AjaxResult.success(commonDeviceService.delete(id)); + } + +} diff --git a/maibu-external/src/main/java/com/maibu/own/controller/PathPlanController.java b/maibu-external/src/main/java/com/maibu/own/controller/PathPlanController.java new file mode 100644 index 0000000..6f950e3 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/controller/PathPlanController.java @@ -0,0 +1,37 @@ +package com.maibu.own.controller; + +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.own.entity.PathPlan; +import com.fastbee.own.entity.WorkOrder; +import com.fastbee.own.service.PathPlanService; +import com.fastbee.own.service.WorkOrderService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/iot/path") +public class PathPlanController extends BaseController { + + @Autowired + private PathPlanService pathPlanService; + + @PostMapping("/save") + public AjaxResult save(@RequestBody PathPlan pathPlan) { + pathPlanService.save(pathPlan); + return AjaxResult.success(); + } + + @GetMapping("/list") + public TableDataInfo list() { + startPage(); + // 限制当前用户机构 + return getDataTable(pathPlanService.list()); + } + + @GetMapping("/getPath") + public AjaxResult getPath() { + return AjaxResult.success(pathPlanService.getPath()); + } +} diff --git a/maibu-external/src/main/java/com/maibu/own/controller/WorkOrderController.java b/maibu-external/src/main/java/com/maibu/own/controller/WorkOrderController.java new file mode 100644 index 0000000..fce79ef --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/controller/WorkOrderController.java @@ -0,0 +1,41 @@ +package com.maibu.own.controller; + +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.own.entity.WorkOrder; +import com.fastbee.own.service.WorkOrderService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/iot/workOrder") +public class WorkOrderController extends BaseController { + + @Autowired + private WorkOrderService workOrderService; + + @PostMapping("/save") + public AjaxResult save(@RequestBody WorkOrder workOrder) { + workOrderService.save(workOrder); + return AjaxResult.success(); + } + + @GetMapping("/list") + public TableDataInfo list(String searchVale) { + startPage(); + // 限制当前用户机构 + return getDataTable(workOrderService.list(searchVale)); + } + + @GetMapping("/detail") + public AjaxResult detail(@RequestParam String id) { + return AjaxResult.success(workOrderService.detail(id)); + } + + @GetMapping("/delete") + public AjaxResult delete(@RequestParam String id) { + return AjaxResult.success(workOrderService.delete(id)); + } + +} diff --git a/maibu-external/src/main/java/com/maibu/own/entity/NodePosition.java b/maibu-external/src/main/java/com/maibu/own/entity/NodePosition.java new file mode 100644 index 0000000..3c14dfd --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/entity/NodePosition.java @@ -0,0 +1,12 @@ +package com.maibu.own.entity; + +import lombok.Data; + +@Data +public class NodePosition { + + private String longitude; //经度 + private String latitude; //纬度 + private Integer index; + +} diff --git a/maibu-external/src/main/java/com/maibu/own/entity/OrderStatus.java b/maibu-external/src/main/java/com/maibu/own/entity/OrderStatus.java new file mode 100644 index 0000000..8c8213f --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/entity/OrderStatus.java @@ -0,0 +1,16 @@ +package com.maibu.own.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum OrderStatus { + New("待处理"), + Executing("进行中"), + Canceled("已取消"), + Finished("已完成"); + + private final String desc; + +} diff --git a/maibu-external/src/main/java/com/maibu/own/entity/OrderType.java b/maibu-external/src/main/java/com/maibu/own/entity/OrderType.java new file mode 100644 index 0000000..b5ba82d --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/entity/OrderType.java @@ -0,0 +1,15 @@ +package com.maibu.own.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum OrderType { + Security("安防"), + Inspection("巡检"), + Cleaning("清洗"); + + private final String desc; + +} diff --git a/maibu-external/src/main/java/com/maibu/own/entity/PathPlan.java b/maibu-external/src/main/java/com/maibu/own/entity/PathPlan.java new file mode 100644 index 0000000..344d74a --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/entity/PathPlan.java @@ -0,0 +1,30 @@ +package com.maibu.own.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.fastbee.common.core.domain.BaseDO; +import com.fastbee.iot.domain.DeviceTaskPlanRule; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +@Data +@NoArgsConstructor +@TableName("iot_path_plan") +public class PathPlan extends BaseDO { + + @TableId(type = IdType.AUTO) + private Long id; + + @TableField(typeHandler = JacksonTypeHandler.class) + private List nodePosition; //点位信息 + + private String name; +} diff --git a/maibu-external/src/main/java/com/maibu/own/entity/WorkOrder.java b/maibu-external/src/main/java/com/maibu/own/entity/WorkOrder.java new file mode 100644 index 0000000..20c12fc --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/entity/WorkOrder.java @@ -0,0 +1,42 @@ +package com.maibu.own.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@TableName("iot_work_order") +public class WorkOrder implements Serializable { + @TableId(type = IdType.AUTO) + private Long id; + + private String orderNo; + + private String orderName; + + private OrderType orderType; + + private Integer priority; // 高中低 0 1 2 + + private OrderStatus orderStatus; + + private String operator; + + private LocalDateTime createTime; + + private String creator; + + private String description; + + private String handleResult; + + private boolean isDel; + +} diff --git a/maibu-external/src/main/java/com/maibu/own/mapper/PathPlanMapper.java b/maibu-external/src/main/java/com/maibu/own/mapper/PathPlanMapper.java new file mode 100644 index 0000000..372ddc4 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/mapper/PathPlanMapper.java @@ -0,0 +1,11 @@ +package com.maibu.own.mapper; + +import com.fastbee.framework.mybatis.mapper.BaseMapperX; +import com.fastbee.own.entity.PathPlan; +import org.springframework.stereotype.Repository; + + +@Repository +public interface PathPlanMapper extends BaseMapperX { + +} diff --git a/maibu-external/src/main/java/com/maibu/own/mapper/WorkOrderMapper.java b/maibu-external/src/main/java/com/maibu/own/mapper/WorkOrderMapper.java new file mode 100644 index 0000000..e9ace2d --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/mapper/WorkOrderMapper.java @@ -0,0 +1,11 @@ +package com.maibu.own.mapper; + +import com.fastbee.framework.mybatis.mapper.BaseMapperX; +import com.fastbee.own.entity.WorkOrder; +import org.springframework.stereotype.Repository; + + +@Repository +public interface WorkOrderMapper extends BaseMapperX { + +} diff --git a/maibu-external/src/main/java/com/maibu/own/service/CommonDeviceService.java b/maibu-external/src/main/java/com/maibu/own/service/CommonDeviceService.java new file mode 100644 index 0000000..b793843 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/service/CommonDeviceService.java @@ -0,0 +1,52 @@ +package com.maibu.own.service; + +import com.fastbee.framework.mybatis.LambdaQueryWrapperX; +import com.fastbee.iot.domain.IoTCommonDevice; +import com.fastbee.iot.mapper.IoTCommonDevicesMapper; +import com.fastbee.iot.memory.CommonMemory; +import com.fastbee.own.entity.WorkOrder; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class CommonDeviceService { + + + @Autowired + private IoTCommonDevicesMapper commonDevicesMapper; + + + public List unRegisterList() { + return (List) CommonMemory.unRegisterCommonDeviceMap.values(); + } + + public List register() { + return (List) CommonMemory.unRegisterCommonDeviceMap.values(); + } + + public void save(IoTCommonDevice commonDevice) { + if (commonDevice != null) { + if (commonDevice.getId() != null) { + commonDevicesMapper.updateById(commonDevice); + } else { + commonDevicesMapper.insert(commonDevice); + } + } + } + + public List list(String searchVale) { + return commonDevicesMapper.selectList(); + } + + public IoTCommonDevice detail(String id) { + return commonDevicesMapper.selectById(id); + } + + public int delete(String id) { + return commonDevicesMapper.deleteById(id); + } + +} diff --git a/maibu-external/src/main/java/com/maibu/own/service/PathPlanService.java b/maibu-external/src/main/java/com/maibu/own/service/PathPlanService.java new file mode 100644 index 0000000..b7c65a1 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/service/PathPlanService.java @@ -0,0 +1,41 @@ +package com.maibu.own.service; + +import com.fastbee.framework.mybatis.LambdaQueryWrapperX; +import com.fastbee.own.entity.PathPlan; +import com.fastbee.own.mapper.PathPlanMapper; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class PathPlanService { + + + @Autowired + private PathPlanMapper pathPlanMapper; + + public void save(PathPlan pathPlan) { + if (pathPlan != null) { + if (pathPlan.getId() != null) { + pathPlanMapper.updateById(pathPlan); + } else { + pathPlanMapper.insert(pathPlan); + } + } + } + + public List list() { + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX<>(); + return pathPlanMapper.selectList(wrapper); + } + + + public PathPlan getPath() { + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX<>(); + return CollectionUtils.isEmpty(pathPlanMapper.selectList(wrapper)) ? null : pathPlanMapper.selectList(wrapper).get(0); + } + + +} diff --git a/maibu-external/src/main/java/com/maibu/own/service/WorkOrderService.java b/maibu-external/src/main/java/com/maibu/own/service/WorkOrderService.java new file mode 100644 index 0000000..3833492 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/own/service/WorkOrderService.java @@ -0,0 +1,54 @@ +package com.maibu.own.service; + +import com.fastbee.framework.mybatis.LambdaQueryWrapperX; +import com.fastbee.own.entity.WorkOrder; +import com.fastbee.own.mapper.WorkOrderMapper; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class WorkOrderService { + + + @Autowired + private WorkOrderMapper workOrderMapper; + + public void save(WorkOrder workOrder) { + if (workOrder != null) { + if (workOrder.getId() != null) { + workOrderMapper.updateById(workOrder); + } else { + workOrderMapper.insert(workOrder); + } + } + } + + public List list(String searchVale) { + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX<>(); + if (!StringUtils.isEmpty(searchVale)) { + wrapper.and(w -> w + .like(WorkOrder::getOrderName, searchVale) + .or() + .like(WorkOrder::getOrderNo, searchVale) + .or() + .like(WorkOrder::getCreator, searchVale) + .or() + .like(WorkOrder::getDescription, searchVale) + ); + return workOrderMapper.selectList(wrapper); + } + return null; + } + + public WorkOrder detail(String id) { + return workOrderMapper.selectById(id); + } + + public int delete(String id) { + return workOrderMapper.deleteById(id); + } + +} diff --git a/maibu-external/src/main/java/com/maibu/thermal/controller/ThermalController.java b/maibu-external/src/main/java/com/maibu/thermal/controller/ThermalController.java new file mode 100644 index 0000000..8d1ffbb --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/thermal/controller/ThermalController.java @@ -0,0 +1,248 @@ +package com.maibu.thermal.controller; + + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.thermal.model.AnalyzeRequest; +import com.fastbee.thermal.model.ProbeRequest; +import com.fastbee.thermal.model.ReportRequest; +import com.fastbee.thermal.service.ThermalService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/thermal") +@Validated +@Slf4j +public class ThermalController { + + @Autowired + private ThermalService thermalService; + + @GetMapping("/defaults") + public AjaxResult getDefaults() { + try { + Map defaults = thermalService.getDefaults(); + return AjaxResult.success(defaults); + } catch (Exception e) { + log.error("接口错误", e); + return AjaxResult.error(); + } + } + + @PostMapping("/analyze") + public AjaxResult analyze(@Valid @RequestBody AnalyzeRequest request) { + try { + Map result = thermalService.analyze(request); + return AjaxResult.success(result); + } catch (RuntimeException e) { + log.error("接口错误", e); + return AjaxResult.error(); + } + } + + @PostMapping("/probe") + public AjaxResult probe(@RequestBody ProbeRequest request) { + try { + Map result = thermalService.probe(request); + return AjaxResult.success(result); + } catch (RuntimeException e) { + log.error("接口错误", e); + return AjaxResult.error(); + } + } + + + @GetMapping("/report") + public AjaxResult report(@RequestParam String analysisId, HttpServletResponse response) { + try { + String result = thermalService.generateReport(analysisId); + try (OutputStream os = response.getOutputStream()) { + + response.setContentType("application/octet-stream"); + + // 文件名(防止中文乱码) + String fileName = URLEncoder.encode("report.html", "UTF-8"); + response.setHeader("Content-Disposition", + "attachment;filename=" + fileName); + + // 写出 + os.write(result.getBytes(StandardCharsets.UTF_8)); + os.flush(); + + } catch (Exception e) { + e.printStackTrace(); + } + return AjaxResult.success(result); + } catch (RuntimeException e) { + log.error("接口错误", e); + return AjaxResult.error(); + } + } + + @GetMapping("/downloadReport") + public void getReport(HttpServletResponse response) { + + File file = new File("/www/wwwroot/Java/Platform/tiff/pv_array_thermal_train5/station_A_thermal_report.html"); + + // 1. 判断文件是否存在 + if (!file.exists() || !file.isFile()) { + throw new RuntimeException("文件不存在"); + } + + try (InputStream inputStream = new FileInputStream(file); + OutputStream outputStream = response.getOutputStream()) { + + // 2. 设置响应头 + response.setContentType("text/html;charset=UTF-8"); + + String fileName = URLEncoder.encode(file.getName(), "UTF-8"); + response.setHeader("Content-Disposition", + "attachment;filename=" + fileName); + + // 3. 文件流输出 + byte[] buffer = new byte[1024]; + int len; + while ((len = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, len); + } + + outputStream.flush(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + +// @GetMapping("/image") +// public void getImage(HttpServletResponse response) { +// File file = new File("/www/wwwroot/thermal/tiff/pv_array_thermal_train5/pv_array_overlay.png"); +// boolean b = isGreenShieldEncrypt(file); +// // 1️⃣ 文件是否存在 +// if (!file.exists() || file.isDirectory()) { +// response.setStatus(HttpServletResponse.SC_NOT_FOUND); +// return; +// } +// try { +// // 2️⃣ 自动识别文件类型(image/jpeg、image/png) +// String contentType = Files.probeContentType(file.toPath()); +// if (contentType == null) { +// contentType = "application/octet-stream"; +// } +// +// response.setContentType(contentType); +// response.setHeader("Content-Disposition", "inline; filename=" + file.getName()); +// +// // 3️⃣ 流式读取写出 +// try (InputStream is = new FileInputStream(file); +// OutputStream os = response.getOutputStream()) { +// +// byte[] buffer = new byte[8192]; +// int len; +// while ((len = is.read(buffer)) != -1) { +// os.write(buffer, 0, len); +// } +// os.flush(); +// } +// } catch (Exception e) { +// e.printStackTrace(); +// response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); +// } +// } + + @GetMapping("/image") + public String getImage() { + try { +// File file = new File("/www/wwwroot/thermal/tiff/pv_array_thermal_train5/pv_array_overlay.png"); +// boolean b = isGreenShieldEncrypt(file); + Path filePath = Paths.get("/www/wwwroot/Java/Platform/tiff/pv_array_thermal_train5/pv_array_overlay.png"); + byte[] bytes = Files.readAllBytes(filePath); + + // Java8 也支持 + String contentType = Files.probeContentType(filePath); + if (contentType == null) { + contentType = "image/jpeg"; + } + + String base64 = Base64.getEncoder().encodeToString(bytes); + return "data:" + contentType + ";base64," + base64; + + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + + private static final byte[] PNG_MAGIC = {(byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; + // JPG 标准文件头魔数 + private static final byte[] JPG_MAGIC = {(byte) 0xFF, (byte) 0xD8, (byte) 0xFF}; + + /** + * 判断文件是否被绿盾加密(图片专用) + * + * @param file 待检测文件 + * @return true=已被绿盾加密 / false=原生未加密 + */ + public static boolean isGreenShieldEncrypt(File file) { + if (!file.exists() || !file.isFile()) { + throw new RuntimeException("文件不存在或不是文件"); + } + + // 读取前32字节头信息 + byte[] headBuf = new byte[32]; + try (FileInputStream fis = new FileInputStream(file)) { + int readLen = fis.read(headBuf); + if (readLen < 8) { + return true; // 头部异常,判定加密 + } + } catch (IOException e) { + // 绿盾拦截IO会抛异常,直接判定加密 + return true; + } + + // 对比PNG头 + boolean isPngNative = true; + for (int i = 0; i < PNG_MAGIC.length; i++) { + if (headBuf[i] != PNG_MAGIC[i]) { + isPngNative = false; + break; + } + } + if (isPngNative) { + return false; + } + + // 对比JPG头 + boolean isJpgNative = true; + for (int i = 0; i < JPG_MAGIC.length; i++) { + if (headBuf[i] != JPG_MAGIC[i]) { + isJpgNative = false; + break; + } + } + if (isJpgNative) { + return false; + } + + // 魔数完全不匹配:绿盾加密文件 + return true; + } + +} diff --git a/maibu-external/src/main/java/com/maibu/thermal/model/AnalyzeRequest.java b/maibu-external/src/main/java/com/maibu/thermal/model/AnalyzeRequest.java new file mode 100644 index 0000000..89aab88 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/thermal/model/AnalyzeRequest.java @@ -0,0 +1,70 @@ +package com.maibu.thermal.model; + +import javax.validation.constraints.*; + +public class AnalyzeRequest { + private String thermalPath; + private String arrayMaskPath; + private String panelGeojsonPath; + + @DecimalMin("-100.0") + @DecimalMax("500.0") + private double thresholdC = 48.0; + + @Min(1) + @Max(1000000) + private int minAreaPx = 25; + + @Min(1) + @Max(1000000) + private int maxRegions = 120; + + @Min(1) + @Max(99) + private int openK = 3; + + @Min(1) + @Max(99) + private int closeK = 3; + + @Min(0) + @Max(1000) + private int shrinkIn = 2; + + private boolean showMaskEdge = true; + private boolean includePanelSegments = false; + + // Getters and setters + public String getThermalPath() { return thermalPath; } + public void setThermalPath(String thermalPath) { this.thermalPath = thermalPath; } + + public String getArrayMaskPath() { return arrayMaskPath; } + public void setArrayMaskPath(String arrayMaskPath) { this.arrayMaskPath = arrayMaskPath; } + + public String getPanelGeojsonPath() { return panelGeojsonPath; } + public void setPanelGeojsonPath(String panelGeojsonPath) { this.panelGeojsonPath = panelGeojsonPath; } + + public double getThresholdC() { return thresholdC; } + public void setThresholdC(double thresholdC) { this.thresholdC = thresholdC; } + + public int getMinAreaPx() { return minAreaPx; } + public void setMinAreaPx(int minAreaPx) { this.minAreaPx = minAreaPx; } + + public int getMaxRegions() { return maxRegions; } + public void setMaxRegions(int maxRegions) { this.maxRegions = maxRegions; } + + public int getOpenK() { return openK; } + public void setOpenK(int openK) { this.openK = openK; } + + public int getCloseK() { return closeK; } + public void setCloseK(int closeK) { this.closeK = closeK; } + + public int getShrinkIn() { return shrinkIn; } + public void setShrinkIn(int shrinkIn) { this.shrinkIn = shrinkIn; } + + public boolean isShowMaskEdge() { return showMaskEdge; } + public void setShowMaskEdge(boolean showMaskEdge) { this.showMaskEdge = showMaskEdge; } + + public boolean isIncludePanelSegments() { return includePanelSegments; } + public void setIncludePanelSegments(boolean includePanelSegments) { this.includePanelSegments = includePanelSegments; } +} \ No newline at end of file diff --git a/maibu-external/src/main/java/com/maibu/thermal/model/ProbeRequest.java b/maibu-external/src/main/java/com/maibu/thermal/model/ProbeRequest.java new file mode 100644 index 0000000..9625973 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/thermal/model/ProbeRequest.java @@ -0,0 +1,37 @@ +package com.maibu.thermal.model; + +import javax.validation.constraints.*; + +public class ProbeRequest { + @NotBlank + private String analysisId; + + @NotNull + private double x; + + @NotNull + private double y; + + private String coordinateSpace = "source"; + private Double displayWidth; + private Double displayHeight; + + // Getters and setters + public String getAnalysisId() { return analysisId; } + public void setAnalysisId(String analysisId) { this.analysisId = analysisId; } + + public double getX() { return x; } + public void setX(double x) { this.x = x; } + + public double getY() { return y; } + public void setY(double y) { this.y = y; } + + public String getCoordinateSpace() { return coordinateSpace; } + public void setCoordinateSpace(String coordinateSpace) { this.coordinateSpace = coordinateSpace; } + + public Double getDisplayWidth() { return displayWidth; } + public void setDisplayWidth(Double displayWidth) { this.displayWidth = displayWidth; } + + public Double getDisplayHeight() { return displayHeight; } + public void setDisplayHeight(Double displayHeight) { this.displayHeight = displayHeight; } +} \ No newline at end of file diff --git a/maibu-external/src/main/java/com/maibu/thermal/model/RangeData.java b/maibu-external/src/main/java/com/maibu/thermal/model/RangeData.java new file mode 100644 index 0000000..788ea5e --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/thermal/model/RangeData.java @@ -0,0 +1,22 @@ +package com.maibu.thermal.model; + +import lombok.Data; + +@Data +public class RangeData { + + private double startX; + private double startY; + private double endX; + private double endY; + private double temp; + private String code; + private double lat; + private double lon; + + + public boolean contains(double x, double y) { + return x >= startX && x < endX && y >= startY && y < endY; // 左闭右开 + } + +} diff --git a/maibu-external/src/main/java/com/maibu/thermal/model/ReportRequest.java b/maibu-external/src/main/java/com/maibu/thermal/model/ReportRequest.java new file mode 100644 index 0000000..3684a89 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/thermal/model/ReportRequest.java @@ -0,0 +1,40 @@ +package com.maibu.thermal.model; + +import javax.validation.constraints.*; + +public class ReportRequest { + @NotBlank + private String analysisId; + private String projectName = ""; + private String siteName = ""; + private String clientName = ""; + private String inspector = "Thermal Inspector"; + private String reportDate; + private String equipment = "无人机热成像巡检"; + private String notes = ""; + + // Getters and setters + public String getAnalysisId() { return analysisId; } + public void setAnalysisId(String analysisId) { this.analysisId = analysisId; } + + public String getProjectName() { return projectName; } + public void setProjectName(String projectName) { this.projectName = projectName; } + + public String getSiteName() { return siteName; } + public void setSiteName(String siteName) { this.siteName = siteName; } + + public String getClientName() { return clientName; } + public void setClientName(String clientName) { this.clientName = clientName; } + + public String getInspector() { return inspector; } + public void setInspector(String inspector) { this.inspector = inspector; } + + public String getReportDate() { return reportDate; } + public void setReportDate(String reportDate) { this.reportDate = reportDate; } + + public String getEquipment() { return equipment; } + public void setEquipment(String equipment) { this.equipment = equipment; } + + public String getNotes() { return notes; } + public void setNotes(String notes) { this.notes = notes; } +} \ No newline at end of file diff --git a/maibu-external/src/main/java/com/maibu/thermal/model/StoredAnalysis.java b/maibu-external/src/main/java/com/maibu/thermal/model/StoredAnalysis.java new file mode 100644 index 0000000..c65f955 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/thermal/model/StoredAnalysis.java @@ -0,0 +1,31 @@ +package com.maibu.thermal.model; + +import java.time.LocalDateTime; +import java.util.Map; + +public class StoredAnalysis { + private ThermalAnalysisInput request; + private Map result; + private LocalDateTime createdAt; + private String overlayImageUrl; + + public StoredAnalysis(ThermalAnalysisInput request, Map result, LocalDateTime createdAt, String overlayImageUrl) { + this.request = request; + this.result = result; + this.createdAt = createdAt; + this.overlayImageUrl = overlayImageUrl; + } + + // Getters and setters + public ThermalAnalysisInput getRequest() { return request; } + public void setRequest(ThermalAnalysisInput request) { this.request = request; } + + public Map getResult() { return result; } + public void setResult(Map result) { this.result = result; } + + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + + public String getOverlayImageUrl() { return overlayImageUrl; } + public void setOverlayImageUrl(String overlayImageUrl) { this.overlayImageUrl = overlayImageUrl; } +} \ No newline at end of file diff --git a/maibu-external/src/main/java/com/maibu/thermal/model/ThermalAnalysisInput.java b/maibu-external/src/main/java/com/maibu/thermal/model/ThermalAnalysisInput.java new file mode 100644 index 0000000..f35b892 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/thermal/model/ThermalAnalysisInput.java @@ -0,0 +1,93 @@ +package com.maibu.thermal.model; + +import java.util.HashMap; +import java.util.Map; + +public class ThermalAnalysisInput { + private String thermalPath; + private String arrayMaskPath = ""; + private String panelGeojsonPath = ""; + private double thresholdC = 48.0; + private int minAreaPx = 25; + private int maxRegions = 120; + private int openK = 3; + private int closeK = 3; + private int shrinkIn = 2; + private boolean showMaskEdge = true; + + public ThermalAnalysisInput() {} + + public ThermalAnalysisInput(String thermalPath, String arrayMaskPath, String panelGeojsonPath, double thresholdC, int minAreaPx, int maxRegions, int openK, int closeK, int shrinkIn, boolean showMaskEdge) { + this.thermalPath = thermalPath; + this.arrayMaskPath = arrayMaskPath; + this.panelGeojsonPath = panelGeojsonPath; + this.thresholdC = thresholdC; + this.minAreaPx = minAreaPx; + this.maxRegions = maxRegions; + this.openK = openK; + this.closeK = closeK; + this.shrinkIn = shrinkIn; + this.showMaskEdge = showMaskEdge; + } + + public ThermalAnalysisInput normalized() { + return new ThermalAnalysisInput( + thermalPath != null ? thermalPath.trim() : "", + arrayMaskPath != null ? arrayMaskPath.trim() : "", + panelGeojsonPath != null ? panelGeojsonPath.trim() : "", + thresholdC, + minAreaPx, + maxRegions, + openK, + closeK, + shrinkIn, + showMaskEdge + ); + } + + public Map toPublicDict() { + Map data = new HashMap<>(); + data.put("thermalPath", thermalPath); + data.put("arrayMaskPath", arrayMaskPath); + data.put("panelGeojsonPath", panelGeojsonPath); + data.put("thresholdC", thresholdC); + data.put("minAreaPx", minAreaPx); + data.put("maxRegions", maxRegions); + data.put("openK", openK); + data.put("closeK", closeK); + data.put("shrinkIn", shrinkIn); + data.put("showMaskEdge", showMaskEdge); + return data; + } + + // Getters and setters + public String getThermalPath() { return thermalPath; } + public void setThermalPath(String thermalPath) { this.thermalPath = thermalPath; } + + public String getArrayMaskPath() { return arrayMaskPath; } + public void setArrayMaskPath(String arrayMaskPath) { this.arrayMaskPath = arrayMaskPath; } + + public String getPanelGeojsonPath() { return panelGeojsonPath; } + public void setPanelGeojsonPath(String panelGeojsonPath) { this.panelGeojsonPath = panelGeojsonPath; } + + public double getThresholdC() { return thresholdC; } + public void setThresholdC(double thresholdC) { this.thresholdC = thresholdC; } + + public int getMinAreaPx() { return minAreaPx; } + public void setMinAreaPx(int minAreaPx) { this.minAreaPx = minAreaPx; } + + public int getMaxRegions() { return maxRegions; } + public void setMaxRegions(int maxRegions) { this.maxRegions = maxRegions; } + + public int getOpenK() { return openK; } + public void setOpenK(int openK) { this.openK = openK; } + + public int getCloseK() { return closeK; } + public void setCloseK(int closeK) { this.closeK = closeK; } + + public int getShrinkIn() { return shrinkIn; } + public void setShrinkIn(int shrinkIn) { this.shrinkIn = shrinkIn; } + + public boolean isShowMaskEdge() { return showMaskEdge; } + public void setShowMaskEdge(boolean showMaskEdge) { this.showMaskEdge = showMaskEdge; } +} \ No newline at end of file diff --git a/maibu-external/src/main/java/com/maibu/thermal/model/ThermalReportMeta.java b/maibu-external/src/main/java/com/maibu/thermal/model/ThermalReportMeta.java new file mode 100644 index 0000000..b475787 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/thermal/model/ThermalReportMeta.java @@ -0,0 +1,61 @@ +package com.maibu.thermal.model; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.Map; + +public class ThermalReportMeta { + private String projectName = ""; + private String siteName = ""; + private String clientName = ""; + private String inspector = "Thermal Inspector"; + private String reportDate; + private String equipment = "无人机热成像巡检"; + private String notes = "建议优先复核高温异常点,并结合现场电气测试、可见光照片与遮挡检查确认根因。"; + + public ThermalReportMeta() {} + + public ThermalReportMeta(String projectName, String siteName, String clientName, String inspector, String reportDate, String equipment, String notes) { + this.projectName = projectName; + this.siteName = siteName; + this.clientName = clientName; + this.inspector = inspector; + this.reportDate = reportDate; + this.equipment = equipment; + this.notes = notes; + } + + public Map toPublicDict() { + Map data = new HashMap<>(); + data.put("projectName", projectName); + data.put("siteName", siteName); + data.put("clientName", clientName); + data.put("inspector", inspector); + data.put("reportDate", reportDate); + data.put("equipment", equipment); + data.put("notes", notes); + return data; + } + + // Getters and setters + public String getProjectName() { return projectName; } + public void setProjectName(String projectName) { this.projectName = projectName; } + + public String getSiteName() { return siteName; } + public void setSiteName(String siteName) { this.siteName = siteName; } + + public String getClientName() { return clientName; } + public void setClientName(String clientName) { this.clientName = clientName; } + + public String getInspector() { return inspector; } + public void setInspector(String inspector) { this.inspector = inspector; } + + public String getReportDate() { return reportDate; } + public void setReportDate(String reportDate) { this.reportDate = reportDate; } + + public String getEquipment() { return equipment; } + public void setEquipment(String equipment) { this.equipment = equipment; } + + public String getNotes() { return notes; } + public void setNotes(String notes) { this.notes = notes; } +} \ No newline at end of file diff --git a/maibu-external/src/main/java/com/maibu/thermal/service/AnalysisStore.java b/maibu-external/src/main/java/com/maibu/thermal/service/AnalysisStore.java new file mode 100644 index 0000000..83b29b9 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/thermal/service/AnalysisStore.java @@ -0,0 +1,28 @@ +package com.maibu.thermal.service; + +import com.fastbee.thermal.model.StoredAnalysis; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class AnalysisStore { + private final Map store = new ConcurrentHashMap<>(); + + public void put(String analysisId, StoredAnalysis analysis) { + store.put(analysisId, analysis); + } + + public StoredAnalysis get(String analysisId) { + return store.get(analysisId); + } + + public boolean exists(String analysisId) { + return store.containsKey(analysisId); + } + + public void remove(String analysisId) { + store.remove(analysisId); + } +} \ No newline at end of file diff --git a/maibu-external/src/main/java/com/maibu/thermal/service/ThermalService.java b/maibu-external/src/main/java/com/maibu/thermal/service/ThermalService.java new file mode 100644 index 0000000..16dafa2 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/thermal/service/ThermalService.java @@ -0,0 +1,895 @@ +package com.maibu.thermal.service; + +import com.fastbee.thermal.model.*; +import com.fastbee.thermal.util.FileUtil; +import com.fastbee.thermal.util.HtmlTemplateUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class ThermalService { + @Autowired + private AnalysisStore analysisStore; + + private final Path baseDir; + private final Path frontendDir; + private final Path runtimeDir; + private final Path artifactDir; + private final Path analysisDir; + private final Path reportDir; + + private final String defaultThermalTif; + private final String defaultArrayFilledMask; + private final String defaultPanelPixelGeojson; + + public ThermalService() { + // Get base directory + this.baseDir = Paths.get(System.getProperty("user.dir")).getParent(); + this.frontendDir = baseDir.resolve("frontend"); + this.runtimeDir = baseDir.resolve("runtime"); + this.artifactDir = runtimeDir.resolve("artifacts"); + this.analysisDir = artifactDir.resolve("analyses"); + this.reportDir = artifactDir.resolve("reports"); + + // Default file paths + this.defaultThermalTif = baseDir.resolve("tiff").resolve("thermal_002_transparent_reflectance_grayscale_cleaned_full.tif").toString(); + this.defaultArrayFilledMask = baseDir.resolve("tiff").resolve("pv_array_thermal_train5").resolve("pv_array_filled_mask.png").toString(); + this.defaultPanelPixelGeojson = baseDir.resolve("tiff").resolve("pv_array_thermal_train5").resolve("pv_panels_pixel.geojson").toString(); + + // Create directories if they don't exist + createDirectories(); + } + + private void createDirectories() { + try { + if (!analysisDir.toFile().exists()) { + analysisDir.toFile().mkdirs(); + } + if (!reportDir.toFile().exists()) { + reportDir.toFile().mkdirs(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Map getDefaults() { + Map defaults = new HashMap<>(); + + // Get default analysis input + ThermalAnalysisInput analysisInput = getDefaultAnalysisInput(); + + // Get site name from thermal path + String siteName = ""; + if (analysisInput.getThermalPath() != null && !analysisInput.getThermalPath().isEmpty()) { + File thermalFile = new File(analysisInput.getThermalPath()); + String fileName = thermalFile.getName(); + int dotIndex = fileName.lastIndexOf('.'); + if (dotIndex > 0) { + siteName = fileName.substring(0, dotIndex); + } + } + + // Get default report meta + ThermalReportMeta reportMeta = getDefaultReportMeta(siteName); + + defaults.put("analysis", analysisInput.toPublicDict()); + defaults.put("report", reportMeta.toPublicDict()); + + return defaults; + } + + public Map analyze(AnalyzeRequest request) { + // Build thermal analysis input + ThermalAnalysisInput analysisInput = buildRequest(request); + + // Validate input files + validateFile(analysisInput.getThermalPath(), "热成像TIFF"); + if (!analysisInput.getArrayMaskPath().isEmpty()) { + validateFile(analysisInput.getArrayMaskPath(), "阵列掩膜PNG"); + } + if (!analysisInput.getPanelGeojsonPath().isEmpty()) { + validateFile(analysisInput.getPanelGeojsonPath(), "板框GeoJSON"); + } + + // Run detection analysis + Map result; + try { + result = runDetectionAnalysis(analysisInput); + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } + + // Generate analysis ID and timestamp + String analysisId = UUID.randomUUID().toString().replace("-", ""); + LocalDateTime createdAt = LocalDateTime.now(); + + // Generate overlay image + String fileName = analysisId + ".png"; + Path overlayPath = analysisDir.resolve(fileName); + + // Encode and write overlay image + byte[] overlayImageBytes = encodeRgbPngBytes(result.get("view")); + try { + FileUtil.writeBinary(overlayPath, overlayImageBytes); + } catch (IOException e) { + throw new RuntimeException("Failed to save overlay image: " + e.getMessage()); + } + + // Generate overlay image URL + String overlayUrl = "/thermal-web-artifacts/analyses/" + fileName; + + // Store analysis + StoredAnalysis storedAnalysis = new StoredAnalysis(analysisInput, result, createdAt, overlayUrl); + analysisStore.put(analysisId, storedAnalysis); + + // Serialize analysis result + return serializeAnalysisResult(analysisId, analysisInput, result, overlayUrl, createdAt, request.isIncludePanelSegments()); + } + + public Map probe(ProbeRequest request) { + // Get stored analysis + StoredAnalysis stored = analysisStore.get(request.getAnalysisId()); + if (stored == null) { + throw new RuntimeException("分析结果不存在或已失效,请重新分析。"); + } + + // Resolve source coordinates + double[] srcCoords = resolveProbeSourceXY(stored, request); + double srcX = srcCoords[0]; + double srcY = srcCoords[1]; + + // Get probe data + Map probeData = probeSourceLocation(stored.getRequest(), stored.getResult(), request.getX(), request.getY()); + + // Build response + Map response = new HashMap<>(); + response.put("analysis_id", request.getAnalysisId()); + response.put("coordinate_space", request.getCoordinateSpace()); + response.put("source_x", (int) Math.round(srcX)); + response.put("source_y", (int) Math.round(srcY)); + response.put("probe", probeData); + + return response; + } + + public String generateReport(String analysisId) { + // Get stored analysis + StoredAnalysis stored = analysisStore.get(analysisId); + if (stored == null) { + throw new RuntimeException("分析结果不存在或已失效,请重新分析。"); + } + return buildThermalReportPayload(stored.getResult()); + } + + private ThermalAnalysisInput getDefaultAnalysisInput() { + return new ThermalAnalysisInput( + defaultThermalTif, + defaultArrayFilledMask, + defaultPanelPixelGeojson, + 48.0, + 25, + 120, + 3, + 3, + 2, + true + ); + } + + private ThermalReportMeta getDefaultReportMeta(String siteName) { + return new ThermalReportMeta( + siteName, + siteName, + "", + "Thermal Inspector", + LocalDateTime.now().toLocalDate().toString(), + "无人机热成像巡检", + "建议优先复核高温异常点,并结合现场电气测试、可见光照片与遮挡检查确认根因。" + ); + } + + private ThermalAnalysisInput buildRequest(AnalyzeRequest request) { + return new ThermalAnalysisInput( + request.getThermalPath(), + request.getArrayMaskPath(), + request.getPanelGeojsonPath(), + request.getThresholdC(), + request.getMinAreaPx(), + request.getMaxRegions(), + request.getOpenK(), + request.getCloseK(), + request.getShrinkIn(), + request.isShowMaskEdge() + ).normalized(); + } + + private void validateFile(String filePath, String fileType) { +// if (filePath == null || filePath.isEmpty()) { +// throw new RuntimeException(fileType + "路径不能为空"); +// } +// File file = new File(filePath); +// if (!file.exists()) { +// throw new RuntimeException("未找到" + fileType + ": " + filePath); +// } +// if (!file.isFile()) { +// throw new RuntimeException(fileType + "路径不是文件: " + filePath); +// } + } + + public static List dataList = new ArrayList<>(); + + @PostConstruct + public void intData() { + for (int x = 1; x < 8; x++) { + int sx = (x - 1) * 90; + int ex = x * 90; + double lat = 39.9042 + 0.0001 * (x - 1); + for (int y = 1; y < 5; y++) { + int sy = (y - 1) * 90; + int ey = y * 90; + double lon = 116.4074 + 0.0001 * (y - 1); + RangeData data = new RangeData(); + data.setStartX(sx); + data.setEndX(ex); + data.setStartY(sy); + data.setEndY(ey); + double min = 45.2; + double max = 65.8; + double random = min + (max - min) * Math.random(); + random = Math.round(random * 100.0) / 100.0; + data.setTemp(random); + data.setCode(y + "-" + x); + data.setLat(lat); + data.setLon(lon); + dataList.add(data); + } + } + } + + private Map runDetectionAnalysis(ThermalAnalysisInput input) { + // Mock implementation - in a real scenario, this would call the actual thermal analysis library + Map result = new HashMap<>(); + + // Mock summary + Map summary = new HashMap<>(); + summary.put("width", 1920); + summary.put("height", 1080); + summary.put("valid_px", 2073600); + + List list = dataList.stream().sorted(Comparator.comparing(RangeData::getTemp).reversed()).collect(Collectors.toList()); + + summary.put("p95_temp", 50.05); + summary.put("max_temp", list.get(0).getTemp()); + + + summary.put("array_hotspots", 15); + summary.put("panel_hotspots", 10); + summary.put("filtered_hotspots", 5); + summary.put("array_mask_px", 1800000); + summary.put("panel_polygon_count", 100); + summary.put("panel_mask_px", 1500000); + summary.put("array_src", "pv_array_filled_mask.png"); + summary.put("panel_src", "pv_panels_pixel.geojson"); + summary.put("threshold", input.getThresholdC()); + summary.put("thermal_name", new File(input.getThermalPath()).getName()); + + + List> finalDf = mockFinalDf(); + + // Build panel display table + List> panelDisplayDf = buildPanelDisplayTable(finalDf); + + // Build temp bucket table + List> tempBucketDf = buildTempBucketTable(panelDisplayDf, input.getThresholdC()); + + // Mock panel_segments + List> panelSegments = mockPanelSegments(); + + result.put("summary", summary); + result.put("view", new ArrayList<>(Collections.nCopies(10, 0))); // Mock view + result.put("view_scale", 1.0); + result.put("final_df", finalDf); + result.put("panel_overview", panelDisplayDf); + result.put("temp_buckets", tempBucketDf); + result.put("panel_segments", panelSegments); + + return result; + } + + private List> mockPanelSegments() { + List> panelSegments = new ArrayList<>(); + + // Create mock panel segments + for (int i = 1; i <= 5; i++) { + Map segment = new HashMap<>(); + segment.put("panel_id", "1-" + i); + segment.put("parent_id", "1"); + + // Create a simple polygon for each panel + List> poly = new ArrayList<>(); + int xStart = 100 + (i - 1) * 100; + int yStart = 100; + poly.add(new ArrayList<>(Arrays.asList((double) xStart, (double) yStart))); + poly.add(new ArrayList<>(Arrays.asList((double) xStart + 90, (double) yStart))); + poly.add(new ArrayList<>(Arrays.asList((double) xStart + 90, (double) yStart + 90))); + poly.add(new ArrayList<>(Arrays.asList((double) xStart, (double) yStart + 90))); + segment.put("poly", poly); + + panelSegments.add(segment); + } + + return panelSegments; + } + + private byte[] encodeRgbPngBytes(Object view) { + // Mock implementation - in a real scenario, this would encode the actual image + // Return a simple 1x1 PNG image + return new byte[]{(byte) 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1, 8, 6, 0, 0, 0, 14, 114, (byte) 205, (byte) 199, 0, 0, 0, 11, 73, 68, 65, 84, 8, (byte) 215, 99, 120, (byte) 173, (byte) 168, (byte) 213, 10, 0, 0, 0, 5, 0, 1, (byte) 134, 11, (byte) 252, 10, 0, 0, 0, 0, 73, 69, 78, 68, (byte) 174, 66, 96, (byte) 130}; + } + + private Map serializeAnalysisResult(String analysisId, ThermalAnalysisInput request, Map result, String overlayImageUrl, LocalDateTime createdAt, boolean includePanelSegments) { + Map serialized = new HashMap<>(); + serialized.put("analysis_id", analysisId); + serialized.put("request", request.toPublicDict()); + serialized.put("result", result); + serialized.put("overlay_image_url", overlayImageUrl); + serialized.put("created_at", createdAt.toString()); + return serialized; + } + + private double[] resolveProbeSourceXY(StoredAnalysis stored, ProbeRequest request) { + if ("source".equals(request.getCoordinateSpace())) { + return new double[]{request.getX(), request.getY()}; + } + + if (request.getDisplayWidth() == null || request.getDisplayHeight() == null) { + throw new RuntimeException("coordinate_space=display 时必须传 display_width 和 display_height。"); + } + + // Get view dimensions from result + Map result = stored.getResult(); + Map summary = (Map) result.get("summary"); + int renderedWidth = (int) summary.get("width"); + int renderedHeight = (int) summary.get("height"); + double scale = (double) result.get("view_scale"); + + double viewX = request.getX() * renderedWidth / Math.max(request.getDisplayWidth(), 1.0); + double viewY = request.getY() * renderedHeight / Math.max(request.getDisplayHeight(), 1.0); + + // Convert to source coordinates + return new double[]{viewX / Math.max(scale, 1e-6), viewY / Math.max(scale, 1e-6)}; + } + + + private Map probeSourceLocation(ThermalAnalysisInput request, Map result, double x, double y) { + // Mock implementation - in a real scenario, this would use the actual thermal data + Map probeData = new HashMap<>(); + + // Get summary and view scale + Map summary = (Map) result.get("summary"); + double scale = (double) result.get("view_scale"); + + // Calculate source coordinates with clipping + int srcW = (int) summary.get("width"); + int srcH = (int) summary.get("height"); + int xSrc = (int) Math.round(x); + xSrc = Math.max(0, Math.min(xSrc, srcW - 1)); + int ySrc = (int) Math.round(y); + ySrc = Math.max(0, Math.min(ySrc, srcH - 1)); + + // Mock temperature + double tempC = 52.3; // In a real scenario, this would come from the thermal data + + // Find panel segment at point + Map panelSeg = findPanelSegmentAtPoint(result.get("panel_segments"), xSrc, ySrc); + String panelId = panelSeg != null && panelSeg.containsKey("panel_id") ? cleanIdText(panelSeg.get("panel_id").toString()) : ""; + String blockId = panelSeg != null && panelSeg.containsKey("parent_id") ? cleanIdText(panelSeg.get("parent_id").toString()) : ""; + + // Mock GPS data + double lat = 39.9042; + double lon = 116.4074; + + // Build probe data + probeData.put("x", xSrc); + probeData.put("y", ySrc); + probeData.put("plot_x", (int) Math.round(xSrc * scale)); + probeData.put("plot_y", (int) Math.round(ySrc * scale)); + + RangeData rangeData = dataList.stream().filter(item -> item.contains(x, y)).findFirst().orElse(null); + if (rangeData != null) { + probeData.put("temp_c", rangeData.getTemp()); + probeData.put("panel_id", rangeData.getCode()); + probeData.put("block_id", rangeData.getCode()); + probeData.put("lat", rangeData.getLat()); + probeData.put("lon", rangeData.getLon()); + } else { + probeData.put("temp_c", ""); + probeData.put("panel_id", panelId.isEmpty() ? "未命中板框" : panelId); + probeData.put("block_id", blockId.isEmpty() ? "-" : blockId); + probeData.put("lat", roundToDecimalPlaces(lat, 6)); + probeData.put("lon", roundToDecimalPlaces(lon, 6)); + } + probeData.put("gps_text", formatGps(lat, lon)); + + return probeData; + } + + private Map findPanelSegmentAtPoint(Object panelSegments, int x, int y) { + if (panelSegments instanceof List) { + List segments = (List) panelSegments; + System.out.println("Panel segments size: " + segments.size()); + + for (Object segment : segments) { + // Check if segment is a map with required properties + if (segment instanceof Map) { + Map segMap = (Map) segment; + // Check if the segment contains a polygon + if (segMap.containsKey("poly")) { + Object polyObj = segMap.get("poly"); + // Check if poly is a list of coordinates + if (polyObj instanceof List) { + List poly = (List) polyObj; + // Check if the point is inside the polygon + boolean isInside = isPointInPolygon(x, y, poly); + if (isInside) { + System.out.println("Point (" + x + ", " + y + ") is inside panel segment"); + // Convert to Map and return + Map result = new HashMap<>(); + if (segMap.containsKey("panel_id")) { + result.put("panel_id", segMap.get("panel_id").toString()); + } + if (segMap.containsKey("parent_id")) { + result.put("parent_id", segMap.get("parent_id").toString()); + } + return result; + } + } + } + } + } + } else { + System.out.println("Panel segments is not a list: " + (panelSegments != null ? panelSegments.getClass() : "null")); + } + System.out.println("No panel segment found for point (" + x + ", " + y + ")"); + return null; + } + + private boolean isPointInPolygon(int x, int y, List polygon) { + boolean inside = false; + int n = polygon.size(); + + // Iterate through each edge of the polygon + for (int i = 0, j = n - 1; i < n; j = i++) { + // Get current and next point + Object pointI = polygon.get(i); + Object pointJ = polygon.get(j); + + // Check if points are valid coordinate pairs + if (!(pointI instanceof List) || !(pointJ instanceof List)) { + continue; + } + + List coordI = (List) pointI; + List coordJ = (List) pointJ; + + if (coordI.size() < 2 || coordJ.size() < 2) { + continue; + } + + try { + // Get coordinates + double xi = Double.parseDouble(coordI.get(0).toString()); + double yi = Double.parseDouble(coordI.get(1).toString()); + double xj = Double.parseDouble(coordJ.get(0).toString()); + double yj = Double.parseDouble(coordJ.get(1).toString()); + + // Check if the point's y-coordinate is within the edge's y-range + boolean intersect = ((yi > y) != (yj > y)) && + (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) { + inside = !inside; + } + } catch (NumberFormatException e) { + // Ignore invalid coordinates + } + } + + return inside; + } + + private double roundToDecimalPlaces(double value, int places) { + double scale = Math.pow(10, places); + return Math.round(value * scale) / scale; + } + + private String buildThermalReportPayload(Map result) { + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + + result.put("reportDate", sdf.format(new Date())); + List> s = (List>) result.get("temp_buckets"); + result.put("t1", s.get(0).get("数量")); + result.put("t2", s.get(1).get("数量")); + result.put("t3", s.get(2).get("数量")); + + Map summary = (Map) result.get("summary"); + result.put("p95_temp", summary.get("p95_temp")); + result.put("max_temp", summary.get("max_temp")); + + // 获取面板概览数据 + List> panelOverview = (List>) result.get("panel_overview"); + + // 生成异常明细表格HTML + String anomalyDetailRows = buildAnomalyDetailTableRows(panelOverview, 48); + result.put("anomalyDetailRows", anomalyDetailRows); + + String html = HtmlTemplateUtil.renderFromFile("templates/reportTemplate.html", result); + + + return html; + } + + private String buildAnomalyDetailTableRows(List> panelOverview, double thresholdC) { + if (panelOverview == null || panelOverview.isEmpty()) { + return "暂无异常数据"; + } + + StringBuilder htmlBuilder = new StringBuilder(); + int totalItems = panelOverview.size(); + + for (int i = 0; i < panelOverview.size(); i++) { + Map row = panelOverview.get(i); + + // 获取各字段数据 + String code = getSafeString(row, "编号"); + Double temp = getSafeDouble(row, "温度(℃)"); + String area = getSafeString(row, "逆变器"); + String gps = getSafeString(row, "GPS位置"); + Integer blockId = getSafeInteger(row, "块号"); + + // 固定优先级分配:前3个高,中间3个中,后面低 + String priorityText; + String priorityClass; + if (i < 3) { + priorityText = "高优先级"; + priorityClass = "priority-high"; + } else if (i < 6) { + priorityText = "中优先级"; + priorityClass = "priority-medium"; + } else { + priorityText = "低优先级"; + priorityClass = "priority-low"; + } + + // 计算超阈值,带加号 + String overThreshold = temp != null ? String.format("+%.2f℃", temp - thresholdC) : "-"; + + // 温度格式化为两位小数 + String tempStr = temp != null ? String.format("%.2f℃", temp) : "-"; + + // 生成像素坐标,格式为 "x=xxx, y=xxx" + String pixelCoords = getSafeString(row, "阵列号"); + + // 构建表格行 + htmlBuilder.append(""); + htmlBuilder.append("").append(code).append(""); + htmlBuilder.append("") + .append(priorityText).append(""); + htmlBuilder.append("").append(tempStr).append(""); + htmlBuilder.append("").append(overThreshold).append(""); + htmlBuilder.append("").append(area != null ? area : "-").append(""); + htmlBuilder.append("").append(pixelCoords).append(""); + htmlBuilder.append("").append(blockId != null ? blockId : "-").append(""); + htmlBuilder.append("").append(gps).append(""); + htmlBuilder.append(""); + } + + return htmlBuilder.toString(); + } + + private String generatePixelCoords(String code) { + // 根据编号生成模拟像素坐标,格式为 "x=xxx, y=xxx" + if (code == null || code.isEmpty()) { + return "-"; + } + + try { + // 解析编号格式如 "1-2" + String[] parts = code.split("-"); + if (parts.length == 2) { + int block = Integer.parseInt(parts[0]); + int panel = Integer.parseInt(parts[1]); + int x = (block - 1) * 1000 + panel * 100; + int y = (panel - 1) * 500 + 100; + return String.format("x=%d, y=%d", x, y); + } + } catch (Exception e) { + // 忽略解析错误 + } + + return "-"; + } + + private String getSafeString(Map map, String key) { + Object value = map.get(key); + return value != null ? value.toString() : ""; + } + + private Double getSafeDouble(Map map, String key) { + Object value = map.get(key); + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + return null; + } + + private Integer getSafeInteger(Map map, String key) { + Object value = map.get(key); + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return null; + } + + + private List> mockFinalDf() { + List> finalDf = new ArrayList<>(); + + List list = dataList.stream().sorted(Comparator.comparing(RangeData::getTemp).reversed()).collect(Collectors.toList()); + + // Mock some hotspots + for (int i = 1; i <= 10; i++) { + Map hotspot = new HashMap<>(); + RangeData rangeData = list.get(i - 1); + hotspot.put("panel_id", rangeData.getCode()); + hotspot.put("最高温(℃)", rangeData.getTemp()); + hotspot.put("面积(px)", 100 + i * 10); + hotspot.put("纬度", rangeData.getLat()); + hotspot.put("经度", rangeData.getLon()); + finalDf.add(hotspot); + } + + return finalDf; + } + + private List> buildPanelDisplayTable(List> hotspots) { + List> result = new ArrayList<>(); + + if (hotspots == null || hotspots.isEmpty()) { + return result; + } + + // Process hotspots + Map> namedHotspots = new HashMap<>(); + List> unnamedHotspots = new ArrayList<>(); + + for (Map hotspot : hotspots) { + Map processed = new HashMap<>(hotspot); + + // Set 编号 + if (hotspot.containsKey("panel_id")) { + processed.put("编号", cleanIdText(hotspot.get("panel_id").toString())); + } else if (hotspot.containsKey("sub_id")) { + processed.put("编号", cleanIdText(hotspot.get("sub_id").toString())); + } else if (hotspot.containsKey("热斑ID")) { + try { + int hotId = Integer.parseInt(hotspot.get("热斑ID").toString()); + processed.put("编号", "热斑#" + hotId); + } catch (Exception e) { + processed.put("编号", ""); + } + } else { + processed.put("编号", ""); + } + + if (processed.get("编号") != null) { + String s = String.valueOf(processed.get("编号").toString()); + String[] sl = s.split("-"); + if (sl.length > 1) { + processed.put("逆变器", sl[0]); + processed.put("阵列号", sl[1]); + } + } + + // Set 温度(℃) + if (hotspot.containsKey("最高温(℃)")) { + try { + processed.put("温度(℃)", Double.parseDouble(hotspot.get("最高温(℃)").toString())); + } catch (Exception e) { + processed.put("温度(℃)", null); + } + } else { + processed.put("温度(℃)", null); + } + + // Set 面积(px) + if (hotspot.containsKey("面积(px)")) { + try { + processed.put("面积(px)", Integer.parseInt(hotspot.get("面积(px)").toString())); + } catch (Exception e) { + processed.put("面积(px)", null); + } + } else { + processed.put("面积(px)", null); + } + + // Set 纬度和经度 + double lat = 0.0, lon = 0.0; + if (hotspot.containsKey("纬度")) { + try { + lat = Double.parseDouble(hotspot.get("纬度").toString()); + processed.put("纬度", lat); + } catch (Exception e) { + processed.put("纬度", null); + } + } else { + processed.put("纬度", null); + } + + if (hotspot.containsKey("经度")) { + try { + lon = Double.parseDouble(hotspot.get("经度").toString()); + processed.put("经度", lon); + } catch (Exception e) { + processed.put("经度", null); + } + } else { + processed.put("经度", null); + } + + // Set GPS位置 + processed.put("GPS位置", formatGps(lat, lon)); + + // Set 异常点数 + processed.put("异常点数", 1); + + // Set 块号 + String id = processed.get("编号").toString(); + if (!id.isEmpty()) { + try { + String blockId = id.split("-")[0]; + processed.put("块号", Integer.parseInt(blockId)); + } catch (Exception e) { + processed.put("块号", null); + } + } else { + processed.put("块号", null); + } + + // Separate named and unnamed hotspots + if (!id.isEmpty()) { + if (!namedHotspots.containsKey(id)) { + namedHotspots.put(id, processed); + } + } else { + unnamedHotspots.add(processed); + } + } + + // Calculate anomaly count for named hotspots + Map anomalyCount = new HashMap<>(); + for (Map hotspot : hotspots) { + String id = ""; + if (hotspot.containsKey("panel_id")) { + id = cleanIdText(hotspot.get("panel_id").toString()); + } else if (hotspot.containsKey("sub_id")) { + id = cleanIdText(hotspot.get("sub_id").toString()); + } + + if (!id.isEmpty()) { + anomalyCount.put(id, anomalyCount.getOrDefault(id, 0) + 1); + } + } + + // Update anomaly count + for (Map.Entry> entry : namedHotspots.entrySet()) { + entry.getValue().put("异常点数", anomalyCount.getOrDefault(entry.getKey(), 1)); + } + + // Combine named and unnamed hotspots + List> combined = new ArrayList<>(namedHotspots.values()); + combined.addAll(unnamedHotspots); + + // Sort by temperature and area + combined.sort(new Comparator>() { + @Override + public int compare(Map o1, Map o2) { + // Compare temperature + Double temp1 = (Double) o1.get("温度(℃)"); + Double temp2 = (Double) o2.get("温度(℃)"); + if (temp1 == null && temp2 == null) return 0; + if (temp1 == null) return 1; + if (temp2 == null) return -1; + int tempCompare = temp2.compareTo(temp1); // Descending + if (tempCompare != 0) return tempCompare; + + // Compare area + Integer area1 = (Integer) o1.get("面积(px)"); + Integer area2 = (Integer) o2.get("面积(px)"); + if (area1 == null && area2 == null) return 0; + if (area1 == null) return 1; + if (area2 == null) return -1; + return area2.compareTo(area1); // Descending + } + }); + + return combined; + } + + private List> buildTempBucketTable(List> panelDf, double thresholdC) { + List> result = new ArrayList<>(); + + if (panelDf == null || panelDf.isEmpty()) { + return result; + } + + // Collect temperatures + List temps = new ArrayList<>(); + for (Map row : panelDf) { + if (row.containsKey("温度(℃)") && row.get("温度(℃)") != null) { + try { + temps.add((Double) row.get("温度(℃)")); + } catch (Exception e) { + // Ignore invalid temperatures + } + } + } + + if (temps.isEmpty()) { + return result; + } + + // Define bins + double[] bins = {thresholdC, thresholdC + 5.0, thresholdC + 10.0, Double.POSITIVE_INFINITY}; + String[] labels = { + String.format("%.1f~%.1f℃", thresholdC, thresholdC + 5.0), + String.format("%.1f~%.1f℃", thresholdC + 5.0, thresholdC + 10.0), + String.format(">=%.1f℃", thresholdC + 10.0) + }; + + // Count temperatures in each bin + int[] counts = new int[3]; + for (double temp : temps) { + if (temp < bins[1]) { + counts[0]++; + } else if (temp < bins[2]) { + counts[1]++; + } else { + counts[2]++; + } + } + + // Build result + for (int i = 0; i < 3; i++) { + Map bucket = new HashMap<>(); + bucket.put("温度区间", labels[i]); + bucket.put("数量", counts[i]); + result.add(bucket); + } + + return result; + } + + private String cleanIdText(String text) { + if (text == null) return ""; + return text.trim().replaceAll("\\s+", " "); + } + + private String formatGps(double lat, double lon) { + if (lat == 0.0 && lon == 0.0) return ""; + return String.format("%.6f, %.6f", lat, lon); + } +} \ No newline at end of file diff --git a/maibu-external/src/main/java/com/maibu/thermal/util/FileUtil.java b/maibu-external/src/main/java/com/maibu/thermal/util/FileUtil.java new file mode 100644 index 0000000..f542005 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/thermal/util/FileUtil.java @@ -0,0 +1,15 @@ +package com.maibu.thermal.util; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; + +public class FileUtil { + + public static void writeBinary(Path path, byte[] data) throws IOException { + try (FileOutputStream fos = new FileOutputStream(path.toFile())) { + fos.write(data); + } + } + +} \ No newline at end of file diff --git a/maibu-external/src/main/java/com/maibu/thermal/util/HtmlTemplateUtil.java b/maibu-external/src/main/java/com/maibu/thermal/util/HtmlTemplateUtil.java new file mode 100644 index 0000000..178cee1 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/thermal/util/HtmlTemplateUtil.java @@ -0,0 +1,80 @@ +package com.maibu.thermal.util; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class HtmlTemplateUtil { + + // 匹配 ${xxx} + private static final Pattern PATTERN = Pattern.compile("\\$\\{(\\w+)}"); + + /** + * 渲染模板(读取文件 + 替换变量) + * @param templatePath resources目录下路径,例如:templates/test.html + * @param data 替换数据 + */ + public static String renderFromFile(String templatePath, Map data) { + String template = loadTemplate(templatePath); + return render(template, data); + } + + /** + * 替换占位符 + */ + public static String render(String template, Map data) { + if (template == null || template.isEmpty()) { + return template; + } + + Matcher matcher = PATTERN.matcher(template); + StringBuffer sb = new StringBuffer(); + + while (matcher.find()) { + String key = matcher.group(1); + Object value = data.get(key); + + String replacement = (value == null) ? "" : value.toString(); + + // 防止 $ 和 \ 导致异常 + replacement = Matcher.quoteReplacement(replacement); + + matcher.appendReplacement(sb, replacement); + } + + matcher.appendTail(sb); + return sb.toString(); + } + + /** + * 读取 resources 下的 html 文件 + */ + private static String loadTemplate(String path) { + StringBuilder sb = new StringBuilder(); + + try (InputStream is = HtmlTemplateUtil.class + .getClassLoader() + .getResourceAsStream(path); + BufferedReader reader = new BufferedReader( + new InputStreamReader(is, StandardCharsets.UTF_8))) { + + if (is == null) { + throw new RuntimeException("模板文件不存在: " + path); + } + + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + + } catch (Exception e) { + throw new RuntimeException("读取模板失败", e); + } + + return sb.toString(); + } +} \ No newline at end of file diff --git a/maibu-external/src/main/java/com/maibu/uav/controller/UAVController.java b/maibu-external/src/main/java/com/maibu/uav/controller/UAVController.java new file mode 100644 index 0000000..8a42cb5 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/controller/UAVController.java @@ -0,0 +1,136 @@ +package com.maibu.uav.controller; + +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.uav.dto.*; +import com.fastbee.uav.service.UAVService; +import io.minio.errors.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.List; + +@RestController +@RequestMapping("/iot/UAV") +public class UAVController extends BaseController { + + @Autowired + private UAVService uavService; + + + @GetMapping("/getProjectDeviceList") + public AjaxResult getProjectDeviceList() { + UavDeviceStateResponseDTO result = uavService.getProjectDeviceList(); + return AjaxResult.success(result); + } + + @GetMapping("/getDeviceHMS") + public String getDeviceHMS(@RequestParam String deviceSn) { + return uavService.getDeviceHMS(deviceSn); + } + + @GetMapping("/getDeviceState") + public String getDeviceState(@RequestParam String deviceSn) { + return uavService.getDeviceState(deviceSn); + } + + @GetMapping("/getWayline") + public String getWayline() { + return uavService.getWayline(); + } + + @GetMapping("/getWaylineDetail") + public String getWaylineDetail(@RequestParam String waylineId) { + return uavService.getWaylineDetail(waylineId); + } + + @PostMapping("/createFlightTask") + public String createFlightTask(@RequestBody FlightTaskCreateDTO dto) { + return uavService.createFlightTask(dto); + } + + @PostMapping("/flightTaskCommand") + public String flightTaskCommand(@RequestBody FlightTaskCommandDTO dto) { + return uavService.flightTaskCommand(dto); + } + + /** + * 在线直播 + * + * @param dto + * @return + */ + @PostMapping("/liveStream") + public AjaxResult liveStream(@RequestBody CameraLiveParamDTO dto) { + return AjaxResult.success(uavService.liveStream(dto)); + } + + @GetMapping("/getFlightTask") + public String getFlightTask(@RequestParam String sn) { + return uavService.flightTaskList(sn); + } + + @GetMapping("/getFlightTaskDetail") + public String flightTaskDetail(@RequestParam String taskId) { + return uavService.flightTaskDetail(taskId); + } + + /** + * 获取飞行轨迹 + * + * @param taskId + * @return + */ + @GetMapping("/getFlightTaskTrack") + public String getFlightTaskTrack(@RequestParam String taskId) { + return uavService.getFlightTaskTrack(taskId); + } + + /** + * 上传航线文件并通知完成 + * + * @param file + * @param name + * @return + * @throws ServerException + * @throws InsufficientDataException + * @throws ErrorResponseException + * @throws IOException + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + * @throws InvalidResponseException + * @throws XmlParserException + * @throws InternalException + */ + @PostMapping("/uploadWayline") + public Object uploadWayline(MultipartFile file, @RequestParam String name) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException { + return uavService.uploadWayline(file, name); + } + + /** + * 更新飞行任务状态 + * + * @param taskId status suspended 任务挂起/ restored任务恢复 + * @return + */ + @PostMapping("/updateFlightTaskStatus") + public String updateFlightTaskStatus(@RequestParam String taskId, @RequestParam String status) { + return uavService.updateFlightTaskStatus(taskId, status); + } + + @PostMapping("/liveHeartbeat") + public AjaxResult liveHeartbeat(@RequestParam String sn, @RequestParam String cameraIndex) { + uavService.liveHeartbeat(sn, cameraIndex); + return AjaxResult.success(); + } + + @PostMapping("/changeCamera") + public String changeCamera(@RequestParam String sn, @RequestParam String cameraIndex, @RequestParam String cameraPosition) { + return uavService.changeCamera(sn, cameraIndex, cameraPosition); + } + +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/CameraLiveParamDTO.java b/maibu-external/src/main/java/com/maibu/uav/dto/CameraLiveParamDTO.java new file mode 100644 index 0000000..97d7976 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/CameraLiveParamDTO.java @@ -0,0 +1,16 @@ +package com.maibu.uav.dto; + +import com.fastbee.uav.entity.CameraInfo; +import lombok.Data; + +import java.util.List; + +@Data +public class CameraLiveParamDTO { + private String getway_sn; + private String drone_sn; + private List getway_camera_list; + private List drone_camera_list; + private Long video_expire; + private String quality_type; //adaptive 自动 smooth流畅 ultra_high_definition超清 +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/CameraLiveResultDTO.java b/maibu-external/src/main/java/com/maibu/uav/dto/CameraLiveResultDTO.java new file mode 100644 index 0000000..f9decf5 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/CameraLiveResultDTO.java @@ -0,0 +1,12 @@ +package com.maibu.uav.dto; + +import lombok.Data; + +@Data +public class CameraLiveResultDTO { + private String sn; + private String camera_index; + private String url; + private Long expire_ts; + private String url_type; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/CameraLiveStreamDTO.java b/maibu-external/src/main/java/com/maibu/uav/dto/CameraLiveStreamDTO.java new file mode 100644 index 0000000..24fb9b5 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/CameraLiveStreamDTO.java @@ -0,0 +1,11 @@ +package com.maibu.uav.dto; + +import lombok.Data; + +@Data +public class CameraLiveStreamDTO { + private String sn; + private String camera_index; + private Long video_expire; + private String quality_type; //adaptive 自动 smooth流畅 ultra_high_definition超清 +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/CameraLiveStreamResultDTO.java b/maibu-external/src/main/java/com/maibu/uav/dto/CameraLiveStreamResultDTO.java new file mode 100644 index 0000000..0a39c5b --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/CameraLiveStreamResultDTO.java @@ -0,0 +1,10 @@ +package com.maibu.uav.dto; + +import lombok.Data; + +@Data +public class CameraLiveStreamResultDTO { + private String url; + private Long expire_ts; + private String url_type; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/FlightTaskCommandDTO.java b/maibu-external/src/main/java/com/maibu/uav/dto/FlightTaskCommandDTO.java new file mode 100644 index 0000000..b56df3f --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/FlightTaskCommandDTO.java @@ -0,0 +1,9 @@ +package com.maibu.uav.dto; + +import lombok.Data; + +@Data +public class FlightTaskCommandDTO { + private UAVCommand command; + private String deviceSn; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/FlightTaskCreateDTO.java b/maibu-external/src/main/java/com/maibu/uav/dto/FlightTaskCreateDTO.java new file mode 100644 index 0000000..6c4fd50 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/FlightTaskCreateDTO.java @@ -0,0 +1,16 @@ +package com.maibu.uav.dto; + +import lombok.Data; + +@Data +public class FlightTaskCreateDTO { + private String name; + private String sn; + private String wayline_uuid; + private String time_zone; + private Integer rth_altitude; + private String rth_mode; + private String wayline_precision_type; + private String resumable_status; + private String task_type; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/SK2ResultDTO.java b/maibu-external/src/main/java/com/maibu/uav/dto/SK2ResultDTO.java new file mode 100644 index 0000000..a09e1ba --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/SK2ResultDTO.java @@ -0,0 +1,10 @@ +package com.maibu.uav.dto; + +import lombok.Data; + +@Data +public class SK2ResultDTO { + private int code; + private String message; + private Object data; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/UAVCommand.java b/maibu-external/src/main/java/com/maibu/uav/dto/UAVCommand.java new file mode 100644 index 0000000..26d3a6a --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/UAVCommand.java @@ -0,0 +1,9 @@ +package com.maibu.uav.dto; + +public enum UAVCommand { + return_home, //返航 + return_specific_home,//蛙跳任务指定目标机场返航 + return_home_cancel, //取消返航 + flighttask_pause, //飞行任务暂停 + flighttask_recovery //飞行任务恢复 +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/UavDeviceStateDTO.java b/maibu-external/src/main/java/com/maibu/uav/dto/UavDeviceStateDTO.java new file mode 100644 index 0000000..e0cbe6b --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/UavDeviceStateDTO.java @@ -0,0 +1,12 @@ +package com.maibu.uav.dto; + +import com.fastbee.uav.entity.DeviceModel; +import com.fastbee.uav.entity.UavDeviceState; +import lombok.Data; + +@Data +public class UavDeviceStateDTO { + private String device_sn; + private DeviceModel device_model; + private UavDeviceState device_state; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/UavDeviceStateResponseDTO.java b/maibu-external/src/main/java/com/maibu/uav/dto/UavDeviceStateResponseDTO.java new file mode 100644 index 0000000..40fc93a --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/UavDeviceStateResponseDTO.java @@ -0,0 +1,15 @@ +package com.maibu.uav.dto; + +import com.fastbee.uav.entity.DeviceModel; +import com.fastbee.uav.entity.UavDeviceState; +import lombok.Data; + +import java.util.List; + +@Data +public class UavDeviceStateResponseDTO { + private Integer total; + private Integer online; + private Integer offline; + private List detail; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/UavDeviceStateResponseDetailDTO.java b/maibu-external/src/main/java/com/maibu/uav/dto/UavDeviceStateResponseDetailDTO.java new file mode 100644 index 0000000..c194e19 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/UavDeviceStateResponseDetailDTO.java @@ -0,0 +1,30 @@ +package com.maibu.uav.dto; + +import com.fastbee.uav.entity.CameraInfo; +import lombok.Data; + +import java.util.List; + +@Data +public class UavDeviceStateResponseDetailDTO { + private String device_sn; + private String gateway_sn; + private String callsign; + private String drone_callsign; + private Integer onlineStatus; //1在线2离线 + private Integer drone_onlineStatus; //1在线2离线 + private Double latitude; + private Double longitude; + private Integer capacity_percent; + private Double wind_speed; + private Double height; + private Float environment_temperature; + private Object network_state; + private Object position_state; + private Double home_distance; + private Object live_capacity; + private String rainfall; + + private List gateway_camera_list; + private List drone_camera_list; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/UavProjectDeviceDTO.java b/maibu-external/src/main/java/com/maibu/uav/dto/UavProjectDeviceDTO.java new file mode 100644 index 0000000..8b60ace --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/UavProjectDeviceDTO.java @@ -0,0 +1,11 @@ +package com.maibu.uav.dto; + +import com.fastbee.uav.entity.UavDevice; +import lombok.Data; + +@Data +public class UavProjectDeviceDTO { + + private UavDevice gateway; + private UavDevice drone; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/UavStsTokenCredentials.java b/maibu-external/src/main/java/com/maibu/uav/dto/UavStsTokenCredentials.java new file mode 100644 index 0000000..d811952 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/UavStsTokenCredentials.java @@ -0,0 +1,12 @@ +package com.maibu.uav.dto; + +import lombok.Data; + +@Data +public class UavStsTokenCredentials { + private String access_key_id; + private String access_key_secret; + private Integer expire; + private String security_token; + private Integer platform; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/dto/UavStsTokenDTO.java b/maibu-external/src/main/java/com/maibu/uav/dto/UavStsTokenDTO.java new file mode 100644 index 0000000..07de0df --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/dto/UavStsTokenDTO.java @@ -0,0 +1,13 @@ +package com.maibu.uav.dto; + +import lombok.Data; + +@Data +public class UavStsTokenDTO { + private String endpoint; + private String provider; + private String region; + private String bucket; + private String object_key_prefix; + private UavStsTokenCredentials credentials; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/entity/CameraInfo.java b/maibu-external/src/main/java/com/maibu/uav/entity/CameraInfo.java new file mode 100644 index 0000000..edfd5e9 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/entity/CameraInfo.java @@ -0,0 +1,13 @@ +package com.maibu.uav.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class CameraInfo { + private String camera_index; + private List available_camera_positions; + private String camera_position; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/entity/DeviceModel.java b/maibu-external/src/main/java/com/maibu/uav/entity/DeviceModel.java new file mode 100644 index 0000000..24d93e6 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/entity/DeviceModel.java @@ -0,0 +1,17 @@ +package com.maibu.uav.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Map; + +@Data +public class DeviceModel { + private String key; + private String domain; + private String type; + private String sub_type; + private String name; + @JsonProperty("class") + private String className; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/entity/UavBattery.java b/maibu-external/src/main/java/com/maibu/uav/entity/UavBattery.java new file mode 100644 index 0000000..3949246 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/entity/UavBattery.java @@ -0,0 +1,12 @@ +package com.maibu.uav.entity; + +import lombok.Data; + +@Data +public class UavBattery { + private Object batteries; + private Integer capacity_percent; + private Integer landing_power; + private Integer remain_flight_time; + private Integer return_home_power; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/entity/UavDevice.java b/maibu-external/src/main/java/com/maibu/uav/entity/UavDevice.java new file mode 100644 index 0000000..545421e --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/entity/UavDevice.java @@ -0,0 +1,16 @@ +package com.maibu.uav.entity; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class UavDevice { + private String sn; + private String callsign; + private DeviceModel device_model; + private Boolean device_online_status; + private Integer mode_code; + private List camera_list; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/entity/UavDeviceState.java b/maibu-external/src/main/java/com/maibu/uav/entity/UavDeviceState.java new file mode 100644 index 0000000..0dceb42 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/entity/UavDeviceState.java @@ -0,0 +1,64 @@ +package com.maibu.uav.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class UavDeviceState { + private Long activation_time; + private Double attitude_head; + private Double attitude_pitch; + private Double attitude_roll; + private UavBattery battery; + + private String best_link_gateway; + private Object camera_watermark_settings; + private Object cameras; + private Double commander_flight_height; + private String commander_flight_mode; + private String commander_mode_lost_action; + private String no_consistency_upgrade_required; + private String control_source; + private String current_rth_mode; + private Object dongle_infos; + private Double elevation; + private String firmware_upgrade_status; + private String firmware_version; + private String flysafe_database_version; + private String gear; + private Double height; + private Double height_limit; + private Double home_distance; + private Double home_latitude; + private Double home_longitude; + private Double horizontal_speed; + private String not_reaching_the_geo_zone; + private String is_near_height_limit; + private Double latitude; //经纬度 + private Double longitude; + private Double low_battery_warning_threshold; + private Object maintain_status; + private String mode_code; + private String mode_code_reason; + private String night_lights_state; + private Object obstacle_avoidance; + private Boolean offline_map_enable; + private Object position_state; + private Integer rth_mode; + private Double serious_low_battery_warning_threshold; + private Object storage; + private Double total_flight_distance; + private Double total_flight_sorties; + private Double total_flight_time; + private String track_id; + private Double vertical_speed; + private String wind_direction; + private Double wind_speed; + private Object wireless_link_topo; + @JsonProperty("80-0-0") + private Object key80; + private Float environment_temperature; + private Object network_state; + private Object live_capacity; + private String rainfall; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/entity/UavProject.java b/maibu-external/src/main/java/com/maibu/uav/entity/UavProject.java new file mode 100644 index 0000000..1b0eb17 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/entity/UavProject.java @@ -0,0 +1,16 @@ +package com.maibu.uav.entity; + +import lombok.Data; + +import java.util.Map; + +@Data +public class UavProject { + private String name; + private String introduction; + private String uuid; + private String org_uuid; + private Map project_work_center_point; + private Long created_at; + private Long updated_at; +} diff --git a/maibu-external/src/main/java/com/maibu/uav/service/UAVService.java b/maibu-external/src/main/java/com/maibu/uav/service/UAVService.java new file mode 100644 index 0000000..d1304fc --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/service/UAVService.java @@ -0,0 +1,407 @@ +package com.maibu.uav.service; + +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.json.JsonUtils; +import com.fastbee.iot.domain.DevicePlanTask; +import com.fastbee.uav.dto.*; +import com.fastbee.uav.entity.UavDevice; +import com.fastbee.uav.entity.UavDeviceState; +import com.fastbee.uav.utils.UavHttpUtils; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; +import io.minio.credentials.StaticProvider; +import io.minio.errors.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +@Service +@Slf4j +public class UAVService { + + + @Value("${UAV.baseUrl:https://es-flight-api-cn.djigate.com}") + private String baseUrl; + + @Autowired + private UavHttpUtils httpService; + + + public static final Map latestActiveTime = new ConcurrentHashMap<>(); + + public static final Map liveCameraMap = new ConcurrentHashMap<>(); + + public static final Long outTime = 30000L; + + + public UavDeviceStateResponseDTO getProjectDeviceList() { + try { + String url = baseUrl + "/openapi/v0.1/project/device"; + String result = httpService.doGet(url, null); + SK2ResultDTO resultDTO = JsonUtils.parseObject(result, SK2ResultDTO.class); + Map map = (Map) resultDTO.getData(); + UavDeviceStateResponseDTO responseDTO = new UavDeviceStateResponseDTO(); + if (!CollectionUtils.isEmpty(map)) { + List> l = (List>) map.get("list"); + if (!CollectionUtils.isEmpty(l)) { + List detail = new ArrayList<>(); + responseDTO.setDetail(detail); + AtomicReference total = new AtomicReference<>(0); + AtomicReference online = new AtomicReference<>(0); + AtomicReference offline = new AtomicReference<>(0); + l.forEach(x -> { + // 方式1:如果droneObj是Map,用TypeReference反序列化(推荐) + UavProjectDeviceDTO up = JsonUtils.parseObject( + JsonUtils.toJsonString(x), // 先转JSON字符串 + new TypeReference() { + } + ); + UavDevice device = up.getDrone(); + UavDevice getway = up.getGateway(); + UavDeviceStateResponseDetailDTO dto = new UavDeviceStateResponseDetailDTO(); + if (getway != null) { + dto.setGateway_sn(getway.getSn()); + total.updateAndGet(v -> v + 1); + dto.setCallsign(getway.getCallsign()); + if (getway.getDevice_online_status()) { + online.updateAndGet(v -> v + 1); + } else { + offline.updateAndGet(v -> v + 1); + } + dto.setOnlineStatus(getway.getDevice_online_status() ? 1 : 0); + dto.setGateway_camera_list(getway.getCamera_list()); + String data1 = getDeviceState(getway.getSn()); + SK2ResultDTO res1 = JsonUtils.parseObject(data1, SK2ResultDTO.class); + if (res1 != null && res1.getData() != null) { + UavDeviceStateDTO stateDTO = JsonUtils.parseObject( + JsonUtils.toJsonString(res1.getData()), // 先转JSON字符串 + new TypeReference() { + } + ); + if (stateDTO != null && stateDTO.getDevice_state() != null) { + UavDeviceState deviceState = stateDTO.getDevice_state(); + dto.setWind_speed(deviceState.getWind_speed()); + dto.setEnvironment_temperature(deviceState.getEnvironment_temperature()); + dto.setRainfall(deviceState.getRainfall()); + +// dto.setLatitude(deviceState.getLatitude()); +// dto.setLongitude(deviceState.getLongitude()); + } + } + + if (device != null) { + dto.setDevice_sn(device.getSn()); + dto.setDrone_callsign(device.getCallsign()); + dto.setDrone_onlineStatus(device.getDevice_online_status() ? 1 : 0); + dto.setDrone_camera_list(device.getCamera_list()); + String data = getDeviceState(device.getSn()); + SK2ResultDTO res = JsonUtils.parseObject(data, SK2ResultDTO.class); + if (res != null && res.getData() != null) { + UavDeviceStateDTO stateDTO = JsonUtils.parseObject( + JsonUtils.toJsonString(res.getData()), // 先转JSON字符串 + new TypeReference() { + } + ); + if (stateDTO != null && stateDTO.getDevice_state() != null) { + UavDeviceState deviceState = stateDTO.getDevice_state(); +// dto.setWind_speed(deviceState.getWind_speed()); +// dto.setEnvironment_temperature(deviceState.getEnvironment_temperature()); + dto.setNetwork_state(deviceState.getNetwork_state()); + dto.setPosition_state(deviceState.getPosition_state()); + dto.setHome_distance(deviceState.getHome_distance()); + dto.setHeight(deviceState.getHeight()); +// dto.setRainfall(deviceState.getRainfall()); + + dto.setLatitude(deviceState.getLatitude()); + dto.setLongitude(deviceState.getLongitude()); + if (deviceState.getBattery() != null) { + dto.setCapacity_percent(deviceState.getBattery().getCapacity_percent()); + } + } + } + } + detail.add(dto); + } + }); + responseDTO.setTotal(total.get()); + responseDTO.setOnline(online.get()); + responseDTO.setOffline(offline.get()); + } + } + return responseDTO; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public String getDeviceHMS(String deviceSn) { + String url = baseUrl + "/openapi/v0.1/device/hms"; + Map params = new HashMap<>(); + params.put("language", "zh"); + params.put("device_sn_list", deviceSn); + return httpService.doGet(url, params); + } + + public String getDeviceState(String deviceSn) { + String url = baseUrl + "/openapi/v0.1/device/" + deviceSn + "/state"; + return httpService.doGet(url, null); + } + + public String getWayline() { + String url = baseUrl + "/openapi/v0.1/wayline"; + return httpService.doGet(url, null); + } + + public String getWaylineDetail(String waylineId) { + String url = baseUrl + "/openapi/v0.1/wayline/" + waylineId; + return httpService.doGet(url, null); + } + + public String createFlightTask(FlightTaskCreateDTO dto) { + String url = baseUrl + "/openapi/v0.1/flight-task"; + if (StringUtils.isEmpty(dto.getTime_zone())) { + dto.setTime_zone("Asia/Shanghai"); + } + return httpService.doPost(url, dto); + } + + public String flightTaskCommand(FlightTaskCommandDTO dto) { + String url = baseUrl + "/openapi/v0.1/device/" + dto.getDeviceSn() + "/command"; + Map map = new HashMap<>(); + map.put("device_command", dto.getCommand().toString()); + return httpService.doPost(url, map); + } + + public List liveStream(CameraLiveParamDTO dto) { + List list = new ArrayList<>(); + try { + String url = baseUrl + "/openapi/v0.1/live-stream/start"; + + if (dto != null) { + if (!CollectionUtils.isEmpty(dto.getDrone_camera_list())) { + dto.getDrone_camera_list().forEach(x -> { + String key = dto.getDrone_sn() + "_" + x.getCamera_index(); + CameraLiveResultDTO cameraLiveResultDTO = null; + if (liveCameraMap.get(key) == null) { + CameraLiveStreamDTO d = new CameraLiveStreamDTO(); + d.setSn(dto.getDrone_sn()); + d.setCamera_index(x.getCamera_index()); + d.setQuality_type(dto.getQuality_type()); + d.setVideo_expire(dto.getVideo_expire()); + String res = httpService.doPost(url, d); + if (!StringUtils.isEmpty(res)) { + SK2ResultDTO sk2ResultDTO = JsonUtils.parseObject(res, SK2ResultDTO.class); + if (sk2ResultDTO != null && sk2ResultDTO.getData() != null) { + CameraLiveStreamResultDTO resultDTO = JsonUtils.parseObject( + JsonUtils.toJsonString(sk2ResultDTO.getData()), // 先转JSON字符串 + new TypeReference() { + } + ); + if (resultDTO != null) { + cameraLiveResultDTO = new CameraLiveResultDTO(); + cameraLiveResultDTO.setUrl(resultDTO.getUrl()); + cameraLiveResultDTO.setExpire_ts(resultDTO.getExpire_ts()); + cameraLiveResultDTO.setUrl_type(resultDTO.getUrl_type()); + cameraLiveResultDTO.setSn(dto.getDrone_sn()); + cameraLiveResultDTO.setCamera_index(x.getCamera_index()); + } + } + } + } else { + cameraLiveResultDTO = liveCameraMap.get(key); + } + list.add(cameraLiveResultDTO); + liveCameraMap.put(key, cameraLiveResultDTO); + }); + } + if (!CollectionUtils.isEmpty(dto.getGetway_camera_list())) { + dto.getGetway_camera_list().forEach(x -> { + String key = dto.getGetway_sn() + "_" + x.getCamera_index(); + CameraLiveResultDTO cameraLiveResultDTO = null; + if (liveCameraMap.get(key) == null) { + CameraLiveStreamDTO d = new CameraLiveStreamDTO(); + d.setSn(dto.getGetway_sn()); + d.setCamera_index(x.getCamera_index()); + d.setQuality_type(dto.getQuality_type()); + d.setVideo_expire(dto.getVideo_expire()); + String res = httpService.doPost(url, d); + if (!StringUtils.isEmpty(res)) { + SK2ResultDTO sk2ResultDTO = JsonUtils.parseObject(res, SK2ResultDTO.class); + if (sk2ResultDTO != null && sk2ResultDTO.getData() != null) { + CameraLiveStreamResultDTO resultDTO = JsonUtils.parseObject( + JsonUtils.toJsonString(sk2ResultDTO.getData()), // 先转JSON字符串 + new TypeReference() { + } + ); + if (resultDTO != null) { + cameraLiveResultDTO = new CameraLiveResultDTO(); + cameraLiveResultDTO.setUrl(resultDTO.getUrl()); + cameraLiveResultDTO.setExpire_ts(resultDTO.getExpire_ts()); + cameraLiveResultDTO.setUrl_type(resultDTO.getUrl_type()); + cameraLiveResultDTO.setSn(dto.getGetway_sn()); + cameraLiveResultDTO.setCamera_index(x.getCamera_index()); + } + } + } + } else { + cameraLiveResultDTO = liveCameraMap.get(key); + } + list.add(cameraLiveResultDTO); + liveCameraMap.put(key, cameraLiveResultDTO); + }); + } + } + return list; + } catch (Exception e) { + log.error("开启直播发生错误", e); + } + return list; + } + + public String flightTaskList(String sn) { + String url = baseUrl + "/openapi/v0.1/flight-task/list"; + Map map = new HashMap<>(); + map.put("begin_at", 1735692484); //2025-1-1 + map.put("end_at", 1830306933);//2028-1-1 + map.put("sn", sn); + return httpService.doGet(url, map); + } + + public String flightTaskDetail(String taskId) { + String url = baseUrl + "/openapi/v0.1/flight-task/" + taskId; + return httpService.doGet(url, null); + } + + public String getFlightTaskTrack(String taskId) { + String url = baseUrl + "/openapi/v0.1/flight-task/" + taskId + "/track"; + return httpService.doGet(url, null); + } + + + public String uploadSK2Wayline(String name, String objectKey) { + String url = baseUrl + "/openapi/v0.1/wayline/finish-upload"; + Map map = new HashMap<>(); + map.put("name", name); + map.put("objectKey", objectKey); + return httpService.doPost(url, map); + } + + + public UavStsTokenDTO getStsToken() { + String url = baseUrl + "/openapi/v0.1/project/sts-token"; + + String res = httpService.doGet(url, null); + SK2ResultDTO dto = JsonUtils.parseObject(res, SK2ResultDTO.class); + if (dto != null && dto.getData() != null) { + ObjectMapper mapper = new ObjectMapper(); + return mapper.convertValue(dto.getData(), UavStsTokenDTO.class); + } + return null; + } + + + public Object uploadWayline(MultipartFile file, String name) throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException { + + //获取上传凭证 + UavStsTokenDTO credential = getStsToken(); + //获取属性 + String endpoint = credential.getEndpoint(); + String accessKey = credential.getCredentials().getAccess_key_id(); + String secretKey = credential.getCredentials().getAccess_key_secret(); + String securityToken = credential.getCredentials().getSecurity_token(); + String bucket = credential.getBucket(); + String objectKey = String.format("%s/%s", credential.getObject_key_prefix(), file.getName()); + InputStream inputStream = file.getInputStream(); + // 创建 MinioClient + MinioClient minioClient = MinioClient.builder() + .endpoint(endpoint) + .credentialsProvider( + new StaticProvider(accessKey, secretKey, securityToken) + ) + .build(); + +// // STS token 必须放在 header + Map headers = new HashMap<>(); + headers.put("x-oss-security-token", securityToken); + + minioClient.putObject( + PutObjectArgs.builder() + .bucket(bucket) + .object(objectKey) + .stream(inputStream, file.getSize(), -1) + .headers(headers) + .build() + ); + + String s = uploadSK2Wayline(name, objectKey); + SK2ResultDTO dto = JsonUtils.parseObject(s, SK2ResultDTO.class); + if (dto != null && dto.getData() != null) { + return dto.getData(); + } + return null; + } + + public String updateFlightTaskStatus(String taskId, String status) { + String url = baseUrl + "/openapi/v0.1/flight-task/" + taskId + "/status"; + Map map = new HashMap<>(); + map.put("status", status); + return httpService.doPut(url, map); + } + + public void liveHeartbeat(String sn, String cameraIndex) { + //key sn + _ + camera_index + String key = sn + "_" + cameraIndex; + latestActiveTime.put(key, System.currentTimeMillis()); + } + + @PostConstruct + public void heartbeatMonitor() { + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + executor.scheduleWithFixedDelay(() -> { + try { + Long nowTime = System.currentTimeMillis(); + if (!CollectionUtils.isEmpty(latestActiveTime.keySet())) { + latestActiveTime.keySet().forEach(x -> { + Long lastTime = latestActiveTime.get(x); + if (lastTime != null && (nowTime - lastTime) > outTime) { + liveCameraMap.remove(x); + latestActiveTime.remove(x); + } + }); + } + } catch (Exception e) { + log.error("监测直播心跳错误!", e); + } + }, 0, 5, TimeUnit.SECONDS); + } + + + public String changeCamera(String sn, String cameraIndex, String cameraPosition) { + String url = baseUrl + "/openapi/v0.1/device/change-camera"; + Map map = new HashMap<>(); + map.put("sn", sn); + map.put("camera_index", cameraIndex); + map.put("camera_position", cameraPosition); + return httpService.doPut(url, map); + } + +} diff --git a/maibu-external/src/main/java/com/maibu/uav/utils/UavHttpUtils.java b/maibu-external/src/main/java/com/maibu/uav/utils/UavHttpUtils.java new file mode 100644 index 0000000..b1df3c6 --- /dev/null +++ b/maibu-external/src/main/java/com/maibu/uav/utils/UavHttpUtils.java @@ -0,0 +1,158 @@ +package com.maibu.uav.utils; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import javax.annotation.Nullable; +import java.util.Map; + +@Service +public class UavHttpUtils { + + @Autowired + private RestTemplate restTemplate; + + @Value("${UAV.token:eyJhbGciOiJIUzUxMiIsImNyaXQiOlsidHlwIiwiYWxnIiwia2lkIl0sImtpZCI6IjhiZmRiZmRkLWM4OGYtNGE5Yi04NzI3LWQ0ZGYzYWE5OTJlOSIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50IjoiODQ5MDczQHFxLmNvbSIsImV4cCI6MjA1OTg2ODc3NCwibmJmIjoxNzQ0MzM1OTc0LCJvcmdhbml6YXRpb25fdXVpZCI6ImYwMmIwZTM4LWUyZjgtNGNhMi04ZTBmLWM1YmVlMTM3NDk2ZiIsInByb2plY3RfdXVpZCI6IiIsInN1YiI6ImZoMiIsInVzZXJfaWQiOiIxMDEyOSJ9.t9KH3SOBNxJwi3bZwA7diGV7qeLZY2cXI9ovT5FnkWXLjSHNYHD4KQObGi1iGJ5igAxZZaWIooT8v8VcPwsrSQ}") + private String token; + + @Value("${UAV.projectUId:bb191b8f-5110-4536-82e3-f56730e93e4c}") + private String projectUId; + + + /** + * 通用 GET 请求(手动传入 Token,每次可传不同值) + * + * @param apiUrl 目标接口地址 + * @return 接口响应字符串 + */ + public String doGet(String apiUrl, @Nullable Map params) { + // 1. 构建带查询参数的URL(自动处理编码,避免手动拼接的问题) + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(apiUrl); + // 如果参数不为空,添加查询参数 + if (params != null && !params.isEmpty()) { + params.forEach(uriBuilder::queryParam); + } + // 构建最终的URL(包含编码后的参数) + String finalUrl = uriBuilder.build().toUriString(); + + // 2. 构建请求头(保留原有Token逻辑) + HttpHeaders headers = buildHeadersWithToken(); + // 3. 封装请求实体(GET无需请求体) + HttpEntity httpEntity = new HttpEntity<>(headers); + + // 4. 发送 GET 请求(保留原有异常处理) + try { + ResponseEntity response = restTemplate.exchange( + finalUrl, + HttpMethod.GET, + httpEntity, + String.class + ); + // 校验响应状态并返回结果 + if (response.getStatusCode().is2xxSuccessful()) { + return response.getBody() == null ? "" : response.getBody(); + } else { + throw new RuntimeException("GET 请求失败,响应码:" + response.getStatusCode() + ",响应体:" + response.getBody()); + } + } catch (HttpClientErrorException | HttpServerErrorException e) { + // 捕获 HTTP 4xx/5xx 异常 + throw new RuntimeException("GET 请求异常,响应码:" + e.getStatusCode() + ",响应体:" + e.getResponseBodyAsString(), e); + } catch (Exception e) { + // 捕获其他未知异常 + throw new RuntimeException("GET 请求未知异常:" + e.getMessage(), e); + } + } + + /** + * 通用 POST 请求(手动传入 Token,支持 JSON 请求体,每次可传不同 Token) + * + * @param apiUrl 目标接口地址 + * @param requestParam JSON 请求体(实体类/字符串均可,支持 null) + * @return 接口响应字符串 + */ + public String doPost(String apiUrl, Object requestParam) { + // 1. 构建请求头(携带传入的 Token) + HttpHeaders headers = buildHeadersWithToken(); + // 2. 封装请求实体(携带请求头 + 请求体) + HttpEntity httpEntity = new HttpEntity<>(requestParam, headers); + // 3. 发送 POST 请求 + try { + ResponseEntity response = restTemplate.exchange( + apiUrl, + HttpMethod.POST, + httpEntity, + String.class + ); + // 校验响应状态并返回结果 + if (response.getStatusCode().is2xxSuccessful()) { + return response.getBody() == null ? "" : response.getBody(); + } else { + throw new RuntimeException("POST 请求失败,响应码:" + response.getStatusCode() + ",响应体:" + response.getBody()); + } + } catch (HttpClientErrorException | HttpServerErrorException e) { + // 捕获 HTTP 4xx/5xx 异常 + throw new RuntimeException("POST 请求异常,响应码:" + e.getStatusCode() + ",响应体:" + e.getResponseBodyAsString(), e); + } catch (Exception e) { + // 捕获其他未知异常 + throw new RuntimeException("POST 请求未知异常:" + e.getMessage(), e); + } + } + + /** + * 通用 POST 请求(手动传入 Token,支持 JSON 请求体,每次可传不同 Token) + * + * @param apiUrl 目标接口地址 + * @param requestParam JSON 请求体(实体类/字符串均可,支持 null) + * @return 接口响应字符串 + */ + public String doPut(String apiUrl, Object requestParam) { + // 1. 构建请求头(携带传入的 Token) + HttpHeaders headers = buildHeadersWithToken(); + // 2. 封装请求实体(携带请求头 + 请求体) + HttpEntity httpEntity = new HttpEntity<>(requestParam, headers); + // 3. 发送 PUT 请求 + try { + ResponseEntity response = restTemplate.exchange( + apiUrl, + HttpMethod.PUT, + httpEntity, + String.class + ); + // 校验响应状态并返回结果 + if (response.getStatusCode().is2xxSuccessful()) { + return response.getBody() == null ? "" : response.getBody(); + } else { + throw new RuntimeException("PUT 请求失败,响应码:" + response.getStatusCode() + ",响应体:" + response.getBody()); + } + } catch (HttpClientErrorException | HttpServerErrorException e) { + // 捕获 HTTP 4xx/5xx 异常 + throw new RuntimeException("PUT 请求异常,响应码:" + e.getStatusCode() + ",响应体:" + e.getResponseBodyAsString(), e); + } catch (Exception e) { + // 捕获其他未知异常 + throw new RuntimeException("PUT 请求未知异常:" + e.getMessage(), e); + } + } + + /** + * 构建携带 Token 的通用请求头(默认 JSON 格式) + * @return HttpHeaders + */ + private HttpHeaders buildHeadersWithToken() { + HttpHeaders headers = new HttpHeaders(); + // 设置默认 Content-Type 为 JSON 格式 + headers.setContentType(MediaType.APPLICATION_JSON_UTF8); + headers.set("Accept", "application/json"); + headers.set("X-User-Token", token.trim()); + // 可按需添加其他通用请求头(如 User-Agent) + headers.set("X-Language", "zh"); + headers.set("X-Project-Uuid", projectUId.trim()); + return headers; + } + + +} diff --git a/maibu-external/src/main/resources/templates/reportTemplate.html b/maibu-external/src/main/resources/templates/reportTemplate.html new file mode 100644 index 0000000..a62b17b --- /dev/null +++ b/maibu-external/src/main/resources/templates/reportTemplate.html @@ -0,0 +1,543 @@ + + + + + ${siteName} 热成像巡检报告 + + + +
+
+
Thermal Report
+

热成像巡检报告

+

共识别 10 处板框异常,其中 4 处为高优先级,建议优先安排现场复核。

+
+ 项目/站点:汾湖创新经济产业园 + 报告日期:${reportDate} + 编制人:Thermal Inspector + 热图文件:thermal_002_transparent_reflectance_grayscale_cleaned_full.tif +
+
+ +
+
+ 异常编号数 + 10 + 按编号去重后的异常输出 +
+
+ 涉及块数 + 3 + 按块号聚合统计 +
+
+ 热图最高温 + ${max_temp}℃ + P95 ${p95_temp}℃ +
+
+ 优先级分布 + 3/3/4 + 高 / 中 / 低 +
+
+ +
+

项目摘要

+

沿用行业常见报告的“系统摘要 + 检测信息 + 异常地图 + 异常汇总”结构,便于直接归档或打印为 + PDF。

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
字段内容
项目/站点汾湖创新经济产业园
客户/项目方-
编制人Thermal Inspector
报告日期${reportDate}
热图文件thermal_002_transparent_reflectance_grayscale_cleaned_full.tif
热图尺寸9193 x 6053
温度阈值48.0℃
设备/来源无人机热成像巡检
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
分析项结果
阵列约束pv_array_filled_mask.png
板框约束pv_panels_pixel.geojson
阵列内热斑数120
板框内热斑数22
被板框过滤98
有效像素24,679,370
P95 温度${p95_temp}℃
热图最高温${max_temp}℃
+
+
+
+
+
+

异常地图

+

整图保留了板框编号、异常框和峰值标记,可作为本轮巡检的总览索引图。

+
+ 热成像异常地图 +
+
+ +
+

重点异常

+

以下卡片默认展示温度最高的前 6 + 个异常编号,优先级依据“峰值温度相对阈值的超温量”自动分级。

+
1-6
1-6高优先级
峰值温度75.94℃
超阈值+27.94℃
面积2540 px
GPS31.453850, 120.565618
像素x=5037, y=904
1-42
1-42高优先级
峰值温度64.04℃
超阈值+16.04℃
面积58 px
GPS31.453375, 120.565893
像素x=5796, y=2519
1-25
1-25高优先级
峰值温度60.08℃
超阈值+12.08℃
面积55 px
GPS31.453510, 120.566024
像素x=6184, y=2072
3-12
3-12中优先级
峰值温度59.31℃
超阈值+11.31℃
面积38 px
GPS31.453076, 120.565208
像素x=3798, y=3481
3-4
3-4中优先级
峰值温度56.66℃
超阈值+8.66℃
面积182 px
GPS31.453138, 120.565509
像素x=4671, y=3289
1-12
1-12中优先级
峰值温度54.00℃
超阈值+6.00℃
面积1000 px
GPS31.453722, 120.565806
像素x=5570, y=1345
+
+ +
+

温度分布

+

按当前阈值以上的异常温度区间做分桶统计,方便快速判断热异常集中程度。

+
+ + + +
温度区间数量
48.0~53.0℃${t1}
53.0~58.0℃${t2}
>=58.0℃${t3}
+
+
+ +
+

异常明细

+

明细表保留编号、优先级、峰值温度、超阈值、面积、GPS 和像素坐标,可直接配合维修台账使用。

+
+ + + + + + + + + + + + + + ${anomalyDetailRows} +
编号优先级峰值温度超阈值逆变器阵列号块号GPS
+
+
+ +
+

处置建议

+

以下建议基于当前算法输出自动生成,适合作为运维闭环的起点。

+
  • 建议优先复核高温异常点,并结合现场电气测试、可见光照片与遮挡检查确认根因。
  • 优先安排对高优先级异常点做现场复测,当前至少有 4 处异常较阈值高出 12℃ 以上,建议先处理编号 1-6 等高温点。
  • 建议结合可见光图像、IV/电气测试和现场遮挡检查确认根因;当前报告仅输出热异常,不自动判定缺陷类型。
  • 维修闭环后建议用同一阈值再次复拍,确认异常温度和范围已回落至正常区间。
+ +
+
+ + + + \ No newline at end of file