diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..5ea51169 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ + +## [v2.0.0] - 2024-01-31 + +### 新增功能 +- 支持netty mqtt broker ([#1]()) +- 支持多种编码协议管理([#2]()) +- 支持emqx5.0([#3]()) + +### 功能优化 +- 设备列表卡片优化,设备详情页面优化 +- 物模型功能优化 +- 产品管理功能优化 + +### 代码重构 +- java项目代码目录重新调整 +- 消息网关重构 + +### 其他事务 +- 新增Git提交规范([#Git提交规范](https://gitee.com/kerwincui/wumei-smart/blob/master/doc/Git%E6%8F%90%E4%BA%A4%E8%A7%84%E8%8C%83.md)) +- 新增贡献者指南([#贡献者指南](https://gitee.com/kerwincui/wumei-smart/blob/master/doc/%E8%B4%A1%E7%8C%AE%E8%80%85%E6%8C%87%E5%8D%97.md)) +- 新增功能规划([#功能规划](https://gitee.com/kerwincui/wumei-smart/blob/master/RoadMap.md)) +- 修改项目AGPL3协议商用授权说明 diff --git a/README.md b/README.md index c1372050..45236085 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,40 @@ 1. FastBee开源物联网平台,简单易用,更适合中小企业和个人学习使用。适用于智能家居、智慧办公、智慧社区、农业监测、水利监测、工业控制等。 2. 系统后端采用Spring boot;前端采用Vue;消息服务器采用EMQX;移动端支持微信小程序、安卓、苹果和H5采用Uniapp;数据库采用Mysql、TDengine和Redis;设备端支持ESP32、ESP8266、树莓派、合宙等; - + ### 二、系统功能 -- 权限管理: 用户管理、部门管理、岗位管理、菜单管理、角色管理、字典和参数管理等 -- 系统监控: 操作日志、登录日志、系统日志、在线用户、服务监控、连接池监控、缓存监控等 -- 产品管理: 产品、产品物模型、产品分类、产品固件、设备授权码、自定义告警等 -- 设备管理: 设备控制、设备分组、设备定时、设备日志、监测统计、设备定位、设备分享、设备禁用、OTA升级、实时状态、影子模式、实时监测、加密认证等 -- EMQ管理: Mqtt客户端、监听器、消息主题、消息订阅、插件管理、规则引擎、资源 -- 硬件 SDK: 支持WIFI和MQTT连接、物模型响应、实时监测、定时上报数据、AES加密、NTP时间、AP配网等 -- 物模型管理: 属性(设备状态和监测数据),功能(执行特定任务),事件(设备主动上报给云端) -- 扩展模块: 智能音响(小度、天猫精灵、小爱同学)、web组态等 -- 其他功能:网关、TCP/Modbus/协议和netty-mqtt支持、视频监控、多租户、场景联动、数据可视化平台、统计、新闻资讯、通知公告、支持TDengine时序数据库 + +| 系统功能 | 功能说明 | 开源版本 | 商业版本 | +|:--------:|:-----------------------------------------------:|------|--------------| +| 产品管理 | 产品详情、产品物模型、产品分类、设备授权、产品固件 | 支持 | 支持 | +| 设备管理 | 设备详情、设备分组、设备日志、设备分享、设备实时控制、实时状态、数据监测 | 支持 | 支持 | +| 物模型管理 | 属性(设备状态和监测数据),功能(执行特定任务),事件(设备主动上报给云端) | 支持 | 支持 | +| MQTT接入 | emqx开源版、netty版本MqttBroker | 支持 | 支持 | +| 硬件 SDK | ESP-IDF、Arduino、RaspberryPi、合宙等平台设备接入 | 支持 | 支持 | +| 视频监控接入 | 基于GB/T28181协议支持主流厂商监控设备接入,直播、设备录制回放、 云端录像和云台控制 | 支持设备接入和直播 | 支持 | +| 多协议管理 | 硬件设备多种协议支持管理 | 支持JSON | 支持多种 | +| TCP接入 | 基于Netty搭建的TCP服务器 | 不支持 | 支持 | +| UDP接入 | 基于Netty搭建的UDP服务器 | 不支持 | 支持 | +| Modbus接入 | 透传Modbus/边缘网关接入Modbus设备 | 不支持 | 支持 | +| 采集点管理 | 网关设备管理子设备接入 | 不支持 | 支持 | +| OTA升级 | 固件在线升级 | 不支持 | 支持 | +| 设备模拟器调试 | Modbus设备在线调试 | 不支持 | 支持 | +| 数据大屏 | 数据大屏可视化,将图表或页面元素封装为基础组件,0代码即可完成业务需求。 | 不支持 | 支持 | +| 规则引擎-规则脚本 | 可视化规则引擎编写,支持js,java等脚本修改消息结构,处理设备上行/下行/上线/下线/数据解析/数据转换 | 不支持 | 支持 | +| 场景联动 | 基于规则引擎生成场景联动 | 不支持 | 支持 | +| 告警&&告警配置&&告警记录 | 告警: 设备告警/平台告警判定 告警配置: 基于规则引擎开发的平台告警判定 告警记录:设备告警记录入库 | 不支持 | 支持 | +| 通知渠道 &&通知面板&&通知日志 | 阿里云短信/腾讯云短信阿里云语言/腾讯云语音/QQ邮箱/163邮箱/微信小程序/企业微信群机器人/企业微信应用信息/钉钉消息通知/钉钉群机器人 | 不支持 | 支持 | +| 多租户 | 系统内租户的管理,独占一套系统配置,数据相互隔离。如:租户权限、过期时间、用户数量、企业信息等 | 不支持 | 支持 | +| 移动端app | 移动端(安卓 / 苹果 / 微信小程序) | 不支持 | 支持 | +| 云云对接 -智能音响(小度、天猫精灵、小爱同学) | 云云对接 | 不支持 | 额外付费模块 | +| web组态 | 自定义数据大屏/2D/3D | 不支持 | 额外付费模块 | +| 萤石云,海康sdk接入、AI SDK接入 | 视频接入 | 不支持 | 额外付费模块 | +| 数据存储 | 设备数据处理 | redis存储最后一条数据,不支持实时更新 | redis存储最新数据,实时更新/mysql存储系统数据/TDengine时序数据库存储设备数据 | +| 设备接入数/上行数据并发支持 | 设备接入数,以及设备数据并发量 | 支持小规模设备接入,同步处理数据 | 支持规模量大设备。消息队列削峰,线程池异步处理高并发数据 | +| 技术支持 | | 不支持 | 提供一定的技术支持/技术方案 | + + ![](https://oscimg.oschina.net/oscnet/up-a9a7fdaf40208becd26c2485783bc0f86e6.png) @@ -26,55 +48,63 @@ | ![](https://oscimg.oschina.net/oscnet/up-ad98a81677e5e68d660866770e3266ca4cf.png) | ![](https://oscimg.oschina.net/oscnet/up-68caf860d0659444e73f42717a03d2fdf72.png) | ![](https://oscimg.oschina.net/oscnet/up-cf690f6058042dccb567ff382ea9432ebab.png) |![](https://oscimg.oschina.net/oscnet/up-c4a7971510127324d6566dd6ea95d571483.jpg) | ![](https://oscimg.oschina.net/oscnet/up-4ce09be3599e3ff7ed91fe182572abd258b.jpg) | -### 三、技术栈 +### 三、技术栈 * 服务端 - - 相关技术:Spring boot、MyBatis、Spring Security、Jwt、Mysql、Redis、TDengine、EMQX、Netty等 - - 开发工具:IDEA +- 相关技术:Spring boot、MyBatis、Spring Security、Jwt、Mysql、Redis、TDengine、EMQX、Netty等 +- 开发工具:IDEA * Web端 - - 相关技术:ES6、Vue、Vuex、Vue-router、Vue-cli、Axios、Element-ui、Echart等 - - 开发工具:Visual Studio Code +- 相关技术:ES6、Vue、Vuex、Vue-router、Vue-cli、Axios、Element-ui、Echart等 +- 开发工具:Visual Studio Code * 移动端(微信小程序 / Android / Ios / H5) - - 相关技术:uniapp、[uView](https://www.uviewui.com/)、[uChart](https://www.ucharts.cn/) - - 开发工具:HBuilder +- 相关技术:uniapp、[uView](https://www.uviewui.com/)、[uChart](https://www.ucharts.cn/) +- 开发工具:HBuilder * 硬件端 - - 相关技术: ESP-IDF、Arduino、FreeRTOS、Python、Lua等 - - 开发工具:Visual Studio Code 和 Arduino等 +- 相关技术: ESP-IDF、Arduino、FreeRTOS、Python、Lua等 +- 开发工具:Visual Studio Code 和 Arduino等 ### 四、项目目录 -     spring-boot --------------- 后端
-     vue ----------------------- 前端
-     docker -------------------- docker部署文件
-     sdk ----------------------- 硬件SDK,已集成多种设备
+     app -------------------- 移动端(微信小程序 / Android / Ios / H5) 商业版开源
+     docker ---------------- docker部署文件
+     sdk -------------------- 硬件SDK,已集成多种设备
+     spring-boot ---------- 后端
+     vue -------------------- 前端
### 五、商用授权 项目采用AGPL3协议,可用于个人学习和使用,商业用途需要赞助项目,获得授权,并提供商业版本源码、可视化平台和移动端源码。赞助过的用户请下载商业版本源码。 - [授权详情>>](https://fastbee.cn/doc/pages/sponsor/)   [商业版本源码>>](https://fastbee.cn/doc/pages/sponsor/) -- [移动端源码>>](https://fastbee.cn/doc/pages/sponsor/)   [可视化平台源码>>](https://fastbee.cn/doc/pages/sponsor/) +- [移动端源码>>](https://fastbee.cn/doc/pages/sponsor/)   [可视化平台源码>>](https://fastbee.cn/doc/pages/sponsor/) +- 二开项目同样遵守AGPL3.0协议进行开源,可以向原作者申请授权 +- 如果商业项目想转闭源,可以向原作者申请或者购买闭源授权 +### 六、贡献代码 +- [贡献者指南>>](./doc/贡献者指南.md) +- [Git提交规范>>](./doc/Git提交规范.md) +- [功能规划>>](./RoadMap.md) -### 六、其他 +### 七、其他 1. QQ交流群:🚀946029159 🚀1073236354(已满) -2. 权限管理基于ruoyi-vue系统开发,Mqtt消息服务器使用EMQX4.0开源版 +2. 权限管理基于ruoyi-vue系统开发,Mqtt消息服务器使用EMQX5.0开源版 - * [在线演示](https://iot.fastbee.cn/) - * [项目使用文档](https://fastbee.cn/doc/) - * [若依权限管理系统文档](http://doc.ruoyi.vip/ruoyi-vue/) - * [EMQX4.0消息服务器文档](https://www.emqx.io/docs/zh/v4.0/) - * [uCharts高性能跨平台图表库](https://www.ucharts.cn) +* [开源版本在线演示](http://101.33.237.12/) +* [商业版本在线演示](https://iot.fastbee.cn/) +* [项目使用文档](https://fastbee.cn/doc/) +* [若依权限管理系统文档](http://doc.ruoyi.vip/ruoyi-vue/) +* [EMQX5.0消息服务器文档](https://www.emqx.io/docs/zh/v5.0/) +* [uCharts高性能跨平台图表库](https://www.ucharts.cn) 3. 项目贡献者(如有遗漏请联系作者): - - [小驿物联](https://gitee.com/iot-xiaoyi)、[CrazyDull](https://gitee.com/crazyDull)、[YBZX](https://github.com/YBZX)、 [CQAdu](https://gitee.com/iot.adu)、[孙阿龙](https://gitee.com/sunalong)、[xxmfl](https://gitee.com/xxmfl)、[董晓龙-3715687@qq.com](https://fastbee.cn/) - - [SXH](https://gitee.com/sixiaohu)、 [Redamancy_zxp](https://gitee.com/redamancy-zxp)、 [LEE](https://gitee.com/yueming188)、 [LemonTree](https://gitee.com/fishhunterplus)、 [Tang](https://gitee.com/mexiaotang)、 [Tang](https://gitee.com/mexiaotang)、[KUN](https://gitee.com/L_KUN_KUN) +- [小驿物联](https://gitee.com/iot-xiaoyi)、[CrazyDull](https://gitee.com/crazyDull)、[YBZX](https://github.com/YBZX)、 [CQAdu](https://gitee.com/iot.adu)、[孙阿龙](https://gitee.com/sunalong)、[xxmfl](https://gitee.com/xxmfl)、[董晓龙-3715687@qq.com](https://fastbee.cn/) +- [SXH](https://gitee.com/sixiaohu)、 [Redamancy_zxp](https://gitee.com/redamancy-zxp)、 [LEE](https://gitee.com/yueming188)、 [LemonTree](https://gitee.com/fishhunterplus)、 [Tang](https://gitee.com/mexiaotang)、 [Tang](https://gitee.com/mexiaotang)、[KUN](https://gitee.com/L_KUN_KUN) 4. 主要参与用户: - [Guanshubiao](https://gitee.com/guanshubiao):熟悉物联网开发,完善和优化系统的网关架构和部分功能等 - [帐篷](https://gitee.com/zhuangpengli):熟悉物联网开发,完善视频监控模块和部分协议等 - [JaminDeng](https://gitee.com/jamin-deng):熟悉物联网开发,完善平台前端设计可视化等 -### 七、部分图片 +### 八、部分图片 ![](https://oscimg.oschina.net/oscnet/up-972dea7b54eca705dcc8bf2fe0680b12c09.png) ![](https://oscimg.oschina.net/oscnet/up-6d89f1558797a9becf07c20f92c1407a13a.png) @@ -103,4 +133,3 @@ - diff --git a/RoadMap.md b/RoadMap.md new file mode 100644 index 00000000..bcfe1bae --- /dev/null +++ b/RoadMap.md @@ -0,0 +1,57 @@ +# 架构优化 +- 代码简化 +- 业务&协议解耦 +- 关键组件支持横向拓展 +- 网络协议支持横向拓展,包括:mqtt broker,tcp,coap,udp,sip等 +- 协议插件化 +- 编码脚本化 +- 业务代码模版化 +- 消息总线 + +# 功能优化 +- 网关/子网关:上线,绑定,拓扑,消息代理/透传/轮询等功能完善 +- 设备详情简化,运行状态,监测,历史记录等界面优化,组件可编辑 +- 设备OT能力完善,配合SDK完善设备产测,设备出厂配置,设备一键注网,设备批量上线,设备升级,设备维护&替换等流程 +- 设备IT能力完善,更方便对接联动第三方系统,打通数据烟囱,集成更多三方SDK +- 消息路由,消息流转,消息编解码,消息过滤更加灵活 + +# 功能拓展 +- 规则引擎 +- 组态 +- 可视化大屏 +- 视频监控协议 +- 视频分析,图形识别能力 +- coap协议 +- sip协议 +- snmp协议 +- tr069/tr369协议 +- plc对接 +- modbus rtu/ascii/tcp等协议 +- ai能力 +- gpt能力 + +# 设备SDK +- esp-idf框架搭建 +- 基于esp-aliyun编写fastbee相关mqtt接入示例 +- esp-modbus示例完善,增加mqtt透传,主站轮询指令下发等特性 +- esp-aliyun重构,升级esp-idf5.0 +- esp-fastbee组件模块开发 +- rt-thread,openwrt等os接入组件开发 +- 尝试适配更多硬件方案,rk,esp32,stm32,合宙等等 +- 免费帮10家不同类型设备厂商接入平台 + +# 文档完善 +- 新手入门任务 +- 各模块使用说明书 +- 调试方式文档 +- 二次开发说明书 +- 三方工具关键组件使用说明 +- 简化代码二发和部署流程 +- 交付方式优化:docker镜像,一键部署包等方式 + +# 社区建设 +- 发展一批Committer +- 规范issues和pr流程 +- 完善测试用例和测试流程 +- 完善自动化发布,代码静态检查,自动化测试等流程 +- 与行业伙伴共同打造5-8个垂直行业典型案例 \ No newline at end of file diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md new file mode 100644 index 00000000..5ea51169 --- /dev/null +++ b/doc/CHANGELOG.md @@ -0,0 +1,22 @@ + +## [v2.0.0] - 2024-01-31 + +### 新增功能 +- 支持netty mqtt broker ([#1]()) +- 支持多种编码协议管理([#2]()) +- 支持emqx5.0([#3]()) + +### 功能优化 +- 设备列表卡片优化,设备详情页面优化 +- 物模型功能优化 +- 产品管理功能优化 + +### 代码重构 +- java项目代码目录重新调整 +- 消息网关重构 + +### 其他事务 +- 新增Git提交规范([#Git提交规范](https://gitee.com/kerwincui/wumei-smart/blob/master/doc/Git%E6%8F%90%E4%BA%A4%E8%A7%84%E8%8C%83.md)) +- 新增贡献者指南([#贡献者指南](https://gitee.com/kerwincui/wumei-smart/blob/master/doc/%E8%B4%A1%E7%8C%AE%E8%80%85%E6%8C%87%E5%8D%97.md)) +- 新增功能规划([#功能规划](https://gitee.com/kerwincui/wumei-smart/blob/master/RoadMap.md)) +- 修改项目AGPL3协议商用授权说明 diff --git a/doc/Git提交规范.md b/doc/Git提交规范.md new file mode 100644 index 00000000..d4036165 --- /dev/null +++ b/doc/Git提交规范.md @@ -0,0 +1,63 @@ +# 一,conventional commit(约定式提交) +Conventional Commits 是一种用于给提交信息增加人机可读含义的规范。它提供了一组用于创建清晰的提交历史的简单规则。 +## 1.1 作用 +- 自动化生成 CHANGELOG +- 基于提交类型,自动决定语义化的版本变更 +- 向项目相关合作开发者发送变更信心 +- 触发自动化构建和部署流程 +- 给开发者提供一个更加结构化的提交历史,便于减低对项目做贡献的难度 +## 1.2 提交格式 +提交说明的结构如下: +(): +空行 +[可选的正文] +空行 +[可选的脚注] +### 1.2.1 Header +Header部分只有一行,包括三个字段: type(必需)、scope(必需)和subject(必需)。 + +#### (1)type 类型 +type用于说明 commit 的类别,只允许使用下面7个标识: +- feat:新功能(feature) +- fix:修补bug +- docs:文档(documentation) +- style: 格式(不影响代码运行的变动) +- refactor:重构(即不是新增功能,也不是修改bug的代码变动) +- test:增加测试 +- build:构建过程或辅助工具的变动 +#### (2)scope 范围 +scope用于说明 commit 影响的范围,比如指标模板、规则上下线等等,视项目不同而不同。 +#### (3)subject 主题 +subject是 commit 目的的简短描述,不超过50个字符。 +1. 以动词开头,使用第一人称现在时,比如change,而不是changed或changes +2. 第一个字母小写 +3. 结尾不加句号(.) +### 1.2.2 Body 正文 + Body 部分是对本次 commit 的详细描述,可以分成多行。 +1. 需要简要的列出各种正向或者反向的测试场景,测试通过,填pass。 +2. 增加修改人信息 + +## 1.3 例子 +### 1.3.1 feat例子 +``` +feat(规则上下线、构建、生效、仿真生效): 添加规则上下线功能 + +1. 规则上下线主流程,引擎正常订阅,fldl生成正常。 pass +2. 规则上下线,传入不存在的规则编号,异常提示。 pass + +提交人:xxx +``` +### 1.3.2 fix例子 +``` +fix(模型模块): 模型测试失败 + +1. 导入mar模型,包含衍生字段。 pass +2. 导入mar模型,不包含衍生字段。 pass + +提交人:xxx +``` +## 1.4 IDEA插件推荐 + +1. 安装路径:File->Settings->Plugins->Marketplace->搜索Conventional Commit,点击安装即可。 +2. 提交的时候点击中间的小红点创建提交消息,根据对话框提示填写相关信息即可生成规范的提交消息。 + diff --git a/doc/贡献者指南.md b/doc/贡献者指南.md new file mode 100644 index 00000000..8d931a0f --- /dev/null +++ b/doc/贡献者指南.md @@ -0,0 +1,69 @@ +> 非常欢迎参与项目贡献,我们致力于维护一个互相帮助,共同成长社区。 + +# 贡献方式 +在Fastbee 社区,贡献方式有很多: + +- 💻代码:可以帮助社区完成一些任务、编写新的feature或者是修复一些bug; +- ⚠️测试:可以来参与测试代码的编写,包括了单元测试、集成测试、e2e测试; +- ✅编译:构建或者辅助工具建议,包括:Docker,K8s,CI/CD,辅助调试工具等; +- 📖文档:可以编写或完善文档,来帮助用户更好地了解和使用 物联网平台; +- 🤔讨论:可以参与 Fastbee 新的feature的讨论,将您的想法跟 Fastbee 融合; +- 💬建议:也可以对项目或者社区提出一些建议,促进社区的良性发展; +- ❗疑问:问出一个好的问题,同样也可以促进项目发展,拓宽思考方向; + +> 即便是小到错别字的修正我们也都非常欢迎 :) + +# 提PR有以下注意点 +PR统一在Gitee平台上进行提交,如果你不知道如何提交PR,可以在Gitee平台里去学习。这里不作说明。 +- fork后切换到develop分支,请以这个分支为开发基准。 +- 所有的PR提交到develop分支,这个分支为开发分支。 +- 如果你作了功能性的变动,请带上你的测试用例,测试用例规范可以参考之前的测试用例。 +- 所有的PR必须关联至少一个issue,如果没有相关issue,请自行创建一个。 +- 正式提交PR之前,请确保所有的测试用例都通过。 +- Git提交消息,需要按照[Git提交规范](./Git提交规范.md)。 + +# 提交 Pull Request +1. 首先您需要 Fork 目标仓库 Fastbee repository. +2. 然后 用git命令 将代码下载到本地: + +``` + git clone https://gitee.com/zhuangpengli/FastBee +``` + +3. 下载完成后,请参考目标仓库README 文件对项目进行初始化。 +4. 接着,您可以参考如下命令进行代码的提交, 切换新的分支, 进行开发: + +``` +# 根据项目需要创建对应分支 +git checkout -b feat-xxx 或者 fix-xxx 等等 +``` + +5. 提交 commit , commit 描述信息需要符合[约定格式](./Git提交规范.md). + +``` +git add +git commit -m '[docs]feature: necessary instructions' +``` +6. 推送到远程仓库 +``` +git push origin feat-xxx +``` +7. 然后您就可以在 Gitee 上发起新的 PR (Pull Request)。 + +> 注意!!! PR 的标题需要符合我们的规范,并且在 PR 中写上必要的说明,来方便 Committer 和其他贡献者进行代码审查。等待PR代码被合并 + +> 在提交了 PR 后,Committer 或者社区的小伙伴们会对您提交的代码进行审查(Code Review),会提出一些修改建议,或者是进行一些讨论,请及时关注您的PR。 +若后续需要改动,不需要发起一个新的 PR,在原有的分支上提交 commit 并推送到远程仓库后,PR会自动更新。 + + +# 代码被合并后 +在代码被合并后,您就可以在本地和远程仓库删除这个开发分支了: +``` +git branch -d feat-xxx +git push origin --delete feat-xxx在主分支上, +``` +您可以执行以下操作来同步上游仓库: +``` +git remote add upstream https://gitee.com/zhuangpengli/FastBee.git +#Bind the remote warehouse, if it has been executed, it does not need to be executed againgit checkout master git pull upstream master +``` \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..eeff60a5 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,50 @@ +FROM ubuntu:20.04 as build + + +RUN export DEBIAN_FRONTEND=noninteractive &&\ + apt-get update && \ + apt-get install -y --no-install-recommends openjdk-8-jre-headless openjdk-8-jdk-headless git maven nodejs npm openssl && \ + mkdir -p /opt/fastbee/java /opt/fastbee/vue + + +RUN cd /home && \ + git clone "https://gitee.com/zhuangpengli/FastBee" && \ + cp /home/FastBee/docker/settings.xml /usr/share/maven/conf/ + +RUN cd /home/FastBee/vue && \ + npm install --registry=https://registry.npmmirror.com && \ + npm run build:prod && \ + cp -rf /home/FastBee/vue/dist/* /opt/fastbee/vue/ + +RUN cd /home/FastBee/springboot && \ + mvn clean package -Dmaven.test.skip=true && \ + cp -rf /home/FastBee/springboot/fastbee-admin/target/fastbee-admin.jar /opt/fastbee/java + + +FROM openjdk:8-jre + +EXPOSE 8080/tcp +EXPOSE 1883/tcp +EXPOSE 8083/tcp +EXPOSE 8888/tcp +EXPOSE 8889/tcp +EXPOSE 5061/udp + +ENV LC_ALL zh_CN.UTF-8 + +COPY --from=build /opt/fastbee/java /opt + +WORKDIR /opt + +CMD ["java", "-jar", "/server.jar"] + +FROM nginx:stable + +EXPOSE 80/tcp +EXPOSE 443/tcp + +ENV LC_ALL zh_CN.UTF-8 + +COPY --from=build /opt/fastbee/vue/* /usr/share/nginx/html +COPY ./data/nginx/ssl/* /usr/share/nginx/ssl +COPY ./data/nginx/nginx.conf /etc/nginx/nginx.conf \ No newline at end of file diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..82eca33f --- /dev/null +++ b/docker/README.md @@ -0,0 +1,39 @@ +## 1.clone本项目 +``` + git clone https://gitee.com/zhuangpengli/fastbee-docker.git + cd fastbee-docker + cp -rf ./data /var +``` +## 2.编译java包 +``` + git clone https://gitee.com/zhuangpengli/FastBee.git + cd FastBee/springboot + # 编译emqx版本 请修改fastbee-admin下面 application.yml + # server: + # broker: + # enabled: false + # openws: false + # 编译netty mqtt版本 保持默认配置 + mvn clean package -Dmaven.test.skip=true + cp ./fastbee-admin/target/fastbee-admin.jar /var/data/java/fastbee-admin.jar +``` + +## 3.打包前端目录 +``` + git clone https://gitee.com/zhuangpengli/FastBee.git + cd FastBee/vue + npm install + npm run build:prod + cp -rf ./dist/* /var/data/nginx/vue +``` + +## 4.启动项目 +``` + cd /var/data + setenforce 0 + chmod 777 -R /var/data + # 使用emqx版本mqtt broker输入该命令: + sudo cp -rf docker-compose-emqx.yml docker-compose.yml + # 使用netty mqtt则使用默认脚本直接启动 + docker-compose up -d +``` diff --git a/docker/data/docker-compose-emqx.yml b/docker/data/docker-compose-emqx.yml new file mode 100644 index 00000000..e33883c3 --- /dev/null +++ b/docker/data/docker-compose-emqx.yml @@ -0,0 +1,117 @@ +version: '2' + +networks: + network: + ipam: + driver: default + config: + - subnet: '177.7.0.0/16' + +services: + redis: + image: redis:7.0.0 + container_name: redis + ports: + - 6379:6379 + privileged: true + networks: + network: + ipv4_address: 177.7.0.10 + volumes: + - /var/data/redis:/usr/local/etc/redis + - /var/data/redis/data:/data + command: [ '-- requirepass fastbee', '-- appendonly yes' ] + + mysql: + image: mysql:5.7 + container_name: mysql + ports: + - 3306:3306 + privileged: true + networks: + network: + ipv4_address: 177.7.0.11 + volumes: + - /var/data/mysql/mysql:/var/lib/mysql + - /var/data/mysql/mysql.cnf:/etc/mysql/conf.d/mysql.cnf + - /var/data/mysql/initdb:/docker-entrypoint-initdb.d + environment: + MYSQL_DATABASE: fastbee + MYSQL_ROOT_PASSWORD: fastbee + command: + [ + 'mysqld', + '--character-set-server=utf8', + '--collation-server=utf8_unicode_ci', + '--default-time-zone=+8:00', + '--lower-case-table-names=1' + ] + + emqx: + image: emqx:5.1 + container_name: emqx + ports: + - 1883:1883 + - 8083:8083 + - 8084:8084 + - 18083:18083 + privileged: true + networks: + network: + ipv4_address: 177.7.0.12 + volumes: + - /etc/localtime:/etc/localtime + - /var/data/emqx/etc/emqx.conf:/opt/emqx/etc/emqx.conf + - /var/data/emqx/etc/acl.conf:/opt/emqx/etc/acl.conf + - /var/data/emqx/etc/log:/opt/emqx/log + - /var/data/emqx/data/:/opt/emqx/data + environment: + SET_CONTAINER_TIMEZONE: "true" + CONTAINER_TIMEZONE: Asia/Shanghai + + java: + image: openjdk:8-jre + container_name: java + ports: + - 8080:8080 + - 8888:8888 + - 8889:8889/udp + - 5061:5061/udp + privileged: true + networks: + network: + ipv4_address: 177.7.0.13 + depends_on: + - emqx + - redis + - mysql + - tdengine + volumes: + - /var/data/java/fastbee-admin.jar:/server.jar + - /var/data/java/libtaos.so:/usr/lib/libtaos.so + - /var/data/java/uploadPath:/uploadPath + - /var/data/java/logs:/logs + - /etc/localtime:/etc/localtime + environment: + TZ: Asia/Shanghai + entrypoint: java -jar /server.jar + + nginx: + image: nginx:stable + container_name: nginx + ports: + - 80:80 + - 443:443 + privileged: true + networks: + network: + ipv4_address: 177.7.0.15 + depends_on: + - java + volumes: + - /var/data/nginx/vue:/usr/share/nginx/html + - /var/data/nginx/h5:/usr/share/nginx/h5 + - /var/data/nginx/www:/usr/share/nginx/www + - /var/data/nginx/ssl:/usr/share/nginx/ssl + - /var/data/nginx/nginx-emqx.conf:/etc/nginx/nginx.conf + - /var/data/nginx:/var/log/nginx diff --git a/docker/data/docker-compose.yml b/docker/data/docker-compose.yml index 8e42ad9c..cdd92e22 100644 --- a/docker/data/docker-compose.yml +++ b/docker/data/docker-compose.yml @@ -17,11 +17,10 @@ services: networks: network: ipv4_address: 177.7.0.10 - restart: unless-stopped volumes: - /var/data/redis:/usr/local/etc/redis - /var/data/redis/data:/data - command: [ '-- requirepass wumei-smart', '-- appendonly yes' ] + command: [ '-- requirepass fastbee', '-- appendonly yes' ] mysql: image: mysql:5.7 @@ -32,14 +31,13 @@ services: networks: network: ipv4_address: 177.7.0.11 - restart: unless-stopped volumes: - /var/data/mysql/mysql:/var/lib/mysql - /var/data/mysql/mysql.cnf:/etc/mysql/conf.d/mysql.cnf - /var/data/mysql/initdb:/docker-entrypoint-initdb.d environment: - MYSQL_DATABASE: wumeismart - MYSQL_ROOT_PASSWORD: wumei-smart + MYSQL_DATABASE: fastbee + MYSQL_ROOT_PASSWORD: fastbee command: [ 'mysqld', @@ -49,48 +47,27 @@ services: '--lower-case-table-names=1' ] - emqx: - image: emqx/emqx:v4.0.0 - container_name: emqx - ports: - - 1883:1883 - - 8081:8081 - - 8083:8083 - - 8883:8883 - - 8084:8084 - - 18083:18083 - privileged: true - networks: - network: - ipv4_address: 177.7.0.12 - restart: unless-stopped - volumes: - - /etc/localtime:/etc/localtime - - /var/data/emqx/conf/emqx_auth_http.conf:/opt/emqx/etc/plugins/emqx_auth_http.conf - - /var/data/emqx/conf/emqx_web_hook.conf:/opt/emqx/etc/plugins/emqx_web_hook.conf - - /var/data/emqx/data/loaded_plugins:/opt/emqx/data/loaded_plugins - environment: - EMQX_ALLOW__ANONYMOUS: "false" - SET_CONTAINER_TIMEZONE: "true" - CONTAINER_TIMEZONE: Asia/Shanghai - java: image: openjdk:8-jre container_name: java ports: - 8080:8080 + - 1883:1883 + - 8083:8083 + - 8888:8888 + - 8889:8889/udp - 5061:5061/udp privileged: true networks: network: - ipv4_address: 177.7.0.13 + ipv4_address: 177.7.0.12 depends_on: - - emqx - redis - mysql - restart: unless-stopped + - tdengine volumes: - - /var/data/java/wumei-admin.jar:/server.jar + - /var/data/java/fastbee-admin.jar:/server.jar + - /var/data/java/libtaos.so:/usr/lib/libtaos.so - /var/data/java/uploadPath:/uploadPath - /var/data/java/logs:/logs - /etc/localtime:/etc/localtime @@ -103,17 +80,20 @@ services: container_name: nginx ports: - 80:80 - - 15060:15060/udp + - 443:443 privileged: true networks: network: - ipv4_address: 177.7.0.14 + ipv4_address: 177.7.0.13 depends_on: - java - restart: unless-stopped volumes: - - /var/data/nginx/html:/usr/share/nginx/html + - /var/data/nginx/vue:/usr/share/nginx/html + - /var/data/nginx/h5:/usr/share/nginx/h5 + - /var/data/nginx/www:/usr/share/nginx/www + - /var/data/nginx/ssl:/usr/share/nginx/ssl - /var/data/nginx/nginx.conf:/etc/nginx/nginx.conf - /var/data/nginx:/var/log/nginx + diff --git a/docker/data/emqx/etc/acl.conf b/docker/data/emqx/etc/acl.conf new file mode 100644 index 00000000..a64287a4 --- /dev/null +++ b/docker/data/emqx/etc/acl.conf @@ -0,0 +1,32 @@ +%%-------------------------------------------------------------------- +%% -type(ipaddr() :: {ipaddr, string()}). +%% +%% -type(ipaddrs() :: {ipaddrs, string()}). +%% +%% -type(username() :: {user | username, string()} | {user | username, {re, regex()}}). +%% +%% -type(clientid() :: {client | clientid, string()} | {client | clientid, {re, regex()}}). +%% +%% -type(who() :: ipaddr() | ipaddrs() |username() | clientid() | +%% {'and', [ipaddr() | ipaddrs()| username() | clientid()]} | +%% {'or', [ipaddr() | ipaddrs()| username() | clientid()]} | +%% all). +%% +%% -type(action() :: subscribe | publish | all). +%% +%% -type(topic_filters() :: string()). +%% +%% -type(topics() :: [topic_filters() | {eq, topic_filters()}]). +%% +%% -type(permission() :: allow | deny). +%% +%% -type(rule() :: {permission(), who(), access(), topics()} | {permission(), all}). +%%-------------------------------------------------------------------- + +{allow, {username, {re, "^dashboard$"}}, subscribe, ["$SYS/#"]}. + +{allow, {ipaddr, "127.0.0.1"}, all, ["$SYS/#", "#"]}. + +{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}. + +{allow, all}. diff --git a/docker/data/emqx/etc/emqx.conf b/docker/data/emqx/etc/emqx.conf new file mode 100644 index 00000000..a15f8c98 --- /dev/null +++ b/docker/data/emqx/etc/emqx.conf @@ -0,0 +1,92 @@ +## NOTE: +## This config file overrides data/configs/cluster.hocon, +## and is merged with environment variables which start with 'EMQX_' prefix. +## +## Config changes made from EMQX dashboard UI, management HTTP API, or CLI +## are stored in data/configs/cluster.hocon. +## To avoid confusion, please do not store the same configs in both files. +## +## See https://docs.emqx.com/en/enterprise/v5.0/configuration/configuration.html +## Configuration full example can be found in emqx.conf.example + +node { + name = "emqx@177.7.0.12" + cookie = "emqxsecretcookie" + data_dir = "data" +} + +cluster { + name = emqxcl + discovery_strategy = manual +} + + +dashboard { + listeners.http { + bind = 18083 + } + default_username = "admin" + default_password = "admin123" +} + +authorization { + deny_action = ignore + no_match = allow + cache = { enable = true } +} + + +## http 认证 +authentication = [ + { + mechanism = password_based + backend = http + enable = true + method = post + url = "http://java:8080/iot/tool/mqtt/authv5" + body { + clientid = "${clientid}" + username = "${username}" + password = "${password}" + peerhost = "${peerhost}" + } + headers { + "Content-Type" = "application/json" + "X-Request-Source" = "EMQX" + } + } +] + +# WebHook(匹配上线和下线规则后触发) +bridges { + webhook.fastbee_hook = + { + enable = true + connect_timeout = 15s + retry_interval = 60s + pool_type = random + pool_size = 8 + enable_pipelining = 100 + max_retries = 2 + request_timeout = 15s + method = post + url = "http://java:8080/iot/tool/mqtt/webhookv5" + body = "{\"clientid\" : \"${clientid}\",\"event\" : \"${event}\",\"peername\" : \"${peername}\"}" + headers = { accept = "application/json" "cache-control" = "no-cache" connection = "keep-alive" "content-type" = "application/json" "keep-alive" = "timeout=5"} + } +} + +# 规则(处理上线和下线) +rule_engine { + ignore_sys_message = true + jq_function_default_timeout = 10s + rules.fastbee_rule = + { + sql = "SELECT * FROM \"t/#\",\"$events/client_connected\", \"$events/client_disconnected\", \"$events/session_subscribed\"" + actions = ["webhook:fastbee_hook"] + enable = true + description = "处理设备上下线和订阅完主题的规则" + } +} + + diff --git a/docker/data/java/libtaos.so b/docker/data/java/libtaos.so new file mode 100644 index 00000000..54eabf39 Binary files /dev/null and b/docker/data/java/libtaos.so differ diff --git a/docker/data/java/放置fastbee-admin.jar包.txt b/docker/data/java/放置fastbee-admin.jar包.txt new file mode 100644 index 00000000..551af7e4 --- /dev/null +++ b/docker/data/java/放置fastbee-admin.jar包.txt @@ -0,0 +1 @@ +springboot打包后的文件 diff --git a/docker/data/mysql/initdb/fastbee2.0.sql b/docker/data/mysql/initdb/fastbee2.0.sql new file mode 100644 index 00000000..0c21e817 --- /dev/null +++ b/docker/data/mysql/initdb/fastbee2.0.sql @@ -0,0 +1,2607 @@ +/* + Navicat Premium Data Transfer + + Source Server : 81.71.97.58 + Source Server Type : MySQL + Source Server Version : 50743 + Source Host : 81.71.97.58:3306 + Source Schema : fastbee + + Target Server Type : MySQL + Target Server Version : 50743 + File Encoding : 65001 + + Date: 26/09/2023 22:22:29 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for gen_table +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table`; +CREATE TABLE `gen_table` ( + `table_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '表名称', + `table_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '表描述', + `sub_table_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联子表的表名', + `sub_table_fk_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '子表关联的外键名', + `class_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '实体类名称', + `tpl_category` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'crud' COMMENT '使用的模板(crud单表操作 tree树表操作)', + `package_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成包路径', + `module_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成模块名', + `business_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成业务名', + `function_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成功能名', + `function_author` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '生成功能作者', + `gen_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '生成代码方式(0zip压缩包 1自定义路径)', + `gen_path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '/' COMMENT '生成路径(不填默认项目路径)', + `options` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '其它生成选项', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`table_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of gen_table +-- ---------------------------- + +-- ---------------------------- +-- Table structure for gen_table_column +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table_column`; +CREATE TABLE `gen_table_column` ( + `column_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `table_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '归属表编号', + `column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列名称', + `column_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列描述', + `column_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '列类型', + `java_type` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JAVA类型', + `java_field` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'JAVA字段名', + `is_pk` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否主键(1是)', + `is_increment` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否自增(1是)', + `is_required` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否必填(1是)', + `is_insert` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否为插入字段(1是)', + `is_edit` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否编辑字段(1是)', + `is_list` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否列表字段(1是)', + `is_query` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否查询字段(1是)', + `query_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'EQ' COMMENT '查询方式(等于、不等于、大于、小于、范围)', + `html_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', + `dict_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', + `sort` int(11) NULL DEFAULT NULL COMMENT '排序', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`column_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表字段' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of gen_table_column +-- ---------------------------- + + +-- ---------------------------- +-- Table structure for iot_category +-- ---------------------------- +DROP TABLE IF EXISTS `iot_category`; +CREATE TABLE `iot_category` ( + `category_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '产品分类ID', + `category_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '产品分类名称', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', + `tenant_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '租户名称', + `is_sys` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否系统通用(0-否,1-是)', + `parent_id` bigint(20) NULL DEFAULT NULL COMMENT '父级ID', + `order_num` int(4) NULL DEFAULT NULL COMMENT '显示顺序', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`category_id`) USING BTREE, + INDEX `iot_category_index_tenant_id`(`tenant_id`) USING BTREE, + INDEX `iot_category_index_parent_id`(`parent_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '产品分类' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_category +-- ---------------------------- +INSERT INTO `iot_category` VALUES (1, '电工照明', 1, 'admin', 1, 0, 1, '0', '', '2022-03-01 11:44:37', '', '2023-04-10 01:12:48', '例如:通断器、开关、插座、窗帘、灯'); +INSERT INTO `iot_category` VALUES (2, '家居安防', 1, 'admin', 1, 0, 2, '0', '', '2021-12-18 14:46:52', '', '2021-12-18 14:49:48', '例如:智能门锁、摄像头、智能窗帘'); +INSERT INTO `iot_category` VALUES (3, '环境电器', 1, 'admin', 1, 0, 3, '0', '', '2021-12-18 14:50:24', '', '2023-04-10 01:12:53', '例如:加湿器、风扇、扫地机器人'); +INSERT INTO `iot_category` VALUES (4, '大家电', 1, 'admin', 1, 0, 4, '0', '', '2021-12-18 14:50:58', '', '2021-12-18 14:52:30', '例如:冰箱、热水器、电视'); +INSERT INTO `iot_category` VALUES (5, '厨房电器', 1, 'admin', 1, 0, 5, '0', '', '2021-12-18 14:51:42', '', '2021-12-18 14:52:35', '例如:油烟机、烤箱、电饭煲'); +INSERT INTO `iot_category` VALUES (6, '个护健康', 1, 'admin', 1, 0, 6, '0', '', '2021-12-18 14:52:15', '', '2021-12-18 14:52:40', '例如:洗衣机、按摩椅'); +INSERT INTO `iot_category` VALUES (7, '其他', 1, 'admin', 1, 0, 7, '0', '', '2021-12-18 14:52:54', '', '2021-12-20 15:04:33', '其他'); + +-- ---------------------------- +-- Table structure for iot_device +-- ---------------------------- +DROP TABLE IF EXISTS `iot_device`; +CREATE TABLE `iot_device` ( + `device_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '设备ID', + `device_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '设备名称', + `product_id` bigint(20) NOT NULL COMMENT '产品ID', + `product_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '产品名称', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `user_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户昵称', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', + `tenant_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '租户名称', + `serial_number` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '设备编号', + `gw_dev_code` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '子设备网关编号', + `firmware_version` float(11, 2) NOT NULL COMMENT '固件版本', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '设备状态(1-未激活,2-禁用,3-在线,4-离线)', + `rssi` tinyint(11) NULL DEFAULT NULL COMMENT '信号强度(\r\n信号极好4格[-55— 0],\r\n信号好3格[-70— -55],\r\n信号一般2格[-85— -70],\r\n信号差1格[-100— -85])', + `is_shadow` tinyint(1) NULL DEFAULT NULL COMMENT '是否启用设备影子(0=禁用,1=启用)', + `location_way` tinyint(1) NULL DEFAULT NULL COMMENT '定位方式(1=ip自动定位,2=设备定位,3=自定义)', + `things_model_value` json NULL COMMENT '物模型值', + `network_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备所在地址', + `network_ip` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备入网IP', + `longitude` double(11, 6) NULL DEFAULT NULL COMMENT '设备经度', + `latitude` double(11, 6) NULL DEFAULT NULL COMMENT '设备纬度', + `active_time` datetime(0) NULL DEFAULT NULL COMMENT '激活时间', + `summary` json NULL COMMENT '设备摘要,格式[{\"name\":\"device\"},{\"chip\":\"esp8266\"}]', + `img_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图片地址', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + `is_simulate` tinyint(1) UNSIGNED ZEROFILL NULL DEFAULT 0 COMMENT '是否是模拟设备', + `slave_id` int(10) NULL DEFAULT NULL COMMENT '从机id', + PRIMARY KEY (`device_id`) USING BTREE, + UNIQUE INDEX `iot_device_index_serial_number`(`serial_number`) USING BTREE, + INDEX `iot_device_index_product_id`(`product_id`) USING BTREE, + INDEX `iot_device_index_tanant_id`(`tenant_id`) USING BTREE, + INDEX `iot_device_index_user_id`(`user_id`) USING BTREE, + INDEX `iot_device_index_create_time`(`create_time`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 141 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '设备' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_device +-- ---------------------------- +INSERT INTO `iot_device` VALUES (108, '★温湿度开关', 41, '★智能开关产品', 1, 'admin', 1, 'admin', 'D1ELV3A5TOJS', NULL, 1.00, 4, -51, 1, 1, '[{\"id\": \"irc\", \"name\": \"射频遥控\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"switch\", \"name\": \"设备开关\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"report_monitor\", \"name\": \"上报数据\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"humidity\", \"name\": \"空气湿度\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"co2\", \"name\": \"二氧化碳\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"light_color\", \"name\": \"灯光色值\", \"value\": \" , , , \", \"shadow\": \" , , , \", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"gear\", \"name\": \"运行档位\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"reset\", \"name\": \"设备重启\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"status\", \"name\": \"上报状态\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"temperature\", \"name\": \"空气温度\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"message\", \"name\": \"屏显消息\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"brightness\", \"name\": \"室内亮度\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}]', ' 本机地址', '127.0.0.1', 113.128512, 23.027759, '2023-02-26 00:00:00', '{\"chip\": \"esp8266\", \"name\": \"wumei-smart\", \"author\": \"kerwincui\", \"create\": \"2022-06-06\", \"version\": 1.6}', NULL, '0', '', '2025-02-25 23:15:56', '', '2023-09-22 15:08:07', NULL, NULL, NULL); +INSERT INTO `iot_device` VALUES (109, '★网关设备', 55, '★网关产品', 1, 'admin', 1, 'admin', 'D1PGLPG58KZ2', NULL, 1.00, 4, -73, 1, 3, '[{\"id\": \"category_gear\", \"name\": \"运行档位\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"switch\", \"name\": \"设备开关\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"category_switch\", \"name\": \"设备开关\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"category_light\", \"ts\": \"2023-09-25 17:56:08.848\", \"name\": \"光照\", \"value\": \"68\", \"shadow\": \"68\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"device_report_monitor\", \"name\": \"上报监测数据\", \"value\": \" , , , , , , \", \"shadow\": \" , , , , , , \", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"light_color\", \"name\": \"灯光色值\", \"value\": \" , , , \", \"shadow\": \" , , , \", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"gear\", \"name\": \"运行档位\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"message\", \"name\": \"屏显消息\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"temperature\", \"ts\": \"2023-09-25 17:56:08.582\", \"name\": \"空气温度\", \"value\": \"23.69\", \"shadow\": \"23.69\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"device_irc\", \"name\": \"射频遥控\", \"value\": \" , , , , , , \", \"shadow\": \" , , , , , , \", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"brightness\", \"ts\": \"2023-09-25 17:56:08.671\", \"name\": \"室内亮度\", \"value\": \"5387\", \"shadow\": \"5387\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"report_monitor\", \"name\": \"上报监测数据\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"device_switch\", \"ts\": \"2023-09-25 17:56:26.188\", \"name\": \"设备开关\", \"value\": \"1,1,1, ,1,1, \", \"shadow\": \"1,1,1, ,1,1, \", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"category_temperature\", \"ts\": \"2023-09-25 17:56:09.203\", \"name\": \"空气温度-只读\", \"value\": \"95\", \"shadow\": \"95\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"device_co2\", \"ts\": \"2023-09-25 17:56:11.229\", \"name\": \"二氧化碳\", \"value\": \"3780,2612,2145,3988,5697, , \", \"shadow\": \"3780,2612,2145,3988,5697, , \", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"device_gear\", \"ts\": \"2023-09-25 17:56:28.066\", \"name\": \"运行档位\", \"value\": \"0,0,0, ,0,0, \", \"shadow\": \"0,0,0, ,0,0, \", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"category_humidity\", \"ts\": \"2023-09-25 17:56:09.025\", \"name\": \"空气湿度\", \"value\": \"90\", \"shadow\": \"90\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"category_report_monitor\", \"name\": \"上报监测数据\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"category_irc\", \"name\": \"射频遥控\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"reset\", \"name\": \"设备重启\", \"value\": \"\", \"shadow\": \"\", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"device_temperature\", \"ts\": \"2023-09-25 17:56:11.45\", \"name\": \"空气温度-只读\", \"value\": \"86,39,4,80,52, , \", \"shadow\": \"86,39,4,80,52, , \", \"isChart\": 0, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}]', '云南省曲靖市 移通', '183.225.206.92', 104.802435, 26.496407, '2023-02-26 00:00:00', '{\"chip\": \"esp8266\", \"name\": \"wumei-smart\", \"author\": \"kerwincui\", \"create\": \"2022-06-06\", \"version\": 1.6}', NULL, '0', '', '2025-02-25 23:17:31', '', '2023-09-25 23:14:52', NULL, NULL, NULL); +INSERT INTO `iot_device` VALUES (140, '¥视频监控', 88, '¥视频监控产品', 1, 'admin', 1, 'admin', '11010200001320000001', NULL, 1.00, 4, 0, 0, 1, NULL, '广东省 移通', '120.231.214.134', NULL, NULL, '2023-04-11 21:14:16', '{\"port\": 5060, \"firmware\": \"V5.7.4\", \"transport\": \"UDP\", \"streammode\": \"UDP\", \"hostaddress\": \"192.168.2.119:5060\", \"manufacturer\": \"Hikvision\"}', NULL, '0', '', '2023-04-11 21:12:35', '', '2023-04-11 22:11:01', NULL, 0, NULL); + +-- ---------------------------- +-- Table structure for iot_device_group +-- ---------------------------- +DROP TABLE IF EXISTS `iot_device_group`; +CREATE TABLE `iot_device_group` ( + `device_id` bigint(20) NOT NULL COMMENT '设备ID', + `group_id` bigint(20) NOT NULL COMMENT '分组ID', + PRIMARY KEY (`device_id`, `group_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '设备分组' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_device_group +-- ---------------------------- + +-- ---------------------------- +-- Table structure for iot_device_job +-- ---------------------------- +DROP TABLE IF EXISTS `iot_device_job`; +CREATE TABLE `iot_device_job` ( + `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID', + `job_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '任务名称', + `job_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名', + `cron_expression` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'cron执行表达式', + `misfire_policy` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '3' COMMENT '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + `concurrent` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '是否并发执行(0允许 1禁止)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1暂停)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注信息', + `device_id` bigint(20) NULL DEFAULT NULL COMMENT '设备ID', + `serial_number` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '设备编号', + `device_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备名称', + `is_advance` tinyint(1) NULL DEFAULT NULL COMMENT '是否详细corn表达式(1=是,0=否)', + `actions` json NULL COMMENT '执行的动作集合', + `job_type` tinyint(1) NULL DEFAULT NULL COMMENT '任务类型(1=设备定时,2=设备告警,3=场景联动)', + `product_id` bigint(20) NULL DEFAULT NULL COMMENT '产品ID', + `product_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '产品名称', + `scene_id` bigint(20) NULL DEFAULT NULL COMMENT '场景联动ID', + `alert_id` bigint(20) NULL DEFAULT NULL COMMENT '告警ID', + `alert_trigger` json NULL COMMENT '定时告警触发器', + PRIMARY KEY (`job_id`, `job_name`, `job_group`) USING BTREE, + INDEX `iot_device_job_index_device_id`(`device_id`) USING BTREE, + INDEX `iot_device_job_index_product_id`(`product_id`) USING BTREE, + INDEX `iot_device_job_index_scene_id`(`scene_id`) USING BTREE, + INDEX `iot_device_job_index_alert_id`(`alert_id`) USING BTREE, + INDEX `iot_device_job_index_serial_number`(`serial_number`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备定时' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_device_job +-- ---------------------------- +INSERT INTO `iot_device_job` VALUES (4, 'P', 'DEFAULT', '0 08 11 ? * 1,2,3,4,5,6,7', '2', '1', '0', 'admin', '2023-04-15 11:08:37', '', NULL, '', 108, 'D1ELV3A5TOJS', '★温湿度开关', 0, '[{\"id\": \"gear\", \"name\": \"运行档位\", \"type\": 2, \"value\": \"2\", \"deviceId\": 108, \"deviceName\": \"★温湿度开关\"}]', 1, 41, '★智能开关产品', NULL, NULL, NULL); +INSERT INTO `iot_device_job` VALUES (5, '告警定时触发', 'DEFAULT', '0 13 11 ? * 1,2,3,4,5,6,7', '2', '1', '0', '', '2023-04-15 11:14:06', '', NULL, '', NULL, NULL, '告警定时触发', 0, '[{\"id\": \"gear\", \"name\": \"运行档位\", \"type\": 2, \"value\": \"1\", \"productId\": 96, \"productName\": \"★网关产品\"}]', 2, 96, '★网关产品', NULL, 50, '{\"id\": \"temperature\", \"name\": \"空气温度\", \"type\": 1, \"jobId\": 0, \"value\": \"1\", \"params\": {}, \"source\": 2, \"status\": 1, \"alertId\": 50, \"operator\": \"=\", \"isAdvance\": 0, \"productId\": 96, \"productName\": \"★网关产品\", \"cronExpression\": \"0 13 11 ? * 1,2,3,4,5,6,7\"}'); + +-- ---------------------------- +-- Table structure for iot_device_log +-- ---------------------------- +DROP TABLE IF EXISTS `iot_device_log`; +CREATE TABLE `iot_device_log` ( + `log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '设备监测信息ID', + `identity` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '标识符', + `model_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '物模型名称', + `log_type` tinyint(1) NOT NULL COMMENT '类型(1=属性上报,2=调用功能,3=事件上报,4=设备升级,5=设备上线,6=设备离线)', + `log_value` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '日志值', + `device_id` bigint(20) NULL DEFAULT NULL COMMENT '设备ID', + `device_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '设备名称', + `serial_number` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '设备编号', + `is_monitor` tinyint(1) UNSIGNED ZEROFILL NOT NULL DEFAULT 0 COMMENT '是否监测数据(1=是,0=否)', + `mode` tinyint(1) UNSIGNED ZEROFILL NOT NULL DEFAULT 0 COMMENT '模式(1=影子模式,2=在线模式,3=其他)', + `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID', + `user_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '用户昵称', + `tenant_id` bigint(20) NULL DEFAULT NULL COMMENT '租户ID', + `tenant_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '租户名称', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `remark` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`log_id`) USING BTREE, + INDEX `iot_device_log_index_serial_number`(`serial_number`) USING BTREE, + INDEX `iot_device_log_index_tenant_id`(`tenant_id`) USING BTREE, + INDEX `iot_device_log_index_user_id`(`user_id`) USING BTREE, + INDEX `iot_device_log_index_device_id`(`device_id`) USING BTREE, + INDEX `index_serialNumber_createTime`(`serial_number`, `create_time`) USING BTREE, + INDEX `index_isMonitor_serialNumber_createTime`(`serial_number`, `is_monitor`, `create_time`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '设备日志' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_device_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for iot_device_template +-- ---------------------------- +DROP TABLE IF EXISTS `iot_device_template`; +CREATE TABLE `iot_device_template` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id', + `product_id` bigint(20) NULL DEFAULT NULL COMMENT '产品id', + `template_id` bigint(20) NULL DEFAULT NULL COMMENT '采集点模板id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 40 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '设备采集点模板关联对象' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_device_template +-- ---------------------------- +INSERT INTO `iot_device_template` VALUES (2, 112, 1); +INSERT INTO `iot_device_template` VALUES (3, 118, 4); +INSERT INTO `iot_device_template` VALUES (4, 120, 6); +INSERT INTO `iot_device_template` VALUES (5, 121, 1); +INSERT INTO `iot_device_template` VALUES (7, 123, 11); +INSERT INTO `iot_device_template` VALUES (33, 119, 2); +INSERT INTO `iot_device_template` VALUES (34, 121, 3); +INSERT INTO `iot_device_template` VALUES (35, 122, 3); +INSERT INTO `iot_device_template` VALUES (36, 125, 6); +INSERT INTO `iot_device_template` VALUES (38, 127, 7); +INSERT INTO `iot_device_template` VALUES (39, 128, 1); + +-- ---------------------------- +-- Table structure for iot_device_user +-- ---------------------------- +DROP TABLE IF EXISTS `iot_device_user`; +CREATE TABLE `iot_device_user` ( + `device_id` bigint(20) NOT NULL COMMENT '设备ID', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', + `tenant_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '租户名称', + `device_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '设备名称', + `phonenumber` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号码', + `user_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户昵称', + `is_owner` tinyint(11) NOT NULL COMMENT '是否为设备所有者(0=否,1=是)', + `perms` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户物模型权限,多个以英文逗号分隔', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`device_id`, `user_id`) USING BTREE, + INDEX `iot_device_user_index_user_id`(`user_id`) USING BTREE, + INDEX `iot_device_user_index_tenant_id`(`tenant_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '设备用户' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_device_user +-- ---------------------------- +INSERT INTO `iot_device_user` VALUES (108, 1, 1, 'admin', '★温湿度开关', '15888888888', 'admin', 1, NULL, '0', '', '2023-02-25 23:15:57', '', NULL, NULL); +INSERT INTO `iot_device_user` VALUES (109, 1, 1, 'admin', '★网关设备', '15888888888', 'admin', 1, NULL, '0', '', '2023-02-25 23:17:32', '', NULL, NULL); +INSERT INTO `iot_device_user` VALUES (109, 3, 1, 'admin', '★网关设备', '15888888881', 'fastbee-t2', 0, 'ota,timer,log,monitor,statistic,reset,gear,switch', '0', '', '2023-09-03 01:17:03', '', '2023-09-03 11:05:06', NULL); +INSERT INTO `iot_device_user` VALUES (109, 7, 1, 'admin', '★网关设备', '18257292958', 'shenzehui', 0, NULL, '0', '', '2023-08-24 08:26:34', '', NULL, NULL); +INSERT INTO `iot_device_user` VALUES (109, 8, 1, 'admin', '★网关设备', '15752221201', 'shadow', 0, NULL, '0', '', '2023-08-24 08:25:44', '', NULL, NULL); +INSERT INTO `iot_device_user` VALUES (118, 1, 1, 'admin', '¥MODBUS网关设备', '15888888888', 'admin', 1, NULL, '0', '', '2023-02-28 16:49:18', '', NULL, NULL); +INSERT INTO `iot_device_user` VALUES (140, 1, 1, 'admin', '¥视频监控', '15888888888', 'admin', 1, NULL, '0', '', '2023-04-11 21:12:37', '', NULL, NULL); + +-- ---------------------------- +-- Table structure for iot_event_log +-- ---------------------------- +DROP TABLE IF EXISTS `iot_event_log`; +CREATE TABLE `iot_event_log` ( + `log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '设备事件日志ID', + `identity` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '标识符', + `model_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '物模型名称', + `log_type` tinyint(1) NOT NULL COMMENT '类型(3=事件上报,5=设备上线,6=设备离线)', + `log_value` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '日志值', + `device_id` bigint(20) NULL DEFAULT NULL COMMENT '设备ID', + `device_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '设备名称', + `serial_number` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '设备编号', + `is_monitor` tinyint(1) UNSIGNED ZEROFILL NOT NULL DEFAULT 0 COMMENT '是否监测数据(1=是,0=否)', + `mode` tinyint(1) UNSIGNED ZEROFILL NOT NULL DEFAULT 0 COMMENT '模式(1=影子模式,2=在线模式,3=其他)', + `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID', + `user_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '用户昵称', + `tenant_id` bigint(20) NULL DEFAULT NULL COMMENT '租户ID', + `tenant_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '租户名称', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `remark` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`log_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '事件日志' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_event_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for iot_function_log +-- ---------------------------- +DROP TABLE IF EXISTS `iot_function_log`; +CREATE TABLE `iot_function_log` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '设备功能日志ID', + `identify` varchar(64) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '标识符', + `fun_type` int(2) NOT NULL COMMENT '1==服务下发,2=属性获取,3.OTA升级', + `fun_value` varchar(64) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '日志值', + `message_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '消息id', + `device_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '设备名称', + `serial_number` varchar(64) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '设备编号', + `mode` int(2) NULL DEFAULT NULL COMMENT '模式(1=影子模式,2=在线模式,3=其他)', + `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户id', + `result_msg` varchar(128) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '下发结果描述', + `result_code` int(3) NULL DEFAULT NULL COMMENT '下发结果代码', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `remark` varchar(128) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `show_value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '显示值', + `model_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '物模型名称', + `reply_time` datetime(0) NULL DEFAULT NULL COMMENT '设备回复时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `iot_function_log_id_uindex`(`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '设备服务下发日志' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_function_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for iot_group +-- ---------------------------- +DROP TABLE IF EXISTS `iot_group`; +CREATE TABLE `iot_group` ( + `group_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分组ID', + `group_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '分组名称', + `group_order` tinyint(11) NOT NULL DEFAULT 0 COMMENT '分组排序', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `user_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户昵称', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`group_id`) USING BTREE, + INDEX `iot_group_index_user_id`(`user_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '设备分组' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_group +-- ---------------------------- +INSERT INTO `iot_group` VALUES (2, '卧室', 2, 1, 'admin', '0', '', '2021-12-29 13:12:42', '', '2023-04-09 22:37:06', '卧室设备'); +INSERT INTO `iot_group` VALUES (3, '厨房', 3, 1, 'admin', '0', '', '2021-12-29 13:12:59', '', '2021-12-29 13:13:48', '厨房设备'); +INSERT INTO `iot_group` VALUES (4, '书房', 4, 1, 'admin', '0', '', '2021-12-29 13:13:10', '', '2021-12-29 13:13:54', '书房设备'); +INSERT INTO `iot_group` VALUES (5, '卫生间', 5, 1, 'admin', '0', '', '2021-12-29 13:13:18', '', '2021-12-29 13:14:03', '卫生间设备'); +INSERT INTO `iot_group` VALUES (6, '走道', 6, 1, 'admin', '0', '', '2021-12-29 13:13:26', '', '2021-12-29 13:14:11', '走道设备'); + +-- ---------------------------- +-- Table structure for iot_product +-- ---------------------------- +DROP TABLE IF EXISTS `iot_product`; +CREATE TABLE `iot_product` ( + `product_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '产品ID', + `product_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '产品名称', + `protocol_code` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '协议编号', + `category_id` bigint(20) NOT NULL COMMENT '产品分类ID', + `category_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '产品分类名称', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', + `tenant_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '租户名称', + `is_sys` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否系统通用(0-否,1-是)', + `is_authorize` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否启用授权码(0-否,1-是)', + `mqtt_account` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'mqtt账号', + `mqtt_password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'mqtt密码', + `mqtt_secret` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '产品秘钥', + `status` tinyint(1) NULL DEFAULT NULL COMMENT '状态(1-未发布,2-已发布)', + `things_models_json` json NULL COMMENT '物模型JSON(属性、功能、事件)', + `device_type` tinyint(1) NULL DEFAULT 1 COMMENT '设备类型(1-直连设备、2-网关设备、3-监控设备)', + `network_method` tinyint(1) NULL DEFAULT 1 COMMENT '联网方式(1=wifi、2=蜂窝(2G/3G/4G/5G)、3=以太网、4=其他)', + `vertificate_method` tinyint(1) NULL DEFAULT 1 COMMENT '认证方式(1-简单认证、2-加密认证、3-简单+加密)', + `img_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图片地址', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + `transport` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '产品支持的传输协议', + PRIMARY KEY (`product_id`) USING BTREE, + INDEX `iot_product_index_category_id`(`category_id`) USING BTREE, + INDEX `iot_product_index_tenant_id`(`tenant_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 131 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '产品' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_product +-- ---------------------------- +INSERT INTO `iot_product` VALUES (41, '★智能开关产品', 'JSON', 1, '电工照明', 1, 'admin', 1, 0, 'FastBee', 'P47T6OD5IPFWHUM6', 'KX3TSH4Q4OS835DO', 2, '{\"events\": [{\"id\": \"exception\", \"name\": \"设备发生异常\", \"type\": 3, \"order\": 0, \"regId\": \"exception\", \"isChart\": 0, \"datatype\": {\"type\": \"string\", \"maxLength\": 1024}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"height_temperature\", \"name\": \"环境温度过高\", \"type\": 3, \"order\": 0, \"regId\": \"height_temperature\", \"isChart\": 0, \"datatype\": {\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}], \"functions\": [{\"id\": \"report_monitor\", \"name\": \"上报数据\", \"type\": 2, \"order\": 10, \"regId\": \"report_monitor\", \"isChart\": 0, \"datatype\": {\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"message\", \"name\": \"屏显消息\", \"type\": 2, \"order\": 7, \"regId\": \"message\", \"isChart\": 0, \"datatype\": {\"type\": \"string\", \"maxLength\": 1024}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"light_color\", \"name\": \"灯光色值\", \"type\": 2, \"order\": 5, \"regId\": \"light_color\", \"isChart\": 0, \"datatype\": {\"type\": \"array\", \"arrayType\": \"integer\", \"arrayCount\": \"3\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"switch\", \"name\": \"设备开关\", \"type\": 2, \"order\": 9, \"regId\": \"switch\", \"isChart\": 0, \"datatype\": {\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"reset\", \"name\": \"设备重启\", \"type\": 2, \"order\": 6, \"regId\": \"reset\", \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"重启\", \"value\": \"restart\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"irc\", \"name\": \"射频遥控\", \"type\": 2, \"order\": 11, \"regId\": \"irc\", \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"遥控学习\", \"value\": \"FFXX01\"}, {\"text\": \"遥控清码\", \"value\": \"FFXX02\"}, {\"text\": \"打开开关\", \"value\": \"FFXX03\"}, {\"text\": \"关闭开关\", \"value\": \"FFXX04\"}, {\"text\": \"暂停\", \"value\": \"FFXX05\"}, {\"text\": \"锁定\", \"value\": \"FFXX06\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"gear\", \"name\": \"运行档位\", \"type\": 2, \"order\": 8, \"regId\": \"gear\", \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"showWay\": \"select\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"status\", \"name\": \"上报状态\", \"type\": 2, \"order\": 12, \"regId\": \"status\", \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"更新状态\", \"value\": \"update_status\"}]}, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}], \"properties\": [{\"id\": \"co2\", \"name\": \"二氧化碳\", \"type\": 1, \"order\": 2, \"regId\": \"co2\", \"isChart\": 1, \"datatype\": {\"max\": 6000, \"min\": 100, \"step\": 1, \"type\": \"integer\", \"unit\": \"ppm\"}, \"isHistory\": 1, \"isMonitor\": 1, \"isReadonly\": 1}, {\"id\": \"brightness\", \"name\": \"室内亮度\", \"type\": 1, \"order\": 4, \"regId\": \"brightness\", \"isChart\": 1, \"datatype\": {\"max\": 10000, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"cd/m2\"}, \"isHistory\": 1, \"isMonitor\": 1, \"isReadonly\": 1}, {\"id\": \"temperature\", \"name\": \"空气温度\", \"type\": 1, \"order\": 1, \"regId\": \"temperature\", \"isChart\": 1, \"datatype\": {\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}, \"isHistory\": 1, \"isMonitor\": 1, \"isReadonly\": 1}, {\"id\": \"humidity\", \"name\": \"空气湿度\", \"type\": 1, \"order\": 3, \"regId\": \"humidity\", \"isChart\": 1, \"datatype\": {\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"%\"}, \"isHistory\": 1, \"isMonitor\": 1, \"isReadonly\": 1}]}', 1, 1, 3, NULL, '0', '', '2025-08-14 00:06:33', '', '2023-09-25 22:58:17', NULL, 'MQTT'); +INSERT INTO `iot_product` VALUES (55, '★网关产品', 'JSON', 1, '电工照明', 1, 'admin', 1, 0, 'FastBee', 'P467433O1MT8MXS2', 'KWF32S3H95LH14LO', 2, '{\"events\": [{\"id\": \"exception\", \"name\": \"设备发生异常\", \"type\": 3, \"order\": 0, \"regId\": \"exception\", \"isChart\": 0, \"datatype\": {\"type\": \"string\", \"maxLength\": 1024}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"height_temperature\", \"name\": \"环境温度过高\", \"type\": 3, \"order\": 0, \"regId\": \"height_temperature\", \"isChart\": 0, \"datatype\": {\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}], \"functions\": [{\"id\": \"report_monitor\", \"name\": \"上报监测数据\", \"type\": 2, \"order\": 11, \"regId\": \"report_monitor\", \"isChart\": 0, \"datatype\": {\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"reset\", \"name\": \"设备重启\", \"type\": 2, \"order\": 0, \"regId\": \"reset\", \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"重启\", \"value\": \"restart\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"message\", \"name\": \"屏显消息\", \"type\": 2, \"order\": 0, \"regId\": \"message\", \"isChart\": 0, \"datatype\": {\"type\": \"string\", \"maxLength\": 1024}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"light_color\", \"name\": \"灯光色值\", \"type\": 2, \"order\": 0, \"regId\": \"light_color\", \"isChart\": 0, \"datatype\": {\"type\": \"array\", \"arrayType\": \"integer\", \"arrayCount\": \"3\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"gear\", \"name\": \"运行档位\", \"type\": 2, \"order\": 7, \"regId\": \"gear\", \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"showWay\": \"select\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"switch\", \"name\": \"设备开关\", \"type\": 2, \"order\": 8, \"regId\": \"switch\", \"isChart\": 0, \"datatype\": {\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}], \"properties\": [{\"id\": \"brightness\", \"name\": \"室内亮度\", \"type\": 1, \"order\": 0, \"regId\": \"brightness\", \"isChart\": 1, \"datatype\": {\"max\": 10000, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"cd/m2\"}, \"isHistory\": 1, \"isMonitor\": 1, \"isReadonly\": 1}, {\"id\": \"temperature\", \"name\": \"空气温度\", \"type\": 1, \"order\": 0, \"regId\": \"temperature\", \"isChart\": 1, \"datatype\": {\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}, \"isHistory\": 1, \"isMonitor\": 1, \"isReadonly\": 1}, {\"id\": \"category\", \"name\": \"功能分组\", \"type\": 1, \"order\": 9, \"regId\": \"category\", \"isChart\": 0, \"datatype\": {\"type\": \"object\", \"params\": [{\"id\": \"category_light\", \"name\": \"光照\", \"order\": 1, \"isChart\": 1, \"datatype\": {\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"decimal\", \"unit\": \"mm\"}, \"isHistory\": 1, \"isMonitor\": 1, \"isReadonly\": 1}, {\"id\": \"category_humidity\", \"name\": \"空气湿度\", \"order\": 2, \"isChart\": 1, \"datatype\": {\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"%\"}, \"isHistory\": 0, \"isMonitor\": 1, \"isReadonly\": 1}, {\"id\": \"category_temperature\", \"name\": \"空气温度-只读\", \"order\": 3, \"isChart\": 0, \"datatype\": {\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 1}, {\"id\": \"category_report_monitor\", \"name\": \"上报监测数据\", \"order\": 7, \"isChart\": 0, \"datatype\": {\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}, {\"id\": \"category_gear\", \"name\": \"运行档位\", \"order\": 5, \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}, {\"id\": \"category_switch\", \"name\": \"设备开关\", \"order\": 4, \"isChart\": 0, \"datatype\": {\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}, {\"id\": \"category_irc\", \"name\": \"射频遥控\", \"order\": 6, \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"遥控配对\", \"value\": \"FFXX01\"}, {\"text\": \"遥控清码\", \"value\": \"FFXX02\"}, {\"text\": \"打开开关\", \"value\": \"FFXX03\"}, {\"text\": \"关闭开关\", \"value\": \"FFXX04\"}, {\"text\": \"暂停\", \"value\": \"FFXX05\"}, {\"text\": \"锁定\", \"value\": \"FFXX06\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}]}, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"device\", \"name\": \"子设备\", \"type\": 1, \"order\": 10, \"regId\": \"device\", \"isChart\": 0, \"datatype\": {\"type\": \"array\", \"params\": [{\"id\": \"device_co2\", \"name\": \"二氧化碳\", \"order\": 0, \"isChart\": 1, \"datatype\": {\"max\": 6000, \"min\": 100, \"step\": 1, \"type\": \"integer\", \"unit\": \"ppm\", \"enumList\": [{\"text\": \"\", \"value\": \"\"}], \"arrayType\": \"int\"}, \"isHistory\": 1, \"isMonitor\": 1, \"isReadonly\": 1, \"isSharePerm\": 0}, {\"id\": \"device_temperature\", \"name\": \"空气温度-只读\", \"order\": 4, \"datatype\": {\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 1}, {\"id\": \"device_gear\", \"name\": \"运行档位\", \"order\": 6, \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}, {\"id\": \"device_switch\", \"name\": \"设备开关\", \"order\": 5, \"isChart\": 0, \"datatype\": {\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}, {\"id\": \"device_report_monitor\", \"name\": \"上报监测数据\", \"order\": 9, \"isChart\": 0, \"datatype\": {\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}, {\"id\": \"device_irc\", \"name\": \"射频遥控\", \"order\": 1, \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"遥控学习\", \"value\": \"FFXX01\"}, {\"text\": \"遥控清码\", \"value\": \"FFXX02\"}, {\"text\": \"打开开关\", \"value\": \"FFXX03\"}, {\"text\": \"关闭开关\", \"value\": \"FFXX04\"}, {\"text\": \"暂停\", \"value\": \"FFXX05\"}, {\"text\": \"锁定\", \"value\": \"FFXX06\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}], \"arrayType\": \"object\", \"arrayCount\": \"5\"}, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}]}', 2, 2, 3, NULL, '0', '', '2025-02-25 22:51:39', '', '2023-09-16 11:46:43', NULL, 'MQTT'); +INSERT INTO `iot_product` VALUES (88, '¥视频监控产品', NULL, 2, '家居安防', 1, 'admin', 1, 0, 'FastBee', 'P0IB9M8A7J4R056V', 'K69914VL8175ZY21', 2, '{}', 3, 1, 3, NULL, '0', '', '2023-04-11 21:11:54', '', NULL, NULL, 'GB28181'); + +-- ---------------------------- +-- Table structure for iot_product_authorize +-- ---------------------------- +DROP TABLE IF EXISTS `iot_product_authorize`; +CREATE TABLE `iot_product_authorize` ( + `authorize_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '授权码ID', + `authorize_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '授权码', + `product_id` bigint(20) NOT NULL COMMENT '产品ID', + `device_id` bigint(20) NULL DEFAULT NULL COMMENT '设备ID', + `serial_number` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备编号', + `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID', + `user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名称', + `status` tinyint(1) NULL DEFAULT NULL COMMENT '状态(1-未使用,2-使用中)', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NOT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`authorize_id`) USING BTREE, + INDEX `iot_product_authorize_index_product_id`(`product_id`) USING BTREE, + INDEX `iot_product_authorize_index_device_id`(`device_id`) USING BTREE, + INDEX `iot_product_authorize_index_serial_number`(`serial_number`) USING BTREE, + INDEX `iot_product_authorize_index_user_id`(`user_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '产品授权码表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_product_authorize +-- ---------------------------- + +-- ---------------------------- +-- Table structure for iot_protocol +-- ---------------------------- +DROP TABLE IF EXISTS `iot_protocol`; +CREATE TABLE `iot_protocol` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id', + `protocol_code` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '协议编码', + `protocol_name` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '协议名称', + `protocol_file_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '协议jar包,js包,c程序上传地址', + `protocol_type` int(11) NOT NULL DEFAULT 0 COMMENT '协议类型 0:未知 1:jar,2.js,3.c', + `jar_sign` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '协议文件摘要(文件的md5)', + `create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', + `update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间', + `protocol_status` int(11) NOT NULL DEFAULT 0 COMMENT '0:草稿 1:启用 2:停用', + `del_flag` int(11) NOT NULL DEFAULT 0 COMMENT '0:正常 1:删除', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `UNIQUE_CODE`(`protocol_code`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '协议表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_protocol +-- ---------------------------- +INSERT INTO `iot_protocol` VALUES (1, 'JSON', 'JSON协议', '/', 0, '系统内置JSON编解码协议', '2023-03-01 05:46:43', '2023-04-10 14:42:12', 1, 0); +INSERT INTO `iot_protocol` VALUES (5, '商业版非标协议', '商业版本支持更多非标协议', '', 0, '商业版本支持更多非标协议', '2023-08-23 01:31:39', '2023-08-23 01:31:39', 1, 0); + +-- ---------------------------- +-- Records of iot_simulate_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for iot_social_platform +-- ---------------------------- +DROP TABLE IF EXISTS `iot_social_platform`; +CREATE TABLE `iot_social_platform` ( + `social_platform_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '第三方登录平台主键', + `platform` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '第三方登录平台', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT ' 0:启用 ,1:禁用', + `client_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '第三方平台申请Id', + `secret_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '第三方平台密钥', + `redirect_uri` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户认证后跳转地址', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '删除标记位(0代表存在,1代表删除)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '创建者', + `create_time` datetime(0) NOT NULL COMMENT '创建时间', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `bind_uri` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '绑定注册登录uri,http://localhost/login?bindId=', + `redirect_login_uri` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '跳转登录uri,http://localhost/login?loginId=', + `error_msg_uri` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '错误提示uri,http://localhost/login?errorId=', + PRIMARY KEY (`social_platform_id`) USING BTREE, + UNIQUE INDEX `iot_social_platform_platform_uindex`(`platform`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '第三方登录平台控制' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_social_platform +-- ---------------------------- +INSERT INTO `iot_social_platform` VALUES (1, 'QQ', '0', '102005066', 'PhkaBYgZ99999', 'https://iot.wumei.live/auth/callback/qq', '0', 'admin', '2022-04-18 11:21:28', '2022-04-20 16:29:23', 'admin', NULL, 'http://localhost/login?bindId=', 'http://localhost/login?loginId=', 'http://localhost/login?errorId='); +INSERT INTO `iot_social_platform` VALUES (2, 'wechat_open_web', '0', 'wxd8e66af0d2ac0b9d', 'c1172c438d407d8ebce2ac0e314a18db', 'https://81.71.97.58/prod-api/auth/callback/wechat_open_web', '0', 'admin', '2023-08-23 11:41:37', '2023-09-14 17:16:25', 'admin', NULL, 'https://81.71.97.58/login?bindId=', 'https://81.71.97.58/login?loginId=', 'https://81.71.97.58/login?errorId='); +INSERT INTO `iot_social_platform` VALUES (3, 'wechat_open_mobile', '0', 'wx6be3f0d7bf7154e1', 'b6c1d0da60bd5250857d211cdc64fdc9', 'http://localhost', '0', 'admin', '2023-08-28 14:21:29', NULL, NULL, NULL, 'http://localhost', 'http://localhost', 'http://localhost'); +INSERT INTO `iot_social_platform` VALUES (4, 'wechat_open_mini_program', '0', 'wx5bfbadf52adc17f3', '1faddfc3fa6ab2f9ce937f41fcfc7c52', 'http://localhost', '0', 'admin', '2023-09-12 15:39:48', NULL, NULL, NULL, 'http://localhost', 'http://localhost', 'http://localhost'); + +-- ---------------------------- +-- Table structure for iot_social_user +-- ---------------------------- +DROP TABLE IF EXISTS `iot_social_user`; +CREATE TABLE `iot_social_user` ( + `social_user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '第三方系统用户表主键', + `uuid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '第三方系统的唯一ID', + `source` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '第三方用户来源', + `access_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户的授权令牌', + `expire_in` int(11) NULL DEFAULT NULL COMMENT '第三方用户的授权令牌的有效期(部分平台可能没有)', + `refresh_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '刷新令牌(部分平台可能没有)', + `open_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '第三方用户的 open id(部分平台可能没有)', + `uid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '第三方用户的 ID(部分平台可能没有)', + `access_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '个别平台的授权信息(部分平台可能没有)', + `union_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '第三方用户的 union id(部分平台可能没有)', + `scope` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '第三方用户授予的权限(部分平台可能没有)', + `token_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '个别平台的授权信息(部分平台可能没有)', + `id_token` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'id token(部分平台可能没有)', + `mac_algorithm` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '小米平台用户的附带属性(部分平台可能没有)', + `mac_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '小米平台用户的附带属性(部分平台可能没有)', + `code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户的授权code(部分平台可能没有)', + `oauth_token` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'Twitter平台用户的附带属性(部分平台可能没有)', + `oauth_token_secret` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'Twitter平台用户的附带属性(部分平台可能没有)', + `create_time` datetime(0) NOT NULL COMMENT '创建时间', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '创建者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '删除标记位(0代表存在,2代表删除)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '绑定状态(0:未绑定,1:绑定)', + `sys_user_id` int(11) NULL DEFAULT NULL COMMENT '用户ID', + `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名', + `nickname` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户昵称', + `avatar` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户头像', + `gender` tinyint(4) NULL DEFAULT NULL COMMENT '用户性别', + `source_client` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '第三方用户来源客户端(web、app、小程序)', + PRIMARY KEY (`social_user_id`) USING BTREE, + UNIQUE INDEX `iot_social_user_pk`(`social_user_id`) USING BTREE, + UNIQUE INDEX `iot_social_user_unique_key`(`uuid`, `source`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 211 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '第三方登录用户' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_social_user +-- ---------------------------- +INSERT INTO `iot_social_user` VALUES (1, 'ojefY6BdTpoXOLjdpsFQXWixAG_Q', 'WECHAT_OPEN', '71_7qVQ56lx6qdC7mmArXFwQD8Nl6BTjayw4HJdfHdPoXS0sEHDffiSYa4k8dIK7XG7puk2asZ0s0Rj_Pk8ahqdDQICL4FumjWmXHm3ql2si-M', 7200, '71_rh7a79t0eJmC0JyJrQjABF3zZdkNhP7oAUm3Jj6Rk1skL_i4V3ITlM3ViYO0PA_NCKn9ba85pz2vttdloreR0lWmUxK-VOm3XaMt33vZ9a0', 'ojefY6BdTpoXOLjdpsFQXWixAG_Q', NULL, NULL, 'oL1Fu5x1fapbFrUGWUStT0Vs6f4I', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-08-23 11:48:04', 'System', '2023-09-15 17:23:12', 'System', '1', '1', 8, 'shadow', 'shadow', 'https://thirdwx.qlogo.cn/mmopen/vi_32/dNibaEkibxjJZSffkH5gQKtCg0pqfz39PGbPcQ8IhADianIaEYqibvD2JhrxYLMeQexBGVR6VOl9MR4gtsYiaxEqPFA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (2, 'ojefY6Pny526TwBwsyfUhzBB_szg', 'WECHAT_OPEN', '71_HlBJGUovm8cvZoEljoFkrAbRXtqt3mWNqxEOfMGsse-2Sie51YjkfJQbrSZySyIsf9sYTIwXj7EjbPO5GciN_xqEsSRCzyG6qIvUvkyNIBs', 7200, '71_Bc5n4-MS-25vBkt8p8BAxeuAZBawwmx4ryi-KCJxzi0OKY73HinwKYRTPZaw08kXgpD6zToRAjqIoRuyt-mNwEgfeN50hW8Unk5NuK4Bdpo', 'ojefY6Pny526TwBwsyfUhzBB_szg', NULL, NULL, 'oL1Fu589vTytNQy2okIKQnKBUmRU', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-08-24 08:22:53', 'System', NULL, NULL, '0', '0', NULL, '🌲', '🌲', 'https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLTLicc0w6SgPWibR5Z92j9AdW9aC4QxuFjQcJXcekbjc13fkHD7iaZc7CwEHtUq9FQalub6vOZ46LZA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (3, 'ojefY6AZPO1PPy9K4tWd8xdLWUss', 'WECHAT_OPEN', '71_x8HU8YdqqKMXts7KJ3T0hLOvmlP5YIi0pkTQ9bLA8vRusmEEUQFKyBpbG2UAFRDMEJvpp6cKGh9EGkiRdj7zSA4aHP2r-luXHSkAhP7zyvU', 7200, '71_AFMUcIv8tP4PGiHrOwJVN0B8bpCGyKXuG8ZCDiVGF5zaG10MTTLTmTJXNHJHmZzs3h6X9kbLY8sukNk83uj3QI3_J5SGOtihOUNMa9g7Ir0', 'ojefY6AZPO1PPy9K4tWd8xdLWUss', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-08-24 09:41:51', 'System', '2023-08-24 10:36:16', 'System', '0', '0', NULL, 'oh', 'oh', 'https://thirdwx.qlogo.cn/mmopen/vi_32/RNia2ASTn210r40Tb91yfWgmiaWXGPXF7rNnic5lkes9avGbZQ0365uZObT1JicIQpiba7MDuHicScKUxnYWWyTN5VAw/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (4, 'ojefY6JUjFaO7RBRqgcrRGLxPVFA', 'WECHAT_OPEN', '71_qkQqwgA9RYmL5oQASwBxwwR4loysQQc3YRqGJRJONSxpNrPLlLdNibDk5YINFYjfnCxwnhjVPQqhX7xHaGE_UZMX5e1JaWKCJdrgwP62LxY', 7200, '71_oEKp3JLiEtv5668rprSxidUBbcd30cZQ2Bbt_tL5XdWQX52Yb3po5t5ynFwlA4n-7dLt5rGR3E1FKb9Qw8Xso8SfSfF-4CBF9ZCjSxppIvI', 'ojefY6JUjFaO7RBRqgcrRGLxPVFA', NULL, NULL, 'oL1Fu55Rkr9A69wS6buTQz7zdkDc', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-08-24 09:58:59', 'System', NULL, NULL, '0', '0', NULL, 'shadow 张', 'shadow 张', 'https://thirdwx.qlogo.cn/mmopen/vi_32/EcUsiaR4Y1WkyibJXHDEPAiazbERr1BXAnzZWjh2SiayuawoEaT0icDzL2dZtuu0ia6Z7AJZZbiaxDJb8iaJxTnyk7Xicgw/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (5, 'ojefY6I66aL78LElFNjsA_rY7JIE', 'WECHAT_OPEN', '71_sanZ8NfbnEANzqUTQQAY6CRoafcBaV1eS4KPtcAv_rkOlunJQyVeJJzFbXSge3QxsCvljt65TTpyiLSifSjADJdaSZGtKzhF7IXlo5km2Po', 7200, '71_JbHfJV6zy02mK8ZnCKA3Yyhe2upHOqeah6IeZxzO3CKVAcOqH7CGbsk9GFsK3bqDD1SF8jp05ncC8XfkzR5BS4A3s_QIQjt44bviFckymtE', 'ojefY6I66aL78LElFNjsA_rY7JIE', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-08-24 11:49:31', 'System', '2023-08-24 14:10:29', 'System', '0', '0', NULL, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/tgaqAZ2oTMAZfF4cHRPib77yOLRSv9ibPibQfkQiclB8kwBuicB3vDcLfTnfU6HWZRNRqjmSXjWYYY5fNdOAR8CSxzg/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (6, '71420ce6-5300-4495-92da-6d1a4a7e2fdd', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 09:32:12', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (7, '6ec4d0be-bde5-466b-b3cf-5b3736d15ba7', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 09:40:17', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (8, 'ac2d836d-29fe-4e01-9f3d-bc54d4168855', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 09:45:52', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (9, 'ef2510ab-8fdd-4433-b0ff-1b57ef2f0fa9', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt10T_V0r-s6plNgUfVWrzns', NULL, NULL, 'oU5Yyt_J3cry6qhOzJE1qW-tdiVA', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 09:47:34', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (10, 'b7c2ee2f-d644-46b9-812d-104b1d122fe0', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 09:50:43', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (11, '214e90ab-5b09-4aec-a6d1-4100c21db1b6', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 09:51:42', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (12, 'a23aec75-40df-4878-b33f-a1aacc6b45f6', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 09:53:12', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (13, '37de2b33-e690-40f6-a981-fc7182503606', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:00:02', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (14, '8c1e964e-9bb8-4876-bc92-6a41faaf2097', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:09:12', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (15, 'a7b77ebb-0815-4983-b0ad-b9b2a37593ca', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:09:59', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (16, '0ee69f21-448a-420f-a092-68418ae96c01', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:10:34', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (17, '0c928db4-03b5-40c9-9971-3b13d39ba4b8', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:11:41', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (18, '8f0daa3d-f332-423b-ad37-95e740a109e8', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:12:44', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (19, 'd15e0ae9-f49d-4e55-9965-79e260e8ffd0', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:13:33', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (20, 'b4da93b9-21c7-4e76-937b-2162024a9c6c', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:14:37', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (21, 'a16423aa-99ce-427e-89cf-a9f13955acec', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:15:54', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (22, '7771c72d-741f-4f66-bec4-414471ebb5db', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:16:34', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (23, '9def0641-b670-45cc-8297-6c3171f12025', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:17:36', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (24, 'e8f70c8b-bc52-4b38-aaf7-6a8df9ffb8cd', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:18:23', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (25, 'a359a333-4348-4786-973e-f15b9fa6ce94', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:19:15', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (26, '061154f9-ac4b-4035-a549-cd602422427a', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt10T_V0r-s6plNgUfVWrzns', NULL, NULL, 'oU5Yyt_J3cry6qhOzJE1qW-tdiVA', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:19:55', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (27, '10003d18-0b00-458d-966e-3a54b218c83b', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:21:18', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (28, '376f2a15-662d-4820-bb62-683dd555fdbf', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:24:01', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (29, 'd4e4fe18-1f99-4361-b509-e464fdc806a2', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:24:42', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (30, '7b37261c-b420-45d5-8cd0-7ab6e4787621', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:25:17', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (31, '7635bed2-0d6e-4924-b9d7-af0235fc2ecc', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:25:26', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (32, '6aa3715d-b3a6-4b12-8bb4-1fc3af297d99', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:26:09', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (33, '0222ba55-f5a8-4218-a23d-949ecc78c405', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:26:52', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (34, '0bb2118c-b365-4dae-943e-84e0f8dca104', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:27:50', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (35, '5c065548-d171-44b8-a5bb-b93db08b22b3', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:28:12', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (36, '79429286-c4c7-4db8-9902-6f10ffd26e06', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:28:53', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (37, '4eb61991-8715-427b-b6a1-6e479f3faafc', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:29:44', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (38, 'cbcf9cfb-109f-4806-9bd0-5e9a4ba0c108', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:30:33', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (39, '810f0806-21e0-487b-b2fe-c9d037b20c81', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:31:28', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (40, '7da601d4-765c-40bf-aa2d-96bdef88c9ff', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:32:23', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (41, 'e3e29354-d06b-4d18-b172-a86e563a20f1', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:33:16', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (42, 'c73ece08-7a9e-462f-ad04-e5a5ce05a8b5', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:33:47', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (43, '7b48faf5-b023-46d7-9332-8ead5d82895e', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:34:27', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (44, 'aa8db705-654d-43d9-8ec2-3b090bb9685d', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:35:18', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (45, 'b3f319a4-a4aa-48bd-9a43-ff03b09e9b0f', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:36:18', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (46, '297e229a-545e-47a1-8acd-85708430d78d', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:37:10', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (47, '74d77e94-a9c2-4fca-9b47-e1c7ea0ab419', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:37:45', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (48, '0a750c6d-2e14-4e12-bd11-5ae390b04451', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:57:10', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (49, '6b353153-2eb0-4916-ad83-f5465ccd4480', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 10:57:55', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (50, '10be370a-38e0-4de4-822b-e6b1a07f0ff2', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 11:47:28', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (51, '8634fc78-e8d0-447f-af90-51fd29536ae4', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 15:40:06', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (52, '4e49726d-9078-47de-8752-5fac79a5b9b1', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 15:45:31', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (53, '77e28b94-5f6e-4dda-bc15-a411ce9423e8', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 15:49:07', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (54, '17b87cd8-6a14-4834-9d9e-e4121d563079', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 15:53:20', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (55, 'b2b7ddc7-6e48-4f24-9d03-8c481b6fc165', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 15:57:00', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (56, 'c394c827-3f3f-42c3-88db-b5e3edc5a3e8', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 15:58:51', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (57, '16bf6849-9619-4448-9b0f-1ad4a999e33f', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 15:59:47', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (58, '8f19e5f6-e197-4b81-9585-ea3ea655dd2a', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:01:56', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (59, '647ce6e6-5aa4-4504-80db-fb1df687c0b6', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:03:22', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (60, '04f69ab8-5a84-495c-8dfa-7f19a1a32c63', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:05:03', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (61, '520bf6de-19a5-4b06-b905-6c61a37a1809', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:06:05', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (62, 'e70c584c-5d6f-4cdd-9d63-42606b21941f', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:07:37', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (63, 'd85b3a8e-4917-470c-8412-9c7ccbdd26e6', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:08:13', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (64, '21e06b88-33cd-4ddc-9d3e-22a0cc79d8ab', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:09:43', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (65, '70903b36-f5da-4784-8505-9e0ee7842a7c', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:10:31', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (66, '4300b8fb-4d5a-4c83-b45f-f46de9070789', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:11:57', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (67, 'a83706e9-c606-4393-b46c-bd589102fa23', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:14:25', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (68, '03b705d7-cb9b-4176-ba52-82caee369ee5', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:16:11', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (69, '9c1aa75d-0afe-4d9b-93a4-82ff51476b83', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:18:05', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (70, 'd5153a89-5dca-4ce1-b225-cdf1f06734b3', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:20:23', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (71, '6f0dc304-3697-4d5e-b6f5-dcae95c6037d', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:23:43', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (72, '1a8d1436-b1fc-4648-8ef3-8986b200f609', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:25:12', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (73, '87c74066-e8db-4312-ba9c-8d4a58d65b88', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:26:02', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (74, '7c36eace-b7c5-405f-a2a1-b1fa95d7b526', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:26:36', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (75, '9147f792-55be-4ca6-824d-b8c4a42960b3', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:27:48', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (76, '2183b103-858d-4bb4-9111-83fc8ddf24c1', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:34:12', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (77, '9594b67d-6651-4237-887d-ebadfabc8ca3', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:35:55', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (78, '60649e01-d220-4bbc-a21c-211ce67bd9a7', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:36:29', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (79, '83aa1836-36c5-4e74-aebb-9263843ce8fa', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:36:41', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (80, 'd7dfb927-1f17-41f7-868b-6ee0b8ec2d2f', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:40:14', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (81, '9898d830-f0f3-4dbd-a687-a49709b855de', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:42:14', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (82, 'e7a5b6b0-3e50-4892-b4cc-e628fb803be6', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:43:31', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (83, '34454957-3dfd-4819-8930-46daf47d2f81', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:45:03', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (84, '384b5ce9-5bf7-43b0-950d-496737c3a8dd', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:45:37', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (85, 'dd6de292-124f-44e6-a0fd-057cfeed410c', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:46:25', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (86, 'a370f0b0-fb7f-41cf-a4ed-eafb873cd50b', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:48:31', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (87, 'bfb06fff-b0bb-43a4-8cf0-bf4f4b3445f6', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:50:00', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (88, '53634812-48d2-420a-9e3c-2ac032b5d9cc', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:50:52', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (89, '994f638f-cf08-4846-9630-94bb509ab7db', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:51:27', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (90, '76813df6-4bdd-4add-981e-8e782a3b772a', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:52:16', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (91, 'df0c3f75-a865-4621-988d-ec494e9ea407', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:53:22', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (92, '07f1722d-d9d4-43f7-8f7e-81c6446d7b3f', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:57:11', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (93, '1d91f1b5-a008-446a-ad12-3210e899c55d', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:57:50', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (94, '9b0f8585-4432-401a-9ae1-f363a158a133', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:58:27', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (95, '33b73d87-df3c-41b4-8abc-d2c51bf4657e', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 16:59:41', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (96, '08daa4b5-b5e0-4217-8249-d47928afcf82', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:02:57', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (97, 'a1d11647-2316-4636-85ce-a589084c383a', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:03:27', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (98, 'c8579e89-0858-4ba7-9e00-f1ea2a208be8', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:06:37', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (99, 'd3837059-b25b-4221-9fd0-aed6f958b8a0', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:07:15', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (100, 'a47efa4b-e840-4e07-a710-459fa9f01e5e', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:08:43', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (101, 'dca0f5d4-df0f-4d95-830e-2ae8aa60a550', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:15:11', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (102, 'be678468-abc2-48db-b34d-47e81352bf5c', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:16:49', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (103, 'ae4265e7-e192-46f5-8a9e-53d6ba6aee3e', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:20:34', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (104, '232a0407-b8f0-40b2-bf70-b368aecc3f48', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:22:02', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (105, '09aa4d6e-471a-4890-98f1-85ec1f41d16d', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:23:05', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (106, '6dc0c49c-29a6-4292-bb59-d19d3c7dc11f', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:23:49', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (107, 'aafb9719-a7e3-4c25-be9f-150fbc4e345d', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:25:22', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (108, 'ce799416-f0a8-4941-9f84-ff3bf5a5ae00', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:26:16', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (109, '38ad0b87-c569-4831-84ea-f45bfb1b06e6', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:28:00', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (110, 'a0ee9a9f-6f2f-49a2-80e1-f0976cb115d5', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:28:16', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (111, '42b6c87d-a031-4f7e-8670-09672fd00239', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:29:40', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (112, '39812b97-1a6d-4255-ba0f-86e8e6150bf7', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:29:55', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (113, '38df607a-72e4-45c5-9b9a-fda85114e7a8', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:30:43', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (114, '3d1dbb97-c0d4-4790-9bd1-57d2a26c945e', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:34:02', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (115, 'f9544196-41ca-4f54-926e-2f43bd5482ef', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:34:31', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (116, 'b954f7a1-e544-4ac1-a103-54cfbc9ce982', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:35:13', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (117, '53484323-65ce-4720-91e6-51f81741df2d', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:36:26', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (118, 'cae0d43e-f1d9-427a-9e2e-a276848a9e6a', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:37:04', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (119, '3d1dd8c7-2d3d-46c7-aec0-8aeae75bd53c', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:38:24', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (120, 'd206b8cc-4663-4253-919c-eecbf4bdae2d', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:39:14', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (121, '30f22b8a-39f3-4088-a917-e7b0bb315d96', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:40:18', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (122, 'b58fa0aa-f173-4a25-b245-3eedae47c4bf', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:40:43', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (123, '6de207b2-2f61-465f-8e2d-7115220cfb0a', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:41:10', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (124, 'a2734f2b-41c2-4b5c-a9e7-0301482c2db1', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:42:30', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (125, '0142a300-7a9a-4a09-990e-f9d53363168c', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:43:54', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (126, 'a1207e54-0e14-424a-a485-9a560bce8058', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:49:37', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (127, 'cd2425fc-dab4-4f73-bc61-1b9f43920801', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:51:55', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (128, 'ee27889d-4dbb-4ed0-8184-81d0c56cee06', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:53:38', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (129, 'd85434f8-c11b-438c-890e-8e5732f91a22', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:55:11', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (130, '0cff5da9-3cd4-4363-80be-c3cccb523a95', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:57:34', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (131, '58cf157e-c55c-4cf4-90ee-6f9546ea60de', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:58:23', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (132, '3ad40393-bdc1-4cc5-90cb-c40c45737c07', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 17:59:36', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (133, '268d0dec-0c8c-4017-adf0-c87c7a5705f5', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:03:33', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (134, 'a925f086-1f57-4ffc-bb0b-656e27cac343', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:05:28', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (135, '0d344981-1748-4b0c-bd5e-38db0d9d4903', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:07:31', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (136, '545b520c-d306-4ae9-affa-ead01383de6b', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:07:44', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (137, '2cf65789-0d5a-4a04-a86b-71b4c4632fab', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:08:02', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (138, '69b4eb4c-89e3-4ff1-ba0a-ec181cfa269e', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:11:22', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (139, 'eae37130-5acb-4a7c-a466-867202b5de22', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:12:07', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (140, '74471c0e-fb55-4545-b23a-944f56a01d9e', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:13:54', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (141, '3dc79a1b-6974-4338-a05e-732a08c06d9f', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:15:26', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (142, '1bb4277e-05dc-455a-86f1-a4d7a570a7c9', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:21:19', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (143, 'f6c85ba2-6d99-4509-9fce-e32762a3d9fc', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:25:01', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (144, '99f90484-cd36-4caf-b874-64cdec51c973', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:26:24', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (145, '254a881a-36c0-46a8-89c1-47ec0902cc1c', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:28:43', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (146, '40814010-a24d-448f-9841-ea26f75e3d4f', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:29:17', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (147, 'b9d31023-b415-4a05-88be-e8f6077ba1c1', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-12 18:30:22', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (148, '8d98204f-cc93-4ae7-961a-ffc88f16ad22', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:02:29', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (149, 'cfa57e94-fa7b-4cd7-8b0e-f84c2a32517a', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:03:05', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (150, 'b0742424-3b90-4c92-91b6-da7d0c1042e4', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:04:31', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (151, '92fb0d86-db06-4bd2-88a6-2c5e5659c2dd', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:10:44', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (152, '157c9ab2-260f-4fc9-953c-244e66a416dc', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:14:04', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (153, '58250a3d-9d20-4c06-a161-b89a5cbe2205', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:17:25', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (154, '503ec9f6-9c51-494c-abe6-d2025b6d5de8', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:23:32', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (155, '7da9ee7e-00f1-400c-9e82-aa756d33c2f7', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:25:14', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (156, '576eb582-a62f-4de4-a7c6-5a79d06a317e', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:26:11', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (157, '35fe81bd-a801-411e-a588-83e2073b53d1', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:36:01', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (158, '13cc3148-7818-4aae-b404-5aa5b21bcb43', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:37:12', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (159, 'c667755d-08bf-4575-8763-ece99a8d34ff', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:38:20', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (160, 'e9cb8b8f-0f5a-47f0-ac70-04b75341a2cd', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:40:18', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (161, '128cb9e9-52a8-4ecc-8b4f-14d0377a9bdb', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:41:53', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (162, 'e71e95b5-4d16-45d4-ad99-c719c971ee4f', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:46:18', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (163, 'c22e9388-e8e1-4965-8689-67d614efa611', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:47:29', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (164, 'f3232bc6-a8d0-48c0-a08f-0f7ad79dfaa1', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:48:36', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (165, '0bd9b8c6-bfe6-41f3-80ad-2bad1f56348b', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:51:38', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (166, '4c04dbef-6f2d-48a7-a549-55089038e197', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:53:04', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (167, '2c3b82a0-1cdb-4ccf-80ec-41d49f5c1bcf', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:54:21', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (168, '20d05c1d-c3c2-4f89-8093-44b548165d42', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:55:38', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (169, '6364f00a-44f9-41c6-a462-1edf968fe4e5', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 09:59:29', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (170, '6598c9c5-9e50-432c-aaa2-636e0e8d44f9', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:07:53', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (171, '3909b811-7a5a-46c1-b390-6d21d8857670', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:10:11', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (172, 'd3da6d29-b931-4f25-ba5b-bd091af19ff5', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:21:05', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (173, '11040fcd-6f8b-470b-aedc-1411b36165c7', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:23:34', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (174, 'c3ac6479-d9c2-4205-a257-abefe814a842', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:26:47', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (175, 'f0cf37ba-eeeb-44ea-a870-8bc74c079d5c', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:39:49', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (176, 'babe0cad-db0c-4f07-b478-89f8d89bc197', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:42:21', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (177, 'df2b5aae-e7da-4793-8f42-d7025855208e', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:46:08', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (178, '4b6bd295-aa7c-4549-b82f-fd0d8be4a439', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:49:22', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (179, '2e3ffaa3-d499-46a7-aa1b-b2ae27ef4d32', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:50:20', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (180, '8670c605-1b89-4448-9de3-e4d15a1b0bad', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:52:38', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (181, 'fc90c9b0-c5d1-41b2-be40-1ea9ee2e52f2', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:53:25', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (182, '4cb82fc7-e322-439d-bd49-59e22b4ba8de', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:55:25', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (183, '9d4f9fd7-0564-4128-be27-25030761f141', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 10:58:06', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (184, 'bfe86e54-30e8-4c0c-ae49-497863b73708', 'WECHAT_OPEN_MOBILE', NULL, NULL, NULL, 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 11:00:58', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (185, '89953f9b-30af-446c-9682-27358d8f4d76', 'WECHAT_OPEN_MOBILE', '72_1zj9L8T0iKDEqORM5reOYHBG3nL_5jbvb3TfFjGQqw5jv9-0YmI02SgOto6PZXZ7Y1R0o8nm5EeJc8bR8547MfZMBbICbasV2YDtW0XbfSQ', 7200, '72_0Ae6bzB8bTMtnrrfuHkNDN37ko1GEOTLsbqbE3tbQAXNHNd6n_7-HIie0v_zC_clqDr4mOmJBOtkvjCzVHDWW7OS0pAdoDUovfy2n-Y7WD8', 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 11:01:17', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/NEJsVswPcQnxDy3UBXcVGIpMwvn2FmJ6I1k5DoNZk0UcZwQEm7lCewbdneEEfCeVkVNXOIQOXw8evKBea5MBdA/132', 0, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (186, '09277a77-53a8-4cf9-8396-0442d33166ab', 'WECHAT_OPEN_MOBILE', '72_StpVhV87sy7cSsbkVoOJyxCdtlD3JxcdxG5vO8vgZ1eR7C2mORJdxYynL9SJR__pD_rdt2IHbfcSIH2Fpw5wh-BVMu3TTKeESp6qD4Nl1go', 7200, '72_LbZu1ZoNlOHPoqHVRROZ_eGiaMHwqRE4dy3xa6QxpbvnlV5mfAiyyiI0wfUYcnCyruuudNtMlW50NBZNcAxBPeBu3TYILuhlA3rYzCgaNqo', 'oRrdQt10T_V0r-s6plNgUfVWrzns', NULL, NULL, 'oU5Yyt_J3cry6qhOzJE1qW-tdiVA', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 11:03:08', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, 'oh', 'oh', 'https://thirdwx.qlogo.cn/mmopen/vi_32/5M4DYfvJP2dlsqQVmiclkxeGZrnnMf7hj6ryUR35INtKBicn7Whq0oPCdcJfotYxslSPGH2d8s2exvIicu8FYDXicg/132', 0, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (187, '79aca6b3-0552-4f19-8720-10efa034834f', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-13 14:34:42', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (188, 'e4f08938-b416-4713-8c37-a157d006991f', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-14 12:03:46', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (189, '2392005b-1079-4e6b-9625-a295f5e482ec', 'WECHAT_OPEN_MOBILE', '72_Yg_G7_l9nv0ikzJ6Oe7h1q9bz5oU_cJ3TYUMhKxO2ZKudg1bYfhAYygNt95C_qRy_zqiAZVO8jYvxj89iQaRvl5dgKLJl4gAh2GZus6xYfw', 7200, '72_NWbKK4ucUpHoMmLTKSGxTKfMOau99ebYJoWHJkWtVndGLVB6SmwVSwOlavfbh1KHDKqh4RNw28FWl4SauyBCoicugrLnH528z3RyIqrYF9M', 'oRrdQt4tGXZURgaPWika1a7yRzOU', NULL, NULL, 'oU5YytwWSaDv2FD83GB_Ax1pbpzk', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-14 15:14:17', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/NEJsVswPcQnxDy3UBXcVGIpMwvn2FmJ6I1k5DoNZk0UcZwQEm7lCewbdneEEfCeVkVNXOIQOXw8evKBea5MBdA/132', 0, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (190, '13fec398-b9a0-4a3f-b878-bc892a0104b0', 'WECHAT_OPEN_MINI_PROGRAM', NULL, NULL, NULL, 'o02g45SvGqn2RyGeLcv0-OA3SxuU', NULL, NULL, 'oL1Fu5zL-AW5yb0w8naA7tB61vMo', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-14 15:15:04', 'System', '2023-09-22 09:07:14', NULL, '1', '1', 6, NULL, NULL, NULL, NULL, 'wechat_open_mini_program'); +INSERT INTO `iot_social_user` VALUES (191, '338c3d89-a231-4b57-b5f0-cfe3d1f13850', 'WECHAT_OPEN_MOBILE', '72_4DWd6gR3GQIiXPpuagGrM5eILwEO4SfHWJIDwLMqp8I_2FJpBwdNL-Or9UhQ7T8BNKoV0GZ2lh9FaCHk_7LyABY3lmABBpee6Ok4W_KBX08', 7200, '72_iH1rgWvH35a-0O2aXSFSB9qWyXPF_JcChEpn-WFVeFzI1Scfw_1ZxhD8_tEZY5Wk5OMNCqh9WyOKBtO11pL0bUQIWyzYgoPiJHCA2NCrukw', 'oRrdQt10T_V0r-s6plNgUfVWrzns', NULL, NULL, 'oU5Yyt_J3cry6qhOzJE1qW-tdiVA', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-14 15:34:45', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, 'oh', 'oh', 'https://thirdwx.qlogo.cn/mmopen/vi_32/5M4DYfvJP2dlsqQVmiclkxeGZrnnMf7hj6ryUR35INtKBicn7Whq0oPCdcJfotYxsla7j7Dvh5eWLLNxttY6gyYA/132', 0, 'wechat_open_mobile'); +INSERT INTO `iot_social_user` VALUES (192, 'oyyyv6RrMpgR22_BHD-Ne7TWsVMo', 'WECHAT_OPEN', '72_1RLeJ9QnQYJkiMs87FTBBMoK5h9ISBM3XZtPLveA6IetBtyejnDk5c7f-p0mC1RTHLxMkjC-e9KfX8l2y-0XqCPKav_0R20C-Tzbw95wlt0', 7200, '72_lcw1_ihZeBiAUFq4YPt3gc10tRBDMMhLKEsV-f7fZrZkd8xK5-tKbRXaCZL-Cbf4QkPbxnCPWb4svWODgBKMjUXc7g4W4qEkqgNg-4zllss', 'oyyyv6RrMpgR22_BHD-Ne7TWsVMo', NULL, NULL, 'oL1Fu5x1fapbFrUGWUStT0Vs6f4I', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-14 17:16:47', 'System', '2023-09-15 17:23:12', NULL, '1', '1', 8, 'shadow', 'shadow', 'https://thirdwx.qlogo.cn/mmopen/vi_32/74LllXzhnGtAwmn3AEwrRDYFegYX00yJphUlyk6iaQNYNWnLwSMuZ0JXnXicav8n01D0cgL9ptRrG4GX2NttSNcg/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (193, 'bd40330e-9ff0-4c2b-ba00-ff2c3a9cf9e3', 'wechat_open_web', '72_pg1i2Xl9vrW288PCqZybI56qq3-2Yin1o8nvKFAPCRx9OaS8HxJNHUVb6xuG5j_fQdwMecInJYxUpvTTUlJ659DsFOVYrr4RBCTI0sAEqBU', NULL, '72_v0sNSaJQyj8XUCk1bRPIl0A7TB8n4pN0mC807o7YGbPVXugt7aId7RAyb2hi9gsvd0aX2R4vAW-BnDtCTHPm_Un_AkW-mM8SYc44JuxMhOg', 'oyyyv6RrMpgR22_BHD-Ne7TWsVMo', NULL, NULL, 'oL1Fu5x1fapbFrUGWUStT0Vs6f4I', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-14 17:30:40', 'System', '2023-09-15 17:23:12', 'System', '1', '1', 8, 'shadow', 'shadow', 'https://thirdwx.qlogo.cn/mmopen/vi_32/74LllXzhnGtAwmn3AEwrRDYFegYX00yJphUlyk6iaQNYNWnLwSMuZ0JXnXicav8n01D0cgL9ptRrG4GX2NttSNcg/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (194, 'a8681dc9-f2cc-4316-a6a4-d8d43aa00e19', 'wechat_open_web', '72_44jRuqhWDZYQwgFJMFzJkNRKd3_w0m9npMlL7gv16EoEW2UZy6CbNfy_oSuhqMz28PnUHOJwAyC-Xv--LvNxSePhPu-K-FOajq-1BE10G2o', NULL, '72_wF_NsYq04JgoGpNVvq6EvbZjD7BDDz7XHpGfGA4bT61A_wf3ITbXSFhc57MJUwi_o-HhCfBJ9FnSEBu2nySYkDVTbQ457WZ-ZXDTkwo7OMc', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-14 18:13:09', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (195, '128d79bf-7573-40c6-bbca-ff5fcf2a0a71', 'wechat_open_web', '72_h2CaZanyW6eeDFw4jybBzdqYKIWBovGeEeB29G6WZEPDtZPiXANNrFRLEMmtd_HGLwv8YwqwBinOVTIxtrjwFcue_x_z9YJlgM5pfJFNHJA', NULL, '72_hK0QOqLAqq-5-0sbwA4_Kqs-nweY_HC0LTaprTL8n_nbZD8wwhPkil5b2-0Sk855gQ47NGTwczEAt-pCt8ZyZwkTEX0gtO6kcRp8Ajz2kew', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-14 18:13:47', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (196, '698dbf21-8b29-47fa-9946-19a82d067645', 'wechat_open_web', '72_smafRNdslVfgLL-x0nBV5JZO3jT_r3LJBwYw2HJQYpcGe7KKBSSYtIrqpsvVFY75Z7knhMefGlOf9r6s1sH9sjdmAq5EQAOCx2B5QexT3_U', NULL, '72_ongWXQyEHIhXich6claT-7BzZZJbTAb-0dlr245Ee8GO4I9MFplLxa9P5o8qGmgxWhh3h1HvpyYzCyIJ2xxMaS79GhdOhmv6ZJ0dyJN5tsI', 'oyyyv6RrMpgR22_BHD-Ne7TWsVMo', NULL, NULL, 'oL1Fu5x1fapbFrUGWUStT0Vs6f4I', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-14 18:49:36', 'System', '2023-09-15 17:23:12', 'System', '1', '1', 8, 'shadow', 'shadow', 'https://thirdwx.qlogo.cn/mmopen/vi_32/74LllXzhnGtAwmn3AEwrRDYFegYX00yJphUlyk6iaQNYNWnLwSMuZ0JXnXicav8n01D0cgL9ptRrG4GX2NttSNcg/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (197, 'f0fd123d-b82a-4eb9-a67a-fb81db6b739d', 'wechat_open_web', '72_eexrzOvM96xPvH3Lsl7viBOtnBHFDzFJdIz1d074PJxOhAB9qY0HE-NxnWm67afHT0zphQ4RyVEM1BOohhE32HN_D5yZVcMh24oMrSCPJYg', NULL, '72_W25_phPvOFAfeASpx050O28_pOfgtW0LsnuWYQZI6LIrMeVJnju7FrLTG5xuYG_awhD7crWRzpd-guJs2gZ3ZBVj7bSRtwIgSvgVRfzG0zs', 'oyyyv6RrMpgR22_BHD-Ne7TWsVMo', NULL, NULL, 'oL1Fu5x1fapbFrUGWUStT0Vs6f4I', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-15 11:49:37', 'System', '2023-09-15 17:23:12', 'System', '1', '1', 8, 'shadow', 'shadow', 'https://thirdwx.qlogo.cn/mmopen/vi_32/74LllXzhnGtAwmn3AEwrRDYFegYX00yJphUlyk6iaQNYNWnLwSMuZ0JXnXicav8n01D0cgL9ptRrG4GX2NttSNcg/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (198, '702797d1-04ef-4374-a618-aa2c3370135c', 'wechat_open_web', '72_BqSILWRpNzD35f9vkga9dMSHQp3bftJMa7eBpOXIepLh7zJVdBMUiPghVRz96hvtuCvvY7CryJtM5yuKx0tU4IJer4ZshmLHSw1Fl7jYtlo', NULL, '72_jJ0WZ-4r18IkeIs0YC6FfCS0QtCk9S5N1cqYeCIsKijORm2I1diDUE4zVLzYL5dUfxoVeUdHpYY8EyFBKQiWOzaGSECPPLw2bzQnigQzr-g', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-15 14:27:11', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (199, 'ebe3c9b0-6d17-4e7a-9d4b-a4d127e5f81e', 'wechat_open_web', '72_eahdqYL3gU93PJ1IWa6sIKwL4-XvLvCcMnkLYYI3au_8OQ2ZEwe5YHjnRKVzmXDkEfW_IUwCpSyGBLYaOS_ms4RfSI-TAH_s7lUnh_pwOpQ', NULL, '72_8RcuvSh7R88zZSjHyMfuROguXNJxl28CinaM0DMVnQdlvrLORUkT-ArLdWEY_ukrctsOppiY85xYUtZ8mRKit66-IMJauWckLptvbS6mVtk', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-15 16:14:33', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (200, 'a9865b55-498e-47e5-ab12-bfc028f9401a', 'wechat_open_web', '72_sDs8oTAz4diS5nsjQyO-y2aO2IXDYL0CkVFkHaA5gJDb1YCUqfmG9ejNR0-lsshtYwOOswE5s5hdIMkBXsUt_zuVK5nyTlJ3oi3sVrUEaP4', NULL, '72_Z9mP_wckNOwwmrA0eGB8lUB5CY23FvlLciZBegzfnyg10VW0GOe56QF0uM4rgtTha-kO7Uhasm2FIFdDdGKhriu8t1OYs2PEyYT-GOYauvA', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-15 16:16:30', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (201, '45ef2f02-0498-4a91-9b7e-7ec1fbba1f4f', 'wechat_open_web', '72_pj5qq79ny99dg-nnZztIDdBw6pxQUTOI7GC1-50zdFugMU3JhIUgoWKG_zISSOT63HSkuogl0GTSM7zV-7XaGRZoVvT3ks9e4No7qh4lmcM', NULL, '72_jJaohqyJXjiOai2Y1X2JGxinYVUpGYQhNrNRqRBEGDzY44zuPuNxJSIAvlXbJoZXwi57WfV2FxbLCfeUPCnBzypTR6MyAgnY7UBPzfnkO8E', 'oyyyv6RrMpgR22_BHD-Ne7TWsVMo', NULL, NULL, 'oL1Fu5x1fapbFrUGWUStT0Vs6f4I', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-15 17:22:53', 'System', '2023-09-15 17:23:12', 'System', '1', '1', 8, 'shadow', 'shadow', 'https://thirdwx.qlogo.cn/mmopen/vi_32/74LllXzhnGtAwmn3AEwrRDYFegYX00yJphUlyk6iaQNYNWnLwSMuZ0JXnXicav8n01D0cgL9ptRrG4GX2NttSNcg/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (202, '719938db-a623-427c-b0bb-f93ccc66e12e', 'wechat_open_web', '72_47aVnqUFSV_6B866IIt56eXQ4MCeaON9p6uNXDNd8tM9C1aDLc42MpMvPCh4gUJBr1CPk6W2BjaJdbp53mhvgJQ9mbVTHi3Rgt7S5lYa1uc', NULL, '72_ES4G1cNwL7SR5zxg7wSUtyh1JAMD3iHksu9PhfriYVa7BviNMKTBXq0XA92tRNDGTLbYyjpzXBsihDooqcYziiJjJNJZ8HV573kvdk5Js6g', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-15 18:01:00', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (203, '6c128697-f856-4cec-9b22-9c041e546aa9', 'wechat_open_web', '72_ouu4bb-uHrl6KXeQP_H_nI2cn0GBsiCAq4sJcwK0vEhi4DqPbvInOEpYwX1ljPsrxJ2a6iXf48A8SRDDT0G-jKPhs5qsI7MTUvylc3tIvIA', NULL, '72_8yFIgg2C-J3jomyS19ZQmPsNu_bwxoVe_pOjDQInuid5WH2SLBL5ul8NIXF4kFbBT5S1xDvQ9Wo0yGLgLg4kAwvGRRk2atyA9u85HLXRrCU', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-21 16:40:55', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (204, 'd78d0dfd-ea6a-419c-a95d-916f75ef8645', 'wechat_open_web', '72_In2RD2EUmBPN_cKpj_e_6WhSI0BFkOqOSGMAxPg8frEv3EExLiPv7H7p1VJFBoTXDRMUzxX4uBXJUanI3B38y2IO0T_T21WZWToph0Hqrfk', NULL, '72_ICxLSkfZYijpSu7nJwnOGVjwG0bLkwAlsKxMNYUAJF0O-hkVP44qlnSJjrEXvQ7tpId_-8uygb3EjfSYMBh1h5D-YtjYFTYgj-tQOcponpQ', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-21 16:48:25', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (205, '3057f429-575b-4116-b6c2-2be2ed0daa6a', 'wechat_open_web', '72_8RcuvSh7R88zZSjHyMfuRAU8ESiAbG5qYjyInId2yhXEwIsJ--rVPWI-ZuUTkJzBym4oNf7hFJtvwZWqv1S0H-poe26G0sU_ge92uPjC3J8', NULL, '72_j_d-U4TM9Uc6ZXfG_ckplSQX_umZya-Z-HssZe57QjB0rYvI0LP2nzWusHpAVu352zq7LXTNGd2X239O3wHqlPSJ9dstTukRWWYy3h0nnVQ', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-21 16:50:17', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (206, 'c0013cb3-7862-4477-b178-a1cf885ad278', 'wechat_open_web', '72_VO1uAiIQgD1l49wDcy5oqfq7Qp-e6qX8FDtdvgbGuib3T2JAZZrBC6wKunFue-O5fai49_Y2-Y_0NUldDAgGjFVGJ53FwA9sQH6W9yovX3Y', NULL, '72_ldWlbgKsd3GropvbdrgjzZMFOgKS8i8Cre678hFHSWlrGaQVjPR9WgRZftTWcQHA0vgDQM-wKERD7_LGC1qzKnxEeUPOH1cO_VztQ3ZCvM0', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-21 16:53:02', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (207, '243912df-6515-4821-a2d2-311869eb4b66', 'wechat_open_web', '72_nrEdUC4X_xkmBsNbtePmd4tyBTDyhAzb35Xp0ul9pUfRsMfG8GIVQrIvM-GIJXyvJ1n5BscRwmOKeZxxqfzKiQdq-WjWy0PVmKEJ7YV2p_8', NULL, '72_H4I7KKoI2DSFE3dSVXAzyGxz0OqTXT6d8kMvraXgA4nslgsCM6iLB_7d-aHGOhKjKOQbUAJb8-mOerc9YhfM1oPRb_nW4y1lvswt-QwXrRs', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-21 17:22:13', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (208, 'b14bbe72-eddb-4282-a42b-77281f61f27c', 'wechat_open_web', '72_eahdqYL3gU93PJ1IWa6sIHdTySn-4UB_nLAkQLGDSjY7CTMUE4EOSyKYEFSoZmq9Fs64RdAHCV3PJG4ifk_VlZaFkGopHrytcIScSIfyU6w', NULL, '72_8RcuvSh7R88zZSjHyMfuRKViN1EJKbd_ZCElj0V8epojZFlgv6LJZd_fBKE2hE3q9iUqj-0vPqndpMCdc-FfPUjbvDh4B5w1UtNv78wz1Ak', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-21 17:39:53', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (209, '56f25762-2dcc-4be2-a50e-934eac40e328', 'wechat_open_web', '72_GqYw5ylQK7W9nn_cd8keVairltJ1Fcyp9SsIORxMtkihL1iQecSo5Boclagxl-RXHnmdc7mo82DLkFkSqSTDn0JunfPAtAIyAZl5geONq88', NULL, '72_2PoE8DZBF2qybpGHXPHagdYdPtt3Un3YjomGW7pGF65KKYF8t-qd4scANEK1QguVv1ig6zEDvL7F7iFgNVGkkRTEicSq21-QyZ7jVul6J1Y', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-21 18:31:11', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); +INSERT INTO `iot_social_user` VALUES (210, '3a8b4ac4-8e24-4b23-b850-8fca2d90794b', 'wechat_open_web', '72_jJ0WZ-4r18IkeIs0YC6FfDOthAf9UIUNqALqUbYzBMirZuUh48M4AcVlp0Pjy4KbdA_OA1sWvkL1XWAcLa-KtqM-g-gF8Bz9DBfVUwcKGMU', NULL, '72_qfKdEHN3PH23LcmUsUrXoUmO2EkXUtRStVV_j7RabNi8Kuj8mhZYao9fZYCs_m_djArcug96q0TgX2Nxxnkk8LFOYQ26ODl0fy8ZLW6WW9c', 'oyyyv6arGVpFTY9CsqnSJtorlf-A', NULL, NULL, 'oL1Fu55rzFhAJtwkp2Cyl25PKHu0', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2023-09-22 09:06:39', 'System', '2023-09-22 09:07:14', 'System', '1', '1', 6, '🍎', '🍎', 'https://thirdwx.qlogo.cn/mmopen/vi_32/1MKp7S9bHm212HicjST72CcvmbF1NCcY1SvXskO1vicrlIcuJt6jUBKcSgoOiaGBI2Jr7ic5ZjEpMNMA42joLjFBWA/132', NULL, 'wechat_open_web'); + +-- ---------------------------- +-- Table structure for iot_things_model +-- ---------------------------- +DROP TABLE IF EXISTS `iot_things_model`; +CREATE TABLE `iot_things_model` ( + `model_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '物模型ID', + `model_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '物模型名称', + `product_id` bigint(20) NOT NULL COMMENT '产品ID', + `product_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '产品名称', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', + `tenant_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '租户名称', + `identifier` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '标识符,产品下唯一', + `type` tinyint(1) NOT NULL COMMENT '模型类别(1-属性,2-功能,3-事件)', + `datatype` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '数据类型(integer、decimal、string、bool、array、enum)', + `specs` json NULL COMMENT '数据定义', + `is_chart` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否图表展示(0-否,1-是)', + `is_monitor` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否实时监测(0-否,1-是)', + `is_history` tinyint(1) NULL DEFAULT 0 COMMENT '是否历史存储(0-否,1-是)', + `is_readonly` tinyint(1) NULL DEFAULT 0 COMMENT '是否只读数据(0-否,1-是)', + `is_share_perm` tinyint(1) NULL DEFAULT 0 COMMENT '是否设备分享权限(0-否,1-是)', + `model_order` int(10) NULL DEFAULT 0 COMMENT '排序,值越大,排序越靠前', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + `temp_slave_id` bigint(20) NULL DEFAULT NULL COMMENT '从机id', + `formula` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '计算公式', + `reverse_formula` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '控制公式', + `reg_addr` int(255) NULL DEFAULT NULL COMMENT '寄存器地址值', + `bit_option` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '位定义选项', + `value_type` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '解析类型 1.数值 2.选项', + `is_params` int(1) NULL DEFAULT NULL COMMENT '是否是计算参数', + `quantity` int(2) NULL DEFAULT NULL COMMENT '读取寄存器数量', + `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'modbus功能码', + `parse_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'modbus解析类型', + PRIMARY KEY (`model_id`) USING BTREE, + INDEX `iot_things_model_index_product_id`(`product_id`) USING BTREE, + INDEX `iot_things_model_index_tenant_id`(`tenant_id`) USING BTREE, + INDEX `iot_things_model_index_model_order`(`model_order`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 500 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '物模型' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_things_model +-- ---------------------------- +INSERT INTO `iot_things_model` VALUES (113, '设备开关', 41, '温湿度智能开关', 1, 'admin', 'switch', 2, 'bool', '{\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}', 0, 0, 1, 0, 0, 9, '0', NULL, '2022-08-14 00:06:53', '', '2023-03-31 23:43:43', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (125, '空气温度', 41, '温湿度智能开关', 1, 'admin', 'temperature', 1, 'decimal', '{\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}', 1, 1, 1, 1, 0, 1, '0', NULL, '2022-11-05 23:56:21', '', '2023-03-31 23:44:21', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (126, '空气湿度', 41, '温湿度智能开关', 1, 'admin', 'humidity', 1, 'decimal', '{\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"%\"}', 1, 1, 1, 1, 0, 3, '0', NULL, '2022-11-05 23:56:21', '', '2023-03-31 23:44:12', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (127, '二氧化碳', 41, '温湿度智能开关', 1, 'admin', 'co2', 1, 'integer', '{\"max\": 6000, \"min\": 100, \"step\": 1, \"type\": \"integer\", \"unit\": \"ppm\"}', 1, 1, 1, 1, 0, 2, '0', NULL, '2022-11-05 23:56:21', '', '2023-03-31 23:44:17', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (128, '室内亮度', 41, '温湿度智能开关', 1, 'admin', 'brightness', 1, 'integer', '{\"max\": 10000, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"cd/m2\"}', 1, 1, 1, 1, 0, 4, '0', NULL, '2022-11-05 23:56:21', '', '2023-03-31 23:44:08', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (129, '运行档位', 41, '温湿度智能开关', 1, 'admin', 'gear', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"select\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}', 0, 0, 1, 0, 0, 8, '0', NULL, '2022-11-05 23:56:21', '', '2023-03-31 23:43:49', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (130, '灯光色值', 41, '温湿度智能开关', 1, 'admin', 'light_color', 2, 'array', '{\"type\": \"array\", \"arrayType\": \"integer\", \"arrayCount\": \"3\"}', 0, 0, 1, 0, 0, 5, '0', NULL, '2022-11-05 23:56:21', '', '2023-09-25 22:57:42', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (131, '屏显消息', 41, '温湿度智能开关', 1, 'admin', 'message', 2, 'string', '{\"type\": \"string\", \"maxLength\": 1024}', 0, 0, 1, 0, 0, 7, '0', NULL, '2022-11-05 23:56:21', '', '2023-03-31 23:43:54', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (132, '上报数据', 41, '温湿度智能开关', 1, 'admin', 'report_monitor', 2, 'integer', '{\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}', 0, 0, 0, 0, 0, 10, '0', NULL, '2022-11-05 23:56:21', '', '2023-03-31 23:43:38', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (134, '环境温度过高', 41, '温湿度智能开关', 1, 'admin', 'height_temperature', 3, 'decimal', '{\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}', 0, 0, 1, 0, 0, 0, '0', NULL, '2022-11-05 23:56:29', '', '2023-03-31 23:44:25', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (135, '设备发生异常', 41, '温湿度智能开关', 1, 'admin', 'exception', 3, 'string', '{\"type\": \"string\", \"maxLength\": 1024}', 0, 0, 1, 0, 0, 0, '0', NULL, '2022-11-05 23:56:29', '', '2023-03-31 23:44:29', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (161, '子设备', 96, '网关产品', 1, 'admin', 'device', 1, 'array', '{\"type\": \"array\", \"params\": [{\"id\": \"device_co2\", \"name\": \"二氧化碳\", \"order\": 0, \"isChart\": 1, \"datatype\": {\"max\": 6000, \"min\": 100, \"step\": 1, \"type\": \"integer\", \"unit\": \"ppm\", \"enumList\": [{\"text\": \"\", \"value\": \"\"}], \"arrayType\": \"int\"}, \"isHistory\": 1, \"isMonitor\": 1, \"isReadonly\": 1, \"isSharePerm\": 0}, {\"id\": \"device_temperature\", \"name\": \"空气温度-只读\", \"order\": 4, \"datatype\": {\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 1}, {\"id\": \"device_gear\", \"name\": \"运行档位\", \"order\": 6, \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}, {\"id\": \"device_switch\", \"name\": \"设备开关\", \"order\": 5, \"isChart\": 0, \"datatype\": {\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}, {\"id\": \"device_report_monitor\", \"name\": \"上报监测数据\", \"order\": 9, \"isChart\": 0, \"datatype\": {\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}, {\"id\": \"device_irc\", \"name\": \"射频遥控\", \"order\": 1, \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"遥控学习\", \"value\": \"FFXX01\"}, {\"text\": \"遥控清码\", \"value\": \"FFXX02\"}, {\"text\": \"打开开关\", \"value\": \"FFXX03\"}, {\"text\": \"关闭开关\", \"value\": \"FFXX04\"}, {\"text\": \"暂停\", \"value\": \"FFXX05\"}, {\"text\": \"锁定\", \"value\": \"FFXX06\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}], \"arrayType\": \"object\", \"arrayCount\": \"5\"}', 0, 0, 0, 0, 1, 10, '0', NULL, '2023-02-25 22:51:53', '', '2023-09-25 23:13:21', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (162, '功能分组', 96, '网关产品', 1, 'admin', 'category', 1, 'object', '{\"type\": \"object\", \"params\": [{\"id\": \"category_light\", \"name\": \"光照\", \"order\": 1, \"isChart\": 1, \"datatype\": {\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"decimal\", \"unit\": \"mm\"}, \"isHistory\": 1, \"isMonitor\": 1, \"isReadonly\": 1}, {\"id\": \"category_humidity\", \"name\": \"空气湿度\", \"order\": 2, \"isChart\": 1, \"datatype\": {\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"%\"}, \"isHistory\": 0, \"isMonitor\": 1, \"isReadonly\": 1}, {\"id\": \"category_temperature\", \"name\": \"空气温度-只读\", \"order\": 3, \"isChart\": 0, \"datatype\": {\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 1}, {\"id\": \"category_report_monitor\", \"name\": \"上报监测数据\", \"order\": 7, \"isChart\": 0, \"datatype\": {\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}, {\"id\": \"category_gear\", \"name\": \"运行档位\", \"order\": 5, \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}, {\"id\": \"category_switch\", \"name\": \"设备开关\", \"order\": 4, \"isChart\": 0, \"datatype\": {\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}, {\"id\": \"category_irc\", \"name\": \"射频遥控\", \"order\": 6, \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"遥控配对\", \"value\": \"FFXX01\"}, {\"text\": \"遥控清码\", \"value\": \"FFXX02\"}, {\"text\": \"打开开关\", \"value\": \"FFXX03\"}, {\"text\": \"关闭开关\", \"value\": \"FFXX04\"}, {\"text\": \"暂停\", \"value\": \"FFXX05\"}, {\"text\": \"锁定\", \"value\": \"FFXX06\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0, \"isSharePerm\": 1}]}', 0, 0, 0, 0, 1, 9, '0', NULL, '2023-02-25 22:51:53', '', '2023-09-03 11:03:24', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (163, '空气温度', 96, '网关产品', 1, 'admin', 'temperature', 1, 'decimal', '{\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}', 1, 1, 1, 1, 0, 0, '0', NULL, '2023-02-25 22:52:16', '', '2023-03-31 16:08:03', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (164, '设备开关', 96, '网关产品', 1, 'admin', 'switch', 2, 'bool', '{\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}', 0, 0, 1, 0, 1, 8, '0', NULL, '2023-02-25 22:52:16', '', '2023-09-03 11:03:30', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (165, '运行档位', 96, '网关产品', 1, 'admin', 'gear', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"select\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}', 0, 0, 1, 0, 1, 7, '0', NULL, '2023-02-25 22:52:16', '', '2023-09-03 11:03:41', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (166, '灯光色值', 96, '网关产品', 1, 'admin', 'light_color', 2, 'array', '{\"type\": \"array\", \"arrayType\": \"integer\", \"arrayCount\": \"3\"}', 0, 0, 1, 0, 0, 0, '0', NULL, '2023-02-25 22:52:16', '', '2023-03-31 16:08:09', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (167, '上报监测数据', 96, '网关产品', 1, 'admin', 'report_monitor', 2, 'integer', '{\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}', 0, 0, 1, 0, 1, 11, '0', NULL, '2023-02-25 22:52:16', '', '2023-09-03 11:03:11', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (168, '环境温度过高', 96, '网关产品', 1, 'admin', 'height_temperature', 3, 'decimal', '{\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}', 0, 0, 1, 0, 0, 0, '0', NULL, '2023-02-25 22:52:16', '', '2023-03-31 16:08:15', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (169, '设备发生异常', 96, '网关产品', 1, 'admin', 'exception', 3, 'string', '{\"type\": \"string\", \"maxLength\": 1024}', 0, 0, 1, 0, 0, 0, '0', NULL, '2023-02-25 22:52:16', '', '2023-03-31 16:08:20', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (170, '屏显消息', 96, '网关产品', 1, 'admin', 'message', 2, 'string', '{\"type\": \"string\", \"maxLength\": 1024}', 0, 0, 1, 0, 1, 0, '0', NULL, '2023-02-25 22:52:35', '', '2023-09-03 11:03:55', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (171, '设备重启', 96, '网关产品', 1, 'admin', 'reset', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"重启\", \"value\": \"restart\"}]}', 0, 0, 1, 0, 1, 0, '0', NULL, '2023-02-25 22:52:35', '', '2023-09-03 11:03:48', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (174, '室内亮度', 96, '网关产品', 1, 'admin', 'brightness', 1, 'integer', '{\"max\": 10000, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"cd/m2\"}', 1, 1, 1, 1, 0, 0, '0', NULL, '2023-02-26 00:56:39', '', '2023-09-03 10:40:55', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (175, '设备重启', 41, '智能开关', 1, 'admin', 'reset', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"重启\", \"value\": \"restart\"}]}', 0, 0, 1, 0, 0, 6, '0', NULL, '2023-02-26 02:20:40', '', '2023-04-01 23:40:05', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (385, '射频遥控', 41, '★智能开关', 1, 'admin', 'irc', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"遥控学习\", \"value\": \"FFXX01\"}, {\"text\": \"遥控清码\", \"value\": \"FFXX02\"}, {\"text\": \"打开开关\", \"value\": \"FFXX03\"}, {\"text\": \"关闭开关\", \"value\": \"FFXX04\"}, {\"text\": \"暂停\", \"value\": \"FFXX05\"}, {\"text\": \"锁定\", \"value\": \"FFXX06\"}]}', 0, 0, 1, 0, 0, 11, '0', 'admin', '2023-03-31 23:46:36', '', '2023-04-13 01:38:48', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (439, '上报状态', 41, '★智能开关产品', 1, 'admin', 'status', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"更新状态\", \"value\": \"update_status\"}]}', 0, 0, 0, 0, 0, 12, '0', 'admin', '2023-04-13 01:39:31', '', '2023-04-13 01:39:42', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model` VALUES (493, '漏水值', 112, '★MODBUS协议产品', 1, 'admin', '0', 1, 'integer', '{\"max\": 100, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"度\"}', 0, 0, 1, 0, 0, 0, '0', '', '2023-09-13 23:33:19', '', NULL, NULL, 1, NULL, NULL, 0, NULL, NULL, NULL, 1, '3', 'ushort'); +INSERT INTO `iot_things_model` VALUES (494, '温度', 112, '★MODBUS协议产品', 1, 'admin', '0', 1, 'integer', '{\"max\": 100, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"°\"}', 0, 0, 1, 0, 0, 0, '0', '', '2023-09-13 23:33:19', '', NULL, NULL, 2, NULL, NULL, 0, NULL, NULL, NULL, 1, '3', 'ushort'); +INSERT INTO `iot_things_model` VALUES (495, '电量', 112, '★MODBUS协议产品', 1, 'admin', '1', 1, 'integer', '{\"max\": 100, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"\"}', 0, 0, 1, 0, 0, 0, '0', '', '2023-09-13 23:33:19', '', NULL, NULL, 11, NULL, NULL, 1, NULL, NULL, NULL, 1, '3', 'ushort'); +INSERT INTO `iot_things_model` VALUES (499, '上报状态', 130, 'TCP测试设备', 1, 'admin', 'status', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"更新状态\", \"value\": \"update_status\"}]}', 0, 0, 0, 0, 1, 0, '0', 'admin', '2023-09-19 11:22:55', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + +-- ---------------------------- +-- Table structure for iot_things_model_template +-- ---------------------------- +DROP TABLE IF EXISTS `iot_things_model_template`; +CREATE TABLE `iot_things_model_template` ( + `template_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '物模型ID', + `template_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '物模型名称', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', + `tenant_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '租户名称', + `identifier` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '标识符,产品下唯一', + `type` tinyint(1) NOT NULL COMMENT '模型类别(1-属性,2-功能,3-事件)', + `datatype` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '数据类型(integer、decimal、string、bool、array、enum)', + `specs` json NULL COMMENT '数据定义', + `is_sys` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否系统通用(0-否,1-是)', + `is_chart` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否图表展示(0-否,1-是)', + `is_monitor` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否实时监测(0-否,1-是)', + `is_history` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否历史存储 (0-否,1-是)', + `is_readonly` tinyint(1) NULL DEFAULT 0 COMMENT '是否只读数据(0-否,1-是)', + `is_share_perm` tinyint(1) NULL DEFAULT 0 COMMENT '是否设备分享权限(0-否,1-是)', + `model_order` int(10) NULL DEFAULT 0 COMMENT '排序,值越大,排序越靠前', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + `temp_slave_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '从机id', + `formula` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '计算公式', + `reverse_formula` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '控制公式', + `reg_addr` int(255) NULL DEFAULT NULL COMMENT '寄存器地址值', + `bit_option` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '位定义选项', + `value_type` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '解析类型 1.数值 2.选项', + `is_params` int(1) UNSIGNED ZEROFILL NULL DEFAULT NULL COMMENT '是否是计算参数,默认否 0=否,1=是', + `quantity` int(2) NULL DEFAULT NULL COMMENT '读取寄存器数量', + `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'modbus功能码', + `old_identifier` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '旧的标识符', + `old_temp_slave_id` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '旧的从机id', + `parse_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'modbus解析类型', + PRIMARY KEY (`template_id`) USING BTREE, + INDEX `iot_things_model_template_index_tenant_id`(`tenant_id`) USING BTREE, + INDEX `iot_things_model_template_index_model_order`(`model_order`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 344 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '物模型模板' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_things_model_template +-- ---------------------------- +INSERT INTO `iot_things_model_template` VALUES (1, '空气温度', 1, 'admin', 'temperature', 1, 'decimal', '{\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}', 1, 1, 1, 1, 1, 0, 4, '0', 'admin', '2022-03-09 17:41:49', 'admin', '2023-04-10 01:12:06', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (2, '空气湿度', 1, 'admin', 'humidity', 1, 'decimal', '{\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"%\"}', 1, 1, 1, 1, 1, 0, 3, '0', 'admin', '2022-03-09 17:41:49', 'admin', '2023-04-10 01:12:02', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (3, '二氧化碳', 1, 'admin', 'co2', 1, 'integer', '{\"max\": 6000, \"min\": 100, \"step\": 1, \"type\": \"integer\", \"unit\": \"ppm\"}', 1, 1, 1, 1, 1, 0, 0, '0', 'admin', '2022-03-09 17:41:49', 'admin', '2023-04-10 01:11:57', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (4, '室内亮度', 1, 'admin', 'brightness', 1, 'integer', '{\"max\": 10000, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"cd/m2\"}', 1, 1, 1, 1, 1, 0, 0, '0', 'admin', '2022-03-09 17:41:49', 'admin', '2023-04-10 01:11:53', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (5, '设备开关', 1, 'admin', 'switch', 2, 'bool', '{\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}', 1, 0, 0, 1, 0, 0, 5, '0', 'admin', '2022-03-09 17:41:49', 'admin', '2023-04-10 01:11:48', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (6, '运行档位', 1, 'admin', 'gear', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"select\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}', 1, 0, 0, 1, 0, 0, 6, '0', 'admin', '2022-03-09 17:41:49', 'admin', '2023-04-10 01:11:43', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (7, '灯光色值', 1, 'admin', 'light_color', 2, 'array', '{\"type\": \"array\", \"arrayType\": \"integer\", \"arrayCount\": \"3\"}', 1, 0, 0, 1, 0, 0, 2, '0', 'admin', '2022-03-09 17:41:49', 'admin', '2023-04-10 01:11:38', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (8, '屏显消息', 1, 'admin', 'message', 2, 'string', '{\"type\": \"string\", \"maxLength\": 1024}', 1, 0, 0, 1, 0, 0, 1, '0', 'admin', '2022-03-09 17:41:49', 'admin', '2023-04-10 01:11:32', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (9, '上报监测数据', 1, 'admin', 'report_monitor', 2, 'integer', '{\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}', 1, 0, 0, 0, 0, 0, 9, '0', 'admin', '2022-03-09 17:41:49', 'admin', '2023-04-10 01:11:25', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (10, '环境温度过高', 1, 'admin', 'height_temperature', 3, 'decimal', '{\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}', 1, 0, 0, 1, 0, 0, 8, '0', 'admin', '2022-03-09 17:41:49', 'admin', '2023-04-10 01:11:19', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (11, '设备发生异常', 1, 'admin', 'exception', 3, 'string', '{\"type\": \"string\", \"maxLength\": 1024}', 1, 0, 0, 1, 0, 0, 7, '0', 'admin', '2022-03-09 17:41:49', 'admin', '2023-04-10 01:11:16', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (12, '光照', 1, 'admin', 'light', 1, 'decimal', '{\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"decimal\", \"unit\": \"mm\"}', 0, 1, 1, 1, 1, 0, 0, '0', 'wumei', '2022-05-07 09:41:17', 'admin', '2023-04-10 01:11:12', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (13, '压力', 1, 'admin', 'pressure', 1, 'decimal', '{\"max\": 200, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"帕斯卡\"}', 1, 1, 1, 1, 1, 0, 0, '0', 'admin', '2023-02-20 22:39:18', 'admin', '2023-04-10 01:11:05', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (14, '设备重启', 1, 'admin', 'reset', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"重启\", \"value\": \"restart\"}]}', 1, 0, 0, 1, 0, 0, 0, '0', 'admin', '2023-02-20 23:15:25', 'admin', '2023-04-10 01:11:08', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (15, '电池电压', 1, 'admin', 'voltage', 1, 'decimal', '{\"max\": 5, \"min\": 0, \"step\": 0.001, \"type\": \"decimal\", \"unit\": \"V\"}', 1, 1, 1, 1, 1, 0, 0, '0', 'admin', '2023-02-20 23:17:43', 'admin', '2023-04-10 01:10:56', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (16, '饮水量', 1, 'admin', 'shuiliang', 1, 'integer', '{\"max\": 500, \"min\": 80, \"step\": 1, \"type\": \"integer\", \"unit\": \"ML\"}', 1, 1, 1, 1, 1, 0, 0, '0', 'admin', '2023-02-20 23:18:39', 'admin', '2023-04-10 01:10:52', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (17, '灯光', 1, 'admin', 'light', 1, 'integer', '{\"max\": 1000, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"Lux\"}', 1, 1, 1, 1, 1, 0, 0, '0', 'admin', '2023-02-20 23:19:23', 'admin', '2023-04-10 01:10:49', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (18, '长度', 1, 'admin', 'length', 1, 'integer', '{\"max\": 2000, \"min\": 1, \"step\": 5, \"type\": \"integer\", \"unit\": \"M\"}', 1, 1, 1, 1, 1, 0, 0, '0', 'admin', '2023-02-20 23:20:03', 'admin', '2023-04-10 01:10:44', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (19, '心率', 1, 'admin', 'heart_rate', 1, 'integer', '{\"max\": 250, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}', 1, 1, 1, 1, 1, 0, 0, '0', 'admin', '2023-02-20 23:21:46', 'admin', '2023-04-10 01:12:40', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (20, '光照强度', 1, 'admin', 'light_level', 1, 'integer', '{\"max\": 89.2, \"min\": 2.5, \"step\": 0.1, \"type\": \"integer\", \"unit\": \"L/g\"}', 1, 1, 1, 1, 1, 0, 0, '0', 'admin', '2023-02-20 23:24:36', 'admin', '2023-04-10 01:10:35', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (21, '状态灯色', 1, 'admin', 'color', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"select\", \"enumList\": [{\"text\": \"红色\", \"value\": \"0\"}, {\"text\": \"绿色\", \"value\": \"1\"}, {\"text\": \"蓝色\", \"value\": \"2\"}, {\"text\": \"黄色\", \"value\": \"3\"}]}', 1, 0, 0, 1, 0, 0, 0, '0', 'admin', '2023-02-20 23:26:24', 'admin', '2023-04-10 01:10:32', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (22, '子设备', 1, 'admin', 'device', 2, 'array', '{\"type\": \"array\", \"params\": [{\"id\": \"device_co2\", \"name\": \"二氧化碳\", \"order\": 0, \"isChart\": 1, \"datatype\": {\"max\": 6000, \"min\": 100, \"step\": 1, \"type\": \"integer\", \"unit\": \"ppm\", \"enumList\": [{\"text\": \"\", \"value\": \"\"}], \"arrayType\": \"int\"}, \"isHistory\": 1, \"isMonitor\": 1, \"isReadonly\": 1}, {\"id\": \"device_temperature\", \"name\": \"空气温度-只读\", \"order\": 4, \"isChart\": 0, \"datatype\": {\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\", \"enumList\": [{\"text\": \"\", \"value\": \"\"}], \"arrayType\": \"int\"}, \"isMonitor\": 0, \"isReadonly\": 1}, {\"id\": \"device_gear\", \"name\": \"运行档位\", \"order\": 6, \"datatype\": {\"type\": \"enum\", \"showWay\": \"select\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"device_switch\", \"name\": \"设备开关\", \"order\": 5, \"datatype\": {\"type\": \"bool\", \"enumList\": [{\"text\": \"\", \"value\": \"\"}], \"trueText\": \"打开\", \"arrayType\": \"int\", \"falseText\": \"关闭\"}, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"device_report_monitor\", \"name\": \"上报监测数据\", \"order\": 9, \"datatype\": {\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\", \"enumList\": [{\"text\": \"\", \"value\": \"\"}], \"arrayType\": \"int\"}, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}], \"arrayType\": \"object\", \"arrayCount\": 5}', 1, 0, 0, 0, 0, 0, 10, '0', 'admin', '2023-02-24 01:10:43', 'admin', '2023-04-13 01:33:38', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (23, '功能分组', 1, 'admin', 'group', 2, 'object', '{\"type\": \"object\", \"params\": [{\"id\": \"group_light\", \"name\": \"光照\", \"order\": 1, \"isChart\": 1, \"datatype\": {\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"decimal\", \"unit\": \"mm\"}, \"isHistory\": 1, \"isMonitor\": 1, \"isReadonly\": 1}, {\"id\": \"group_humidity\", \"name\": \"空气湿度\", \"order\": 2, \"isChart\": 1, \"datatype\": {\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"%\"}, \"isMonitor\": 1, \"isReadonly\": 1}, {\"id\": \"group_temperature\", \"name\": \"空气温度-只读\", \"order\": 3, \"isChart\": 0, \"datatype\": {\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}, \"isMonitor\": 0, \"isReadonly\": 1}, {\"id\": \"group_report_monitor\", \"name\": \"上报监测数据\", \"order\": 7, \"datatype\": {\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}, \"isHistory\": 0, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"group_gear\", \"name\": \"运行档位\", \"order\": 5, \"datatype\": {\"type\": \"enum\", \"showWay\": \"select\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"group_switch\", \"name\": \"设备开关\", \"order\": 4, \"datatype\": {\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}, \"isMonitor\": 0, \"isReadonly\": 0}, {\"id\": \"group_irc\", \"name\": \"红外遥控\", \"order\": 6, \"isChart\": 0, \"datatype\": {\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"遥控学习\", \"value\": \"FFXX01\"}, {\"text\": \"遥控清码\", \"value\": \"FFXX02\"}, {\"text\": \"打开开关\", \"value\": \"FFXX03\"}, {\"text\": \"关闭开关\", \"value\": \"FFXX04\"}, {\"text\": \"暂停\", \"value\": \"FFXX05\"}, {\"text\": \"锁定\", \"value\": \"FFXX06\"}]}, \"isHistory\": 1, \"isMonitor\": 0, \"isReadonly\": 0}]}', 1, 0, 0, 0, 0, 0, 11, '0', 'admin', '2023-02-25 22:41:43', 'admin', '2023-08-30 15:29:34', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (201, '频率 ', 1, 'admin', 'frequency', 2, 'integer', '{\"max\": 65535, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"0.001Hz\"}', 1, 0, 0, 1, 0, 0, 0, '0', '', '2023-02-28 16:08:06', 'admin', '2023-04-10 03:37:11', NULL, '3#3', '%s*0.001', '', 27, '', NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (225, '校验位', 1, 'admin', 'check', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"select\", \"enumList\": [{\"text\": \"N\", \"value\": \"0\"}, {\"text\": \"O\", \"value\": \"1\"}, {\"text\": \"E\", \"value\": \"2\"}]}', 1, 0, 0, 1, 1, 0, 0, '0', '', '2023-02-28 16:08:08', 'admin', '2023-04-10 21:36:01', NULL, '3#3', '', '', 771, '', NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (226, '波特率', 1, 'admin', 'baud', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"select\", \"enumList\": [{\"text\": \"1200\", \"value\": \"0\"}, {\"text\": \"2400\", \"value\": \"1\"}, {\"text\": \"4800\", \"value\": \"2\"}, {\"text\": \"9600\", \"value\": \"3\"}, {\"text\": \"19200\", \"value\": \"4\"}]}', 1, 0, 0, 1, 1, 0, 0, '0', '', '2023-02-28 16:08:09', 'admin', '2023-04-10 03:37:32', NULL, '3#3', '', '', 772, '', NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (227, '电压', 1, 'admin', 'voltage', 1, 'integer', '{\"max\": 6, \"min\": 0.1, \"step\": 0.1, \"type\": \"integer\", \"unit\": \"v\"}', 1, 1, 1, 1, 1, 0, 0, '0', '', '2023-02-28 16:08:09', 'admin', '2023-04-10 03:37:16', NULL, '3#3', '', '', 773, '', NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (236, '射频遥控', 1, 'admin', 'irc', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"遥控学习\", \"value\": \"FFXX01\"}, {\"text\": \"遥控清码\", \"value\": \"FFXX02\"}, {\"text\": \"打开开关\", \"value\": \"FFXX03\"}, {\"text\": \"关闭开关\", \"value\": \"FFXX04\"}, {\"text\": \"暂停\", \"value\": \"FFXX05\"}, {\"text\": \"锁定\", \"value\": \"FFXX06\"}]}', 1, 0, 0, 1, 0, 0, 0, '0', 'admin', '2023-03-31 23:46:20', 'admin', '2023-04-10 01:09:46', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (250, '漏水值', 1, 'admin', '0', 1, 'integer', '{\"max\": 100, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"度\"}', 1, 0, 0, 1, 0, 0, 0, '0', '', '2023-04-11 22:35:36', '', '2023-09-13 23:32:34', NULL, '1#1', NULL, NULL, 0, NULL, NULL, NULL, 1, '3', NULL, NULL, 'ushort'); +INSERT INTO `iot_things_model_template` VALUES (251, '温度', 1, 'admin', '0', 1, 'integer', '{\"max\": 100, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"°\"}', 1, 0, 0, 1, 0, 0, 0, '0', '', '2023-04-11 22:36:10', '', '2023-09-13 23:32:51', NULL, '1#2', NULL, NULL, 0, NULL, NULL, NULL, 1, '3', NULL, NULL, 'ushort'); +INSERT INTO `iot_things_model_template` VALUES (252, '电量', 1, 'admin', '1', 1, 'integer', '{\"max\": 100, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"\"}', 1, 0, 0, 1, 0, 0, 0, '0', '', '2023-04-11 22:36:27', '', '2023-09-13 23:33:11', NULL, '1#11', NULL, NULL, 1, NULL, NULL, NULL, 1, '3', NULL, NULL, 'ushort'); +INSERT INTO `iot_things_model_template` VALUES (323, '上报状态', 1, 'admin', 'status', 2, 'enum', '{\"type\": \"enum\", \"showWay\": \"button\", \"enumList\": [{\"text\": \"更新状态\", \"value\": \"update_status\"}]}', 1, 0, 0, 0, 0, 1, 0, '0', 'admin', '2023-04-13 01:35:42', 'admin', '2023-09-03 10:50:16', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (324, 'X位移', 1, 'admin', 'x-shift', 1, 'decimal', '{\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"decimal\", \"unit\": \"mm\"}', 1, 1, 1, 1, 1, 0, 0, '0', '', '2023-08-26 19:36:58', '', NULL, NULL, '2#1', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (325, 'Y位移', 1, 'admin', 'y-shift', 1, 'decimal', '{\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"decimal\", \"unit\": \"mm\"}', 1, 1, 1, 1, 1, 0, 0, '0', '', '2023-08-26 19:37:23', '', '2023-08-26 19:37:32', NULL, '2#1', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (326, 'X位移', 1, 'admin', 'x-shift', 1, 'decimal', '{\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"decimal\", \"unit\": \"mm\"}', 1, 1, 1, 1, 1, 0, 0, '0', '', '2023-08-26 19:38:31', '', NULL, NULL, '2#2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (327, 'Y位移', 1, 'admin', 'y-shift', 1, 'decimal', '{\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"decimal\", \"unit\": \"mm\"}', 1, 1, 1, 1, 1, 0, 0, '0', '', '2023-08-26 19:38:51', '', NULL, NULL, '2#2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (328, '计件数量', 1, 'admin', '0', 1, 'integer', '{\"max\": 10000, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"\"}', 1, 0, 0, 0, 1, 0, 0, '0', '', '2023-08-28 15:05:25', '', NULL, NULL, '3#1', NULL, NULL, 0, NULL, NULL, NULL, 1, '3', NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (329, '参数1', 1, 'admin', '0', 1, 'integer', '{\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"\"}', 1, 0, 0, 0, 1, 0, 0, '0', '', '2023-08-28 15:06:55', '', NULL, NULL, '3#2', NULL, NULL, 0, NULL, NULL, NULL, 1, '3', NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (330, '图片', 1, 'admin', 'image', 1, 'string', '{\"type\": \"string\", \"maxLength\": 10240}', 1, 0, 0, 1, 1, 0, 0, '0', '', '2023-08-28 23:19:30', '', NULL, NULL, '2#1', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (331, '回路状态', 10, 'jamon', 'loop_status', 1, 'array', '{\"type\": \"array\", \"arrayType\": \"integer\"}', 0, 0, 0, 0, 0, 0, 0, '0', '', '2023-08-29 18:21:38', '', NULL, NULL, '4#1', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (332, '回路状态', 10, 'jamon', 'loop_status', 1, 'array', '{\"type\": \"array\", \"arrayType\": \"integer\"}', 0, 0, 0, 0, 0, 0, 0, '0', '', '2023-08-29 18:23:08', '', NULL, NULL, '4#2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (335, '湿度', 1, 'admin', '0', 1, 'integer', '{\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"\"}', 1, 0, 0, 1, 1, 0, 0, '0', '', '2023-08-30 14:05:38', '', '2023-08-30 14:58:28', NULL, '6#1', '%s/10', NULL, 0, NULL, NULL, NULL, 1, '3', NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (336, '温度', 1, 'admin', '1', 1, 'integer', '{\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"°C\"}', 1, 0, 0, 1, 1, 0, 0, '0', '', '2023-08-30 14:06:05', '', '2023-08-30 14:58:38', NULL, '6#1', '%s/10', NULL, 1, NULL, NULL, NULL, 1, '3', NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (341, '视频', 1, 'admin', 'video', 1, 'integer', '{\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"\"}', 1, 0, 0, 1, 1, 0, 0, '0', '', '2023-08-30 23:08:51', '', '2023-08-30 23:25:15', NULL, '2#1', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (342, '图片', 1, 'admin', 'image', 1, 'string', '{\"type\": \"string\", \"maxLength\": 1024}', 1, 0, 0, 1, 1, 0, 0, '0', '', '2023-08-30 23:21:48', '', '2023-08-30 23:25:22', NULL, '2#2', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); +INSERT INTO `iot_things_model_template` VALUES (343, '状态', 1, 'admin', 'status', 1, 'integer', '{\"max\": 100, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"\"}', 1, 0, 0, 1, 1, 0, 0, '0', '', '2023-08-30 23:28:00', '', '2023-08-30 23:28:17', NULL, '2#1', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + +-- ---------------------------- +-- Table structure for news +-- ---------------------------- +DROP TABLE IF EXISTS `news`; +CREATE TABLE `news` ( + `news_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '新闻ID', + `title` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '标题', + `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '内容', + `img_url` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '封面', + `is_top` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否置顶(0-置顶 1-置顶)', + `is_banner` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否banner(0-是banner 1-不是banner)', + `category_id` bigint(20) NOT NULL COMMENT '分类ID', + `category_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '分类名称', + `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '新闻状态(0-未发布,1-已发布)', + `author` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '作者', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`news_id`) USING BTREE, + INDEX `news_index_category_id`(`category_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '新闻资讯' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of news +-- ---------------------------- +INSERT INTO `news` VALUES (1, '广告图一', '

请后台添加内容

', '/iot/tool/download?fileName=/profile/iot/118/2022-0424-215805.png', 0, 1, 2, '相关产品', 1, '物美智能', '0', '', '2022-05-12 12:13:40', '', '2022-05-12 12:13:40', '物美智能'); +INSERT INTO `news` VALUES (2, '广告图二', '

请后台添加内容

', '/iot/tool/download?fileName=/profile/iot/118/2022-0424-215852.png', 0, 1, 1, '新闻资讯', 1, '物美智能', '0', '', '2022-05-12 12:13:42', '', '2022-05-12 12:13:42', '物美智能'); +INSERT INTO `news` VALUES (3, '广告图三', '

后台添加内容

', '/iot/tool/download?fileName=/profile/iot/118/2022-0424-224553.png', 0, 1, 2, '相关产品', 1, '物美智能', '0', '', '2022-05-12 12:13:44', '', '2022-05-12 12:13:44', '物美智能'); +INSERT INTO `news` VALUES (4, '物美智能-快速搭建物联网和智能家居平台', '

物美智能 wumei-smart 是一个简单易用的生活物联网平台。可用于搭建物联网平台以及二次开发和学习。设备接入使用EMQX消息服务器,加密认证;后端采用Spring boot;前端采用Vue;移动端采用Uniapp;数据库采用Mysql和Redis;设备端支持ESP32、ESP8266、树莓派等;


系统功能介绍


1.权限管理: 用户管理、部门管理、岗位管理、菜单管理、角色管理、字典和参数管理等


2.系统监控: 操作日志、登录日志、系统日志、在线用户、服务监控、连接池监控、缓存监控等


3.产品管理: 产品、产品物模型、产品分类、产品固件、授权码等


4.设备管理: 控制、分组、定时、日志、统计、定位、OTA升级、影子模式、实时监测、加密认证等


5.EMQ管理: Mqtt客户端、监听器、消息主题、消息订阅、插件管理、规则引擎、资源


6.硬件 SDK: 支持WIFI和MQTT连接、物模型响应、实时监测、定时上报监测数据、AES加密、NTP时间等


7.物模型管理: 属性(设备状态和监测数据),功能(执行特定任务),事件(设备主动上报给云端)


8.其他(开发中):第三方登录,设备分享、设备告警、场景联动(进度50%),智能音箱、多租户、APP界面自定义(进度40%),时序数据库、分布式集群部署、Granfa监控(进度30%),视频流处理、桌面端模拟器/监控、安卓端模拟器/监控(进度20%)




硬件设备接入流程


1.设备认证:加密认证、简单认证和emqx支持的多种认证方式。

2.设备交互:发布和订阅物模型、设备信息、设备升级和时钟同步等mqtt主题

', '/iot/tool/download?fileName=/profile/iot/1/2022-0508-133031.png', 1, 0, 2, '相关产品', 1, '物美智能', '0', '', '2022-05-12 12:13:46', '', '2022-05-12 12:13:46', '物美智能'); +INSERT INTO `news` VALUES (5, '2022年中国物联网全景图产业链上中下游市场及企业剖析', '

后台添加内容

', '/iot/tool/download?fileName=/profile/iot/118/2022-0424-224151.png', 1, 0, 1, '新闻资讯', 1, '物美智能', '0', '', '2022-05-12 12:13:48', '', '2022-05-12 12:13:48', '物美智能'); +INSERT INTO `news` VALUES (6, 'Arm打造物联网全面解决方案 携手合作伙伴共探智能未来', '

后台添加内容

', '/iot/tool/download?fileName=/profile/iot/118/2022-0424-224352.png', 1, 0, 1, '新闻资讯', 1, '物美智能', '0', '', '2022-05-12 12:13:50', '', '2022-05-12 12:13:50', '物美智能'); +INSERT INTO `news` VALUES (7, '使用ESP32开发板,快速学习物联网开发', '

请后台添加内容

', '/iot/tool/download?fileName=/profile/iot/118/2022-0428-130824.jpg', 1, 0, 2, '相关产品', 1, '物美智能', '0', '', '2022-05-12 12:13:53', '', '2022-05-12 12:13:53', '物美智能'); +INSERT INTO `news` VALUES (8, '物联网赛道观察之无源物联网', '

无源物联网,即终端无外接能量源,采用获取环境能量的方式进行供能的物联网技术。在当前物联网技术发展条件下,终端覆盖率是一个亟待解决的问题,而无源物联网凭借其极低的部署和维护成本、灵活多变的应用场景成为解决更广范围内终端供能需求问题、实现“千亿级互联”愿景的关键。


无源物联网技术的发展最终有赖于环境能量采集、低功耗计算与反向散射等低功耗通讯技术的进步。目前无源物联网应用较为成熟的路线主要包括射频识别技术(RFID)与近场通信技术(NFC)两类,覆盖仓储物流、智能制造、智慧零售、资产管理、物业服务等多元应用场景。未来,随着物联网行业的碎片化整合以及以Bluetooth、5G、LoRa等为媒介进行能量采集与信息传输的技术路线的逐渐成熟,当前困扰行业的诸多问题将会逐步得到解决,随之而来的是更包罗多样的无源终端需求与极具潜力的应用场景。

', '/iot/tool/download?fileName=/profile/iot/118/2022-0424-215643.png', 1, 0, 1, '新闻资讯', 1, '物美智能', '0', '', '2022-05-12 12:13:55', '', '2022-05-12 12:13:55', '物美智能'); + +-- ---------------------------- +-- Table structure for news_category +-- ---------------------------- +DROP TABLE IF EXISTS `news_category`; +CREATE TABLE `news_category` ( + `category_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类ID', + `category_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '分类名称', + `order_num` int(2) NOT NULL COMMENT '显示顺序', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`category_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '新闻分类' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of news_category +-- ---------------------------- +INSERT INTO `news_category` VALUES (1, '新闻资讯', 3, '0', '', '2022-04-11 20:53:55', '', '2022-04-13 15:30:22', '新闻资讯信息'); +INSERT INTO `news_category` VALUES (2, '相关产品', 2, '0', '', '2022-04-11 20:54:16', '', '2022-04-13 15:30:15', '相关产品推荐'); + +-- ---------------------------- +-- Table structure for oauth_access_token +-- ---------------------------- +DROP TABLE IF EXISTS `oauth_access_token`; +CREATE TABLE `oauth_access_token` ( + `token_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `token` blob NULL, + `authentication_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `authentication` blob NULL, + `refresh_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of oauth_access_token +-- ---------------------------- +INSERT INTO `oauth_access_token` VALUES ('d406d946aac7c24cd01a2df1105ec898', 0xACED0005737200436F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E636F6D6D6F6E2E44656661756C744F4175746832416363657373546F6B656E0CB29E361B24FACE0200064C00156164646974696F6E616C496E666F726D6174696F6E74000F4C6A6176612F7574696C2F4D61703B4C000A65787069726174696F6E7400104C6A6176612F7574696C2F446174653B4C000C72656672657368546F6B656E74003F4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F636F6D6D6F6E2F4F417574683252656672657368546F6B656E3B4C000573636F706574000F4C6A6176612F7574696C2F5365743B4C0009746F6B656E547970657400124C6A6176612F6C616E672F537472696E673B4C000576616C756571007E000578707372001E6A6176612E7574696C2E436F6C6C656374696F6E7324456D7074794D6170593614855ADCE7D002000078707372000E6A6176612E7574696C2E44617465686A81014B597419030000787077080000018AA760D163787372004C6F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E636F6D6D6F6E2E44656661756C744578706972696E674F417574683252656672657368546F6B656E2FDF47639DD0C9B70200014C000A65787069726174696F6E71007E0002787200446F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E636F6D6D6F6E2E44656661756C744F417574683252656672657368546F6B656E73E10E0A6354D45E0200014C000576616C756571007E0005787074001B56452D377744386D70414A34497A662D49345F456450316F31626F7371007E000977080000018AA760D12D78737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65536574801D92D18F9B80550200007872002C6A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65436F6C6C656374696F6E19420080CB5EF71E0200014C0001637400164C6A6176612F7574696C2F436F6C6C656374696F6E3B7870737200176A6176612E7574696C2E4C696E6B656448617368536574D86CD75A95DD2A1E020000787200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000043F400000000000027400047265616474000577726974657874000662656172657274001B70385A5F47384A7349327A33786575674430646A494B305A396B38, '2c27d3f4516a653753e8337094cf35e1', 'admin', 'admin-dueros', 0xACED0005737200416F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E4F417574683241757468656E7469636174696F6EBD400B02166252130200024C000D73746F7265645265717565737474003C4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F70726F76696465722F4F4175746832526571756573743B4C00127573657241757468656E7469636174696F6E7400324C6F72672F737072696E676672616D65776F726B2F73656375726974792F636F72652F41757468656E7469636174696F6E3B787200476F72672E737072696E676672616D65776F726B2E73656375726974792E61757468656E7469636174696F6E2E416273747261637441757468656E7469636174696F6E546F6B656ED3AA287E6E47640E0200035A000D61757468656E746963617465644C000B617574686F7269746965737400164C6A6176612F7574696C2F436F6C6C656374696F6E3B4C000764657461696C737400124C6A6176612F6C616E672F4F626A6563743B787000737200266A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654C697374FC0F2531B5EC8E100200014C00046C6973747400104C6A6176612F7574696C2F4C6973743B7872002C6A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65436F6C6C656374696F6E19420080CB5EF71E0200014C00016371007E00047870737200136A6176612E7574696C2E41727261794C6973747881D21D99C7619D03000149000473697A657870000000007704000000007871007E000C707372003A6F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E4F41757468325265717565737400000000000000010200075A0008617070726F7665644C000B617574686F72697469657371007E00044C000A657874656E73696F6E7374000F4C6A6176612F7574696C2F4D61703B4C000B72656469726563745572697400124C6A6176612F6C616E672F537472696E673B4C00077265667265736874003B4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F70726F76696465722F546F6B656E526571756573743B4C000B7265736F7572636549647374000F4C6A6176612F7574696C2F5365743B4C000D726573706F6E7365547970657371007E0011787200386F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E426173655265717565737436287A3EA37169BD0200034C0008636C69656E74496471007E000F4C001172657175657374506172616D657465727371007E000E4C000573636F706571007E0011787074000C61646D696E2D647565726F73737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654D6170F1A5A8FE74F507420200014C00016D71007E000E7870737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C77080000001000000009740004636F64657400066E777064716174000A6772616E745F74797065740012617574686F72697A6174696F6E5F636F646574000573636F706574000A7265616420777269746574000D726573706F6E73655F74797065740004636F646574000C72656469726563745F75726974004468747470733A2F2F7869616F64752E62616964752E636F6D2F73616979612F617574682F65326566636666663936383964633462366166363764373865313039363934647400057374617465740020346635653763303462313731306262653836376535376431373434613534373874000D636C69656E745F7365637265747400205332456E65487864745E4D48684276384E23245E7479366E71244E5159324E6474000A647565726F735F7569647400203466356537633034623137313062626538363765353764313734346135343738740009636C69656E745F696474000C61646D696E2D647565726F7378737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65536574801D92D18F9B80550200007871007E0009737200176A6176612E7574696C2E4C696E6B656448617368536574D86CD75A95DD2A1E020000787200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000103F4000000000000274000472656164740005777269746578017371007E002E770C000000103F40000000000001737200426F72672E737072696E676672616D65776F726B2E73656375726974792E636F72652E617574686F726974792E53696D706C654772616E746564417574686F7269747900000000000002260200014C0004726F6C6571007E000F787074000A524F4C455F41444D494E787371007E00173F40000000000000770800000010000000007874004468747470733A2F2F7869616F64752E62616964752E636F6D2F73616979612F617574682F6532656663666666393638396463346236616636376437386531303936393464707371007E002E770C000000103F4000000000000174000F737065616B65722D73657276696365787371007E002E770C000000103F4000000000000171007E0020787372004F6F72672E737072696E676672616D65776F726B2E73656375726974792E61757468656E7469636174696F6E2E557365726E616D6550617373776F726441757468656E7469636174696F6E546F6B656E00000000000002260200024C000B63726564656E7469616C7371007E00054C00097072696E636970616C71007E00057871007E0003017372001F6A6176612E7574696C2E436F6C6C656374696F6E7324456D7074794C6973747AB817B43CA79EDE0200007870737200486F72672E737072696E676672616D65776F726B2E73656375726974792E7765622E61757468656E7469636174696F6E2E57656241757468656E7469636174696F6E44657461696C7300000000000002260200024C000D72656D6F74654164647265737371007E000F4C000973657373696F6E496471007E000F787074000F303A303A303A303A303A303A303A3170707372002E636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E6D6F64656C2E4C6F67696E55736572000000000000000102000B4C000762726F7773657271007E000F4C00066465707449647400104C6A6176612F6C616E672F4C6F6E673B4C000A65787069726554696D6571007E00434C000669706164647271007E000F4C000D6C6F67696E4C6F636174696F6E71007E000F4C00096C6F67696E54696D6571007E00434C00026F7371007E000F4C000B7065726D697373696F6E7371007E00114C0005746F6B656E71007E000F4C00047573657274002F4C636F6D2F666173746265652F636F6D6D6F6E2F636F72652F646F6D61696E2F656E746974792F537973557365723B4C000675736572496471007E004378707400094368726F6D652031317372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000677371007E00470000018AAC097C437400093132372E302E302E31740008E58685E7BD9149507371007E00470000018AA6E3204374000A57696E646F77732031307371007E002E770C000000023F400000000000017400052A3A2A3A2A7874002439326462396566652D656438662D343965392D613839392D3664393431633333393237637372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E5379735573657200000000000000010200124C000661766174617271007E000F4C000764656C466C616771007E000F4C00046465707474002F4C636F6D2F666173746265652F636F6D6D6F6E2F636F72652F646F6D61696E2F656E746974792F537973446570743B4C000664657074496471007E00434C0005656D61696C71007E000F4C00096C6F67696E446174657400104C6A6176612F7574696C2F446174653B4C00076C6F67696E497071007E000F4C00086E69636B4E616D6571007E000F4C000870617373776F726471007E000F4C000B70686F6E656E756D62657271007E000F5B0007706F73744964737400115B4C6A6176612F6C616E672F4C6F6E673B4C0006726F6C65496471007E00435B0007726F6C6549647371007E00554C0005726F6C657371007E00084C000373657871007E000F4C000673746174757371007E000F4C000675736572496471007E00434C0008757365724E616D6571007E000F78720029636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E42617365456E7469747900000000000000010200074C0008637265617465427971007E000F4C000A63726561746554696D6571007E00544C0006706172616D7371007E000E4C000672656D61726B71007E000F4C000B73656172636856616C756571007E000F4C0008757064617465427971007E000F4C000A75706461746554696D6571007E0054787074000561646D696E7372000E6A6176612E7574696C2E44617465686A81014B597419030000787077080000017DBE4ED850787371007E00173F400000000000007708000000100000000078740009E7AEA1E79086E59198707070740000740001307372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E53797344657074000000000000000102000C4C0009616E636573746F727371007E000F4C00086368696C6472656E71007E00084C000764656C466C616771007E000F4C000664657074496471007E00434C0008646570744E616D6571007E000F4C0005656D61696C71007E000F4C00066C656164657271007E000F4C00086F726465724E756D7400134C6A6176612F6C616E672F496E74656765723B4C0008706172656E74496471007E00434C000A706172656E744E616D6571007E000F4C000570686F6E6571007E000F4C000673746174757371007E000F7871007E005670707371007E00173F40000000000000770800000010000000007870707070740009302C3130302C3130317371007E000B00000000770400000000787071007E004974000CE7A094E58F91E983A8E997A870740006E789A9E7BE8E737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C75657871007E0048000000017371007E0047000000000000006570707400013171007E00497400103136343737303730374071712E636F6D7371007E005977080000018AA651C3B8787400093132372E302E302E3174000FE89C82E4BFA1E7AEA1E79086E5919874003C2432612431302451416F77377962733734666B53574A444A6B56544E656F6746376D686E69684637535445727437385078446848694E6E6F3449557574000B31353838383838383838387070707371007E000B000000017704000000017372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E537973526F6C65000000000000000102000D5A001164657074436865636B5374726963746C795A0004666C61675A00116D656E75436865636B5374726963746C794C00096461746153636F706571007E000F4C000764656C466C616771007E000F5B00076465707449647371007E00555B00076D656E7549647371007E00554C000B7065726D697373696F6E7371007E00114C0006726F6C65496471007E00434C0007726F6C654B657971007E000F4C0008726F6C654E616D6571007E000F4C0008726F6C65536F727471007E00604C000673746174757371007E000F7871007E005670707371007E00173F4000000000000077080000001000000000787070707000000074000131707070707371007E0047000000000000000174000561646D696E74000FE8B685E7BAA7E7AEA1E79086E5919871007E00687400013078740001307400013071007E007674000561646D696E71007E0076, '4cabc0e9bcfa34131342209bdaf275eb'); + +-- ---------------------------- +-- Table structure for oauth_approvals +-- ---------------------------- +DROP TABLE IF EXISTS `oauth_approvals`; +CREATE TABLE `oauth_approvals` ( + `userId` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `clientId` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `scope` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `status` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `expiresAt` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0), + `lastModifiedAt` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP(0) +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of oauth_approvals +-- ---------------------------- +INSERT INTO `oauth_approvals` VALUES ('admin', 'admin-dueros', 'read', 'APPROVED', '2023-10-18 22:12:45', '2023-09-18 22:12:45'); +INSERT INTO `oauth_approvals` VALUES ('admin', 'admin-dueros', 'write', 'APPROVED', '2023-10-18 22:12:45', '2023-09-18 22:12:45'); + +-- ---------------------------- +-- Table structure for oauth_client_details +-- ---------------------------- +DROP TABLE IF EXISTS `oauth_client_details`; +CREATE TABLE `oauth_client_details` ( + `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '客户端ID', + `resource_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '客户端所能访问的资源id集合,多个资源时用逗号(,)分隔', + `client_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '客户端秘钥', + `scope` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限范围,可选值包括read,write,trust;若有多个权限范围用逗号(,)分隔', + `authorized_grant_types` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '授权模式,可选值包括authorization_code,password,refresh_token,implicit,client_credentials, 若支持多个grant_type用逗号(,)分隔', + `web_server_redirect_uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '回调地址', + `authorities` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限', + `access_token_validity` int(11) NULL DEFAULT NULL COMMENT '设定客户端的access_token的有效时间值(单位:秒)', + `refresh_token_validity` int(11) NULL DEFAULT NULL COMMENT '设定客户端的refresh_token的有效时间值(单位:秒)', + `additional_information` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '预留的字段,在Oauth的流程中没有实际的使用,可选,但若设置值,必须是JSON格式的数据', + `autoapprove` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设置用户是否自动Approval操作, 默认值为 \'false\', 可选值包括 \'true\',\'false\', \'read\',\'write\'. \n该字段只适用于grant_type=\"authorization_code\"的情况,当用户登录成功后,若该值为\'true\'或支持的scope值,则会跳过用户Approve的页面, 直接授权. ', + `type` tinyint(1) NULL DEFAULT NULL COMMENT '1=小度(DuerOS),2=天猫精灵(ALiGenie),3=小米小爱', + PRIMARY KEY (`client_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of oauth_client_details +-- ---------------------------- +INSERT INTO `oauth_client_details` VALUES ('admin-dueros', 'speaker-service', 'S2EneHxdt^MHhBv8N#$^ty6nq$NQY2Nd', 'read,write', 'authorization_code,refresh_token', 'https://xiaodu.baidu.com/saiya/auth/e2efcfff9689dc4b6af67d78e109694d', 'ROLE_ADMIN', 7200, 7200, NULL, 'false', 1); +INSERT INTO `oauth_client_details` VALUES ('fastbee-dueros', 'speaker-service', 'S2EneHxdt^MHhBv8N#$^ty6nq$NQY2Nc', 'read,write', 'authorization_code,refresh_token', 'https://xiaodu.baidu.com/saiya/auth/35dc8a5b53719ea6bbb7bd818ca8d5b6', 'ROLE_ADMIN', 7200, 7200, NULL, 'false', 1); +INSERT INTO `oauth_client_details` VALUES ('speaker', 'speaker-service', '$2a$10$jMEhxWXpc6KsMyFF0JJ3kuoVHOp.tEsTCvaJHnQqfGtYKo4.scv/m', 'read,write', 'client_credentials,password,authorization_code,implicit,refresh_token', 'https://xiaodu.baidu.com/saiya/auth/22c6bd1489c8396f00cc25bf2d9d0206', 'ROLE_ADMIN', 7200, 7200, NULL, 'false', 1); +INSERT INTO `oauth_client_details` VALUES ('tianmao', 'speaker-service', '$2a$10$jMEhxWXpc6KsMyFF0JJ3kuoVHOp.tEsTCvaJHnQqfGtYKo4.scv/m', 'read,write', 'authorization_code,refresh_token', '\r\nhttps://xiaodu.baidu.com/saiya/auth/22c6bd1489c8396f00cc25bf2d9d0206', 'ROLE_ADMIN', 7200, 7200, NULL, 'true', 2); +INSERT INTO `oauth_client_details` VALUES ('xiaoai', 'speaker-service', '$2a$10$jMEhxWXpc6KsMyFF0JJ3kuoVHOp.tEsTCvaJHnQqfGtYKo4.scv/m', 'read,write', 'authorization_code,refresh_token', 'https://xiaodu.baidu.com/saiya/auth/22c6bd1489c8396f00cc25bf2d9d0206', 'ROLE_ADMIN', 7200, 7200, NULL, 'true', 3); +INSERT INTO `oauth_client_details` VALUES ('xiaoyi', 'speaker-service', '$2a$10$jMEhxWXpc6KsMyFF0JJ3kuoVHOp.tEsTCvaJHnQqfGtYKo4.scv/m', 'read,write', 'authorization_code,refresh_token', 'https://xiaodu.baidu.com/saiya/auth/22c6bd1489c8396f00cc25bf2d9d0206', 'ROLE_ADMIN', 7200, 7200, NULL, 'false', 4); + +-- ---------------------------- +-- Table structure for oauth_client_token +-- ---------------------------- +DROP TABLE IF EXISTS `oauth_client_token`; +CREATE TABLE `oauth_client_token` ( + `token_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `token` blob NULL, + `authentication_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of oauth_client_token +-- ---------------------------- + +-- ---------------------------- +-- Table structure for oauth_code +-- ---------------------------- +DROP TABLE IF EXISTS `oauth_code`; +CREATE TABLE `oauth_code` ( + `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `authentication` blob NULL +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of oauth_code +-- ---------------------------- +INSERT INTO `oauth_code` VALUES ('mLAeh7', 0xACED0005737200416F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E4F417574683241757468656E7469636174696F6EBD400B02166252130200024C000D73746F7265645265717565737474003C4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F70726F76696465722F4F4175746832526571756573743B4C00127573657241757468656E7469636174696F6E7400324C6F72672F737072696E676672616D65776F726B2F73656375726974792F636F72652F41757468656E7469636174696F6E3B787200476F72672E737072696E676672616D65776F726B2E73656375726974792E61757468656E7469636174696F6E2E416273747261637441757468656E7469636174696F6E546F6B656ED3AA287E6E47640E0200035A000D61757468656E746963617465644C000B617574686F7269746965737400164C6A6176612F7574696C2F436F6C6C656374696F6E3B4C000764657461696C737400124C6A6176612F6C616E672F4F626A6563743B787000737200266A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654C697374FC0F2531B5EC8E100200014C00046C6973747400104C6A6176612F7574696C2F4C6973743B7872002C6A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65436F6C6C656374696F6E19420080CB5EF71E0200014C00016371007E00047870737200136A6176612E7574696C2E41727261794C6973747881D21D99C7619D03000149000473697A657870000000007704000000007871007E000C707372003A6F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E4F41757468325265717565737400000000000000010200075A0008617070726F7665644C000B617574686F72697469657371007E00044C000A657874656E73696F6E7374000F4C6A6176612F7574696C2F4D61703B4C000B72656469726563745572697400124C6A6176612F6C616E672F537472696E673B4C00077265667265736874003B4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F70726F76696465722F546F6B656E526571756573743B4C000B7265736F7572636549647374000F4C6A6176612F7574696C2F5365743B4C000D726573706F6E7365547970657371007E0011787200386F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E426173655265717565737436287A3EA37169BD0200034C0008636C69656E74496471007E000F4C001172657175657374506172616D657465727371007E000E4C000573636F706571007E0011787074000C61646D696E2D647565726F73737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654D6170F1A5A8FE74F507420200014C00016D71007E000E7870737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F400000000000067708000000080000000474000D726573706F6E73655F74797065740004636F646574000C72656469726563745F75726974004468747470733A2F2F7869616F64752E62616964752E636F6D2F73616979612F617574682F6532656663666666393638396463346236616636376437386531303936393464740009636C69656E745F696471007E001474000573636F706574000A7265616420777269746578737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65536574801D92D18F9B80550200007871007E0009737200176A6176612E7574696C2E4C696E6B656448617368536574D86CD75A95DD2A1E020000787200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000103F4000000000000274000472656164740005777269746578017371007E0023770C000000103F40000000000001737200426F72672E737072696E676672616D65776F726B2E73656375726974792E636F72652E617574686F726974792E53696D706C654772616E746564417574686F7269747900000000000002260200014C0004726F6C6571007E000F787074000A524F4C455F41444D494E787371007E00173F40000000000000770800000010000000007874004468747470733A2F2F7869616F64752E62616964752E636F6D2F73616979612F617574682F6532656663666666393638396463346236616636376437386531303936393464707371007E0023770C000000103F4000000000000174000F737065616B65722D73657276696365787371007E0023770C000000103F4000000000000171007E001A787372004F6F72672E737072696E676672616D65776F726B2E73656375726974792E61757468656E7469636174696F6E2E557365726E616D6550617373776F726441757468656E7469636174696F6E546F6B656E00000000000002260200024C000B63726564656E7469616C7371007E00054C00097072696E636970616C71007E00057871007E0003017372001F6A6176612E7574696C2E436F6C6C656374696F6E7324456D7074794C6973747AB817B43CA79EDE0200007870737200486F72672E737072696E676672616D65776F726B2E73656375726974792E7765622E61757468656E7469636174696F6E2E57656241757468656E7469636174696F6E44657461696C7300000000000002260200024C000D72656D6F74654164647265737371007E000F4C000973657373696F6E496471007E000F787074000F303A303A303A303A303A303A303A3170707372002E636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E6D6F64656C2E4C6F67696E55736572000000000000000102000B4C000762726F7773657271007E000F4C00066465707449647400104C6A6176612F6C616E672F4C6F6E673B4C000A65787069726554696D6571007E00384C000669706164647271007E000F4C000D6C6F67696E4C6F636174696F6E71007E000F4C00096C6F67696E54696D6571007E00384C00026F7371007E000F4C000B7065726D697373696F6E7371007E00114C0005746F6B656E71007E000F4C00047573657274002F4C636F6D2F666173746265652F636F6D6D6F6E2F636F72652F646F6D61696E2F656E746974792F537973557365723B4C000675736572496471007E003878707400094368726F6D652031317372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000677371007E003C0000018AAC097C437400093132372E302E302E31740008E58685E7BD9149507371007E003C0000018AA6E3204374000A57696E646F77732031307371007E0023770C000000023F400000000000017400052A3A2A3A2A7874002439326462396566652D656438662D343965392D613839392D3664393431633333393237637372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E5379735573657200000000000000010200124C000661766174617271007E000F4C000764656C466C616771007E000F4C00046465707474002F4C636F6D2F666173746265652F636F6D6D6F6E2F636F72652F646F6D61696E2F656E746974792F537973446570743B4C000664657074496471007E00384C0005656D61696C71007E000F4C00096C6F67696E446174657400104C6A6176612F7574696C2F446174653B4C00076C6F67696E497071007E000F4C00086E69636B4E616D6571007E000F4C000870617373776F726471007E000F4C000B70686F6E656E756D62657271007E000F5B0007706F73744964737400115B4C6A6176612F6C616E672F4C6F6E673B4C0006726F6C65496471007E00385B0007726F6C6549647371007E004A4C0005726F6C657371007E00084C000373657871007E000F4C000673746174757371007E000F4C000675736572496471007E00384C0008757365724E616D6571007E000F78720029636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E42617365456E7469747900000000000000010200074C0008637265617465427971007E000F4C000A63726561746554696D6571007E00494C0006706172616D7371007E000E4C000672656D61726B71007E000F4C000B73656172636856616C756571007E000F4C0008757064617465427971007E000F4C000A75706461746554696D6571007E0049787074000561646D696E7372000E6A6176612E7574696C2E44617465686A81014B597419030000787077080000017DBE4ED850787371007E00173F400000000000007708000000100000000078740009E7AEA1E79086E59198707070740000740001307372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E53797344657074000000000000000102000C4C0009616E636573746F727371007E000F4C00086368696C6472656E71007E00084C000764656C466C616771007E000F4C000664657074496471007E00384C0008646570744E616D6571007E000F4C0005656D61696C71007E000F4C00066C656164657271007E000F4C00086F726465724E756D7400134C6A6176612F6C616E672F496E74656765723B4C0008706172656E74496471007E00384C000A706172656E744E616D6571007E000F4C000570686F6E6571007E000F4C000673746174757371007E000F7871007E004B70707371007E00173F40000000000000770800000010000000007870707070740009302C3130302C3130317371007E000B00000000770400000000787071007E003E74000CE7A094E58F91E983A8E997A870740006E789A9E7BE8E737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C75657871007E003D000000017371007E003C000000000000006570707400013171007E003E7400103136343737303730374071712E636F6D7371007E004E77080000018AA651C3B8787400093132372E302E302E3174000FE89C82E4BFA1E7AEA1E79086E5919874003C2432612431302451416F77377962733734666B53574A444A6B56544E656F6746376D686E69684637535445727437385078446848694E6E6F3449557574000B31353838383838383838387070707371007E000B000000017704000000017372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E537973526F6C65000000000000000102000D5A001164657074436865636B5374726963746C795A0004666C61675A00116D656E75436865636B5374726963746C794C00096461746153636F706571007E000F4C000764656C466C616771007E000F5B00076465707449647371007E004A5B00076D656E7549647371007E004A4C000B7065726D697373696F6E7371007E00114C0006726F6C65496471007E00384C0007726F6C654B657971007E000F4C0008726F6C654E616D6571007E000F4C0008726F6C65536F727471007E00554C000673746174757371007E000F7871007E004B70707371007E00173F4000000000000077080000001000000000787070707000000074000131707070707371007E003C000000000000000174000561646D696E74000FE8B685E7BAA7E7AEA1E79086E5919871007E005D7400013078740001307400013071007E006B74000561646D696E71007E006B); +INSERT INTO `oauth_code` VALUES ('1YESo2', 0xACED0005737200416F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E4F417574683241757468656E7469636174696F6EBD400B02166252130200024C000D73746F7265645265717565737474003C4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F70726F76696465722F4F4175746832526571756573743B4C00127573657241757468656E7469636174696F6E7400324C6F72672F737072696E676672616D65776F726B2F73656375726974792F636F72652F41757468656E7469636174696F6E3B787200476F72672E737072696E676672616D65776F726B2E73656375726974792E61757468656E7469636174696F6E2E416273747261637441757468656E7469636174696F6E546F6B656ED3AA287E6E47640E0200035A000D61757468656E746963617465644C000B617574686F7269746965737400164C6A6176612F7574696C2F436F6C6C656374696F6E3B4C000764657461696C737400124C6A6176612F6C616E672F4F626A6563743B787000737200266A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654C697374FC0F2531B5EC8E100200014C00046C6973747400104C6A6176612F7574696C2F4C6973743B7872002C6A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65436F6C6C656374696F6E19420080CB5EF71E0200014C00016371007E00047870737200136A6176612E7574696C2E41727261794C6973747881D21D99C7619D03000149000473697A657870000000007704000000007871007E000C707372003A6F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E4F41757468325265717565737400000000000000010200075A0008617070726F7665644C000B617574686F72697469657371007E00044C000A657874656E73696F6E7374000F4C6A6176612F7574696C2F4D61703B4C000B72656469726563745572697400124C6A6176612F6C616E672F537472696E673B4C00077265667265736874003B4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F70726F76696465722F546F6B656E526571756573743B4C000B7265736F7572636549647374000F4C6A6176612F7574696C2F5365743B4C000D726573706F6E7365547970657371007E0011787200386F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E426173655265717565737436287A3EA37169BD0200034C0008636C69656E74496471007E000F4C001172657175657374506172616D657465727371007E000E4C000573636F706571007E0011787074000C61646D696E2D647565726F73737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654D6170F1A5A8FE74F507420200014C00016D71007E000E7870737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F400000000000067708000000080000000474000D726573706F6E73655F74797065740004636F646574000C72656469726563745F75726974004468747470733A2F2F7869616F64752E62616964752E636F6D2F73616979612F617574682F6532656663666666393638396463346236616636376437386531303936393464740009636C69656E745F696471007E001474000573636F706574000A7265616420777269746578737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65536574801D92D18F9B80550200007871007E0009737200176A6176612E7574696C2E4C696E6B656448617368536574D86CD75A95DD2A1E020000787200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000103F4000000000000274000472656164740005777269746578017371007E0023770C000000103F40000000000001737200426F72672E737072696E676672616D65776F726B2E73656375726974792E636F72652E617574686F726974792E53696D706C654772616E746564417574686F7269747900000000000002260200014C0004726F6C6571007E000F787074000A524F4C455F41444D494E787371007E00173F40000000000000770800000010000000007874004468747470733A2F2F7869616F64752E62616964752E636F6D2F73616979612F617574682F6532656663666666393638396463346236616636376437386531303936393464707371007E0023770C000000103F4000000000000174000F737065616B65722D73657276696365787371007E0023770C000000103F4000000000000171007E001A787372004F6F72672E737072696E676672616D65776F726B2E73656375726974792E61757468656E7469636174696F6E2E557365726E616D6550617373776F726441757468656E7469636174696F6E546F6B656E00000000000002260200024C000B63726564656E7469616C7371007E00054C00097072696E636970616C71007E00057871007E0003017372001F6A6176612E7574696C2E436F6C6C656374696F6E7324456D7074794C6973747AB817B43CA79EDE0200007870737200486F72672E737072696E676672616D65776F726B2E73656375726974792E7765622E61757468656E7469636174696F6E2E57656241757468656E7469636174696F6E44657461696C7300000000000002260200024C000D72656D6F74654164647265737371007E000F4C000973657373696F6E496471007E000F787074000F303A303A303A303A303A303A303A3170707372002E636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E6D6F64656C2E4C6F67696E55736572000000000000000102000B4C000762726F7773657271007E000F4C00066465707449647400104C6A6176612F6C616E672F4C6F6E673B4C000A65787069726554696D6571007E00384C000669706164647271007E000F4C000D6C6F67696E4C6F636174696F6E71007E000F4C00096C6F67696E54696D6571007E00384C00026F7371007E000F4C000B7065726D697373696F6E7371007E00114C0005746F6B656E71007E000F4C00047573657274002F4C636F6D2F666173746265652F636F6D6D6F6E2F636F72652F646F6D61696E2F656E746974792F537973557365723B4C000675736572496471007E003878707400094368726F6D652031317372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000677371007E003C0000018AAC097C437400093132372E302E302E31740008E58685E7BD9149507371007E003C0000018AA6E3204374000A57696E646F77732031307371007E0023770C000000023F400000000000017400052A3A2A3A2A7874002439326462396566652D656438662D343965392D613839392D3664393431633333393237637372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E5379735573657200000000000000010200124C000661766174617271007E000F4C000764656C466C616771007E000F4C00046465707474002F4C636F6D2F666173746265652F636F6D6D6F6E2F636F72652F646F6D61696E2F656E746974792F537973446570743B4C000664657074496471007E00384C0005656D61696C71007E000F4C00096C6F67696E446174657400104C6A6176612F7574696C2F446174653B4C00076C6F67696E497071007E000F4C00086E69636B4E616D6571007E000F4C000870617373776F726471007E000F4C000B70686F6E656E756D62657271007E000F5B0007706F73744964737400115B4C6A6176612F6C616E672F4C6F6E673B4C0006726F6C65496471007E00385B0007726F6C6549647371007E004A4C0005726F6C657371007E00084C000373657871007E000F4C000673746174757371007E000F4C000675736572496471007E00384C0008757365724E616D6571007E000F78720029636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E42617365456E7469747900000000000000010200074C0008637265617465427971007E000F4C000A63726561746554696D6571007E00494C0006706172616D7371007E000E4C000672656D61726B71007E000F4C000B73656172636856616C756571007E000F4C0008757064617465427971007E000F4C000A75706461746554696D6571007E0049787074000561646D696E7372000E6A6176612E7574696C2E44617465686A81014B597419030000787077080000017DBE4ED850787371007E00173F400000000000007708000000100000000078740009E7AEA1E79086E59198707070740000740001307372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E53797344657074000000000000000102000C4C0009616E636573746F727371007E000F4C00086368696C6472656E71007E00084C000764656C466C616771007E000F4C000664657074496471007E00384C0008646570744E616D6571007E000F4C0005656D61696C71007E000F4C00066C656164657271007E000F4C00086F726465724E756D7400134C6A6176612F6C616E672F496E74656765723B4C0008706172656E74496471007E00384C000A706172656E744E616D6571007E000F4C000570686F6E6571007E000F4C000673746174757371007E000F7871007E004B70707371007E00173F40000000000000770800000010000000007870707070740009302C3130302C3130317371007E000B00000000770400000000787071007E003E74000CE7A094E58F91E983A8E997A870740006E789A9E7BE8E737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C75657871007E003D000000017371007E003C000000000000006570707400013171007E003E7400103136343737303730374071712E636F6D7371007E004E77080000018AA651C3B8787400093132372E302E302E3174000FE89C82E4BFA1E7AEA1E79086E5919874003C2432612431302451416F77377962733734666B53574A444A6B56544E656F6746376D686E69684637535445727437385078446848694E6E6F3449557574000B31353838383838383838387070707371007E000B000000017704000000017372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E537973526F6C65000000000000000102000D5A001164657074436865636B5374726963746C795A0004666C61675A00116D656E75436865636B5374726963746C794C00096461746153636F706571007E000F4C000764656C466C616771007E000F5B00076465707449647371007E004A5B00076D656E7549647371007E004A4C000B7065726D697373696F6E7371007E00114C0006726F6C65496471007E00384C0007726F6C654B657971007E000F4C0008726F6C654E616D6571007E000F4C0008726F6C65536F727471007E00554C000673746174757371007E000F7871007E004B70707371007E00173F4000000000000077080000001000000000787070707000000074000131707070707371007E003C000000000000000174000561646D696E74000FE8B685E7BAA7E7AEA1E79086E5919871007E005D7400013078740001307400013071007E006B74000561646D696E71007E006B); +INSERT INTO `oauth_code` VALUES ('DhdDPY', 0xACED0005737200416F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E4F417574683241757468656E7469636174696F6EBD400B02166252130200024C000D73746F7265645265717565737474003C4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F70726F76696465722F4F4175746832526571756573743B4C00127573657241757468656E7469636174696F6E7400324C6F72672F737072696E676672616D65776F726B2F73656375726974792F636F72652F41757468656E7469636174696F6E3B787200476F72672E737072696E676672616D65776F726B2E73656375726974792E61757468656E7469636174696F6E2E416273747261637441757468656E7469636174696F6E546F6B656ED3AA287E6E47640E0200035A000D61757468656E746963617465644C000B617574686F7269746965737400164C6A6176612F7574696C2F436F6C6C656374696F6E3B4C000764657461696C737400124C6A6176612F6C616E672F4F626A6563743B787000737200266A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654C697374FC0F2531B5EC8E100200014C00046C6973747400104C6A6176612F7574696C2F4C6973743B7872002C6A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65436F6C6C656374696F6E19420080CB5EF71E0200014C00016371007E00047870737200136A6176612E7574696C2E41727261794C6973747881D21D99C7619D03000149000473697A657870000000007704000000007871007E000C707372003A6F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E4F41757468325265717565737400000000000000010200075A0008617070726F7665644C000B617574686F72697469657371007E00044C000A657874656E73696F6E7374000F4C6A6176612F7574696C2F4D61703B4C000B72656469726563745572697400124C6A6176612F6C616E672F537472696E673B4C00077265667265736874003B4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F70726F76696465722F546F6B656E526571756573743B4C000B7265736F7572636549647374000F4C6A6176612F7574696C2F5365743B4C000D726573706F6E7365547970657371007E0011787200386F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E426173655265717565737436287A3EA37169BD0200034C0008636C69656E74496471007E000F4C001172657175657374506172616D657465727371007E000E4C000573636F706571007E0011787074000C61646D696E2D647565726F73737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654D6170F1A5A8FE74F507420200014C00016D71007E000E7870737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C7708000000100000000674000573636F706574000A7265616420777269746574000D726573706F6E73655F74797065740004636F646574000C72656469726563745F75726974004468747470733A2F2F7869616F64752E62616964752E636F6D2F73616979612F617574682F65326566636666663936383964633462366166363764373865313039363934647400057374617465740020346635653763303462313731306262653836376535376431373434613534373874000A647565726F735F7569647400203466356537633034623137313062626538363765353764313734346135343738740009636C69656E745F696471007E001478737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65536574801D92D18F9B80550200007871007E0009737200176A6176612E7574696C2E4C696E6B656448617368536574D86CD75A95DD2A1E020000787200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000103F4000000000000274000472656164740005777269746578017371007E0027770C000000103F40000000000001737200426F72672E737072696E676672616D65776F726B2E73656375726974792E636F72652E617574686F726974792E53696D706C654772616E746564417574686F7269747900000000000002260200014C0004726F6C6571007E000F787074000A524F4C455F41444D494E787371007E00173F40000000000000770800000010000000007874004468747470733A2F2F7869616F64752E62616964752E636F6D2F73616979612F617574682F6532656663666666393638396463346236616636376437386531303936393464707371007E0027770C000000103F4000000000000174000F737065616B65722D73657276696365787371007E0027770C000000103F4000000000000171007E001C787372004F6F72672E737072696E676672616D65776F726B2E73656375726974792E61757468656E7469636174696F6E2E557365726E616D6550617373776F726441757468656E7469636174696F6E546F6B656E00000000000002260200024C000B63726564656E7469616C7371007E00054C00097072696E636970616C71007E00057871007E0003017372001F6A6176612E7574696C2E436F6C6C656374696F6E7324456D7074794C6973747AB817B43CA79EDE0200007870737200486F72672E737072696E676672616D65776F726B2E73656375726974792E7765622E61757468656E7469636174696F6E2E57656241757468656E7469636174696F6E44657461696C7300000000000002260200024C000D72656D6F74654164647265737371007E000F4C000973657373696F6E496471007E000F787074000F303A303A303A303A303A303A303A3170707372002E636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E6D6F64656C2E4C6F67696E55736572000000000000000102000B4C000762726F7773657271007E000F4C00066465707449647400104C6A6176612F6C616E672F4C6F6E673B4C000A65787069726554696D6571007E003C4C000669706164647271007E000F4C000D6C6F67696E4C6F636174696F6E71007E000F4C00096C6F67696E54696D6571007E003C4C00026F7371007E000F4C000B7065726D697373696F6E7371007E00114C0005746F6B656E71007E000F4C00047573657274002F4C636F6D2F666173746265652F636F6D6D6F6E2F636F72652F646F6D61696E2F656E746974792F537973557365723B4C000675736572496471007E003C78707400094368726F6D652031317372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000677371007E00400000018AAC097C437400093132372E302E302E31740008E58685E7BD9149507371007E00400000018AA6E3204374000A57696E646F77732031307371007E0027770C000000023F400000000000017400052A3A2A3A2A7874002439326462396566652D656438662D343965392D613839392D3664393431633333393237637372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E5379735573657200000000000000010200124C000661766174617271007E000F4C000764656C466C616771007E000F4C00046465707474002F4C636F6D2F666173746265652F636F6D6D6F6E2F636F72652F646F6D61696E2F656E746974792F537973446570743B4C000664657074496471007E003C4C0005656D61696C71007E000F4C00096C6F67696E446174657400104C6A6176612F7574696C2F446174653B4C00076C6F67696E497071007E000F4C00086E69636B4E616D6571007E000F4C000870617373776F726471007E000F4C000B70686F6E656E756D62657271007E000F5B0007706F73744964737400115B4C6A6176612F6C616E672F4C6F6E673B4C0006726F6C65496471007E003C5B0007726F6C6549647371007E004E4C0005726F6C657371007E00084C000373657871007E000F4C000673746174757371007E000F4C000675736572496471007E003C4C0008757365724E616D6571007E000F78720029636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E42617365456E7469747900000000000000010200074C0008637265617465427971007E000F4C000A63726561746554696D6571007E004D4C0006706172616D7371007E000E4C000672656D61726B71007E000F4C000B73656172636856616C756571007E000F4C0008757064617465427971007E000F4C000A75706461746554696D6571007E004D787074000561646D696E7372000E6A6176612E7574696C2E44617465686A81014B597419030000787077080000017DBE4ED850787371007E00173F400000000000007708000000100000000078740009E7AEA1E79086E59198707070740000740001307372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E53797344657074000000000000000102000C4C0009616E636573746F727371007E000F4C00086368696C6472656E71007E00084C000764656C466C616771007E000F4C000664657074496471007E003C4C0008646570744E616D6571007E000F4C0005656D61696C71007E000F4C00066C656164657271007E000F4C00086F726465724E756D7400134C6A6176612F6C616E672F496E74656765723B4C0008706172656E74496471007E003C4C000A706172656E744E616D6571007E000F4C000570686F6E6571007E000F4C000673746174757371007E000F7871007E004F70707371007E00173F40000000000000770800000010000000007870707070740009302C3130302C3130317371007E000B00000000770400000000787071007E004274000CE7A094E58F91E983A8E997A870740006E789A9E7BE8E737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C75657871007E0041000000017371007E0040000000000000006570707400013171007E00427400103136343737303730374071712E636F6D7371007E005277080000018AA651C3B8787400093132372E302E302E3174000FE89C82E4BFA1E7AEA1E79086E5919874003C2432612431302451416F77377962733734666B53574A444A6B56544E656F6746376D686E69684637535445727437385078446848694E6E6F3449557574000B31353838383838383838387070707371007E000B000000017704000000017372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E537973526F6C65000000000000000102000D5A001164657074436865636B5374726963746C795A0004666C61675A00116D656E75436865636B5374726963746C794C00096461746153636F706571007E000F4C000764656C466C616771007E000F5B00076465707449647371007E004E5B00076D656E7549647371007E004E4C000B7065726D697373696F6E7371007E00114C0006726F6C65496471007E003C4C0007726F6C654B657971007E000F4C0008726F6C654E616D6571007E000F4C0008726F6C65536F727471007E00594C000673746174757371007E000F7871007E004F70707371007E00173F4000000000000077080000001000000000787070707000000074000131707070707371007E0040000000000000000174000561646D696E74000FE8B685E7BAA7E7AEA1E79086E5919871007E00617400013078740001307400013071007E006F74000561646D696E71007E006F); + +-- ---------------------------- +-- Table structure for oauth_refresh_token +-- ---------------------------- +DROP TABLE IF EXISTS `oauth_refresh_token`; +CREATE TABLE `oauth_refresh_token` ( + `token_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `token` blob NULL, + `authentication` blob NULL +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of oauth_refresh_token +-- ---------------------------- +INSERT INTO `oauth_refresh_token` VALUES ('4cabc0e9bcfa34131342209bdaf275eb', 0xACED00057372004C6F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E636F6D6D6F6E2E44656661756C744578706972696E674F417574683252656672657368546F6B656E2FDF47639DD0C9B70200014C000A65787069726174696F6E7400104C6A6176612F7574696C2F446174653B787200446F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E636F6D6D6F6E2E44656661756C744F417574683252656672657368546F6B656E73E10E0A6354D45E0200014C000576616C75657400124C6A6176612F6C616E672F537472696E673B787074001B56452D377744386D70414A34497A662D49345F456450316F31626F7372000E6A6176612E7574696C2E44617465686A81014B597419030000787077080000018AA760D12D78, 0xACED0005737200416F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E4F417574683241757468656E7469636174696F6EBD400B02166252130200024C000D73746F7265645265717565737474003C4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F70726F76696465722F4F4175746832526571756573743B4C00127573657241757468656E7469636174696F6E7400324C6F72672F737072696E676672616D65776F726B2F73656375726974792F636F72652F41757468656E7469636174696F6E3B787200476F72672E737072696E676672616D65776F726B2E73656375726974792E61757468656E7469636174696F6E2E416273747261637441757468656E7469636174696F6E546F6B656ED3AA287E6E47640E0200035A000D61757468656E746963617465644C000B617574686F7269746965737400164C6A6176612F7574696C2F436F6C6C656374696F6E3B4C000764657461696C737400124C6A6176612F6C616E672F4F626A6563743B787000737200266A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654C697374FC0F2531B5EC8E100200014C00046C6973747400104C6A6176612F7574696C2F4C6973743B7872002C6A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65436F6C6C656374696F6E19420080CB5EF71E0200014C00016371007E00047870737200136A6176612E7574696C2E41727261794C6973747881D21D99C7619D03000149000473697A657870000000007704000000007871007E000C707372003A6F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E4F41757468325265717565737400000000000000010200075A0008617070726F7665644C000B617574686F72697469657371007E00044C000A657874656E73696F6E7374000F4C6A6176612F7574696C2F4D61703B4C000B72656469726563745572697400124C6A6176612F6C616E672F537472696E673B4C00077265667265736874003B4C6F72672F737072696E676672616D65776F726B2F73656375726974792F6F61757468322F70726F76696465722F546F6B656E526571756573743B4C000B7265736F7572636549647374000F4C6A6176612F7574696C2F5365743B4C000D726573706F6E7365547970657371007E0011787200386F72672E737072696E676672616D65776F726B2E73656375726974792E6F61757468322E70726F76696465722E426173655265717565737436287A3EA37169BD0200034C0008636C69656E74496471007E000F4C001172657175657374506172616D657465727371007E000E4C000573636F706571007E0011787074000C61646D696E2D647565726F73737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654D6170F1A5A8FE74F507420200014C00016D71007E000E7870737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C77080000001000000009740004636F6465740006536C4B4F684C74000A6772616E745F74797065740012617574686F72697A6174696F6E5F636F646574000573636F706574000A7265616420777269746574000D726573706F6E73655F74797065740004636F646574000C72656469726563745F75726974004468747470733A2F2F7869616F64752E62616964752E636F6D2F73616979612F617574682F65326566636666663936383964633462366166363764373865313039363934647400057374617465740020346635653763303462313731306262653836376535376431373434613534373874000D636C69656E745F7365637265747400225332456E65487864745E4D48684276384E253233245E7479366E71244E5159324E6474000A647565726F735F7569647400203466356537633034623137313062626538363765353764313734346135343738740009636C69656E745F696474000C61646D696E2D647565726F7378737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65536574801D92D18F9B80550200007871007E0009737200176A6176612E7574696C2E4C696E6B656448617368536574D86CD75A95DD2A1E020000787200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000103F4000000000000274000472656164740005777269746578017371007E002E770C000000103F40000000000001737200426F72672E737072696E676672616D65776F726B2E73656375726974792E636F72652E617574686F726974792E53696D706C654772616E746564417574686F7269747900000000000002260200014C0004726F6C6571007E000F787074000A524F4C455F41444D494E787371007E00173F40000000000000770800000010000000007874004468747470733A2F2F7869616F64752E62616964752E636F6D2F73616979612F617574682F6532656663666666393638396463346236616636376437386531303936393464707371007E002E770C000000103F4000000000000174000F737065616B65722D73657276696365787371007E002E770C000000103F4000000000000171007E0020787372004F6F72672E737072696E676672616D65776F726B2E73656375726974792E61757468656E7469636174696F6E2E557365726E616D6550617373776F726441757468656E7469636174696F6E546F6B656E00000000000002260200024C000B63726564656E7469616C7371007E00054C00097072696E636970616C71007E00057871007E0003017372001F6A6176612E7574696C2E436F6C6C656374696F6E7324456D7074794C6973747AB817B43CA79EDE0200007870737200486F72672E737072696E676672616D65776F726B2E73656375726974792E7765622E61757468656E7469636174696F6E2E57656241757468656E7469636174696F6E44657461696C7300000000000002260200024C000D72656D6F74654164647265737371007E000F4C000973657373696F6E496471007E000F787074000F303A303A303A303A303A303A303A3170707372002E636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E6D6F64656C2E4C6F67696E55736572000000000000000102000B4C000762726F7773657271007E000F4C00066465707449647400104C6A6176612F6C616E672F4C6F6E673B4C000A65787069726554696D6571007E00434C000669706164647271007E000F4C000D6C6F67696E4C6F636174696F6E71007E000F4C00096C6F67696E54696D6571007E00434C00026F7371007E000F4C000B7065726D697373696F6E7371007E00114C0005746F6B656E71007E000F4C00047573657274002F4C636F6D2F666173746265652F636F6D6D6F6E2F636F72652F646F6D61696E2F656E746974792F537973557365723B4C000675736572496471007E004378707400094368726F6D652031317372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000677371007E00470000018AAC097C437400093132372E302E302E31740008E58685E7BD9149507371007E00470000018AA6E3204374000A57696E646F77732031307371007E002E770C000000023F400000000000017400052A3A2A3A2A7874002439326462396566652D656438662D343965392D613839392D3664393431633333393237637372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E5379735573657200000000000000010200124C000661766174617271007E000F4C000764656C466C616771007E000F4C00046465707474002F4C636F6D2F666173746265652F636F6D6D6F6E2F636F72652F646F6D61696E2F656E746974792F537973446570743B4C000664657074496471007E00434C0005656D61696C71007E000F4C00096C6F67696E446174657400104C6A6176612F7574696C2F446174653B4C00076C6F67696E497071007E000F4C00086E69636B4E616D6571007E000F4C000870617373776F726471007E000F4C000B70686F6E656E756D62657271007E000F5B0007706F73744964737400115B4C6A6176612F6C616E672F4C6F6E673B4C0006726F6C65496471007E00435B0007726F6C6549647371007E00554C0005726F6C657371007E00084C000373657871007E000F4C000673746174757371007E000F4C000675736572496471007E00434C0008757365724E616D6571007E000F78720029636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E42617365456E7469747900000000000000010200074C0008637265617465427971007E000F4C000A63726561746554696D6571007E00544C0006706172616D7371007E000E4C000672656D61726B71007E000F4C000B73656172636856616C756571007E000F4C0008757064617465427971007E000F4C000A75706461746554696D6571007E0054787074000561646D696E7372000E6A6176612E7574696C2E44617465686A81014B597419030000787077080000017DBE4ED850787371007E00173F400000000000007708000000100000000078740009E7AEA1E79086E59198707070740000740001307372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E53797344657074000000000000000102000C4C0009616E636573746F727371007E000F4C00086368696C6472656E71007E00084C000764656C466C616771007E000F4C000664657074496471007E00434C0008646570744E616D6571007E000F4C0005656D61696C71007E000F4C00066C656164657271007E000F4C00086F726465724E756D7400134C6A6176612F6C616E672F496E74656765723B4C0008706172656E74496471007E00434C000A706172656E744E616D6571007E000F4C000570686F6E6571007E000F4C000673746174757371007E000F7871007E005670707371007E00173F40000000000000770800000010000000007870707070740009302C3130302C3130317371007E000B00000000770400000000787071007E004974000CE7A094E58F91E983A8E997A870740006E789A9E7BE8E737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C75657871007E0048000000017371007E0047000000000000006570707400013171007E00497400103136343737303730374071712E636F6D7371007E005977080000018AA651C3B8787400093132372E302E302E3174000FE89C82E4BFA1E7AEA1E79086E5919874003C2432612431302451416F77377962733734666B53574A444A6B56544E656F6746376D686E69684637535445727437385078446848694E6E6F3449557574000B31353838383838383838387070707371007E000B000000017704000000017372002D636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E656E746974792E537973526F6C65000000000000000102000D5A001164657074436865636B5374726963746C795A0004666C61675A00116D656E75436865636B5374726963746C794C00096461746153636F706571007E000F4C000764656C466C616771007E000F5B00076465707449647371007E00555B00076D656E7549647371007E00554C000B7065726D697373696F6E7371007E00114C0006726F6C65496471007E00434C0007726F6C654B657971007E000F4C0008726F6C654E616D6571007E000F4C0008726F6C65536F727471007E00604C000673746174757371007E000F7871007E005670707371007E00173F4000000000000077080000001000000000787070707000000074000131707070707371007E0047000000000000000174000561646D696E74000FE8B685E7BAA7E7AEA1E79086E5919871007E00687400013078740001307400013071007E007674000561646D696E71007E0076); + + +-- ---------------------------- +-- Table structure for qrtz_blob_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_blob_triggers`; +CREATE TABLE `qrtz_blob_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `blob_data` blob NULL COMMENT '存放持久化Trigger对象', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + CONSTRAINT `QRTZ_BLOB_TRIGGERS_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Blob类型的触发器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_blob_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_calendars +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_calendars`; +CREATE TABLE `qrtz_calendars` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度名称', + `calendar_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '日历名称', + `calendar` blob NOT NULL COMMENT '存放持久化calendar对象', + PRIMARY KEY (`sched_name`, `calendar_name`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '日历信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_calendars +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_cron_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_cron_triggers`; +CREATE TABLE `qrtz_cron_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `cron_expression` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'cron表达式', + `time_zone_id` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '时区', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + CONSTRAINT `QRTZ_CRON_TRIGGERS_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Cron类型的触发器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_cron_triggers +-- ---------------------------- +INSERT INTO `qrtz_cron_triggers` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME1', 'DEFAULT', '0/10 * * * * ?', 'Asia/Shanghai'); +INSERT INTO `qrtz_cron_triggers` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME2', 'DEFAULT', '0/15 * * * * ?', 'Asia/Shanghai'); +INSERT INTO `qrtz_cron_triggers` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME3', 'DEFAULT', '0/20 * * * * ?', 'Asia/Shanghai'); +INSERT INTO `qrtz_cron_triggers` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME4', 'SYSTEM', '0 0/4 * * * ? ', 'Asia/Shanghai'); +INSERT INTO `qrtz_cron_triggers` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME5', 'SYSTEM', '0 0/1 * * * ? ', 'Asia/Shanghai'); + +-- ---------------------------- +-- Table structure for qrtz_fired_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_fired_triggers`; +CREATE TABLE `qrtz_fired_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度名称', + `entry_id` varchar(95) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度器实例id', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `instance_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度器实例名', + `fired_time` bigint(13) NOT NULL COMMENT '触发的时间', + `sched_time` bigint(13) NOT NULL COMMENT '定时器制定的时间', + `priority` int(11) NOT NULL COMMENT '优先级', + `state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '状态', + `job_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '任务名称', + `job_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '任务组名', + `is_nonconcurrent` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否并发', + `requests_recovery` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否接受恢复执行', + PRIMARY KEY (`sched_name`, `entry_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '已触发的触发器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_fired_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_job_details +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_job_details`; +CREATE TABLE `qrtz_job_details` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度名称', + `job_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务名称', + `job_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务组名', + `description` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '相关介绍', + `job_class_name` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '执行任务类名称', + `is_durable` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '是否持久化', + `is_nonconcurrent` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '是否并发', + `is_update_data` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '是否更新数据', + `requests_recovery` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '是否接受恢复执行', + `job_data` blob NULL COMMENT '存放持久化job对象', + PRIMARY KEY (`sched_name`, `job_name`, `job_group`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '任务详细信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_job_details +-- ---------------------------- +INSERT INTO `qrtz_job_details` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME1', 'DEFAULT', NULL, 'com.fastbee.quartz.util.QuartzDisallowConcurrentExecution', '0', '1', '0', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C7708000000100000000174000F5441534B5F50524F5045525449455373720020636F6D2E666173746265652E71756172747A2E646F6D61696E2E5379734A6F6200000000000000010200084C000A636F6E63757272656E747400124C6A6176612F6C616E672F537472696E673B4C000E63726F6E45787072657373696F6E71007E00094C000C696E766F6B6554617267657471007E00094C00086A6F6247726F757071007E00094C00056A6F6249647400104C6A6176612F6C616E672F4C6F6E673B4C00076A6F624E616D6571007E00094C000D6D697366697265506F6C69637971007E00094C000673746174757371007E000978720029636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E42617365456E7469747900000000000000010200074C0008637265617465427971007E00094C000A63726561746554696D657400104C6A6176612F7574696C2F446174653B4C0006706172616D7371007E00034C000672656D61726B71007E00094C000B73656172636856616C756571007E00094C0008757064617465427971007E00094C000A75706461746554696D6571007E000C787074000561646D696E7372000E6A6176612E7574696C2E44617465686A81014B597419030000787077080000017DBE4ED85078707400007070707400013174000E302F3130202A202A202A202A203F74001172795461736B2E72794E6F506172616D7374000744454641554C547372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000000000001740018E7B3BBE7BB9FE9BB98E8AEA4EFBC88E697A0E58F82EFBC8974000133740001307800); +INSERT INTO `qrtz_job_details` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME2', 'DEFAULT', NULL, 'com.fastbee.quartz.util.QuartzDisallowConcurrentExecution', '0', '1', '0', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C7708000000100000000174000F5441534B5F50524F5045525449455373720020636F6D2E666173746265652E71756172747A2E646F6D61696E2E5379734A6F6200000000000000010200084C000A636F6E63757272656E747400124C6A6176612F6C616E672F537472696E673B4C000E63726F6E45787072657373696F6E71007E00094C000C696E766F6B6554617267657471007E00094C00086A6F6247726F757071007E00094C00056A6F6249647400104C6A6176612F6C616E672F4C6F6E673B4C00076A6F624E616D6571007E00094C000D6D697366697265506F6C69637971007E00094C000673746174757371007E000978720029636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E42617365456E7469747900000000000000010200074C0008637265617465427971007E00094C000A63726561746554696D657400104C6A6176612F7574696C2F446174653B4C0006706172616D7371007E00034C000672656D61726B71007E00094C000B73656172636856616C756571007E00094C0008757064617465427971007E00094C000A75706461746554696D6571007E000C787074000561646D696E7372000E6A6176612E7574696C2E44617465686A81014B597419030000787077080000017DBE4ED85078707400007070707400013174000E302F3135202A202A202A202A203F74001572795461736B2E7279506172616D7328277279272974000744454641554C547372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000000000002740018E7B3BBE7BB9FE9BB98E8AEA4EFBC88E69C89E58F82EFBC8974000133740001317800); +INSERT INTO `qrtz_job_details` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME3', 'DEFAULT', NULL, 'com.fastbee.quartz.util.QuartzDisallowConcurrentExecution', '0', '1', '0', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C7708000000100000000174000F5441534B5F50524F5045525449455373720020636F6D2E666173746265652E71756172747A2E646F6D61696E2E5379734A6F6200000000000000010200084C000A636F6E63757272656E747400124C6A6176612F6C616E672F537472696E673B4C000E63726F6E45787072657373696F6E71007E00094C000C696E766F6B6554617267657471007E00094C00086A6F6247726F757071007E00094C00056A6F6249647400104C6A6176612F6C616E672F4C6F6E673B4C00076A6F624E616D6571007E00094C000D6D697366697265506F6C69637971007E00094C000673746174757371007E000978720029636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E42617365456E7469747900000000000000010200074C0008637265617465427971007E00094C000A63726561746554696D657400104C6A6176612F7574696C2F446174653B4C0006706172616D7371007E00034C000672656D61726B71007E00094C000B73656172636856616C756571007E00094C0008757064617465427971007E00094C000A75706461746554696D6571007E000C787074000561646D696E7372000E6A6176612E7574696C2E44617465686A81014B597419030000787077080000017DBE4ED85078707400007070707400013174000E302F3230202A202A202A202A203F74003872795461736B2E72794D756C7469706C65506172616D7328277279272C20747275652C20323030304C2C203331362E3530442C203130302974000744454641554C547372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000000000003740018E7B3BBE7BB9FE9BB98E8AEA4EFBC88E5A49AE58F82EFBC8974000133740001317800); +INSERT INTO `qrtz_job_details` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME4', 'SYSTEM', NULL, 'com.fastbee.quartz.util.QuartzJobExecution', '0', '0', '0', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C7708000000100000000174000F5441534B5F50524F5045525449455373720020636F6D2E666173746265652E71756172747A2E646F6D61696E2E5379734A6F6200000000000000010200084C000A636F6E63757272656E747400124C6A6176612F6C616E672F537472696E673B4C000E63726F6E45787072657373696F6E71007E00094C000C696E766F6B6554617267657471007E00094C00086A6F6247726F757071007E00094C00056A6F6249647400104C6A6176612F6C616E672F4C6F6E673B4C00076A6F624E616D6571007E00094C000D6D697366697265506F6C69637971007E00094C000673746174757371007E000978720029636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E42617365456E7469747900000000000000010200074C0008637265617465427971007E00094C000A63726561746554696D657400104C6A6176612F7574696C2F446174653B4C0006706172616D7371007E00034C000672656D61726B71007E00094C000B73656172636856616C756571007E00094C0008757064617465427971007E00094C000A75706461746554696D6571007E000C787074000561646D696E7372000E6A6176612E7574696C2E44617465686A81014B59741903000078707708000001869759B0B878707400007070707400013074000E3020302F34202A202A202A203F2074002070726F7047657453657276696365496D706C2E666574636850726F706572747974000653595354454D7372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000047400126D6F64627573E4BA91E7ABAFE8BDAEE8AFA274000131740001307800); +INSERT INTO `qrtz_job_details` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME5', 'SYSTEM', NULL, 'com.fastbee.quartz.util.QuartzDisallowConcurrentExecution', '0', '1', '0', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C7708000000100000000174000F5441534B5F50524F5045525449455373720020636F6D2E666173746265652E71756172747A2E646F6D61696E2E5379734A6F6200000000000000010200084C000A636F6E63757272656E747400124C6A6176612F6C616E672F537472696E673B4C000E63726F6E45787072657373696F6E71007E00094C000C696E766F6B6554617267657471007E00094C00086A6F6247726F757071007E00094C00056A6F6249647400104C6A6176612F6C616E672F4C6F6E673B4C00076A6F624E616D6571007E00094C000D6D697366697265506F6C69637971007E00094C000673746174757371007E000978720029636F6D2E666173746265652E636F6D6D6F6E2E636F72652E646F6D61696E2E42617365456E7469747900000000000000010200074C0008637265617465427971007E00094C000A63726561746554696D657400104C6A6176612F7574696C2F446174653B4C0006706172616D7371007E00034C000672656D61726B71007E00094C000B73656172636856616C756571007E00094C0008757064617465427971007E00094C000A75706461746554696D6571007E000C787074000561646D696E7372000E6A6176612E7574696C2E44617465686A81014B5974190300007870770800000187118D07E078707400007070707400013174000E3020302F31202A202A202A203F207400286465766963654A6F622E74696D696E6755706461746544657669636553746174757353746174757374000653595354454D7372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000000000005740012E8AEBEE5A487E5AE9AE697B6E4BBBBE58AA174000131740001307800); + +-- ---------------------------- +-- Table structure for qrtz_locks +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_locks`; +CREATE TABLE `qrtz_locks` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度名称', + `lock_name` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '悲观锁名称', + PRIMARY KEY (`sched_name`, `lock_name`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '存储的悲观锁信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_locks +-- ---------------------------- +INSERT INTO `qrtz_locks` VALUES ('RuoyiScheduler', 'STATE_ACCESS'); +INSERT INTO `qrtz_locks` VALUES ('RuoyiScheduler', 'TRIGGER_ACCESS'); + +-- ---------------------------- +-- Table structure for qrtz_paused_trigger_grps +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_paused_trigger_grps`; +CREATE TABLE `qrtz_paused_trigger_grps` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度名称', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + PRIMARY KEY (`sched_name`, `trigger_group`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '暂停的触发器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_paused_trigger_grps +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_scheduler_state +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_scheduler_state`; +CREATE TABLE `qrtz_scheduler_state` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度名称', + `instance_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '实例名称', + `last_checkin_time` bigint(13) NOT NULL COMMENT '上次检查时间', + `checkin_interval` bigint(13) NOT NULL COMMENT '检查间隔时间', + PRIMARY KEY (`sched_name`, `instance_name`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '调度器状态表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_scheduler_state +-- ---------------------------- +INSERT INTO `qrtz_scheduler_state` VALUES ('RuoyiScheduler', 'beecue1680023933011', 1680023961720, 15000); + +-- ---------------------------- +-- Table structure for qrtz_simple_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_simple_triggers`; +CREATE TABLE `qrtz_simple_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `repeat_count` bigint(7) NOT NULL COMMENT '重复的次数统计', + `repeat_interval` bigint(12) NOT NULL COMMENT '重复的间隔时间', + `times_triggered` bigint(10) NOT NULL COMMENT '已经触发的次数', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + CONSTRAINT `QRTZ_SIMPLE_TRIGGERS_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '简单触发器的信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_simple_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_simprop_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_simprop_triggers`; +CREATE TABLE `qrtz_simprop_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `str_prop_1` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'String类型的trigger的第一个参数', + `str_prop_2` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'String类型的trigger的第二个参数', + `str_prop_3` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'String类型的trigger的第三个参数', + `int_prop_1` int(11) NULL DEFAULT NULL COMMENT 'int类型的trigger的第一个参数', + `int_prop_2` int(11) NULL DEFAULT NULL COMMENT 'int类型的trigger的第二个参数', + `long_prop_1` bigint(20) NULL DEFAULT NULL COMMENT 'long类型的trigger的第一个参数', + `long_prop_2` bigint(20) NULL DEFAULT NULL COMMENT 'long类型的trigger的第二个参数', + `dec_prop_1` decimal(13, 4) NULL DEFAULT NULL COMMENT 'decimal类型的trigger的第一个参数', + `dec_prop_2` decimal(13, 4) NULL DEFAULT NULL COMMENT 'decimal类型的trigger的第二个参数', + `bool_prop_1` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'Boolean类型的trigger的第一个参数', + `bool_prop_2` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'Boolean类型的trigger的第二个参数', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + CONSTRAINT `QRTZ_SIMPROP_TRIGGERS_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '同步机制的行锁表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_simprop_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_triggers`; +CREATE TABLE `qrtz_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '触发器的名字', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '触发器所属组的名字', + `job_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_job_details表job_name的外键', + `job_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'qrtz_job_details表job_group的外键', + `description` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '相关介绍', + `next_fire_time` bigint(13) NULL DEFAULT NULL COMMENT '上一次触发时间(毫秒)', + `prev_fire_time` bigint(13) NULL DEFAULT NULL COMMENT '下一次触发时间(默认为-1表示不触发)', + `priority` int(11) NULL DEFAULT NULL COMMENT '优先级', + `trigger_state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '触发器状态', + `trigger_type` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '触发器的类型', + `start_time` bigint(13) NOT NULL COMMENT '开始时间', + `end_time` bigint(13) NULL DEFAULT NULL COMMENT '结束时间', + `calendar_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '日程表名称', + `misfire_instr` smallint(2) NULL DEFAULT NULL COMMENT '补偿执行的策略', + `job_data` blob NULL COMMENT '存放持久化job对象', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + INDEX `sched_name`(`sched_name`, `job_name`, `job_group`) USING BTREE, + CONSTRAINT `QRTZ_TRIGGERS_ibfk_1` FOREIGN KEY (`sched_name`, `job_name`, `job_group`) REFERENCES `qrtz_job_details` (`sched_name`, `job_name`, `job_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '触发器详细信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_triggers +-- ---------------------------- +INSERT INTO `qrtz_triggers` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME1', 'DEFAULT', 'TASK_CLASS_NAME1', 'DEFAULT', NULL, 1680023970000, 1680023960000, 5, 'WAITING', 'CRON', 1680023946000, 0, NULL, 2, ''); +INSERT INTO `qrtz_triggers` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME2', 'DEFAULT', 'TASK_CLASS_NAME2', 'DEFAULT', NULL, 1680023955000, -1, 5, 'PAUSED', 'CRON', 1680023947000, 0, NULL, 2, ''); +INSERT INTO `qrtz_triggers` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME3', 'DEFAULT', 'TASK_CLASS_NAME3', 'DEFAULT', NULL, 1680023960000, -1, 5, 'PAUSED', 'CRON', 1680023948000, 0, NULL, 2, ''); +INSERT INTO `qrtz_triggers` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME4', 'SYSTEM', 'TASK_CLASS_NAME4', 'SYSTEM', NULL, 1680024000000, -1, 5, 'WAITING', 'CRON', 1680023950000, 0, NULL, -1, ''); +INSERT INTO `qrtz_triggers` VALUES ('RuoyiScheduler', 'TASK_CLASS_NAME5', 'SYSTEM', 'TASK_CLASS_NAME5', 'SYSTEM', NULL, 1680024000000, -1, 5, 'WAITING', 'CRON', 1680023951000, 0, NULL, -1, ''); + + +-- ---------------------------- +-- Table structure for sys_auth_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_auth_user`; +CREATE TABLE `sys_auth_user` ( + `auth_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '授权ID', + `uuid` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '第三方平台用户唯一ID', + `user_id` bigint(20) NOT NULL COMMENT '系统用户ID', + `login_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录账号', + `user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户昵称', + `avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '头像地址', + `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户邮箱', + `source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户来源', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`auth_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '第三方授权表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_auth_user +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_config +-- ---------------------------- +DROP TABLE IF EXISTS `sys_config`; +CREATE TABLE `sys_config` ( + `config_id` int(5) NOT NULL AUTO_INCREMENT COMMENT '参数主键', + `config_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数名称', + `config_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数键名', + `config_value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '参数键值', + `config_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '系统内置(Y是 N否)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`config_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '参数配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_config +-- ---------------------------- +INSERT INTO `sys_config` VALUES (1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', '2021-12-15 21:36:18', '', NULL, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow'); +INSERT INTO `sys_config` VALUES (2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', '2021-12-15 21:36:18', '', NULL, '初始化密码 123456'); +INSERT INTO `sys_config` VALUES (3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', '2021-12-15 21:36:18', '', NULL, '深色主题theme-dark,浅色主题theme-light'); +INSERT INTO `sys_config` VALUES (5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'true', 'Y', 'admin', '2021-12-15 21:36:18', 'admin', '2021-12-24 22:43:33', '是否开启注册用户功能(true开启,false关闭)'); +INSERT INTO `sys_config` VALUES (6, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', '2023-03-10 23:29:21', '', NULL, '是否开启验证码功能(true开启,false关闭)'); + +-- ---------------------------- +-- Table structure for sys_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dept`; +CREATE TABLE `sys_dept` ( + `dept_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '部门id', + `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父部门id', + `ancestors` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '祖级列表', + `dept_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', + `order_num` int(4) NULL DEFAULT 0 COMMENT '显示顺序', + `leader` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '负责人', + `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '联系电话', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '部门状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`dept_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 110 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dept +-- ---------------------------- +INSERT INTO `sys_dept` VALUES (100, 0, '0', '蜂信物联', 0, 'FastBee', '15888888888', '164770707@qq.com', '0', '0', 'admin', '2021-12-15 21:36:18', 'admin', '2023-02-26 23:06:24'); +INSERT INTO `sys_dept` VALUES (101, 100, '0,100', '北京总公司', 1, '物美', '15888888888', '164770707@qq.com', '0', '0', 'admin', '2021-12-15 21:36:18', 'admin', '2022-03-09 16:49:53'); +INSERT INTO `sys_dept` VALUES (102, 100, '0,100', '深圳分公司', 2, '物美', '15888888888', '164770707@qq.com', '0', '0', 'admin', '2021-12-15 21:36:18', 'admin', '2023-02-26 23:06:07'); +INSERT INTO `sys_dept` VALUES (103, 101, '0,100,101', '研发部门', 1, '物美', '15888888888', '164770707@qq.com', '1', '0', 'admin', '2021-12-15 21:36:18', 'admin', '2022-02-01 23:12:40'); +INSERT INTO `sys_dept` VALUES (104, 101, '0,100,101', '市场部门', 2, '物美', '15888888888', '164770707@qq.com', '0', '0', 'admin', '2021-12-15 21:36:18', '', NULL); +INSERT INTO `sys_dept` VALUES (105, 101, '0,100,101', '测试部门', 3, '物美', '15888888888', '164770707@qq.com', '0', '0', 'admin', '2021-12-15 21:36:18', '', NULL); +INSERT INTO `sys_dept` VALUES (106, 101, '0,100,101', '财务部门', 4, '物美', '15888888888', '164770707@qq.com', '0', '0', 'admin', '2021-12-15 21:36:18', '', NULL); +INSERT INTO `sys_dept` VALUES (107, 101, '0,100,101', '运维部门', 5, '物美', '15888888888', '164770707@qq.com', '0', '0', 'admin', '2021-12-15 21:36:18', '', NULL); +INSERT INTO `sys_dept` VALUES (108, 102, '0,100,102', '市场部门', 1, '物美', '15888888888', '164770707@qq.com', '0', '0', 'admin', '2021-12-15 21:36:18', '', NULL); +INSERT INTO `sys_dept` VALUES (109, 102, '0,100,102', '财务部门', 2, '物美', '15888888888', '164770707@qq.com', '0', '0', 'admin', '2021-12-15 21:36:18', '', NULL); + +-- ---------------------------- +-- Table structure for sys_dict_data +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_data`; +CREATE TABLE `sys_dict_data` ( + `dict_code` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '字典编码', + `dict_sort` int(4) NULL DEFAULT 0 COMMENT '字典排序', + `dict_label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典标签', + `dict_value` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典键值', + `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', + `css_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '样式属性(其他样式扩展)', + `list_class` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '表格回显样式', + `is_default` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'N' COMMENT '是否默认(Y是 N否)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`dict_code`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 408 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典数据表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dict_data +-- ---------------------------- +INSERT INTO `sys_dict_data` VALUES (1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '性别男'); +INSERT INTO `sys_dict_data` VALUES (2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '性别女'); +INSERT INTO `sys_dict_data` VALUES (3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '性别未知'); +INSERT INTO `sys_dict_data` VALUES (4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '显示菜单'); +INSERT INTO `sys_dict_data` VALUES (5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '隐藏菜单'); +INSERT INTO `sys_dict_data` VALUES (6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (10, 1, '默认', 'DEFAULT', 'sys_job_group', '', '', 'Y', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '默认分组'); +INSERT INTO `sys_dict_data` VALUES (11, 2, '系统', 'SYSTEM', 'sys_job_group', '', '', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '系统分组'); +INSERT INTO `sys_dict_data` VALUES (12, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '系统默认是'); +INSERT INTO `sys_dict_data` VALUES (13, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '系统默认否'); +INSERT INTO `sys_dict_data` VALUES (14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '通知'); +INSERT INTO `sys_dict_data` VALUES (15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '公告'); +INSERT INTO `sys_dict_data` VALUES (16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '关闭状态'); +INSERT INTO `sys_dict_data` VALUES (18, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '新增操作'); +INSERT INTO `sys_dict_data` VALUES (19, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '修改操作'); +INSERT INTO `sys_dict_data` VALUES (20, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '删除操作'); +INSERT INTO `sys_dict_data` VALUES (21, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '授权操作'); +INSERT INTO `sys_dict_data` VALUES (22, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '导出操作'); +INSERT INTO `sys_dict_data` VALUES (23, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '导入操作'); +INSERT INTO `sys_dict_data` VALUES (24, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '强退操作'); +INSERT INTO `sys_dict_data` VALUES (25, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '生成操作'); +INSERT INTO `sys_dict_data` VALUES (26, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '清空操作'); +INSERT INTO `sys_dict_data` VALUES (27, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (28, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (100, 1, '属性', '1', 'iot_things_type', '', 'primary', 'Y', '0', 'admin', '2021-12-12 16:41:15', 'admin', '2021-12-15 22:49:37', ''); +INSERT INTO `sys_dict_data` VALUES (101, 2, '功能', '2', 'iot_things_type', '', 'success', 'Y', '0', 'admin', '2021-12-12 16:43:33', 'admin', '2021-12-14 16:33:11', ''); +INSERT INTO `sys_dict_data` VALUES (102, 3, '事件', '3', 'iot_things_type', NULL, 'warning', 'Y', '0', 'admin', '2021-12-12 16:46:04', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (103, 1, '整数', 'integer', 'iot_data_type', '', '', 'Y', '0', 'admin', '2021-12-12 20:20:46', 'admin', '2021-12-14 16:09:56', ''); +INSERT INTO `sys_dict_data` VALUES (104, 2, '小数', 'decimal', 'iot_data_type', NULL, 'default', 'Y', '0', 'admin', '2021-12-12 20:21:21', 'admin', '2021-12-15 22:51:07', NULL); +INSERT INTO `sys_dict_data` VALUES (105, 3, '布尔', 'bool', 'iot_data_type', NULL, 'default', 'Y', '0', 'admin', '2021-12-12 20:22:12', 'admin', '2021-12-15 22:51:02', NULL); +INSERT INTO `sys_dict_data` VALUES (106, 4, '枚举', 'enum', 'iot_data_type', NULL, 'default', 'Y', '0', 'admin', '2021-12-12 20:22:37', 'admin', '2021-12-15 22:50:57', NULL); +INSERT INTO `sys_dict_data` VALUES (107, 5, '字符串', 'string', 'iot_data_type', NULL, 'default', 'Y', '0', 'admin', '2021-12-12 20:22:54', 'admin', '2021-12-15 22:50:52', NULL); +INSERT INTO `sys_dict_data` VALUES (108, 1, '是', '1', 'iot_yes_no', '', 'default', 'Y', '0', 'admin', '2021-12-12 20:25:14', 'admin', '2022-01-02 13:39:09', ''); +INSERT INTO `sys_dict_data` VALUES (109, 2, '否', '0', 'iot_yes_no', '', 'default', 'Y', '0', 'admin', '2021-12-12 20:25:25', 'admin', '2022-01-02 13:39:15', ''); +INSERT INTO `sys_dict_data` VALUES (110, 6, '数组', 'array', 'iot_data_type', NULL, 'default', 'Y', '0', 'admin', '2021-12-13 18:18:04', 'admin', '2021-12-15 22:50:42', NULL); +INSERT INTO `sys_dict_data` VALUES (111, 1, '未发布', '1', 'iot_product_status', NULL, 'info', 'N', '0', 'admin', '2021-12-19 15:01:18', 'admin', '2021-12-19 15:01:55', NULL); +INSERT INTO `sys_dict_data` VALUES (112, 2, '已发布', '2', 'iot_product_status', NULL, 'success', 'N', '0', 'admin', '2021-12-19 15:01:43', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (113, 1, '直连设备', '1', 'iot_device_type', NULL, 'default', 'N', '0', 'admin', '2021-12-19 15:03:49', 'admin', '2021-12-19 15:10:13', NULL); +INSERT INTO `sys_dict_data` VALUES (114, 2, '网关设备', '2', 'iot_device_type', NULL, 'default', 'N', '0', 'admin', '2021-12-19 15:04:28', 'admin', '2023-02-09 16:25:46', NULL); +INSERT INTO `sys_dict_data` VALUES (116, 1, 'WIFI', '1', 'iot_network_method', NULL, 'default', 'N', '0', 'admin', '2021-12-19 15:07:35', 'admin', '2021-12-22 00:11:19', NULL); +INSERT INTO `sys_dict_data` VALUES (117, 2, '蜂窝(2G/3G/4G/5G)', '2', 'iot_network_method', NULL, 'default', 'N', '0', 'admin', '2021-12-19 15:08:30', 'admin', '2022-01-14 02:12:27', NULL); +INSERT INTO `sys_dict_data` VALUES (118, 3, '以太网', '3', 'iot_network_method', NULL, 'default', 'N', '0', 'admin', '2021-12-19 15:09:08', 'admin', '2022-01-14 02:12:39', NULL); +INSERT INTO `sys_dict_data` VALUES (119, 1, '简单认证', '1', 'iot_vertificate_method', NULL, 'default', 'N', '0', 'admin', '2021-12-19 15:13:16', 'admin', '2022-06-05 00:14:48', NULL); +INSERT INTO `sys_dict_data` VALUES (120, 2, '加密认证', '2', 'iot_vertificate_method', NULL, 'default', 'N', '0', 'admin', '2021-12-19 15:13:26', 'admin', '2022-06-05 00:14:57', NULL); +INSERT INTO `sys_dict_data` VALUES (122, 1, 'ESP8266/Arduino', '1', 'iot_device_chip', NULL, 'default', 'N', '0', 'admin', '2021-12-24 15:54:52', 'admin', '2021-12-24 16:07:31', NULL); +INSERT INTO `sys_dict_data` VALUES (123, 3, 'ESP32/Arduino', '2', 'iot_device_chip', NULL, 'default', 'N', '0', 'admin', '2021-12-24 15:55:04', 'admin', '2021-12-24 16:07:26', NULL); +INSERT INTO `sys_dict_data` VALUES (124, 2, 'ESP8266/RTOS', '3', 'iot_device_chip', NULL, 'default', 'N', '0', 'admin', '2021-12-24 15:56:08', 'admin', '2021-12-24 16:07:17', NULL); +INSERT INTO `sys_dict_data` VALUES (127, 4, 'ESP32/ESP-IDF', '4', 'iot_device_chip', NULL, 'default', 'N', '0', 'admin', '2021-12-24 16:07:54', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (128, 5, '树莓派/Python', '5', 'iot_device_chip', NULL, 'default', 'N', '0', 'admin', '2021-12-24 16:08:31', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (129, 0, '未激活', '1', 'iot_device_status', NULL, 'warning', 'N', '0', 'admin', '2021-12-27 22:21:04', 'admin', '2021-12-27 22:22:09', NULL); +INSERT INTO `sys_dict_data` VALUES (130, 0, '禁用', '2', 'iot_device_status', NULL, 'danger', 'N', '0', 'admin', '2021-12-27 22:21:22', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (131, 0, '在线', '3', 'iot_device_status', NULL, 'success', 'N', '0', 'admin', '2021-12-27 22:21:42', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (132, 0, '离线', '4', 'iot_device_status', NULL, 'info', 'N', '0', 'admin', '2021-12-27 22:22:01', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (133, 0, '启用', '1', 'iot_is_enable', NULL, 'success', 'N', '0', 'admin', '2022-01-12 23:25:08', 'admin', '2022-01-12 23:25:30', NULL); +INSERT INTO `sys_dict_data` VALUES (134, 0, '禁用', '0', 'iot_is_enable', NULL, 'info', 'N', '0', 'admin', '2022-01-12 23:25:19', 'admin', '2022-01-12 23:25:38', NULL); +INSERT INTO `sys_dict_data` VALUES (135, 0, '提醒通知', '1', 'iot_alert_level', NULL, 'success', 'N', '0', 'admin', '2022-01-13 14:58:10', 'admin', '2022-01-13 14:58:31', NULL); +INSERT INTO `sys_dict_data` VALUES (136, 0, '轻微问题', '2', 'iot_alert_level', NULL, 'warning', 'N', '0', 'admin', '2022-01-13 14:59:00', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (137, 0, '严重警告', '3', 'iot_alert_level', NULL, 'danger', 'N', '0', 'admin', '2022-01-13 14:59:16', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (138, 0, '不需要处理', '1', 'iot_process_status', NULL, 'default', 'N', '0', 'admin', '2022-01-13 15:06:03', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (139, 0, '未处理', '2', 'iot_process_status', NULL, 'default', 'N', '0', 'admin', '2022-01-13 15:06:14', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (140, 0, '已处理', '3', 'iot_process_status', NULL, 'default', 'N', '0', 'admin', '2022-01-13 15:06:24', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (141, 1, '属性上报', '1', 'iot_device_log_type', NULL, 'primary', 'N', '0', 'admin', '2022-01-13 15:10:32', 'admin', '2022-03-13 00:20:25', NULL); +INSERT INTO `sys_dict_data` VALUES (142, 3, '事件上报', '3', 'iot_device_log_type', NULL, 'danger', 'N', '0', 'admin', '2022-01-13 15:10:43', 'admin', '2022-03-13 00:21:00', NULL); +INSERT INTO `sys_dict_data` VALUES (143, 2, '功能调用', '2', 'iot_device_log_type', NULL, 'warning', 'N', '0', 'admin', '2022-01-13 15:10:55', 'admin', '2022-03-13 00:20:32', NULL); +INSERT INTO `sys_dict_data` VALUES (144, 4, '设备升级', '4', 'iot_device_log_type', NULL, 'success', 'N', '0', 'admin', '2022-01-13 15:11:08', 'admin', '2022-03-13 00:21:06', NULL); +INSERT INTO `sys_dict_data` VALUES (145, 5, '设备上线', '5', 'iot_device_log_type', NULL, 'success', 'N', '0', 'admin', '2022-01-13 15:11:23', 'admin', '2022-03-13 00:21:26', NULL); +INSERT INTO `sys_dict_data` VALUES (146, 6, '设备离线', '6', 'iot_device_log_type', NULL, 'info', 'N', '0', 'admin', '2022-01-13 15:11:32', 'admin', '2022-03-13 00:21:13', NULL); +INSERT INTO `sys_dict_data` VALUES (147, 4, '其他', '4', 'iot_network_method', NULL, 'default', 'N', '0', 'admin', '2022-01-14 02:12:49', 'admin', '2022-01-14 02:13:03', NULL); +INSERT INTO `sys_dict_data` VALUES (148, 6, '安卓/Android', '6', 'iot_device_chip', NULL, 'default', 'N', '0', 'admin', '2022-01-16 12:39:27', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (149, 7, '其他', '7', 'iot_device_chip', NULL, 'default', 'N', '0', 'admin', '2022-01-16 12:39:55', 'admin', '2022-01-16 12:40:13', NULL); +INSERT INTO `sys_dict_data` VALUES (150, 1, '小度平台', '1', 'oauth_platform', NULL, 'primary', 'N', '0', 'admin', '2022-02-07 20:29:23', 'admin', '2022-02-07 22:24:28', NULL); +INSERT INTO `sys_dict_data` VALUES (151, 2, '天猫精灵', '2', 'oauth_platform', NULL, 'danger', 'N', '0', 'admin', '2022-02-07 20:29:41', 'admin', '2022-02-07 22:23:14', NULL); +INSERT INTO `sys_dict_data` VALUES (152, 3, '小米小爱', '3', 'oauth_platform', NULL, 'success', 'N', '0', 'admin', '2022-02-07 20:30:07', 'admin', '2022-02-07 22:23:24', NULL); +INSERT INTO `sys_dict_data` VALUES (153, 4, '其他平台', '4', 'oauth_platform', NULL, 'warning', 'N', '0', 'admin', '2022-02-07 22:23:52', 'admin', '2022-02-07 22:24:02', NULL); +INSERT INTO `sys_dict_data` VALUES (154, 1, '微信登录', 'WECHAT', 'iot_social_platform', NULL, 'default', 'N', '0', 'admin', '2022-04-20 16:41:33', 'admin', '2023-09-22 10:27:54', NULL); +INSERT INTO `sys_dict_data` VALUES (155, 2, 'QQ登录', 'QQ', 'iot_social_platform', NULL, 'default', 'N', '0', 'admin', '2022-04-20 16:42:46', 'admin', '2023-09-22 10:28:03', NULL); +INSERT INTO `sys_dict_data` VALUES (156, 0, '启用', '0', 'iot_social_platform_status', NULL, 'success', 'N', '0', 'admin', '2022-04-20 17:02:48', 'admin', '2022-05-12 17:39:40', '启用'); +INSERT INTO `sys_dict_data` VALUES (157, 1, '未启用', '1', 'iot_social_platform_status', NULL, 'info', 'N', '0', 'admin', '2022-04-20 17:03:15', 'admin', '2022-05-21 13:44:13', '禁用'); +INSERT INTO `sys_dict_data` VALUES (158, 3, '支付宝', 'ALIPAY', 'iot_social_platform', NULL, 'default', 'N', '0', 'admin', '2022-05-12 17:49:24', 'admin', '2022-05-12 17:50:21', NULL); +INSERT INTO `sys_dict_data` VALUES (159, 1, '自动定位', '1', 'iot_location_way', NULL, 'success', 'N', '0', 'admin', '2022-05-21 13:46:51', 'admin', '2022-05-21 13:53:23', 'IP定位,精确到城市'); +INSERT INTO `sys_dict_data` VALUES (160, 2, '设备定位', '2', 'iot_location_way', NULL, 'warning', 'N', '0', 'admin', '2022-05-21 13:46:51', 'admin', '2022-05-21 13:49:21', '最精确定位'); +INSERT INTO `sys_dict_data` VALUES (161, 3, '自定义位置', '3', 'iot_location_way', NULL, 'primary', 'N', '0', 'admin', '2022-05-21 13:48:50', 'admin', '2022-05-21 13:55:45', '位置自定义'); +INSERT INTO `sys_dict_data` VALUES (162, 3, '简单+加密', '3', 'iot_vertificate_method', NULL, 'default', 'N', '0', 'admin', '2022-06-05 00:15:46', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (163, 1, '未使用', '1', 'iot_auth_status', NULL, 'info', 'N', '0', 'admin', '2022-06-07 17:39:22', 'admin', '2022-06-07 17:40:10', NULL); +INSERT INTO `sys_dict_data` VALUES (164, 2, '已使用', '2', 'iot_auth_status', NULL, 'success', 'N', '0', 'admin', '2022-06-07 17:40:01', 'admin', '2022-06-07 23:21:49', NULL); +INSERT INTO `sys_dict_data` VALUES (165, 7, '对象', 'object', 'iot_data_type', NULL, 'default', 'N', '0', 'admin', '2023-02-09 16:20:57', 'admin', '2023-02-09 16:21:08', NULL); +INSERT INTO `sys_dict_data` VALUES (166, 3, '监控设备', '3', 'iot_device_type', NULL, 'default', 'N', '0', 'admin', '2023-02-09 16:26:00', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (198, 0, 'MQTT', 'MQTT', 'iot_transport_type', NULL, 'default', 'N', '0', 'admin', '2023-02-28 16:35:40', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (199, 1, 'TCP', 'TCP', 'iot_transport_type', NULL, 'default', 'N', '0', 'admin', '2023-02-28 16:35:51', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (201, 2, 'UDP', 'UDP', 'iot_transport_type', NULL, 'default', 'N', '0', 'admin', '2023-02-28 16:36:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (230, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2023-03-10 23:28:32', '', NULL, '其他操作'); +INSERT INTO `sys_dict_data` VALUES (231, 0, '事件上报', '3', 'iot_event_type', NULL, 'danger', 'N', '0', 'admin', '2023-03-29 00:25:28', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (232, 0, '设备上线', '5', 'iot_event_type', NULL, 'success', 'N', '0', 'admin', '2023-03-29 00:25:52', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (233, 0, '设备离线', '6', 'iot_event_type', NULL, 'info', 'N', '0', 'admin', '2023-03-29 00:26:09', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (234, 0, '服务下发', '1', 'iot_function_type', NULL, 'primary', 'N', '0', 'admin', '2023-03-29 00:38:26', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (235, 0, '属性获取', '2', 'iot_function_type', NULL, 'success', 'N', '0', 'admin', '2023-03-29 00:38:44', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (237, 0, '读写', '0', 'iot_data_read_write', NULL, 'primary', 'N', '0', 'admin', '2023-04-09 02:12:05', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (238, 0, '只读', '1', 'iot_data_read_write', NULL, 'info', 'N', '0', 'admin', '2023-04-09 02:12:19', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (239, 0, '全部设备', '1', 'oat_update_limit', NULL, 'default', 'N', '0', 'admin', '2023-04-09 23:57:06', 'admin', '2023-04-11 11:53:57', NULL); +INSERT INTO `sys_dict_data` VALUES (240, 1, '指定设备', '2', 'oat_update_limit', NULL, 'default', 'N', '0', 'admin', '2023-04-11 11:53:28', 'admin', '2023-04-11 11:53:52', NULL); +INSERT INTO `sys_dict_data` VALUES (241, 4, 'GB28181', 'GB28181', 'iot_transport_type', NULL, 'primary', 'N', '0', 'admin', '2023-05-12 14:25:39', 'admin', '2023-05-12 14:26:09', NULL); +INSERT INTO `sys_dict_data` VALUES (242, 1, '02(读离散量输入)', '2', 'iot_modbus_status_code', NULL, 'default', 'N', '0', 'admin', '2023-07-03 10:16:48', 'admin', '2023-07-03 10:17:35', NULL); +INSERT INTO `sys_dict_data` VALUES (243, 3, '04(读输入寄存器)', '4', 'iot_modbus_status_code', NULL, 'default', 'N', '0', 'admin', '2023-07-03 10:17:18', 'admin', '2023-07-03 10:17:58', NULL); +INSERT INTO `sys_dict_data` VALUES (247, 4, '微信开放平台网站应用', 'wechat_open_web', 'iot_social_platform', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (248, 5, '微信开放平台移动应用', 'wechat_open_mobile', 'iot_social_platform', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:29:14', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (249, 6, '微信开放平台小程序', 'wechat_open_mini_program', 'iot_social_platform', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:38:12', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (250, 0, '16位 无符号', 'ushort', 'iot_modbus_data_type', NULL, 'default', 'N', '0', 'admin', '2023-09-04 14:11:54', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (251, 1, '16位 有符号', 'short', 'iot_modbus_data_type', NULL, 'default', 'N', '0', 'admin', '2023-09-04 14:12:26', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (252, 2, '32位 有符号(ABCD)', 'long-ABCD', 'iot_modbus_data_type', NULL, 'default', 'N', '0', 'admin', '2023-09-04 14:12:53', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (253, 3, '32位 有符号(CDAB)', 'long-CDAB', 'iot_modbus_data_type', NULL, 'default', 'N', '0', 'admin', '2023-09-04 14:13:21', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (254, 4, '32位 无符号(ABCD)', 'ulong-ABCD', 'iot_modbus_data_type', NULL, 'default', 'N', '0', 'admin', '2023-09-04 14:13:42', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (255, 5, '32位 无符号(CDAB)', 'ulong-CDAB', 'iot_modbus_data_type', NULL, 'default', 'N', '0', 'admin', '2023-09-04 14:14:06', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (256, 6, '32位 浮点数(ABCD)', 'float-ABCD', 'iot_modbus_data_type', NULL, 'default', 'N', '0', 'admin', '2023-09-04 14:14:28', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (257, 7, '32位 浮点数(CDAB)', 'float-CDAB', 'iot_modbus_data_type', NULL, 'default', 'N', '0', 'admin', '2023-09-04 14:14:50', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (258, 8, '位', 'bit', 'iot_modbus_data_type', NULL, 'default', 'N', '0', 'admin', '2023-09-04 14:15:13', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (259, 0, '电灯', 'LIGHT', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (260, 0, '空调', 'AIR_CONDITION', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (261, 0, '窗帘', 'CURTAIN', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (262, 0, '窗纱', 'CURT_SIMP', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (263, 0, '插座', 'SOCKET', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (264, 0, '开关', 'SWITCH', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (265, 0, '冰箱', 'FRIDGE', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (266, 0, '净水器', 'WATER_PURIFIER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (267, 0, '加湿器', 'HUMIDIFIER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (268, 0, '除湿器', 'DEHUMIDIFIER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (269, 0, '电磁炉', 'INDUCTION_COOKER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (270, 0, '空气净化器', 'AIR_PURIFIER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (271, 0, '洗衣机', 'WASHING_MACHINE', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (272, 0, '热水器', 'WATER_HEATER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (273, 0, '燃气灶', 'GAS_STOVE', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (274, 0, '电视机', 'TV_SET', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (275, 0, '网络盒子', 'OTT_BOX', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (276, 0, '油烟机', 'RANGE_HOOD', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (277, 0, '电风扇', 'FAN', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (278, 0, '投影仪', 'PROJECTOR', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (279, 0, '扫地机器人', 'SWEEPING_ROBOT', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (280, 0, '热水壶', 'KETTLE', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (281, 0, '微波炉', 'MICROWAVE_OVEN', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (282, 0, '压力锅', 'PRESSURE_COOKER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (283, 0, '电饭煲', 'RICE_COOKER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (284, 0, '破壁机', 'HIGH_SPEED_BLENDER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (285, 0, '新风机', 'AIR_FRESHER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (286, 0, '晾衣架', 'CLOTHES_RACK', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (287, 0, '烤箱设备', 'OVEN', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (288, 0, '蒸烤箱', 'STEAM_OVEN', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (289, 0, '蒸箱', 'STEAM_BOX', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (290, 0, '电暖器', 'HEATER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (291, 0, '开窗器', 'WINDOW_OPENER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (292, 0, '摄像头', 'WEBCAM', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (293, 0, '相机', 'CAMERA', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (294, 0, '机器人', 'ROBOT', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (295, 0, '打印机', 'PRINTER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (296, 0, '饮水机', 'WATER_COOLER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (297, 0, '鱼缸', 'FISH_TANK', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (298, 0, '浇花器', 'WATERING_DEVICE', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (299, 0, '机顶盒', 'SET_TOP_BOX', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (300, 0, '香薰机', 'AROMATHERAPY_MACHINE', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (301, 0, 'DVD', 'DVD', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (302, 0, '鞋柜', 'SHOE_CABINET', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (303, 0, '走步机', 'WALKING_MACHINE', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (304, 0, '跑步机', 'TREADMILL', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (305, 0, '床', 'BED', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (306, 0, '浴霸', 'YUBA', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (307, 0, '花洒', 'SHOWER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (308, 0, '浴缸', 'BATHTUB', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (309, 0, '消毒柜', 'DISINFECTION_CABINET', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (310, 0, '洗碗机', 'DISHWASHER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (311, 0, '沙发品类', 'SOFA', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (312, 0, '门铃', 'DOOR_BELL', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (313, 0, '电梯', 'ELEVATOR', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (314, 0, '体重秤', 'WEIGHT_SCALE', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (315, 0, '体脂秤', 'BODY_FAT_SCALE', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (316, 0, '壁挂炉', 'WALL_HUNG_GAS_BOILER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (317, 0, '特定设备的组合场景', 'SCENE_TRIGGER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, '描述特定设备的组合场景,设备之间没有相互关联,无特定操作顺序。 例如“打开睡眠模式”包括关灯和锁上房门,但是关灯和锁上房门之间没有必然联系,可以先关灯然后锁上房门,也可以先锁上房门后关灯'); +INSERT INTO `sys_dict_data` VALUES (318, 0, '特定设备的组合场景', 'ACTIVITY_TRIGGER', 'dueros_related_device', NULL, 'default', 'N', '0', 'admin', '2023-08-23 11:28:15', '', NULL, '描述特定设备的组合场景。场景中的设备必须以指定顺序操作。如“观看优酷视频”场景中必须先打开电视机,然后打开HDMI1'); +INSERT INTO `sys_dict_data` VALUES (319, 0, '打开', 'turnOn', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (320, 0, '关闭', 'turnOff', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (321, 0, '定时打开', 'timingTurnOn', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (322, 0, '定时关闭', 'timingTurnOff', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (323, 0, '暂停', 'pause', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (324, 0, '继续', 'continue', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (325, 0, '设置颜色', 'setColor', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (326, 0, '设置灯光色温', 'setColorTemperature', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (327, 0, '增高灯光色温', 'incrementColorTemperature', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (328, 0, '降低灯光色温', 'decrementColorTemperature', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (329, 0, '设置灯光亮度', 'setBrightnessPercentage', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (330, 0, '调亮灯光', 'incrementBrightnessPercentage', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (331, 0, '调暗灯光', 'decrementBrightnessPercentage', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (332, 0, '设置功率', 'setPower', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (333, 0, '增大功率', 'incrementPower', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (334, 0, '减小功率', 'decrementPower', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (335, 0, '升高温度', 'incrementTemperature', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (336, 0, '降低温度', 'decrementTemperature', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (337, 0, '设置温度', 'setTemperature', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (338, 0, '增加风速', 'incrementFanSpeed', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (339, 0, '减小风速', 'decrementFanSpeed', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (340, 0, '设置风速', 'setFanSpeed', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (341, 0, '设置档位', 'setGear', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (342, 0, '设置模式', 'setMode', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (343, 0, '取消设置的模式', 'unSetMode', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (344, 0, '定时设置模式', 'timingSetMode', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (345, 0, '定时取消设置的模式', 'timingUnsetMode', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (346, 0, '调高音量', 'incrementVolume', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (347, 0, '调低音量', 'decrementVolume', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (348, 0, '设置音量', 'setVolume', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (349, 0, '设置静音状态', 'setVolumeMute', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (350, 0, '上一个频道', 'decrementTVChannel', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (351, 0, '下一个频道', 'incrementTVChannel', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (352, 0, '设置频道', 'setTVChannel', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (353, 0, '返回上个频道', 'returnTVChannel', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (354, 0, '开始充电', 'chargeTurnOn', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (355, 0, '停止充电', 'chargeTurnOff', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (356, 0, '查询开关状态', 'getTurnOnState', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (357, 0, '查询油量', 'getOilCapacity', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (358, 0, '查询电量', 'getElectricityCapacity', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (359, 0, '上锁/解锁', 'setLockState', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (360, 0, '查询锁状态', 'getLockState', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (361, 0, '设置吸力', 'setSuction', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (362, 0, '设置水量', 'setWaterLevel', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (363, 0, '设置清扫位置', 'setCleaningLocation', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (364, 0, '执行自定义复杂动作', 'setComplexActions', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (365, 0, '设置移动方向', 'setDirection', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (366, 0, '打印', 'submitPrint', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (367, 0, '查询PM2.5', 'getAirPM25', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (368, 0, '查询PM10', 'getAirPM10', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (369, 0, '查询二氧化碳含量', 'getCO2Quantity', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (370, 0, '查询空气质量', 'getAirQualityIndex', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (371, 0, '查询温度(当前温度和目标温度)', 'getTemperature', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (372, 0, '查询当前温度', 'getTemperatureReading', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (373, 0, '查询目标温度', 'getTargetTemperature', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (374, 0, '查询湿度', 'getHumidity', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (375, 0, '查询目标湿度', 'getTargetHumidity', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (376, 0, '查询水质', 'getWaterQuality', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (377, 0, '查询设备所有状态', 'getState', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (378, 0, '查询剩余时间', 'getTimeLeft', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (379, 0, '查询运行状态', 'getRunningStatus', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (380, 0, '查询运行时间', 'getRunningTime', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (381, 0, '查询设备所在位置', 'getLocation', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (382, 0, '设备定时', 'setTimer', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (383, 0, '取消设备定时', 'timingCancel', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (384, 0, '设备复位', 'reset', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (385, 0, '升高高度', 'incrementHeight', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (386, 0, '降低高度', 'decrementHeight', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (387, 0, '设置摆风角度', 'setSwingAngle', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (388, 0, '查询风速', 'getFanSpeed', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (389, 0, '设置湿度模式', 'setHumidity', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (390, 0, '增大湿度', 'incrementHumidity', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (391, 0, '降低湿度', 'decrementHumidity', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (392, 0, '增大雾量', 'incrementMist', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (393, 0, '见效雾量', 'decrementMist', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (394, 0, '设置雾量', 'setMist', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (395, 0, '设备启动', 'startUp', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (396, 0, '设置电梯楼层', 'setFloor', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (397, 0, '电梯按下', 'decrementFloor', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (398, 0, '电梯按上', 'incrementFloor', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (399, 0, '增加速度', 'incrementSpeed', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (400, 0, '降低速度', 'decrementSpeed', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (401, 0, '设置速度', 'setSpeed', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (402, 0, '获取速度', 'getSpeed', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (403, 0, '获取跑步信息', 'getMotionInfo', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (404, 0, '打开灶眼', 'turnOnBurner', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (405, 0, '关闭灶眼', 'turnOffBurner', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (406, 0, '定时打开灶眼', 'timingTurnOnBurner', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (407, 0, '定时关闭灶眼', 'timingTurnOffBurner', 'dueros_operate_type', NULL, 'default', 'N', '0', 'admin', '2023-09-22 10:35:15', '', NULL, NULL); + +-- ---------------------------- +-- Table structure for sys_dict_type +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_type`; +CREATE TABLE `sys_dict_type` ( + `dict_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '字典主键', + `dict_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典名称', + `dict_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '字典类型', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`dict_id`) USING BTREE, + UNIQUE INDEX `dict_type`(`dict_type`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 134 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '字典类型表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dict_type +-- ---------------------------- +INSERT INTO `sys_dict_type` VALUES (1, '用户性别', 'sys_user_sex', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '用户性别列表'); +INSERT INTO `sys_dict_type` VALUES (2, '菜单状态', 'sys_show_hide', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '菜单状态列表'); +INSERT INTO `sys_dict_type` VALUES (3, '系统开关', 'sys_normal_disable', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '系统开关列表'); +INSERT INTO `sys_dict_type` VALUES (4, '任务状态', 'sys_job_status', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '任务状态列表'); +INSERT INTO `sys_dict_type` VALUES (5, '任务分组', 'sys_job_group', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '任务分组列表'); +INSERT INTO `sys_dict_type` VALUES (6, '系统是否', 'sys_yes_no', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '系统是否列表'); +INSERT INTO `sys_dict_type` VALUES (7, '通知类型', 'sys_notice_type', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '通知类型列表'); +INSERT INTO `sys_dict_type` VALUES (8, '通知状态', 'sys_notice_status', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '通知状态列表'); +INSERT INTO `sys_dict_type` VALUES (9, '操作类型', 'sys_oper_type', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '操作类型列表'); +INSERT INTO `sys_dict_type` VALUES (10, '系统状态', 'sys_common_status', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '登录状态列表'); +INSERT INTO `sys_dict_type` VALUES (100, '物模型类别', 'iot_things_type', '0', 'admin', '2021-12-12 16:39:47', 'admin', '2021-12-15 22:49:19', '属性、动作、事件'); +INSERT INTO `sys_dict_type` VALUES (101, '数据类型', 'iot_data_type', '0', 'admin', '2021-12-12 20:16:48', 'admin', '2021-12-12 20:17:54', 'integer、decimal、bool、string、enum'); +INSERT INTO `sys_dict_type` VALUES (102, '是否', 'iot_yes_no', '0', 'admin', '2021-12-12 20:24:51', 'admin', '2021-12-19 15:12:35', '是、否'); +INSERT INTO `sys_dict_type` VALUES (103, '产品状态', 'iot_product_status', '0', 'admin', '2021-12-19 15:00:13', '', NULL, '未发布、已发布(不能修改)'); +INSERT INTO `sys_dict_type` VALUES (104, '设备类型', 'iot_device_type', '0', 'admin', '2021-12-19 15:03:06', '', NULL, '直连设备、网关子设备、网关设备'); +INSERT INTO `sys_dict_type` VALUES (105, '联网方式', 'iot_network_method', '0', 'admin', '2021-12-19 15:07:12', 'admin', '2022-01-14 02:11:58', 'wifi、蜂窝(2G/3G/4G/5G)、以太网、其他'); +INSERT INTO `sys_dict_type` VALUES (106, '认证方式', 'iot_vertificate_method', '0', 'admin', '2021-12-19 15:11:48', 'admin', '2022-06-05 12:57:02', '1=简单认证、2=加密认证、3=简单+加密'); +INSERT INTO `sys_dict_type` VALUES (107, '设备芯片', 'iot_device_chip', '0', 'admin', '2021-12-24 15:53:27', 'admin', '2022-01-22 00:14:23', 'ESP8266、ESP32、树莓派'); +INSERT INTO `sys_dict_type` VALUES (109, '设备状态', 'iot_device_status', '0', 'admin', '2021-12-27 22:19:55', 'admin', '2021-12-27 22:20:13', '未激活、禁用、在线、离线'); +INSERT INTO `sys_dict_type` VALUES (110, '是否启用', 'iot_is_enable', '0', 'admin', '2022-01-12 23:24:01', 'admin', '2022-01-12 23:24:15', '启用、禁用'); +INSERT INTO `sys_dict_type` VALUES (111, '告警类型', 'iot_alert_level', '0', 'admin', '2022-01-13 14:56:44', 'admin', '2022-01-13 15:04:46', '1=提醒通知,2=轻微问题,3=严重警告'); +INSERT INTO `sys_dict_type` VALUES (112, '处理状态', 'iot_process_status', '0', 'admin', '2022-01-13 15:04:06', 'admin', '2022-01-13 15:06:39', '1=不需要处理,2=未处理,3=已处理'); +INSERT INTO `sys_dict_type` VALUES (113, '设备日志类型', 'iot_device_log_type', '0', 'admin', '2022-01-13 15:09:49', 'admin', '2022-03-13 00:22:43', '1=属性上报,2=调用功能,3=事件上报,4=设备升级,5=设备上线,6=设备离线'); +INSERT INTO `sys_dict_type` VALUES (114, 'Oauth开放平台', 'oauth_platform', '0', 'admin', '2022-02-07 20:27:48', 'admin', '2022-05-21 13:44:50', '1=小度,2=天猫精灵,3=小爱,4=其他'); +INSERT INTO `sys_dict_type` VALUES (115, '第三方登录平台', 'iot_social_platform', '0', 'admin', '2022-04-12 15:28:13', 'admin', '2022-04-12 15:37:48', 'Wechat、QQ、'); +INSERT INTO `sys_dict_type` VALUES (116, '第三方登录平台状态', 'iot_social_platform_status', '0', 'admin', '2022-04-20 17:02:13', 'admin', '2022-04-20 17:02:23', '第三方登录平台状态'); +INSERT INTO `sys_dict_type` VALUES (117, '设备定位方式', 'iot_location_way', '0', 'admin', '2022-05-21 13:45:16', 'admin', '2022-05-21 13:46:06', '1=IP自动定位,2=设备定位,3=自定义'); +INSERT INTO `sys_dict_type` VALUES (118, '授权码状态', 'iot_auth_status', '0', 'admin', '2022-06-07 17:38:56', '', NULL, '1=未分配,2=使用中'); +INSERT INTO `sys_dict_type` VALUES (119, 'SipID状态', 'sip_gen_status', '0', 'admin', '2023-02-19 15:43:36', 'admin', '2023-02-19 15:45:54', '1=未使用,2=使用中'); +INSERT INTO `sys_dict_type` VALUES (120, '监控设备类型', 'video_type', '0', 'admin', '2023-02-22 01:06:38', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (121, '通道类型', 'channel_type', '0', 'admin', '2023-02-22 01:11:51', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (122, '轮询方式', 'data_collect_type', '0', 'admin', '2023-02-28 13:55:45', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (123, '批量采集时间', 'iot_modbus_poll_time', '0', 'admin', '2023-02-28 14:38:21', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (124, '寄存器功能码', 'iot_modbus_status_code', '0', 'admin', '2023-02-28 15:19:02', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (125, '传输协议类型', 'iot_transport_type', '0', 'admin', '2023-02-28 16:35:20', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (126, '设备事件类型', 'iot_event_type', '0', 'admin', '2023-03-29 00:24:51', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (127, '指令下发类型', 'iot_function_type', '0', 'admin', '2023-03-29 00:37:51', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (128, '读写类型', 'iot_data_read_write', '0', 'admin', '2023-04-09 02:11:14', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (129, '升级范围', 'oat_update_limit', '0', 'admin', '2023-04-09 23:51:45', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (130, '云存储平台类型', 'oss_platform_type', '0', 'admin', '2023-04-12 00:26:09', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (131, 'modbus数据类型', 'iot_modbus_data_type', '0', 'admin', '2023-09-04 13:54:17', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (132, '小度音箱关联设备', 'dueros_related_device', '0', 'admin', '2023-09-22 09:45:15', 'admin', '2023-09-22 09:45:15', '小度音箱支持的设备、场景类型,在设备下配置关联'); +INSERT INTO `sys_dict_type` VALUES (133, '小度音箱操作类型', 'dueros_operate_type', '0', 'admin', '2023-09-22 09:45:15', 'admin', '2023-09-22 09:45:15', '小度音箱智能家居设备操作类型,在产品物模型下配置'); + +-- ---------------------------- +-- Table structure for sys_job +-- ---------------------------- +DROP TABLE IF EXISTS `sys_job`; +CREATE TABLE `sys_job` ( + `job_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID', + `job_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '任务名称', + `job_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名', + `invoke_target` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串', + `cron_expression` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT 'cron执行表达式', + `misfire_policy` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '3' COMMENT '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + `concurrent` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '是否并发执行(0允许 1禁止)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1暂停)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注信息', + PRIMARY KEY (`job_id`, `job_name`, `job_group`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '定时任务调度表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_job +-- ---------------------------- +INSERT INTO `sys_job` VALUES (2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(\'ry\')', '0/15 * * * * ?', '3', '1', '1', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_job` VALUES (4, 'modbus云端轮询', 'SYSTEM', 'propGetServiceImpl.fetchProperty', '0 0/1 * * * ? ', '1', '0', '0', 'admin', '2023-02-28 17:28:03', 'admin', '2023-04-08 22:07:42', ''); +INSERT INTO `sys_job` VALUES (5, '设备定时任务', 'SYSTEM', 'deviceJob.timingUpdateDeviceStatusStatus', '0 0/1 * * * ? ', '1', '1', '1', 'admin', '2023-03-24 10:57:48', 'admin', '2023-03-31 21:14:55', ''); + +-- ---------------------------- +-- Table structure for sys_job_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_job_log`; +CREATE TABLE `sys_job_log` ( + `job_log_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务日志ID', + `job_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务名称', + `job_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '任务组名', + `invoke_target` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串', + `job_message` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '日志信息', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '执行状态(0正常 1失败)', + `exception_info` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '异常信息', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`job_log_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '定时任务调度日志表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_job_log +-- ---------------------------- +INSERT INTO `sys_job_log` VALUES (1, 'modbus云端轮询', 'SYSTEM', 'propGetServiceImpl.fetchProperty', 'modbus云端轮询 总共耗时:2毫秒', '0', '', '2023-09-26 22:22:00'); +INSERT INTO `sys_job_log` VALUES (2, '监控在线状态更新', 'SYSTEM', 'deviceJob.updateSipDeviceOnlineStatus(90)', '监控在线状态更新 总共耗时:8毫秒', '0', '', '2023-09-26 22:22:00'); +INSERT INTO `sys_job_log` VALUES (3, 'modbus云端轮询', 'SYSTEM', 'propGetServiceImpl.fetchProperty', 'modbus云端轮询 总共耗时:1毫秒', '0', '', '2023-09-26 22:23:00'); + +-- ---------------------------- +-- Table structure for sys_logininfor +-- ---------------------------- +DROP TABLE IF EXISTS `sys_logininfor`; +CREATE TABLE `sys_logininfor` ( + `info_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '访问ID', + `user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户账号', + `ipaddr` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录IP地址', + `login_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '登录地点', + `browser` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '浏览器类型', + `os` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作系统', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '登录状态(0成功 1失败)', + `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '提示消息', + `login_time` datetime(0) NULL DEFAULT NULL COMMENT '访问时间', + PRIMARY KEY (`info_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统访问记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_logininfor +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID', + `menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称', + `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父菜单ID', + `order_num` int(4) NULL DEFAULT 0 COMMENT '显示顺序', + `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '路由地址', + `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件路径', + `query` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由参数', + `is_frame` int(1) NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)', + `is_cache` int(1) NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)', + `menu_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', + `visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', + `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识', + `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '#' COMMENT '菜单图标', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`menu_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3050 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 4, 'system', NULL, '', 1, 0, 'M', '0', '0', '', 'system', 'admin', '2021-12-15 21:36:18', 'admin', '2023-09-16 16:42:52', '系统管理目录'); +INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 5, 'monitor', NULL, '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', '2021-12-15 21:36:18', 'admin', '2023-08-24 17:21:20', '系统监控目录'); +INSERT INTO `sys_menu` VALUES (3, '系统工具', 0, 6, 'tool', NULL, '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', '2021-12-15 21:36:18', 'admin', '2023-08-24 17:21:28', '系统工具目录'); +INSERT INTO `sys_menu` VALUES (4, '蜂信物联', 0, 9, 'http://fastbee.cn', NULL, '', 0, 0, 'M', '0', '0', '', 'guide', 'admin', '2021-12-15 21:36:18', 'admin', '2023-08-24 17:21:59', '若依官网地址'); +INSERT INTO `sys_menu` VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', '2021-12-15 21:36:18', '', NULL, '用户管理菜单'); +INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', '2021-12-15 21:36:18', '', NULL, '角色管理菜单'); +INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', '2021-12-15 21:36:18', '', NULL, '菜单管理菜单'); +INSERT INTO `sys_menu` VALUES (103, '部门管理', 1, 4, 'dept', 'system/dept/index', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', '2021-12-15 21:36:18', '', NULL, '部门管理菜单'); +INSERT INTO `sys_menu` VALUES (104, '岗位管理', 1, 5, 'post', 'system/post/index', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', '2021-12-15 21:36:18', '', NULL, '岗位管理菜单'); +INSERT INTO `sys_menu` VALUES (105, '字典管理', 1, 6, 'dict', 'system/dict/index', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', '2021-12-15 21:36:18', '', NULL, '字典管理菜单'); +INSERT INTO `sys_menu` VALUES (106, '参数设置', 1, 7, 'config', 'system/config/index', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', '2021-12-15 21:36:18', '', NULL, '参数设置菜单'); +INSERT INTO `sys_menu` VALUES (107, '通知公告', 1, 8, 'notice', 'system/notice/index', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', '2021-12-15 21:36:18', '', NULL, '通知公告菜单'); +INSERT INTO `sys_menu` VALUES (108, '日志管理', 1, 9, 'log', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', '2021-12-15 21:36:18', '', NULL, '日志管理菜单'); +INSERT INTO `sys_menu` VALUES (109, '在线用户', 2, 1, 'online', 'monitor/online/index', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', '2021-12-15 21:36:18', '', NULL, '在线用户菜单'); +INSERT INTO `sys_menu` VALUES (110, '定时任务', 2, 2, 'job', 'monitor/job/index', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', '2021-12-15 21:36:18', '', NULL, '定时任务菜单'); +INSERT INTO `sys_menu` VALUES (111, '数据监控', 2, 3, 'druid', 'monitor/druid/index', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', '2021-12-15 21:36:18', '', NULL, '数据监控菜单'); +INSERT INTO `sys_menu` VALUES (112, '服务监控', 2, 4, 'server', 'monitor/server/index', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', '2021-12-15 21:36:18', '', NULL, '服务监控菜单'); +INSERT INTO `sys_menu` VALUES (113, '缓存监控', 2, 5, 'cache', 'monitor/cache/index', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', '2021-12-15 21:36:18', '', NULL, '缓存监控菜单'); +INSERT INTO `sys_menu` VALUES (114, '表单构建', 3, 1, 'build', 'tool/build/index', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', '2021-12-15 21:36:18', '', NULL, '表单构建菜单'); +INSERT INTO `sys_menu` VALUES (115, '代码生成', 3, 2, 'gen', 'tool/gen/index', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', '2021-12-15 21:36:18', '', NULL, '代码生成菜单'); +INSERT INTO `sys_menu` VALUES (116, '系统接口', 3, 3, 'swagger', 'tool/swagger/index', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', '2021-12-15 21:36:18', '', NULL, '系统接口菜单'); +INSERT INTO `sys_menu` VALUES (124, '缓存列表', 2, 6, 'cacheList', 'monitor/cache/list', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', '2023-03-10 23:22:42', '', NULL, '缓存列表菜单'); +INSERT INTO `sys_menu` VALUES (500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', '2021-12-15 21:36:18', '', NULL, '操作日志菜单'); +INSERT INTO `sys_menu` VALUES (501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', '2021-12-15 21:36:18', '', NULL, '登录日志菜单'); +INSERT INTO `sys_menu` VALUES (1001, '用户查询', 100, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1002, '用户新增', 100, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1003, '用户修改', 100, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1004, '用户删除', 100, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1005, '用户导出', 100, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1006, '用户导入', 100, 6, '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1007, '重置密码', 100, 7, '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1008, '角色查询', 101, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1009, '角色新增', 101, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1010, '角色修改', 101, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1011, '角色删除', 101, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1012, '角色导出', 101, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1013, '菜单查询', 102, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1014, '菜单新增', 102, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1015, '菜单修改', 102, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1016, '菜单删除', 102, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1017, '部门查询', 103, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1018, '部门新增', 103, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1019, '部门修改', 103, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1020, '部门删除', 103, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1021, '岗位查询', 104, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1022, '岗位新增', 104, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1023, '岗位修改', 104, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1024, '岗位删除', 104, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1025, '岗位导出', 104, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1026, '字典查询', 105, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1027, '字典新增', 105, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1028, '字典修改', 105, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1029, '字典删除', 105, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1030, '字典导出', 105, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1031, '参数查询', 106, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1032, '参数新增', 106, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1033, '参数修改', 106, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1034, '参数删除', 106, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1035, '参数导出', 106, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1036, '公告查询', 107, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1037, '公告新增', 107, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1038, '公告修改', 107, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1039, '公告删除', 107, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1040, '操作查询', 500, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1041, '操作删除', 500, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1042, '日志导出', 500, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1043, '登录查询', 501, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1044, '登录删除', 501, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1045, '日志导出', 501, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1046, '在线查询', 109, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1047, '批量强退', 109, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1048, '单条强退', 109, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1049, '任务查询', 110, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1050, '任务新增', 110, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1051, '任务修改', 110, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1052, '任务删除', 110, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1053, '状态修改', 110, 5, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1054, '任务导出', 110, 7, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1055, '生成查询', 115, 1, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1056, '生成修改', 115, 2, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1057, '生成删除', 115, 3, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1058, '导入代码', 115, 2, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1059, '预览代码', 115, 4, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1060, '生成代码', 115, 5, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1065, '账户解锁', 501, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', '2023-03-10 23:23:04', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2000, '设备管理', 0, 0, 'iot', NULL, NULL, 1, 0, 'M', '0', '0', '', 'iot', 'admin', '2021-12-15 23:57:06', 'admin', '2021-12-26 23:55:54', ''); +INSERT INTO `sys_menu` VALUES (2049, '通用物模型', 2000, 1, 'template', 'iot/template/index', NULL, 1, 0, 'C', '0', '0', 'iot:template:list', 'model', 'admin', '2021-12-16 00:41:28', 'admin', '2021-12-26 23:56:09', '通用物模型菜单'); +INSERT INTO `sys_menu` VALUES (2050, '通用物模型查询', 2049, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:template:query', '#', 'admin', '2021-12-16 00:41:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2051, '通用物模型新增', 2049, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:template:add', '#', 'admin', '2021-12-16 00:41:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2052, '通用物模型修改', 2049, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:template:edit', '#', 'admin', '2021-12-16 00:41:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2053, '通用物模型删除', 2049, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:template:remove', '#', 'admin', '2021-12-16 00:41:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2054, '通用物模型导出', 2049, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:template:export', '#', 'admin', '2021-12-16 00:41:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2001, '产品分类', 2000, 2, 'category', 'iot/category/index', NULL, 1, 0, 'C', '0', '0', 'iot:category:list', 'category', 'admin', '2021-12-16 00:40:02', 'admin', '2021-12-26 23:56:20', '产品分类菜单'); +INSERT INTO `sys_menu` VALUES (2002, '产品分类查询', 2001, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:category:query', '#', 'admin', '2021-12-16 00:40:02', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2003, '产品分类新增', 2001, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:category:add', '#', 'admin', '2021-12-16 00:40:02', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2004, '产品分类修改', 2001, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:category:edit', '#', 'admin', '2021-12-16 00:40:02', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2005, '产品分类删除', 2001, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:category:remove', '#', 'admin', '2021-12-16 00:40:02', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2006, '产品分类导出', 2001, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:category:export', '#', 'admin', '2021-12-16 00:40:02', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2043, '产品管理', 2000, 3, 'product', 'iot/product/index', NULL, 1, 0, 'C', '0', '0', 'iot:product:list', 'product', 'admin', '2021-12-16 00:41:18', 'admin', '2021-12-26 23:58:44', '产品菜单'); +INSERT INTO `sys_menu` VALUES (2044, '产品查询', 2043, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:product:query', '#', 'admin', '2021-12-16 00:41:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2045, '产品新增', 2043, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:product:add', '#', 'admin', '2021-12-16 00:41:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2046, '产品修改', 2043, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:product:edit', '#', 'admin', '2021-12-16 00:41:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2047, '产品删除', 2043, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:product:remove', '#', 'admin', '2021-12-16 00:41:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2048, '产品导出', 2043, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:product:export', '#', 'admin', '2021-12-16 00:41:18', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2019, '设备分组', 2000, 4, 'group', 'iot/group/index', NULL, 1, 0, 'C', '0', '0', 'iot:group:list', 'group', 'admin', '2021-12-16 00:40:31', 'admin', '2021-12-26 23:56:54', '设备分组菜单'); +INSERT INTO `sys_menu` VALUES (2020, '设备分组查询', 2019, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:group:query', '#', 'admin', '2021-12-16 00:40:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2021, '设备分组新增', 2019, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:group:add', '#', 'admin', '2021-12-16 00:40:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2022, '设备分组修改', 2019, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:group:edit', '#', 'admin', '2021-12-16 00:40:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2023, '设备分组删除', 2019, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:group:remove', '#', 'admin', '2021-12-16 00:40:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2024, '设备分组导出', 2019, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:group:export', '#', 'admin', '2021-12-16 00:40:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2007, '设备管理', 2000, 5, 'device', 'iot/device/index', NULL, 1, 0, 'C', '0', '0', 'iot:device:list', 'device', 'admin', '2021-12-16 00:40:12', 'admin', '2022-01-08 15:47:14', '设备菜单'); +INSERT INTO `sys_menu` VALUES (2008, '设备查询', 2007, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:device:query', '#', 'admin', '2021-12-16 00:40:12', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2009, '设备新增', 2007, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:device:add', '#', 'admin', '2021-12-16 00:40:12', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2010, '设备修改', 2007, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:device:edit', '#', 'admin', '2021-12-16 00:40:12', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2011, '设备删除', 2007, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:device:remove', '#', 'admin', '2021-12-16 00:40:12', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2012, '设备导出', 2007, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:device:export', '#', 'admin', '2021-12-16 00:40:12', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (3007, '协议管理', 2000, 6, 'protocol', 'iot/protocol/index', NULL, 1, 0, 'C', '0', '0', 'iot:protocol:list', 'connect', 'admin', '2023-02-28 11:26:54', 'admin', '2023-04-12 22:02:14', '协议菜单'); +INSERT INTO `sys_menu` VALUES (3008, '协议查询', 3007, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:protocol:query', '#', 'admin', '2023-02-28 11:26:54', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (3009, '协议新增', 3007, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:protocol:add', '#', 'admin', '2023-02-28 11:26:54', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (3010, '协议修改', 3007, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:protocol:edit', '#', 'admin', '2023-02-28 11:26:54', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (3011, '协议删除', 3007, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:protocol:remove', '#', 'admin', '2023-02-28 11:26:54', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (3012, '协议导出', 3007, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:protocol:export', '#', 'admin', '2023-02-28 11:26:55', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2104, 'EMQ管理', 0, 8, 'http://81.71.97.58:18083/', NULL, NULL, 0, 0, 'M', '0', '0', '', 'mq', 'admin', '2022-02-26 00:42:12', 'admin', '2023-09-26 00:07:24', ''); +INSERT INTO `sys_menu` VALUES (2123, '新闻分类', 1, 10, 'newsCategory', 'iot/newsCategory/index', NULL, 1, 0, 'C', '0', '0', 'iot:newsCategory:list', 'category', 'admin', '2022-04-11 16:47:27', 'admin', '2022-05-12 17:20:51', '新闻分类菜单'); +INSERT INTO `sys_menu` VALUES (2124, '新闻分类查询', 2123, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:newsCategory:query', '#', 'admin', '2022-04-11 16:47:27', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2125, '新闻分类新增', 2123, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:newsCategory:add', '#', 'admin', '2022-04-11 16:47:27', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2126, '新闻分类修改', 2123, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:newsCategory:edit', '#', 'admin', '2022-04-11 16:47:27', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2127, '新闻分类删除', 2123, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:newsCategory:remove', '#', 'admin', '2022-04-11 16:47:27', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2128, '新闻分类导出', 2123, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:newsCategory:export', '#', 'admin', '2022-04-11 16:47:27', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2129, '新闻资讯', 1, 11, 'news', 'iot/news/index', NULL, 1, 0, 'C', '0', '0', 'iot:news:list', 'documentation', 'admin', '2022-04-11 16:47:46', 'admin', '2022-05-12 17:20:58', '新闻资讯菜单'); +INSERT INTO `sys_menu` VALUES (2130, '新闻资讯查询', 2129, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:news:query', '#', 'admin', '2022-04-11 16:47:46', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2131, '新闻资讯新增', 2129, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:news:add', '#', 'admin', '2022-04-11 16:47:46', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2132, '新闻资讯修改', 2129, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:news:edit', '#', 'admin', '2022-04-11 16:47:46', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2133, '新闻资讯删除', 2129, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:news:remove', '#', 'admin', '2022-04-11 16:47:46', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2134, '新闻资讯导出', 2129, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:news:export', '#', 'admin', '2022-04-11 16:47:46', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2136, '产品授权码查询', 2043, 6, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:authorize:query', '#', 'admin', '2022-04-11 17:17:53', 'admin', '2022-06-04 21:21:40', ''); +INSERT INTO `sys_menu` VALUES (2137, '产品授权码新增', 2043, 7, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:authorize:add', '#', 'admin', '2022-04-11 17:17:53', 'admin', '2022-06-04 21:21:59', ''); +INSERT INTO `sys_menu` VALUES (2138, '产品授权码修改', 2043, 8, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:authorize:edit', '#', 'admin', '2022-04-11 17:17:53', 'admin', '2022-06-04 21:22:08', ''); +INSERT INTO `sys_menu` VALUES (2139, '产品授权码删除', 2043, 9, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:authorize:remove', '#', 'admin', '2022-04-11 17:17:53', 'admin', '2022-06-04 21:22:26', ''); +INSERT INTO `sys_menu` VALUES (2140, '产品授权码导出', 2043, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:authorize:export', '#', 'admin', '2022-04-11 17:17:53', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2141, '三方登录', 1, 12, 'platform', 'iot/platform/index', NULL, 1, 1, 'C', '0', '0', 'iot:platform:list', 'cloud', 'admin', '2022-04-11 18:55:34', 'admin', '2023-08-23 11:26:52', ''); +INSERT INTO `sys_menu` VALUES (2142, '平台查询', 2142, 1, '', NULL, NULL, 1, 0, 'F', '0', '0', 'iot:platform:query', '#', 'admin', '2022-04-11 19:10:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2147, '设备分享', 2007, 6, '', NULL, NULL, 1, 0, 'F', '0', '0', 'iot:device:share', '#', 'admin', '2022-06-10 01:08:40', 'admin', '2022-06-10 01:10:46', ''); +INSERT INTO `sys_menu` VALUES (2148, '设备定时', 2007, 7, '', NULL, NULL, 1, 0, 'F', '0', '0', 'iot:device:timer', '#', 'admin', '2022-06-10 01:10:30', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2149, '大屏展示', 3047, 5, 'https://iot.fastbee.cn/bigScreen', NULL, NULL, 0, 0, 'M', '0', '0', '', 'monitor-a', 'admin', '2022-08-13 22:32:11', 'admin', '2023-08-23 23:11:11', ''); +INSERT INTO `sys_menu` VALUES (3031, 'Netty管理', 0, 3, 'netty', NULL, NULL, 1, 0, 'M', '0', '0', '', 'mq', 'admin', '2022-02-26 00:42:12', 'admin', '2023-09-26 00:11:57', ''); +INSERT INTO `sys_menu` VALUES (3032, '客户端', 3031, 1, 'client', 'iot/netty/clients', NULL, 1, 0, 'C', '0', '0', 'monitor:server:list', 'client', 'admin', '2022-02-26 00:45:39', 'admin', '2023-08-23 23:38:08', ''); +INSERT INTO `sys_menu` VALUES (3033, '事件日志', 2000, 1, 'log', 'iot/log/index', NULL, 1, 0, 'F', '0', '0', 'iot:event:list', '#', 'admin', '2023-03-28 14:23:52', '', NULL, '事件日志菜单'); +INSERT INTO `sys_menu` VALUES (3034, '事件日志查询', 3033, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:event:query', '#', 'admin', '2023-03-28 14:23:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (3035, '事件日志新增', 3033, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:event:add', '#', 'admin', '2023-03-28 14:23:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (3036, '事件日志修改', 3033, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:event:edit', '#', 'admin', '2023-03-28 14:23:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (3037, '事件日志删除', 3033, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:event:remove', '#', 'admin', '2023-03-28 14:23:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (3038, '事件日志导出', 3033, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:event:export', '#', 'admin', '2023-03-28 14:23:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (3047, '数据可视化', 0, 7, 'screen', NULL, NULL, 1, 0, 'M', '0', '0', '', 'monitor-a', 'admin', '2023-08-23 23:09:28', 'admin', '2023-08-24 17:21:48', ''); +INSERT INTO `sys_menu` VALUES (3048, 'Mqtt统计', 3031, 2, 'mqtt', 'iot/netty/mqtt', NULL, 1, 0, 'C', '0', '0', 'monitor:server:list', 'monitor', 'admin', '2023-08-23 23:40:28', 'admin', '2023-08-23 23:40:38', ''); + +-- ---------------------------- +-- Table structure for sys_notice +-- ---------------------------- +DROP TABLE IF EXISTS `sys_notice`; +CREATE TABLE `sys_notice` ( + `notice_id` int(4) NOT NULL AUTO_INCREMENT COMMENT '公告ID', + `notice_title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告标题', + `notice_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '公告类型(1通知 2公告)', + `notice_content` longblob NULL COMMENT '公告内容', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '公告状态(0正常 1关闭)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`notice_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知公告表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_notice +-- ---------------------------- +INSERT INTO `sys_notice` VALUES (1, 'FastBeeV1.2版本发布', '2', 0x3C703EE8BF99E698AFE6B58BE8AF95E58685E5AEB9EFBC8CE696B0E78988E69CACE58A9FE883BDEFBC9A3C2F703E3C6F6C3E3C6C693EE694AFE68C81E5A49AE7A79FE688B73C2F6C693E3C6C693EE694AFE68C81E8AEBEE5A487E58886E4BAAB3C2F6C693E3C6C693EE694AFE68C81E697B6E5BA8FE695B0E68DAEE5BA933C2F6C693E3C6C693EE7AE80E58D95E8AEA4E8AF81E5928CE58AA0E5AF86E8AEA4E8AF81E7BB9FE4B8803C2F6C693E3C2F6F6C3E, '0', 'admin', '2021-12-15 21:36:18', 'admin', '2023-09-26 21:21:30', '管理员'); +INSERT INTO `sys_notice` VALUES (2, 'FastBee sdk支持树莓派', '1', 0x3C703EE8BF99E698AFE6B58BE8AF95E58685E5AEB9EFBC8CE79BAEE5898D73646BE694AFE68C81E79A84E78988E69CAC3A3C2F703E3C703E3C62723E3C2F703E3C703E3C62723E3C2F703E3C6F6C3E3C6C693E41726475696E6F20657370383236363C2F6C693E3C6C693E41726475696E6F2065737033323C2F6C693E3C6C693E6573702D6964663C2F6C693E3C6C693E72617370626572727920E6A091E88E93E6B4BE3C2F6C693E3C2F6F6C3E, '0', 'admin', '2021-12-15 21:36:18', 'admin', '2023-09-26 21:21:41', '管理员'); + +-- ---------------------------- +-- Table structure for sys_oper_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_oper_log`; +CREATE TABLE `sys_oper_log` ( + `oper_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '操作日志ID', + `title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '模块标题', + `business_type` int(2) NULL DEFAULT 0 COMMENT '业务类型(0其它 1新增 2修改 3删除)', + `method` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '方法名称', + `request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求方式', + `operator_type` int(1) NULL DEFAULT 0 COMMENT '操作类别(0其它 1后台用户 2手机端用户)', + `oper_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作人员', + `dept_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门名称', + `oper_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求URL', + `oper_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '主机地址', + `oper_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '操作地点', + `oper_param` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '请求参数', + `json_result` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '返回参数', + `status` int(1) NULL DEFAULT 0 COMMENT '操作状态(0正常 1异常)', + `error_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '错误消息', + `oper_time` datetime(0) NULL DEFAULT NULL COMMENT '操作时间', + PRIMARY KEY (`oper_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '操作日志记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_oper_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_post +-- ---------------------------- +DROP TABLE IF EXISTS `sys_post`; +CREATE TABLE `sys_post` ( + `post_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '岗位ID', + `post_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位编码', + `post_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '岗位名称', + `post_sort` int(4) NOT NULL COMMENT '显示顺序', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '状态(0正常 1停用)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`post_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '岗位信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_post +-- ---------------------------- +INSERT INTO `sys_post` VALUES (1, 'ceo', '董事长', 1, '0', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_post` VALUES (2, 'se', '项目经理', 2, '0', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_post` VALUES (3, 'hr', '人力资源', 3, '0', 'admin', '2021-12-15 21:36:18', '', NULL, ''); +INSERT INTO `sys_post` VALUES (4, 'user', '普通员工', 4, '0', 'admin', '2021-12-15 21:36:18', '', NULL, ''); + +-- ---------------------------- +-- Table structure for sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID', + `role_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称', + `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色权限字符串', + `role_sort` int(4) NOT NULL COMMENT '显示顺序', + `data_scope` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', + `dept_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '部门树选择项是否关联显示', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`role_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role +-- ---------------------------- +INSERT INTO `sys_role` VALUES (1, '超级管理员', 'admin', 1, '1', 1, 1, '0', '0', 'admin', '2021-12-15 21:36:18', '', NULL, '超级管理员'); +INSERT INTO `sys_role` VALUES (2, '设备租户', 'tenant', 2, '5', 1, 1, '0', '0', 'admin', '2021-12-16 16:41:30', 'admin', '2023-04-12 19:53:34', '管理产品和设备'); +INSERT INTO `sys_role` VALUES (3, '普通用户', 'general', 3, '5', 1, 1, '0', '0', 'admin', '2021-12-15 21:36:18', 'admin', '2023-02-22 08:17:37', '设备的最终用户,只能管理设备和分组'); +INSERT INTO `sys_role` VALUES (4, '游客', 'visitor', 4, '1', 1, 1, '0', '0', 'admin', '2021-12-16 16:44:30', 'admin', '2023-04-12 22:11:46', '只能查询和新增系统数据'); +INSERT INTO `sys_role` VALUES (5, '管理员', 'manager', 5, '1', 1, 1, '0', '0', 'admin', '2022-06-10 13:54:29', 'admin', '2023-04-12 19:50:29', '普通管理员'); + +-- ---------------------------- +-- Table structure for sys_role_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_dept`; +CREATE TABLE `sys_role_dept` ( + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `dept_id` bigint(20) NOT NULL COMMENT '部门ID', + PRIMARY KEY (`role_id`, `dept_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和部门关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role_dept +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_menu`; +CREATE TABLE `sys_role_menu` ( + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', + PRIMARY KEY (`role_id`, `menu_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role_menu +-- ---------------------------- +INSERT INTO sys_role_menu VALUES (2, 1); +INSERT INTO sys_role_menu VALUES (2, 4); +INSERT INTO sys_role_menu VALUES (2, 107); +INSERT INTO sys_role_menu VALUES (2, 1036); +INSERT INTO sys_role_menu VALUES (2, 2000); +INSERT INTO sys_role_menu VALUES (2, 2001); +INSERT INTO sys_role_menu VALUES (2, 2002); +INSERT INTO sys_role_menu VALUES (2, 2003); +INSERT INTO sys_role_menu VALUES (2, 2004); +INSERT INTO sys_role_menu VALUES (2, 2005); +INSERT INTO sys_role_menu VALUES (2, 2006); +INSERT INTO sys_role_menu VALUES (2, 2007); +INSERT INTO sys_role_menu VALUES (2, 2008); +INSERT INTO sys_role_menu VALUES (2, 2009); +INSERT INTO sys_role_menu VALUES (2, 2010); +INSERT INTO sys_role_menu VALUES (2, 2011); +INSERT INTO sys_role_menu VALUES (2, 2012); +INSERT INTO sys_role_menu VALUES (2, 2019); +INSERT INTO sys_role_menu VALUES (2, 2020); +INSERT INTO sys_role_menu VALUES (2, 2021); +INSERT INTO sys_role_menu VALUES (2, 2022); +INSERT INTO sys_role_menu VALUES (2, 2023); +INSERT INTO sys_role_menu VALUES (2, 2024); +INSERT INTO sys_role_menu VALUES (2, 2043); +INSERT INTO sys_role_menu VALUES (2, 2044); +INSERT INTO sys_role_menu VALUES (2, 2045); +INSERT INTO sys_role_menu VALUES (2, 2046); +INSERT INTO sys_role_menu VALUES (2, 2047); +INSERT INTO sys_role_menu VALUES (2, 2048); +INSERT INTO sys_role_menu VALUES (2, 2049); +INSERT INTO sys_role_menu VALUES (2, 2050); +INSERT INTO sys_role_menu VALUES (2, 2051); +INSERT INTO sys_role_menu VALUES (2, 2052); +INSERT INTO sys_role_menu VALUES (2, 2053); +INSERT INTO sys_role_menu VALUES (2, 2054); +INSERT INTO sys_role_menu VALUES (2, 2129); +INSERT INTO sys_role_menu VALUES (2, 2130); +INSERT INTO sys_role_menu VALUES (2, 2136); +INSERT INTO sys_role_menu VALUES (2, 2137); +INSERT INTO sys_role_menu VALUES (2, 2138); +INSERT INTO sys_role_menu VALUES (2, 2139); +INSERT INTO sys_role_menu VALUES (2, 2140); +INSERT INTO sys_role_menu VALUES (2, 2147); +INSERT INTO sys_role_menu VALUES (2, 2148); +INSERT INTO sys_role_menu VALUES (3, 1); +INSERT INTO sys_role_menu VALUES (3, 4); +INSERT INTO sys_role_menu VALUES (3, 107); +INSERT INTO sys_role_menu VALUES (3, 1036); +INSERT INTO sys_role_menu VALUES (3, 2000); +INSERT INTO sys_role_menu VALUES (3, 2007); +INSERT INTO sys_role_menu VALUES (3, 2008); +INSERT INTO sys_role_menu VALUES (3, 2009); +INSERT INTO sys_role_menu VALUES (3, 2010); +INSERT INTO sys_role_menu VALUES (3, 2011); +INSERT INTO sys_role_menu VALUES (3, 2012); +INSERT INTO sys_role_menu VALUES (3, 2019); +INSERT INTO sys_role_menu VALUES (3, 2020); +INSERT INTO sys_role_menu VALUES (3, 2021); +INSERT INTO sys_role_menu VALUES (3, 2022); +INSERT INTO sys_role_menu VALUES (3, 2023); +INSERT INTO sys_role_menu VALUES (3, 2024); +INSERT INTO sys_role_menu VALUES (3, 2067); +INSERT INTO sys_role_menu VALUES (3, 2068); +INSERT INTO sys_role_menu VALUES (3, 2085); +INSERT INTO sys_role_menu VALUES (3, 2086); +INSERT INTO sys_role_menu VALUES (3, 2087); +INSERT INTO sys_role_menu VALUES (3, 2088); +INSERT INTO sys_role_menu VALUES (3, 2089); +INSERT INTO sys_role_menu VALUES (3, 2090); +INSERT INTO sys_role_menu VALUES (3, 2129); +INSERT INTO sys_role_menu VALUES (3, 2130); +INSERT INTO sys_role_menu VALUES (3, 2147); +INSERT INTO sys_role_menu VALUES (3, 2148); +INSERT INTO sys_role_menu VALUES (3, 2168); +INSERT INTO sys_role_menu VALUES (3, 2169); +INSERT INTO sys_role_menu VALUES (3, 2170); +INSERT INTO sys_role_menu VALUES (3, 2171); +INSERT INTO sys_role_menu VALUES (3, 2172); +INSERT INTO sys_role_menu VALUES (4, 1); +INSERT INTO sys_role_menu VALUES (4, 2); +INSERT INTO sys_role_menu VALUES (4, 3); +INSERT INTO sys_role_menu VALUES (4, 4); +INSERT INTO sys_role_menu VALUES (4, 100); +INSERT INTO sys_role_menu VALUES (4, 101); +INSERT INTO sys_role_menu VALUES (4, 102); +INSERT INTO sys_role_menu VALUES (4, 103); +INSERT INTO sys_role_menu VALUES (4, 104); +INSERT INTO sys_role_menu VALUES (4, 105); +INSERT INTO sys_role_menu VALUES (4, 106); +INSERT INTO sys_role_menu VALUES (4, 107); +INSERT INTO sys_role_menu VALUES (4, 108); +INSERT INTO sys_role_menu VALUES (4, 109); +INSERT INTO sys_role_menu VALUES (4, 110); +INSERT INTO sys_role_menu VALUES (4, 111); +INSERT INTO sys_role_menu VALUES (4, 112); +INSERT INTO sys_role_menu VALUES (4, 113); +INSERT INTO sys_role_menu VALUES (4, 114); +INSERT INTO sys_role_menu VALUES (4, 115); +INSERT INTO sys_role_menu VALUES (4, 116); +INSERT INTO sys_role_menu VALUES (4, 500); +INSERT INTO sys_role_menu VALUES (4, 501); +INSERT INTO sys_role_menu VALUES (4, 1001); +INSERT INTO sys_role_menu VALUES (4, 1008); +INSERT INTO sys_role_menu VALUES (4, 1013); +INSERT INTO sys_role_menu VALUES (4, 1017); +INSERT INTO sys_role_menu VALUES (4, 1021); +INSERT INTO sys_role_menu VALUES (4, 1026); +INSERT INTO sys_role_menu VALUES (4, 1031); +INSERT INTO sys_role_menu VALUES (4, 1036); +INSERT INTO sys_role_menu VALUES (4, 1040); +INSERT INTO sys_role_menu VALUES (4, 1043); +INSERT INTO sys_role_menu VALUES (4, 1046); +INSERT INTO sys_role_menu VALUES (4, 1049); +INSERT INTO sys_role_menu VALUES (4, 1055); +INSERT INTO sys_role_menu VALUES (4, 2000); +INSERT INTO sys_role_menu VALUES (4, 2001); +INSERT INTO sys_role_menu VALUES (4, 2002); +INSERT INTO sys_role_menu VALUES (4, 2003); +INSERT INTO sys_role_menu VALUES (4, 2007); +INSERT INTO sys_role_menu VALUES (4, 2008); +INSERT INTO sys_role_menu VALUES (4, 2009); +INSERT INTO sys_role_menu VALUES (4, 2019); +INSERT INTO sys_role_menu VALUES (4, 2020); +INSERT INTO sys_role_menu VALUES (4, 2021); +INSERT INTO sys_role_menu VALUES (4, 2043); +INSERT INTO sys_role_menu VALUES (4, 2044); +INSERT INTO sys_role_menu VALUES (4, 2045); +INSERT INTO sys_role_menu VALUES (4, 2049); +INSERT INTO sys_role_menu VALUES (4, 2050); +INSERT INTO sys_role_menu VALUES (4, 2051); +INSERT INTO sys_role_menu VALUES (4, 2104); +INSERT INTO sys_role_menu VALUES (4, 2123); +INSERT INTO sys_role_menu VALUES (4, 2124); +INSERT INTO sys_role_menu VALUES (4, 2125); +INSERT INTO sys_role_menu VALUES (4, 2129); +INSERT INTO sys_role_menu VALUES (4, 2130); +INSERT INTO sys_role_menu VALUES (4, 2131); +INSERT INTO sys_role_menu VALUES (4, 2136); +INSERT INTO sys_role_menu VALUES (4, 2137); +INSERT INTO sys_role_menu VALUES (4, 2141); +INSERT INTO sys_role_menu VALUES (4, 2147); +INSERT INTO sys_role_menu VALUES (4, 2148); +INSERT INTO sys_role_menu VALUES (4, 2149); +INSERT INTO sys_role_menu VALUES (4, 3007); +INSERT INTO sys_role_menu VALUES (4, 3008); +INSERT INTO sys_role_menu VALUES (4, 3009); +INSERT INTO sys_role_menu VALUES (4, 3031); +INSERT INTO sys_role_menu VALUES (4, 3032); +INSERT INTO sys_role_menu VALUES (4, 3033); +INSERT INTO sys_role_menu VALUES (4, 3034); +INSERT INTO sys_role_menu VALUES (4, 3035); +INSERT INTO sys_role_menu VALUES (4, 3047); +INSERT INTO sys_role_menu VALUES (5, 1); +INSERT INTO sys_role_menu VALUES (5, 2); +INSERT INTO sys_role_menu VALUES (5, 3); +INSERT INTO sys_role_menu VALUES (5, 4); +INSERT INTO sys_role_menu VALUES (5, 100); +INSERT INTO sys_role_menu VALUES (5, 101); +INSERT INTO sys_role_menu VALUES (5, 102); +INSERT INTO sys_role_menu VALUES (5, 103); +INSERT INTO sys_role_menu VALUES (5, 104); +INSERT INTO sys_role_menu VALUES (5, 105); +INSERT INTO sys_role_menu VALUES (5, 106); +INSERT INTO sys_role_menu VALUES (5, 107); +INSERT INTO sys_role_menu VALUES (5, 108); +INSERT INTO sys_role_menu VALUES (5, 109); +INSERT INTO sys_role_menu VALUES (5, 110); +INSERT INTO sys_role_menu VALUES (5, 111); +INSERT INTO sys_role_menu VALUES (5, 112); +INSERT INTO sys_role_menu VALUES (5, 113); +INSERT INTO sys_role_menu VALUES (5, 114); +INSERT INTO sys_role_menu VALUES (5, 115); +INSERT INTO sys_role_menu VALUES (5, 116); +INSERT INTO sys_role_menu VALUES (5, 124); +INSERT INTO sys_role_menu VALUES (5, 500); +INSERT INTO sys_role_menu VALUES (5, 501); +INSERT INTO sys_role_menu VALUES (5, 1001); +INSERT INTO sys_role_menu VALUES (5, 1002); +INSERT INTO sys_role_menu VALUES (5, 1003); +INSERT INTO sys_role_menu VALUES (5, 1004); +INSERT INTO sys_role_menu VALUES (5, 1005); +INSERT INTO sys_role_menu VALUES (5, 1006); +INSERT INTO sys_role_menu VALUES (5, 1007); +INSERT INTO sys_role_menu VALUES (5, 1008); +INSERT INTO sys_role_menu VALUES (5, 1009); +INSERT INTO sys_role_menu VALUES (5, 1010); +INSERT INTO sys_role_menu VALUES (5, 1011); +INSERT INTO sys_role_menu VALUES (5, 1012); +INSERT INTO sys_role_menu VALUES (5, 1013); +INSERT INTO sys_role_menu VALUES (5, 1014); +INSERT INTO sys_role_menu VALUES (5, 1015); +INSERT INTO sys_role_menu VALUES (5, 1016); +INSERT INTO sys_role_menu VALUES (5, 1017); +INSERT INTO sys_role_menu VALUES (5, 1018); +INSERT INTO sys_role_menu VALUES (5, 1019); +INSERT INTO sys_role_menu VALUES (5, 1020); +INSERT INTO sys_role_menu VALUES (5, 1021); +INSERT INTO sys_role_menu VALUES (5, 1022); +INSERT INTO sys_role_menu VALUES (5, 1023); +INSERT INTO sys_role_menu VALUES (5, 1024); +INSERT INTO sys_role_menu VALUES (5, 1025); +INSERT INTO sys_role_menu VALUES (5, 1026); +INSERT INTO sys_role_menu VALUES (5, 1027); +INSERT INTO sys_role_menu VALUES (5, 1028); +INSERT INTO sys_role_menu VALUES (5, 1029); +INSERT INTO sys_role_menu VALUES (5, 1030); +INSERT INTO sys_role_menu VALUES (5, 1031); +INSERT INTO sys_role_menu VALUES (5, 1032); +INSERT INTO sys_role_menu VALUES (5, 1033); +INSERT INTO sys_role_menu VALUES (5, 1034); +INSERT INTO sys_role_menu VALUES (5, 1035); +INSERT INTO sys_role_menu VALUES (5, 1036); +INSERT INTO sys_role_menu VALUES (5, 1037); +INSERT INTO sys_role_menu VALUES (5, 1038); +INSERT INTO sys_role_menu VALUES (5, 1039); +INSERT INTO sys_role_menu VALUES (5, 1040); +INSERT INTO sys_role_menu VALUES (5, 1041); +INSERT INTO sys_role_menu VALUES (5, 1042); +INSERT INTO sys_role_menu VALUES (5, 1043); +INSERT INTO sys_role_menu VALUES (5, 1044); +INSERT INTO sys_role_menu VALUES (5, 1045); +INSERT INTO sys_role_menu VALUES (5, 1046); +INSERT INTO sys_role_menu VALUES (5, 1047); +INSERT INTO sys_role_menu VALUES (5, 1048); +INSERT INTO sys_role_menu VALUES (5, 1049); +INSERT INTO sys_role_menu VALUES (5, 1050); +INSERT INTO sys_role_menu VALUES (5, 1051); +INSERT INTO sys_role_menu VALUES (5, 1052); +INSERT INTO sys_role_menu VALUES (5, 1053); +INSERT INTO sys_role_menu VALUES (5, 1054); +INSERT INTO sys_role_menu VALUES (5, 1055); +INSERT INTO sys_role_menu VALUES (5, 1056); +INSERT INTO sys_role_menu VALUES (5, 1057); +INSERT INTO sys_role_menu VALUES (5, 1058); +INSERT INTO sys_role_menu VALUES (5, 1059); +INSERT INTO sys_role_menu VALUES (5, 1060); +INSERT INTO sys_role_menu VALUES (5, 1065); +INSERT INTO sys_role_menu VALUES (5, 2000); +INSERT INTO sys_role_menu VALUES (5, 2001); +INSERT INTO sys_role_menu VALUES (5, 2002); +INSERT INTO sys_role_menu VALUES (5, 2003); +INSERT INTO sys_role_menu VALUES (5, 2004); +INSERT INTO sys_role_menu VALUES (5, 2005); +INSERT INTO sys_role_menu VALUES (5, 2006); +INSERT INTO sys_role_menu VALUES (5, 2007); +INSERT INTO sys_role_menu VALUES (5, 2008); +INSERT INTO sys_role_menu VALUES (5, 2009); +INSERT INTO sys_role_menu VALUES (5, 2010); +INSERT INTO sys_role_menu VALUES (5, 2011); +INSERT INTO sys_role_menu VALUES (5, 2012); +INSERT INTO sys_role_menu VALUES (5, 2019); +INSERT INTO sys_role_menu VALUES (5, 2020); +INSERT INTO sys_role_menu VALUES (5, 2021); +INSERT INTO sys_role_menu VALUES (5, 2022); +INSERT INTO sys_role_menu VALUES (5, 2023); +INSERT INTO sys_role_menu VALUES (5, 2024); +INSERT INTO sys_role_menu VALUES (5, 2043); +INSERT INTO sys_role_menu VALUES (5, 2044); +INSERT INTO sys_role_menu VALUES (5, 2045); +INSERT INTO sys_role_menu VALUES (5, 2046); +INSERT INTO sys_role_menu VALUES (5, 2047); +INSERT INTO sys_role_menu VALUES (5, 2048); +INSERT INTO sys_role_menu VALUES (5, 2049); +INSERT INTO sys_role_menu VALUES (5, 2050); +INSERT INTO sys_role_menu VALUES (5, 2051); +INSERT INTO sys_role_menu VALUES (5, 2052); +INSERT INTO sys_role_menu VALUES (5, 2053); +INSERT INTO sys_role_menu VALUES (5, 2054); +INSERT INTO sys_role_menu VALUES (5, 2104); +INSERT INTO sys_role_menu VALUES (5, 2123); +INSERT INTO sys_role_menu VALUES (5, 2124); +INSERT INTO sys_role_menu VALUES (5, 2125); +INSERT INTO sys_role_menu VALUES (5, 2126); +INSERT INTO sys_role_menu VALUES (5, 2127); +INSERT INTO sys_role_menu VALUES (5, 2128); +INSERT INTO sys_role_menu VALUES (5, 2129); +INSERT INTO sys_role_menu VALUES (5, 2130); +INSERT INTO sys_role_menu VALUES (5, 2131); +INSERT INTO sys_role_menu VALUES (5, 2132); +INSERT INTO sys_role_menu VALUES (5, 2133); +INSERT INTO sys_role_menu VALUES (5, 2134); +INSERT INTO sys_role_menu VALUES (5, 2136); +INSERT INTO sys_role_menu VALUES (5, 2137); +INSERT INTO sys_role_menu VALUES (5, 2138); +INSERT INTO sys_role_menu VALUES (5, 2139); +INSERT INTO sys_role_menu VALUES (5, 2140); +INSERT INTO sys_role_menu VALUES (5, 2141); +INSERT INTO sys_role_menu VALUES (5, 2147); +INSERT INTO sys_role_menu VALUES (5, 2148); +INSERT INTO sys_role_menu VALUES (5, 2149); +INSERT INTO sys_role_menu VALUES (5, 3007); +INSERT INTO sys_role_menu VALUES (5, 3008); +INSERT INTO sys_role_menu VALUES (5, 3009); +INSERT INTO sys_role_menu VALUES (5, 3010); +INSERT INTO sys_role_menu VALUES (5, 3011); +INSERT INTO sys_role_menu VALUES (5, 3012); +INSERT INTO sys_role_menu VALUES (5, 3031); +INSERT INTO sys_role_menu VALUES (5, 3032); +INSERT INTO sys_role_menu VALUES (5, 3033); +INSERT INTO sys_role_menu VALUES (5, 3034); +INSERT INTO sys_role_menu VALUES (5, 3035); +INSERT INTO sys_role_menu VALUES (5, 3036); +INSERT INTO sys_role_menu VALUES (5, 3037); +INSERT INTO sys_role_menu VALUES (5, 3038); +INSERT INTO sys_role_menu VALUES (5, 3047); + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门ID', + `user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户账号', + `nick_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称', + `user_type` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '00' COMMENT '用户类型(00系统用户)', + `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户邮箱', + `phonenumber` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '手机号码', + `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', + `avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '头像地址', + `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `login_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '最后登录IP', + `login_date` datetime(0) NULL DEFAULT NULL COMMENT '最后登录时间', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`user_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user +-- ---------------------------- +INSERT INTO `sys_user` VALUES (1, 103, 'admin', '蜂信管理员', '00', '164770707@qq.com', '15888888888', '0', '', '$2a$10$QAow7ybs74fkSWJDJkVTNeogF7mhnihF7STErt78PxDhHiNno4IUu', '0', '0', '120.231.214.134', '2023-09-26 21:22:50', 'admin', '2021-12-15 21:36:18', '', '2023-09-26 21:22:50', '管理员'); +INSERT INTO `sys_user` VALUES (2, 100, 'fastbee-t1', '蜂信租户壹', '00', '', '15888888880', '0', '', '$2a$10$BAWId9C2Nrcwklzl1Ikoau4iqL8XRGvfRjq6Wl.PXWpzwAw0sXMdK', '0', '0', '61.145.97.26', '2023-08-29 14:52:27', 'admin', '2022-04-15 16:21:25', 'admin', '2023-08-29 14:52:26', NULL); +INSERT INTO `sys_user` VALUES (3, 100, 'fastbee-t2', '蜂信租户贰', '00', '', '15888888881', '0', '', '$2a$10$1zMlbW7hGpzA59gpzWGO/ObeASziQ296evjMjHrYdZnxKBLU4WUum', '0', '0', '127.0.0.1', '2022-06-12 00:54:28', 'admin', '2022-04-15 16:22:08', 'admin', '2022-06-12 00:54:30', NULL); +INSERT INTO `sys_user` VALUES (4, 100, 'fastbee-u1', '蜂信用户壹', '00', '', '15888888882', '0', '', '$2a$10$691RJMXZ9HM4sgNTExLPfO5Nw6J6cWgCvcoF9V.jKMnPk5o/8c9VS', '0', '0', '127.0.0.1', '2023-04-12 22:26:39', 'admin', '2022-04-15 16:22:37', 'admin', '2023-04-12 22:26:39', NULL); +INSERT INTO `sys_user` VALUES (5, 100, 'fastbee-u2', '蜂信用户贰', '00', '', '15888888883', '0', '', '$2a$10$x3rM39rewwbi7ayvriGMEOKUHoPCqcL2CYXPLTJRCWYPVvykFIYJq', '0', '0', '127.0.0.1', '2022-06-12 00:55:45', 'admin', '2022-04-15 16:23:13', 'admin', '2022-06-12 00:55:46', NULL); +INSERT INTO `sys_user` VALUES (6, 100, 'fastbee', '游客账号', '00', '', '15888888884', '0', '', '$2a$10$kKeZptrTnSlm0fencX4U2eq.QiaukDs.DckiUsMCwVTxh0IS2LRQ.', '0', '0', '127.0.0.1', '2023-09-21 18:39:29', 'admin', '2022-03-09 16:49:19', 'admin', '2023-09-21 18:39:28', NULL); +INSERT INTO `sys_user` VALUES (7, NULL, 'shenzehui', 'shenzehui', '00', '', '18257292958', '0', '', '$2a$10$UYKWiQF.VWfVvuksS/DMiO234Mwtz.niU7cM/noFgwLVRl7Jjt5pa', '0', '2', '39.189.61.11', '2023-04-16 14:18:09', '', '2023-04-16 14:17:59', '', '2023-04-16 14:18:08', NULL); +INSERT INTO `sys_user` VALUES (8, NULL, 'shadow', 'shadow', '00', '165456465465@qq.com', '15752221201', '0', '', '$2a$10$FXSw4fufDjecEhMxYjji3.7PkrpwkliCBoQO.h8nW0Nhk0bPpxS6u', '0', '2', '39.130.41.108', '2023-09-15 17:21:33', '', '2023-08-23 11:34:23', '', '2023-09-15 17:21:32', NULL); +INSERT INTO `sys_user` VALUES (9, NULL, 'guanshubiao', 'guanshubiao', '00', '', '15217628961', '0', '', '$2a$10$J9kJeP/dzc/SYq8Ev1rFXOigPdN50Kq8MkCX9j56/fQwDXAUkAPYi', '0', '2', '61.145.97.26', '2023-08-29 17:33:16', '', '2023-08-29 14:56:19', '', '2023-08-29 17:33:16', NULL); +INSERT INTO `sys_user` VALUES (10, NULL, 'jamon', 'jamon', '00', '', '13717112711', '0', '', '$2a$10$LMASUfB9IngDi47fQ9Eh7u003VNNh4DcjdPHMyvAQ4mdLXhQgvnpu', '0', '2', '61.145.97.26', '2023-09-01 09:06:23', '', '2023-08-29 15:06:39', '', '2023-09-01 09:06:23', NULL); +INSERT INTO `sys_user` VALUES (11, 101, 'fastbee123', 'fastbee123', '00', '', '18231210622', '0', '', '$2a$10$qpLuw5yAIDLV/.UCIaWRROxhtI2nYpJe/.tbIKwSmy2Pxm.vc26Ri', '0', '2', '27.187.242.251', '2023-08-31 16:22:40', 'admin', '2023-08-31 16:22:21', '', '2023-08-31 16:22:40', NULL); +INSERT INTO `sys_user` VALUES (12, NULL, 'shadow', 'shadow', '00', '', '15752221201', '0', '', '$2a$10$QEYxDoFO6e3wuksc2d7XIOJe0UBzY0EkYR3fKfp8pYfM5bWI4.VO6', '0', '2', '39.130.41.179', '2023-09-19 10:11:00', '', '2023-09-19 10:10:49', '', '2023-09-19 10:11:00', NULL); + +-- ---------------------------- +-- Table structure for sys_user_post +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_post`; +CREATE TABLE `sys_user_post` ( + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `post_id` bigint(20) NOT NULL COMMENT '岗位ID', + PRIMARY KEY (`user_id`, `post_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户与岗位关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user_post +-- ---------------------------- +INSERT INTO `sys_user_post` VALUES (1, 1); +INSERT INTO `sys_user_post` VALUES (6, 4); + +-- ---------------------------- +-- Table structure for sys_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_role`; +CREATE TABLE `sys_user_role` ( + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + PRIMARY KEY (`user_id`, `role_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户和角色关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user_role +-- ---------------------------- +INSERT INTO `sys_user_role` VALUES (1, 1); +INSERT INTO `sys_user_role` VALUES (2, 2); +INSERT INTO `sys_user_role` VALUES (3, 2); +INSERT INTO `sys_user_role` VALUES (4, 3); +INSERT INTO `sys_user_role` VALUES (5, 3); +INSERT INTO `sys_user_role` VALUES (6, 4); + +SET FOREIGN_KEY_CHECKS = 1; + +-- 优化点:添加网站应用个人中心微信绑定配置 +INSERT INTO `sys_dict_data` (`dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (7, '微信开放平台网站应用个人中心绑定', 'wechat_open_web_bind', 'iot_social_platform', NULL, 'default', 'N', '0', 'admin', '2023-10-09 11:28:15', '', NULL, NULL); diff --git a/docker/data/nginx/h5/放置移动端h5打包文件.txt b/docker/data/nginx/h5/放置移动端h5打包文件.txt new file mode 100644 index 00000000..e69de29b diff --git a/docker/data/nginx/nginx-emqx.conf b/docker/data/nginx/nginx-emqx.conf new file mode 100644 index 00000000..ac928c83 --- /dev/null +++ b/docker/data/nginx/nginx-emqx.conf @@ -0,0 +1,91 @@ +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + + gzip on; + gzip_min_length 1k; + gzip_buffers 16 64K; + gzip_http_version 1.1; + gzip_comp_level 5; + gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml; + gzip_vary on; + gzip_proxied expired no-cache no-store private auth; + gzip_disable "MSIE [1-6]\."; + + # Http跳转Https + # server { + # listen 80; + # server_name localhost; + # location / { + # rewrite ^(.*) https://$server_name$1 permanent; + # } + # } + + server { + listen 80; + + # SSL 默认访问端口号为443 + listen 443 ssl; + server_name localhost; + charset utf-8; + + # 证书文件的路径 + ssl_certificate /usr/share/nginx/ssl/fastbee.crt; + # 私钥文件的路径 + ssl_certificate_key /usr/share/nginx/ssl/fastbee.key; + ssl_session_timeout 10m; + # 请按照以下协议配置 + ssl_protocols TLSv1.2 TLSv1.3; + # 请按照以下套件配置,配置加密套件,写法遵循openssl 标准 + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; + ssl_prefer_server_ciphers on; + + # 前端 + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + index index.html index.htm; + } + + # H5移动端 + location /h5 { + alias /usr/share/nginx/h5/; + try_files $uri $uri/ /index.html; + index index.html index.htm; + } + + # 后端接口 + location /prod-api/ { + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://java:8080/; + } + + # wss连接代理到ws + location /mqtt { + proxy_pass http://emqx:8083/mqtt; + proxy_read_timeout 60s; + proxy_set_header Host $host; + proxy_set_header X-Real_IP $remote_addr; + proxy_set_header X-Forwarded-for $remote_addr; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'Upgrade'; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + } +} \ No newline at end of file diff --git a/docker/data/nginx/nginx.conf b/docker/data/nginx/nginx.conf index accaaecb..163655f5 100644 --- a/docker/data/nginx/nginx.conf +++ b/docker/data/nginx/nginx.conf @@ -20,17 +20,49 @@ http { gzip_proxied expired no-cache no-store private auth; gzip_disable "MSIE [1-6]\."; + # Http跳转Https + # server { + # listen 80; + # server_name localhost; + # location / { + # rewrite ^(.*) https://$server_name$1 permanent; + # } + # } + server { - listen 80; + listen 80; + + # SSL 默认访问端口号为443 + listen 443 ssl; server_name localhost; charset utf-8; + # 证书文件的路径 + ssl_certificate /usr/share/nginx/ssl/fastbee.crt; + # 私钥文件的路径 + ssl_certificate_key /usr/share/nginx/ssl/fastbee.key; + ssl_session_timeout 10m; + # 请按照以下协议配置 + ssl_protocols TLSv1.2 TLSv1.3; + # 请按照以下套件配置,配置加密套件,写法遵循openssl 标准 + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; + ssl_prefer_server_ciphers on; + + # 前端 location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; index index.html index.htm; } + + # H5移动端 + location /h5 { + alias /usr/share/nginx/h5/; + try_files $uri $uri/ /index.html; + index index.html index.htm; + } + # 后端接口 location /prod-api/ { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; @@ -39,12 +71,16 @@ http { proxy_pass http://java:8080/; } - location /api/v4/ { - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header REMOTE-HOST $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://emqx:8081/api/v4/; + # wss连接代理到ws + location /mqtt { + proxy_pass http://java:8083/mqtt; + proxy_read_timeout 60s; + proxy_set_header Host $host; + proxy_set_header X-Real_IP $remote_addr; + proxy_set_header X-Forwarded-for $remote_addr; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'Upgrade'; } error_page 500 502 503 504 /50x.html; diff --git a/docker/data/nginx/ssl/fastbee.crt b/docker/data/nginx/ssl/fastbee.crt new file mode 100644 index 00000000..37221757 --- /dev/null +++ b/docker/data/nginx/ssl/fastbee.crt @@ -0,0 +1,68 @@ +-----BEGIN CERTIFICATE----- +MIIGujCCBaKgAwIBAgIQCEZHO+Au3dO0pmMgFV+CXDANBgkqhkiG9w0BAQsFADBf +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMR4wHAYDVQQDExVHZW9UcnVzdCBDTiBSU0EgQ0EgRzEw +HhcNMjEwNzI2MDAwMDAwWhcNMjIwODI1MjM1OTU5WjB+MQswCQYDVQQGEwJDTjES +MBAGA1UECAwJ5bm/5Lic55yBMRIwEAYDVQQHDAnmt7HlnLPluIIxMDAuBgNVBAoM +J+a3seWcs+W4guWTgemBk+mkkOmlrueuoeeQhuaciemZkOWFrOWPuDEVMBMGA1UE +AwwMKi5waW4tZGFvLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +rMBuC6MXRP7NaP5ux7nZmuOy2wKY6xWk/i2POrfGSnX0IeY7phoGliH2EBa+UyCA +rj5B960UYDkpAmh5uCmDOO1Zs4VPwdoyj+OA6Up/LJXVg/txFvZfDkpk7lTjAQz4 +OcYXxv+iMxDkpEnnlLRsUlUs2JdQUAXEzjwlKVVUqzEZ+9QY+BLb88bDQEBjaE4+ +JH95/oRJAby3OH/AXljswh0VUbj3S8pAbiI6f8xoWSn9mjPSGIlABYDgWma2T0RD +9F7sbY7JS/1njPA67i8vzVRMnpkMvYLt4a2qG1THUpFysmT0R2OOb8DlOZiPhnZk +xEvUGeDx8y34FSucWLQTvwIDAQABo4IDUTCCA00wHwYDVR0jBBgwFoAUkZ9eMRWu +EJ+tYMH3wcyqSDQvDCYwHQYDVR0OBBYEFDrSDW+dz8TXCfrIPED7lQX1IGM5MCMG +A1UdEQQcMBqCDCoucGluLWRhby5jboIKcGluLWRhby5jbjAOBgNVHQ8BAf8EBAMC +BaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHUGA1UdHwRuMGwwNKAy +oDCGLmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9HZW9UcnVzdENOUlNBQ0FHMS5j +cmwwNKAyoDCGLmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9HZW9UcnVzdENOUlNB +Q0FHMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBAgIwKTAnBggrBgEFBQcCARYbaHR0 +cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMG8GCCsGAQUFBwEBBGMwYTAhBggrBgEF +BQcwAYYVaHR0cDovL29jc3AuZGNvY3NwLmNuMDwGCCsGAQUFBzAChjBodHRwOi8v +Y3JsLmRpZ2ljZXJ0LWNuLmNvbS9HZW9UcnVzdENOUlNBQ0FHMS5jcnQwDAYDVR0T +AQH/BAIwADCCAX8GCisGAQQB1nkCBAIEggFvBIIBawFpAHYARqVV63X6kSAwtaKJ +afTzfREsQXS+/Um4havy/HD+bUcAAAF64K3X/QAABAMARzBFAiBOmRovvmyKDMxG +fA3H6aFk/x8ce6THZVe4bTW4gEDDJwIhANKatQg5dedqYI78Fevo3ZEUEYUyMB1V +ml1AajGZm/ebAHcAUaOw9f0BeZxWbbg3eI8MpHrMGyfL956IQpoN/tSLBeUAAAF6 +4K3YDgAABAMASDBGAiEAmVXJCrNphalqawzCGHqLOHIbDUEH0S2mXOG/4jpQsKgC +IQDxC3C/aVX/D8CRj3py4OvD3PSkmP5+ZtvLsZDYEliYQQB2AEHIyrHfIkZKEMah +OglCh15OMYsbA+vrS8do8JBilgb2AAABeuCt15EAAAQDAEcwRQIhANIAPeLjWGRz +Qm11rXG9VeBazY520LJ39puYjr4ovR4tAiBwU9cJ5esSS6hOtLrSkhLCeg4i3isp +w30Hn1D77Um2UzANBgkqhkiG9w0BAQsFAAOCAQEAGPOc0d869d9C1YkbdSBwJtoh +BMkf2RFlPSIlGUacELN6gSHP37CIe7CVcwTAS+bDUGRtTD5+8loa96pbLoloEqfD +7Z7js9LE8anXazye/4W81ko3lThUq9PO0B+udKWWrhhD9wC9sfj5u22ruUN/8oyJ +OeMIYj5Xi8RTFH1QgXtg3BX3oKgRWPsib+1BgLQQgchsV709DhlT1oAI4x69JL57 +K5m5eVyK5eY3Y93+5GEQLxMhxJPpBbDbsieBHvvFzZ9sFxRLSW6YlRYPKcCMd80a ++uHNq69hXv4ZBkmoJV9PI7NJDD1e6RmRYj1v9vMarM2rhcL4yYZ1y7LiT5BkkA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFGjCCBAKgAwIBAgIQCgRw0Ja8ihLIkKbfgm7sSzANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0xOTA2MjAxMjI3NThaFw0yOTA2MjAxMjI3NThaMF8xCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xHjAcBgNVBAMTFUdlb1RydXN0IENOIFJTQSBDQSBHMTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALFJ+j1KeZVG4jzgQob23lQ8PJUNhY31ufZihuUx +hYc6HSU4Lw0fxfA43a9DpJl74M3E6F1ZRBOfJ+dWnaiyYD0PxRIQd4wJisti4Uad +vz61IYY/oQ/Elxk/X7GFDquYuxCSyBdHtTVMXCxFSvQ2C/7jWZFDfGGKKNoQSiJy +wDe8iiHbUOakLMmXmOTZyWJnFdR/TH5YNTiMKCNUPHAleG4IigGxDyL/gbwrdDNi +bDA4lUNhD0xNvPjQ8BNKqm5HWDvirUuHdC+4hpi0GJO34O3iiRV16YmWTuVFNboU +LDZ0+PQtctJnatpuZKPGyKX6jCpPvzzPw/EhNDlpEdrYHZMCAwEAAaOCAc4wggHK +MB0GA1UdDgQWBBSRn14xFa4Qn61gwffBzKpINC8MJjAfBgNVHSMEGDAWgBQD3lA1 +VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYB +BQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wMQYIKwYBBQUHAQEEJTAj +MCEGCCsGAQUFBzABhhVodHRwOi8vb2NzcC5kY29jc3AuY24wRAYDVR0fBD0wOzA5 +oDegNYYzaHR0cDovL2NybC5kaWdpY2VydC1jbi5jb20vRGlnaUNlcnRHbG9iYWxS +b290Q0EuY3JsMIHOBgNVHSAEgcYwgcMwgcAGBFUdIAAwgbcwKAYIKwYBBQUHAgEW +HGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgYoGCCsGAQUFBwICMH4MfEFu +eSB1c2Ugb2YgdGhpcyBDZXJ0aWZpY2F0ZSBjb25zdGl0dXRlcyBhY2NlcHRhbmNl +IG9mIHRoZSBSZWx5aW5nIFBhcnR5IEFncmVlbWVudCBsb2NhdGVkIGF0IGh0dHBz +Oi8vd3d3LmRpZ2ljZXJ0LmNvbS9ycGEtdWEwDQYJKoZIhvcNAQELBQADggEBABfg +eXrxIrtlixBv+KMDeqKxtNJbZiLDzJBkGCd4HI63X5eS6BElJBn6mI9eYVrr7qOL +Tp7WiO02Sf1Yrpaz/ePSjZ684o89UAGpxOfbgVSMvo/a07n/220jUWLxzaJhQNLu +lACXwwWsxYf8twP8glkoIHnUUNTlhsyyl1ZzvVC4bDpI4hC6QkJGync1MNqYSMj8 +tZbhQNw3HdSmcTO0Nc/J/pK2VZc6fFbKBgspmzdHc6jMKG2t4lisXEysS3wPcg0a +Nfr1Odl5+myh3MnMK08f6pTXvduLz+QZiIh8IYL+Z6QWgTZ9e2jnV8juumX1I8Ge +7cZdtNnTCB8hFfwGLUA= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/docker/data/nginx/ssl/fastbee.key b/docker/data/nginx/ssl/fastbee.key new file mode 100644 index 00000000..68958d9e --- /dev/null +++ b/docker/data/nginx/ssl/fastbee.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEArMBuC6MXRP7NaP5ux7nZmuOy2wKY6xWk/i2POrfGSnX0IeY7 +phoGliH2EBa+UyCArj5B960UYDkpAmh5uCmDOO1Zs4VPwdoyj+OA6Up/LJXVg/tx +FvZfDkpk7lTjAQz4OcYXxv+iMxDkpEnnlLRsUlUs2JdQUAXEzjwlKVVUqzEZ+9QY ++BLb88bDQEBjaE4+JH95/oRJAby3OH/AXljswh0VUbj3S8pAbiI6f8xoWSn9mjPS +GIlABYDgWma2T0RD9F7sbY7JS/1njPA67i8vzVRMnpkMvYLt4a2qG1THUpFysmT0 +R2OOb8DlOZiPhnZkxEvUGeDx8y34FSucWLQTvwIDAQABAoIBAAdXly9bCCZZ8540 +9vDGklwStcuFib0VsQSCKGZx4LVrdZI3RQbwCW86FElmXg9BM2O0dUmy8bAnqpMi +oGgjKfBlp6+8lVsDP62wsqe3qQraqpxIauvZ65WIGrZ42LxDskBQwTpakdwJXMTE +Xoy9Ollcq2JG1BZo/sG9+d2O3EfGS/yCb0PemXMN+1L3Lds4LaVbWhkJTvRMYpQU +m4wdRnAoKW1GKhFy9mvZ70HROPgmZ+jJkj63ZiOQyIpH1XlM6POdJCJx70ppZ6PP +ckjwvaV7wrrIYxK/tIWfDk4V+VxMc5tFJtjrNB/FwDAqBm9ltT1LjfN/j/cpQtZ7 +xk3kKSkCgYEA2Zo9tOMGdU/1lOZJ2MGQ2kRAeNfcgA/ZA7FlIAoqMc52/OrIiO4u +oAWJ5VkQNhgXazbEMl8ifI3fnSCcEjIcL3zVqIdFAeTOv+Wg1quK/TVENiG1uVnc +Kr/YCbuD0DLXgZ/Szm0+dwYxcRHb5oHpsE0XNl0mEpQUnCbxrSYOKNsCgYEAyzwk +bXczNjx90phzbIYqHG7LmZvWWKa7mqISL8RvyXhhzy9fXoBKhUtkC6XazYaYCO6F +mvSRT9BT/sRV9YgoOONHxscNMv7I2mKg8R6TDIFS/tq3E4A35YMvbVo4k/LfzExZ +FKis6KuUfnA216++CaVAfIRTLSHsO4K2Hw6wE+0CgYEAimB3lSw50yhB96pqk5ik +UOjORwqegiGR07Nfp3xPUNUG/dcgJ1Ov+rsK8fotQPkZC2kMYyv0dliSNw2hskCD +g/9Sr5U14PpsL8QK//ierl7NPc86DOCEDftpmubP7/ok6Z2FJRh7fJ1Hm6vLt04u +GZssg7nAmFfqs1JgpcdpgbkCgYAOo+F35TtSL99ceVDvQ2brL2wJP7mcHz6qb/xh +ZoQq/joFg8MZ+qHjoj+tux/c6FIxaoVDWVTSbA5w7tHGYy2Kk4zLG/Gud74eRTaU +yAANyY8h/r1rcTQVm3KiLPqgZcGLZQCRxWjXRezngsvgk69b4ISZs6qOOMBctRjL +efJjLQKBgFGdU3jg/+MctfR7N2yj3DHfPw6eya+yCjuY01S5E0oEJSE3yiLw7vPQ +kHeyZ5cEXD9y7VTCsfuYqsEwxKViBRc8xyyS7kYEEORLz/sAVU2V3TN7aKVinrEn +MnKh1m2n4wno3AE4huJFbPuXqJO3iUkfhIuLkCFyZtEd72JFO9ns +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/docker/data/nginx/ssl/放置ssl证书.txt b/docker/data/nginx/ssl/放置ssl证书.txt new file mode 100644 index 00000000..e69de29b diff --git a/docker/data/nginx/vue/放置前端打包文件.txt b/docker/data/nginx/vue/放置前端打包文件.txt new file mode 100644 index 00000000..e69de29b diff --git a/docker/data/nginx/www/部署官网.txt b/docker/data/nginx/www/部署官网.txt new file mode 100644 index 00000000..e69de29b diff --git a/docker/data/tdengine/tdengine文件.txt b/docker/data/tdengine/tdengine文件.txt new file mode 100644 index 00000000..e69de29b diff --git a/docker/settings.xml b/docker/settings.xml new file mode 100644 index 00000000..ce9ba05e --- /dev/null +++ b/docker/settings.xml @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + aliyun + central + aliyun maven + http://maven.aliyun.com/nexus/content/groups/public + + + + + + + + + + + + diff --git a/docker/sources.list b/docker/sources.list new file mode 100644 index 00000000..5613ea80 --- /dev/null +++ b/docker/sources.list @@ -0,0 +1,11 @@ +deb http://mirrors.cloud.tencent.com/ubuntu/ bionic main restricted universe multiverse +# deb-src http://mirrors.cloud.tencent.com/ubuntu/ bionic main restricted universe multiverse +deb http://mirrors.cloud.tencent.com/ubuntu/ bionic-updates main restricted universe multiverse +# deb-src http://mirrors.cloud.tencent.com/ubuntu/ bionic-updates main restricted universe multiverse +deb http://mirrors.cloud.tencent.com/ubuntu/ bionic-backports main restricted universe multiverse +# deb-src http://mirrors.cloud.tencent.com/ubuntu/ bionic-backports main restricted universe multiverse +deb http://mirrors.cloud.tencent.com/ubuntu/ bionic-security main restricted universe multiverse +# deb-src http://mirrors.cloud.tencent.com/ubuntu/ bionic-security main restricted universe multiverse + +# deb http://mirrors.cloud.tencent.com/ubuntu/ bionic-proposed main restricted universe multiverse +# deb-src http://mirrors.cloud.tencent.com/ubuntu/ bionic-proposed main restricted universe multiverse diff --git a/sdk/Arduino/Arduino-ESP32/Helper.cpp b/sdk/Arduino/Arduino-ESP32/Helper.cpp index acaa1005..f49702d6 100644 --- a/sdk/Arduino/Arduino-ESP32/Helper.cpp +++ b/sdk/Arduino/Arduino-ESP32/Helper.cpp @@ -461,7 +461,7 @@ String getTime() } delay(500); } - return ""; + return "1672524366000"; } //打印提示信息 diff --git a/sdk/Arduino/Arduino-ESP8266/Helper.cpp b/sdk/Arduino/Arduino-ESP8266/Helper.cpp index a782d8e1..92ba5932 100644 --- a/sdk/Arduino/Arduino-ESP8266/Helper.cpp +++ b/sdk/Arduino/Arduino-ESP8266/Helper.cpp @@ -450,7 +450,7 @@ String getTime() } delay(500); } - return ""; + return "1672524366000"; } //打印提示信息 diff --git a/sdk/Arduino/FastBeeArduino/Apconfig.cpp b/sdk/Arduino/FastBeeArduino/Apconfig.cpp index 3c91c5e9..3e8bb79f 100644 --- a/sdk/Arduino/FastBeeArduino/Apconfig.cpp +++ b/sdk/Arduino/FastBeeArduino/Apconfig.cpp @@ -8,7 +8,7 @@ #include "ApConfig.h" -String randomName = "wumei-device" + (String)random(1000); +String randomName = "fastbee-device" + (String)random(1000); const char *ap_ssid = randomName.c_str(); //开放式网络,不设置密码 const char *ap_password = ""; diff --git a/sdk/Arduino/FastBeeArduino/Config.cpp b/sdk/Arduino/FastBeeArduino/Config.cpp index 85899cf5..df1d2849 100644 --- a/sdk/Arduino/FastBeeArduino/Config.cpp +++ b/sdk/Arduino/FastBeeArduino/Config.cpp @@ -38,29 +38,29 @@ String pEventTopic = "/event/post"; /********************************** begin 可配置的项 **********************************/ // wifi信息 -char *wifiSsid = "MERCURY100"; -char *wifiPwd = "164770707"; +char *wifiSsid = ""; +char *wifiPwd = ""; char *userId = "1"; // 产品启用授权码,则授权码不能为空 char *authCode = ""; // 设备信息配置 -char *deviceNum = "D1H2584G22Q2"; -char *productId = "41"; +char *deviceNum = "D1FJTWOT3HIB"; +char *productId = "588"; float firmwareVersion = 1.0; // 经度和纬度可选,如果产品使用设备定位,则必须传 float latitude = 0; float longitude = 0; // Mqtt配置 -char *mqttHost = "43.143.82.218"; +char *mqttHost = "fastbee.cn"; int mqttPort = 1883; -char *mqttUserName = "wumei-smart"; -char *mqttPwd = "P47T6OD5IPFWHUM6"; -char mqttSecret[17] = "KX3TSH4Q4OS835DO"; +char *mqttUserName = "FastBee"; +char *mqttPwd = "P63653937TRQ8F27"; +char mqttSecret[17] = "KV52PPZ813EFCQD8"; // NTP地址(用于获取时间,修改为自己部署项目的接口地址) -String ntpServer = "http://wumei.live:8080/iot/tool/ntp?deviceSendTime="; +String ntpServer = "http://fastbee.cn:8080/iot/tool/ntp?deviceSendTime="; /********************************** end 可配置的项 **********************************/ diff --git a/sdk/Arduino/FastBeeArduino/FastBeeArduino.ino b/sdk/Arduino/FastBeeArduino/FastBeeArduino.ino index 1c63d676..221b55ca 100644 --- a/sdk/Arduino/FastBeeArduino/FastBeeArduino.ino +++ b/sdk/Arduino/FastBeeArduino/FastBeeArduino.ino @@ -57,6 +57,7 @@ void loop() { // 发布模拟数据,测试用 publishSimulateDataClient(); } + ESP.wdtFeed(); // 喂软件看门狗,防止程序跑偏 } /* diff --git a/sdk/Arduino/FastBeeArduino/User.cpp b/sdk/Arduino/FastBeeArduino/User.cpp index 3aa75ef0..e24239bc 100644 --- a/sdk/Arduino/FastBeeArduino/User.cpp +++ b/sdk/Arduino/FastBeeArduino/User.cpp @@ -97,7 +97,7 @@ String randomPropertyData() { int randInt = 0; StaticJsonDocument<1024> doc; JsonObject objTmeperature = doc.createNestedObject(); - objTmeperature["id"] = "temperature"; + objTmeperature["id"] = "light_current"; randFloat = random(1000, 3000); objTmeperature["value"] = (String)(randFloat / 100); objTmeperature["remark"] = (String)millis(); @@ -159,7 +159,7 @@ void processFunction(String msg) { // 匹配云端定义的功能 const char *id = object["id"]; const char *value = object["value"]; - if (strcmp(id, "switch") == 0) { + if (strcmp(id, "light_switch") == 0) { printMsg("开关 switch:" + (String)value); if (strcmp(value, "1") == 0) { // 打开继电器 diff --git a/sdk/Arduino/FastBeeGateway/Config.cpp b/sdk/Arduino/FastBeeGateway/Config.cpp index b6ba180b..5137c1c4 100644 --- a/sdk/Arduino/FastBeeGateway/Config.cpp +++ b/sdk/Arduino/FastBeeGateway/Config.cpp @@ -38,29 +38,29 @@ String pEventTopic = "/event/post"; /********************************** begin 可配置的项 **********************************/ // wifi信息 -char *wifiSsid = "MERCURY100"; -char *wifiPwd = "FastBee"; -char *userId = "1"; +char *wifiSsid = (char*)"MERCURY100"; +char *wifiPwd = (char*)"FastBee"; +char *userId = (char*)"1"; // 产品启用授权码,则授权码不能为空 -char *authCode = ""; +char *authCode = (char*)""; // 设备信息配置 -char *deviceNum = "D1PGLPG58KZ2"; -char *productId = "96"; +char *deviceNum = (char*)"D1PGLPG58K88"; +char *productId = (char*)"96"; float firmwareVersion = 1.0; // 经度和纬度可选,如果产品使用设备定位,则必须传 float latitude = 0; float longitude = 0; // Mqtt配置 -char *mqttHost = "43.143.82.218"; +char *mqttHost = (char*)"iot.fastbee.cn"; int mqttPort = 1883; -char *mqttUserName = "FastBee"; -char *mqttPwd = "P467433O1MT8MXS2"; +char *mqttUserName = (char*)"FastBee"; +char *mqttPwd = (char*)"P467433O1MT8MXS2"; char mqttSecret[17] = "KWF32S3H95LH14LO"; // NTP地址(用于获取时间,修改为自己部署项目的接口地址) -String ntpServer = "http://wumei.live:8080/iot/tool/ntp?deviceSendTime="; +String ntpServer = "http://fastbee.cn:8080/iot/tool/ntp?deviceSendTime="; /********************************** end 可配置的项 **********************************/ diff --git a/sdk/Arduino/FastBeeGateway/Mqtt.cpp b/sdk/Arduino/FastBeeGateway/Mqtt.cpp index f079180a..5bed6f0b 100644 --- a/sdk/Arduino/FastBeeGateway/Mqtt.cpp +++ b/sdk/Arduino/FastBeeGateway/Mqtt.cpp @@ -67,9 +67,9 @@ void publishProperty(String msg) { // 4.发布功能 void publishFunction(String msg) { - printMsg("发布功能:" + prefix + pFunctionTopic); + printMsg("发布属性(功能):" + prefix + pPropertyTopic); printMsg("消息:" + msg); - mqttClient.publish((prefix + pFunctionTopic).c_str(), msg.c_str()); + mqttClient.publish((prefix + pPropertyTopic).c_str(), msg.c_str()); } // 5.发布事件 diff --git a/sdk/Arduino/FastBeeGateway/User.cpp b/sdk/Arduino/FastBeeGateway/User.cpp index f1c2075f..a671b57c 100644 --- a/sdk/Arduino/FastBeeGateway/User.cpp +++ b/sdk/Arduino/FastBeeGateway/User.cpp @@ -72,7 +72,8 @@ void mqttCallback(char *topic, byte *payload, unsigned int length) { printMsg("当前时间:" + String(now, 0)); } else if (strcmp(topic, (prefix + sPropertyTopic).c_str()) == 0 || strcmp(topic, (prefix + sPropertyOnline).c_str()) == 0) { printMsg("订阅到属性指令..."); - processProperty(data); + printMsg("新版本属性和功能合并为功能主题..."); + // processProperty(data); } else if (strcmp(topic, (prefix + sFunctionTopic).c_str()) == 0 || strcmp(topic, (prefix + sFunctionOnline).c_str()) == 0) { printMsg("订阅到功能指令..."); processFunction(data); @@ -437,6 +438,8 @@ void processFunction(String msg) { delay(1000); } } + // 处理子设备的数据上报 + processSubDeviceReport(id, value); } // 最后发布功能,服务端订阅存储(重要) publishFunction(msg); diff --git a/sdk/合宙/air780e/lua/WeiMeiApp.lua b/sdk/合宙/air780e/lua/WeiMeiApp.lua new file mode 100644 index 00000000..f94f0211 --- /dev/null +++ b/sdk/合宙/air780e/lua/WeiMeiApp.lua @@ -0,0 +1,113 @@ +--- 模块功能:物美MQTT应用 +-- @author 杜兴杰 +-- @module 物美MQTT应用 +-- @license MIT +-- @copyright 杜兴杰 +-- @email 1066950103@qq.com +-- @release 2022.8.5 + +module(..., package.seeall) + +local VERSION = "0.1" +local m_strUserId = "1" +local m_strLongitude = "0" +local m_strLatitude = "0" + +local m_nTemperature = 25 +--local m_nVoltval = 4.0 + + +local rtos_bsp = rtos.bsp() +function adc_pin() -- 根据不同开发板,设置ADC编号 + if rtos_bsp == "AIR101" then -- Air101开发板ADC编号 + return 0,1,255,255,adc.CH_CPU ,adc.CH_VBAT + elseif rtos_bsp == "AIR103" then -- Air103开发板ADC编号 + return 0,1,2,3,adc.CH_CPU ,adc.CH_VBAT + elseif rtos_bsp == "AIR105" then -- Air105开发板ADC编号 + return 0,5,6,255,255,255 + elseif rtos_bsp == "ESP32C3" then -- ESP32C3开发板ADC编号 + return 0,1,2,3,adc.CH_CPU , 255 + elseif rtos_bsp == "ESP32C2" then -- ESP32C2开发板ADC编号 + return 0,1,2,3,adc.CH_CPU , 255 + elseif rtos_bsp == "ESP32S3" then -- ESP32S3开发板ADC编号 + return 0,1,2,3,adc.CH_CPU , 255 + elseif rtos_bsp == "EC618" then --Air780E开发板ADC编号 + return 0,1,255,255,adc.CH_CPU ,adc.CH_VBAT + else + log.info("main", "define ADC pin in main.lua") + return 0, 0,0,0,0,0 + end +end +local adc_pin_0,adc_pin_1,adc_pin_2,adc_pin_3,adc_pin_temp,adc_pin_vbat=adc_pin() + +--模块温度返回回调函数 +--@temp温度,srting类型,如果要对该值进行运算,可以使用带float的固件将该值转为number +local function getTemperatureCb(temp) + m_nTemperature = temp +end + +--- ADC读取测试 +-- @return 无 +-- @usage read() +local function read0() + --ADC0接口用来读取电压 + local ADC_ID = 0 + local adcval,voltval = adc.get(adc_pin_0) + log.info("testAdc0.read",adcval,(voltval-(voltval%3))/3,voltval) + -- 输出计算得出的原始电压值 + m_nVoltval = voltval/3 * 3127 / 1000 + log.info("testAdc0.Vbat", m_nVoltval) +end + +--2秒循环查询模块温度 +--sys.timerLoopStart(misc.getTemperature,1000,getTemperatureCb) +--sys.timerLoopStart(read0,1000) + +-- 开启对应的adc通道 +adc.open(adc_pin_0) + + + +function FunctionData() + local jsonStr = "" +end + +function PropertyData() + local jsonData = {{ + id= "temperature", + value= m_nTemperature , + remark="温度信息" + },{ + id= "voltage", + value= m_nVoltval, + remark="电压信息" + }} + local jsonStr = json.encode(jsonData) + return jsonStr +end + +function InformationData() + local jsonData = { + rssi = mobile.rssi(), + firmwareVersion = VERSION, + status = 3 , + userId = m_strUserId , + longitude = m_strLongitude , + latitude = m_strLatitude, + summary = { + name= "device", + chip = "air724", + author = "duxingjie", + version=0.1, + create = "2022-08-07" + } + } + local jsonStr = json.encode(jsonData) + return jsonStr +end + +return { + InformationData = InformationData, + PropertyData = PropertyData, + FunctionData = FunctionData +} diff --git a/sdk/合宙/air780e/lua/WeiMeiComAuth.lua b/sdk/合宙/air780e/lua/WeiMeiComAuth.lua new file mode 100644 index 00000000..3450ac74 --- /dev/null +++ b/sdk/合宙/air780e/lua/WeiMeiComAuth.lua @@ -0,0 +1,184 @@ +--- 模块功能:物美MQTT认证 +-- @author 杜兴杰 +-- @module 物美MQTT通信 +-- @license MIT +-- @copyright 杜兴杰 +-- @email 1066950103@qq.com +-- @release 2022.8.4 + +module(..., package.seeall) + +local m_strEncryptionMode +local m_strProductId +local m_strDeviceId +local m_strUserId +local m_strUser +local m_strPassword +local m_nTimeout +local m_strIp +local m_strDeviceAuthorizationCode = nil +local m_strProductPassword = nil +local m_InitCallback = nil + +local m_strOutPassword +local m_strClientId +local m_nTimeSyncFlag + +local function CreateCommunicationPassword() + if m_strEncryptionMode == "S" then + if nil == m_strDeviceAuthorizationCode then + m_strOutPassword = m_strPassword + else + m_strOutPassword = m_strPassword .. "&" .. m_strDeviceAuthorizationCode + end + return true + end + + if m_strEncryptionMode == "E" then + m_strOutPassword = AesPassword() + return true + end + return false +end + +local function Callback(nResult) + log.info("Callback" .. nResult) + if m_InitCallback ~= nil then + m_InitCallback(nResult) + end +end + +local function ParameterCheck() + + return true +end + + + +local function CreatedClientId() + m_strClientId = m_strEncryptionMode .. "&" .. m_strDeviceId .. "&" .. m_strProductId .. "&" .. m_strUserId +end + +local function httpCallbackFun(result,prompt,head,body) + log.info("GetTimeHttp.cbFnc",result,prompt) + if result and body then + log.info("GetTimeHttp.cbFnc"..body.."bodyLen="..body:len()) + local nDeviceRunTickMs = 1000--rtos.tick() * 5 + + local nEnd = string.find(body,'}') + local outStr= string.sub(body,1,nEnd-4) .. '}' + log.info("----outStr " .. outStr) + + local jsonObj = json.decode(outStr) --bug json 64 转换不支持 + local nDeviceSendTime = jsonObj["deviceSendTime"] + local nServerSendTime = jsonObj["serverSendTime"] + local nServerRecvTime = jsonObj["serverRecvTime"] + log.info("nDeviceSendTime=" .. nDeviceSendTime) + log.info("nServerSendTime=" .. nServerSendTime) + log.info("nServerRecvTime=" .. nServerRecvTime) + + local nSyncTime = nServerRecvTime --秒为单位 --(nServerRecvTime + nServerSendTime + nDeviceRunTickMs - nDeviceSendTime)/2 --这里运算有问题 可能存在数据移除了 + log.info("nSyncTime=" .. nSyncTime) + rtc.set(nSyncTime) + --创建客户端ID + CreatedClientId() + --创建密码 + CreateCommunicationPassword() + Callback(1) + m_nTimeSyncFlag = 1 + end +end + + function SyncTime() + local nDeviceRunTickMs = 1000--rtos.tick() * 5 + local strUrl = "http://" .. m_strIp .. ":8080/iot/tool/ntp?deviceSendTime=" .. nDeviceRunTickMs + log.info("strUrl" .. strUrl) + --http.request("GET",strUrl,nil,nil,nil,nil,httpCallbackFun) + local code, headers, body = http.request("GET", strUrl).wait() + log.info("http.get", code, headers, body) + httpCallbackFun(code,"NULL",headers,body) +end + +local function GetCurrentTime() + local nCurrentTime = os.time() + log.info("GetCurrentTime= " .. nCurrentTime) + return nCurrentTime +end + +function AesPassword() + local nCurrentTime = GetCurrentTime() + local nExpireTime = m_nTimeout + nCurrentTime --这里存在bug 数据溢出了 + + local strAesSource = nil + if nil == m_strDeviceAuthorizationCode then + strAesSource = m_strPassword .. "&" .. nExpireTime .. "000" + else + strAesSource = m_strPassword .. "&" .. nExpireTime .. "000" .."&" .. m_strDeviceAuthorizationCode + end + --加密内容: mqtt密码 & expireTime & 授权码(可选) + --加密模式:CBC;填充方式:Pkcs5Padding;密钥:1234567890123456;密钥长度:128 bit;偏移量:1234567890666666 + log.info("strAesSource=".. strAesSource .. " m_strProductPassword=" ..m_strProductPassword) + local encodeStr = crypto.cipher_encrypt("AES-128-CBC", "PKCS7", strAesSource, m_strProductPassword, "wumei-smart-open") --todo 密码验证 + log.info("AesPassword strAesSource" .. strAesSource .. " encodeStr " .. encodeStr ) + log.info("AES", "aes-128-cbc", encodeStr:toHex()) + encodeStr = crypto.base64_encode(encodeStr) + log.info("AesPassword strAesSource" .. strAesSource .. " encodeStrbase64 " .. encodeStr ) + return encodeStr + +end + +--- mqtt认证初始化 +-- @string strEncryptionMode,string类型,认证类型:S=简单认证,E=加密认证 +-- @string strProductId,string类型,物美里面定义的产品ID +-- @string strDeviceId,string类型,设备ID 一般用imei +-- @string strUserId,string类型,用户ID +-- @string strUser,string类型,用户 +-- @string strPassword,string类型,密码 +-- @number nTimeout,number类型,延迟秒为单位 +-- @string strIp,string类型,Ip +-- @string strDeviceAuthorizationCode,string类型 设备认证码 不用不填 +-- @string strProductPassword,string类型 产品密码 +-- @return nil +function Init(strEncryptionMode,strProductId,strDeviceId,strUserId,strUser,strPassword,nTimeout,strIp,strDeviceAuthorizationCode,strProductPassword,callback) + m_strEncryptionMode = strEncryptionMode + m_strProductId = strProductId + m_strDeviceId = strDeviceId + m_strUserId = strUserId + m_strUser = strUser + m_strPassword = strPassword + m_nTimeout = nTimeout + m_strIp = strIp + m_strDeviceAuthorizationCode = strDeviceAuthorizationCode + m_strProductPassword = strProductPassword + m_nTimeSyncFlag = 0 + m_InitCallback = callback + --if ParameterCheck() == false then + -- Callback(0) + -- end + SyncTime() +end + +function GetUser() + return m_strUser +end + +function GetPassword() + return m_strOutPassword +end + +function GetClientId() + return m_strClientId +end + +function GetIP() + return m_strIp +end + + +return { + GetUser = GetUser, + GetPassword = GetPassword, + GetClientId = GetClientId, + GetIP = GetIP, + Init=Init +} diff --git a/sdk/合宙/air780e/lua/WeiMeiComInteraction.lua b/sdk/合宙/air780e/lua/WeiMeiComInteraction.lua new file mode 100644 index 00000000..530cdcd0 --- /dev/null +++ b/sdk/合宙/air780e/lua/WeiMeiComInteraction.lua @@ -0,0 +1,304 @@ +--- 模块功能:物美MQTT交互 +-- @author 杜兴杰 +-- @module 物美MQTT通信 +-- @license MIT +-- @copyright 杜兴杰 +-- @email 1066950103@qq.com +-- @release 2022.8.5 + +module(..., package.seeall) + +local m_strProductId +local m_strDeviceNum + +local m_tMessageQueue = {} + +local m_callbackPropertyData = nil +local m_callbackFunctionData = nil +local m_callbackEventData = nil +local m_callbackDeviceInformationData = nil + +local m_timePropertyId = 0 +local m_timeFunctionId = 0 +local m_timeEventId = 0 + +local m_nMonitorCount = 0 +local m_nMonitorTime = 0 +local m_timeMonitorId = 0 + +local function GetSubscriberDeviceInformation() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/info/get" +end + +local function GetSubscriberDeviceInOta() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/ota/get" +end + +local function GetSubscriberDeviceProperty() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/property/get" +end + +local function GetSubscriberDevicePropertyOnline() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/property-online/get" +end + +local function GetSubscriberDeviceFunction() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/function/get" +end + +local function GetSubscriberDeviceFunctionOnline() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/function-online/get" +end + +local function GetSubscriberDeviceMonitor() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/monitor/get" +end + +local function GetSubscriberDeviceNtp() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/ntp/get" +end + +local function GetPublishDeviceInformation() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/info/post" +end + +local function GetPublishDeviceProperty() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/property/post" +end + +local function GetPublishDeviceFunction() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/function/post" +end + +local function GetPublishDeviceEvent() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/event/post" +end + +local function GetPublishDeviceNtp() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/ntp/post" +end + +local function GetPublishMonitorProperty() + return "/" .. m_strProductId .. "/" .. m_strDeviceNum .. "/monitor/post" +end + +local function PropertyPush() + if m_callbackPropertyData ~= nil then + local strTopic = GetPublishDeviceProperty() + local strMessage = m_callbackPropertyData() + local nQos = 1 + table.insert(m_tMessageQueue,{topic=strTopic,payload=strMessage,qosr=nQos}) + log.info("------------------PropertyPush---------------") + end +end + +local function FunctionPush() + if m_callbackFunctionData ~= nil then + local strTopic = GetPublishDeviceFunction() + local strMessage = m_callbackFunctionData() + local nQos = 1 + table.insert(m_tMessageQueue,{topic=strTopic,payload=strMessage,qosr=nQos}) + log.info("------------------FunctionPush---------------") + end +end + +local function EventPush() + if m_callbackEventData ~= nil then + local strTopic = GetPublishDeviceEvent() + local strMessage = m_callbackEventData() + local nQos = 1 + table.insert(m_tMessageQueue,{topic=strTopic,payload=strMessage,qosr=nQos}) + log.info("------------------EventPush---------------") + end +end + +local function MonitorPush() + if m_callbackPropertyData ~= nil then + local strTopic = GetPublishMonitorProperty() + local strMessage = m_callbackPropertyData() + local nQos = 1 + table.insert(m_tMessageQueue,{topic=strTopic,payload=strMessage,qosr=nQos}) + log.info("------------------MonitorPush---------------") + end +end + +local function DeviceMonitorTimeCallback() + MonitorPush() + if m_nMonitorCount ~= 0 then + m_nMonitorCount = m_nMonitorCount -1 + m_timeMonitorId = sys.timerStart( + DeviceMonitorTimeCallback + ,m_nMonitorTime) + else + sys.timerStop(m_timeMonitorId) + m_timeMonitorId= 0 + end + log.info("----m_nMonitorCount=" .. m_nMonitorCount) +end + +local function DeviceInformationPush() + if m_callbackDeviceInformationData ~= nil then + local strTopic = GetPublishDeviceInformation() + local strMessage = m_callbackDeviceInformationData() + local nQos = 1 + table.insert(m_tMessageQueue,{topic=strTopic,payload=strMessage,qosr=nQos}) + end +end + +--- mqtt交互初始化 +-- @string strProductId,string类型,产品ID +-- @string strDeviceNum,string类型,设备号 IMEI +-- @return nil +function Init(strProductId,strDeviceNum) + m_strProductId = strProductId + m_strDeviceNum = strDeviceNum +end + +--- mqtt交互延时初始化 +-- @return nil +function DelayInit() + DeviceInformationPush(); +end +--- mqtt 获取待发送数据 一次取出一天且删除 +-- @return bResult false 没有数据 true 有数据 +-- @return strMessage 要发送的数据 +-- @return strTopic 要发送的主题 +-- @return nQos 发送消息级别 +function GetData() + local bResult = false + local strMessage ="" + local strTopic ="" + local nQos = 0 + if #m_tMessageQueue>0 then + local outMsg = table.remove(m_tMessageQueue,1) + bResult = true + strMessage = outMsg.payload + strTopic = outMsg.topic + nQos = outMsg.qosr + end + return bResult,strMessage,strTopic,nQos +end + +--- mqtt 设置属性的获取获取函数,内部调用发送的时候会执行这个函数 这个函数外部实现 +-- @function callback,函数类型, 数据函 +function SetCallbackPropertyData(callback) + m_callbackPropertyData = callback +end + +--- mqtt 设置功能的获取获取函数,内部调用发送的时候会执行这个函数 这个函数外部实现 +-- @function callback,函数类型, 数据函 +function SetCallbackFunctionData(callback) + m_callbackFunctionData = callback +end + +--- mqtt 设置事件的获取获取函数,内部调用发送的时候会执行这个函数 这个函数外部实现 +-- @function callback,函数类型, 数据函 +function SetCallbackEventData(callback) + m_callbackEventData = callback +end + +--- mqtt 设置设备信息获取函数,这个函数外部实现 +-- @function callback,函数类型, 数据函 +function SetCallbackInformationData(callback) + m_callbackDeviceInformationData = callback +end + +--- mqtt 设置属性发布 +-- @number nTime,数字类型, 0停止定时器 其他值开启定时器 +function SetPropertyPush(nTime) + if m_timePropertyId ~= 0 then + sys.timerStop(m_timePropertyId) + m_timePropertyId = 0 + end + if nTime~= 0 then + m_timePropertyId = sys.timerLoopStart(PropertyPush,nTime) + end +end + +--- mqtt 设置功能发布 +-- @number nTime,数字类型, 0停止定时器 其他值开启定时器 +function SetFunctionPush(nTime) + if m_timeFunctionId ~= 0 then + sys.timerStop(m_timeFunctionId) + m_timeFunctionId = 0 + end + if nTime~= 0 then + m_timeFunctionId = sys.timerLoopStart(FunctionPush,nTime) + end +end + +--- mqtt 设置事件发布 +-- @number nTime,数字类型, 0停止定时器 其他值开启定时器 +function SetEventPush(nTime) + if m_timeEventId ~= 0 then + sys.timerStop(m_timeEventId) + m_timeEventId = 0 + end + if nTime~= 0 then + m_timeEventId = sys.timerLoopStart(EventPush,nTime) + end +end + +--- mqtt 获取要订阅的所有主题 +-- @return tSubscriber,表类型, topic 主题 qos级别 +function GetSubscriberAll() + local tSubscriber ={} + table.insert(tSubscriber,{topic=GetSubscriberDeviceInformation(),qos=1}) + table.insert(tSubscriber,{topic=GetSubscriberDeviceProperty(),qos=1}) + table.insert(tSubscriber,{topic=GetSubscriberDevicePropertyOnline(),qos=1}) + table.insert(tSubscriber,{topic=GetSubscriberDeviceFunction(),qos=1}) + table.insert(tSubscriber,{topic=GetSubscriberDeviceFunctionOnline(),qos=1}) + table.insert(tSubscriber,{topic=GetSubscriberDeviceMonitor(),qos=1}) + table.insert(tSubscriber,{topic=GetSubscriberDeviceNtp(),qos=1}) + return tSubscriber +end + +--- mqtt接受数据 +-- @string topic,string类型,接收到的主题 +-- @string message,string类型,消息内容 +-- @return nil +function OnRecvData(topic , message) + if topic == GetSubscriberDeviceInformation() then + DeviceInformationPush(); + elseif topic == GetSubscriberDeviceInOta() then + local jsonObj = json.decode(message) + local strVersion = jsonObj["version"] + local strUrl = jsonObj["downloadUrl"] + elseif topic == GetSubscriberDeviceProperty() then + PropertyPush(); + elseif topic == GetSubscriberDevicePropertyOnline() then + PropertyPush(); + elseif topic == GetSubscriberDeviceFunction() then + FunctionPush(); + elseif topic == GetSubscriberDeviceFunctionOnline() then + FunctionPush(); + elseif topic == GetSubscriberDeviceMonitor() then + local jsonObj = json.decode(message) + m_nMonitorCount = jsonObj["count"] + m_nMonitorTime = jsonObj["interval"] + if m_timeMonitorId ~= 0 then + sys.timerStop(m_timeMonitorId) + m_timeMonitorId = 0 + end + m_timeMonitorId = sys.timerStart( + DeviceMonitorTimeCallback + ,m_nMonitorTime) + elseif topic == GetSubscriberDeviceNtp() then + log.info("--DeviceNtp---" .. message) + end +end + +return { + Init = Init, + DelayInit = DelayInit, + SetCallbackPropertyData = SetCallbackPropertyData, + SetCallbackFunctionData = SetCallbackFunctionData, + SetCallbackEventData=SetCallbackEventData, + SetCallbackInformationData=SetCallbackInformationData, + SetPropertyPush =SetPropertyPush, + SetFunctionPush =SetFunctionPush, + SetEventPush=SetEventPush, + GetSubscriberAll=GetSubscriberAll, + OnRecvData=OnRecvData, + GetData = GetData +} diff --git a/sdk/合宙/air780e/lua/WuMeiTest.lua b/sdk/合宙/air780e/lua/WuMeiTest.lua new file mode 100644 index 00000000..be264846 --- /dev/null +++ b/sdk/合宙/air780e/lua/WuMeiTest.lua @@ -0,0 +1,130 @@ +--- 模块功能:物美MQTT测试 +-- @author 杜兴杰 +-- @module 物美MQTT通信测试 +-- @license MIT +-- @copyright 杜兴杰 +-- @email 1066950103@qq.com +-- @release 2022.8.5 +module(..., package.seeall) +WeiMeiComAuth = require"WeiMeiComAuth" +WeiMeiComInteraction = require"WeiMeiComInteraction" +WeiMeiApp = require"WeiMeiApp" +--require"misc" +--require"mqtt" + +--[[特别注意, 使用mqtt库需要下列语句]] +_G.sysplus = require("sysplus") + +local ready = false + +--物美配置参数相关配置 +local m_strEncryptionMode = "E" +local m_strProductId = 253 +local m_strDeviceId = nil +local m_strUserId = "1" -- admin +local m_strMqttUser = "FastBee" +local m_strMqttPassword = "P77A4MMCA20V0D0K" +local m_strProductPassword = "K4PAICCX042H88E6" --产品密码 +local m_nMqttAuthenticationTimeout = 24*60*60*1 --24小时 +local m_strMqttIp = "www.fastbee.cn" +local m_strDeviceAuthorizationCode = nill--= "A25040D2E34B483DA371B5F9A315BB43" --设备授权码 + +local mqttc = nil +local m_mqttFlag = 0 + +function AuthenticationResultCallback(nResult) + if nResult == 1 then + log.info("---AuthenticationResultCallback---ok") + WeiMeiComInteraction.Init(m_strProductId,m_strDeviceId) + m_mqttFlag = 1 + end +end + +local function MqttInit() + WeiMeiComAuth.Init(m_strEncryptionMode,m_strProductId,m_strDeviceId,m_strUserId,m_strMqttUser,m_strMqttPassword,m_nMqttAuthenticationTimeout,m_strMqttIp,m_strDeviceAuthorizationCode,m_strProductPassword,AuthenticationResultCallback) +end + +-- 订阅所有主题 +local function GetSubscriberAll() + local tSubscriber = WeiMeiComInteraction.GetSubscriberAll() + while #tSubscriber > 0 do + local ouSubscriber = table.remove(tSubscriber,1) + mqttc:subscribe(ouSubscriber.topic,ouSubscriber.qos) --todo 可能订阅数量有限 + --if m_mqttClient:subscribe({[ouSubscriber.topic]=ouSubscriber.qos}) == nil then + -- log.info("subscribe eeror ") + -- return false + --end + end + return true +end + +--- MQTT连接是否处于激活状态 +-- @return 激活状态返回true,非激活状态返回false +-- @usage mqttTask.isReady() +function isReady() + return ready +end + +--启动MQTT客户端任务 +sys.taskInit( + function() + + local retryConnectCnt = 0 + sys.waitUntil("IP_READY", 50000) + + --创建一个MQTT客户端 + m_strDeviceId = mobile.imei() + MqttInit() + while m_mqttFlag == 0 do + sys.wait(50) + end + log.info("ClientId=" .. WeiMeiComAuth.GetClientId() ) + log.info("User=" .. WeiMeiComAuth.GetUser() ) + log.info("Password=" .. WeiMeiComAuth.GetPassword() ) + log.info("Ip=" .. WeiMeiComAuth.GetIP() ) + + mqttc = mqtt.create(nil,WeiMeiComAuth.GetIP(), 1883, false, ca_file) + mqttc:auth(WeiMeiComAuth.GetClientId(),WeiMeiComAuth.GetUser(),WeiMeiComAuth.GetPassword()) -- client_id必填,其余选填 + mqttc:keepalive(30) -- 默认值240s + mqttc:autoreconn(true, 3000) -- 自动重连机制 + + mqttc:on(function(mqtt_client, event, data, payload) + -- 用户自定义代码 + log.info("mqtt", "event", event, mqtt_client, data, payload) + if event == "conack" then + --连接成功 + sys.publish("mqtt_conack") + if GetSubscriberAll() == true then + WeiMeiComInteraction.SetCallbackInformationData(WeiMeiApp.InformationData) + WeiMeiComInteraction.SetCallbackPropertyData(WeiMeiApp.PropertyData) + WeiMeiComInteraction.SetPropertyPush(1000*30) --30秒钟定时上传一次属性 + WeiMeiComInteraction.DelayInit() + end + elseif event == "recv" then + log.info("mqtt", "downlink", "topic", data, "payload", payload) + WeiMeiComInteraction.OnRecvData(data,payload); + elseif event == "sent" then + log.info("mqtt", "sent", "pkgid", data) + -- elseif event == "disconnect" then + -- 非自动重连时,按需重启mqttc + -- mqtt_client:connect() + end + end) + + mqttc:connect() + sys.waitUntil("mqtt_conack") --todo 超时重启 + while true do + -- mqttc自动处理重连 + result,strMessage,strTopic,nQos = WeiMeiComInteraction.GetData() + if result == true then + local mqttResult = mqttc:publish(strTopic,strMessage,nQos) + if not mqttResult then + break + end + end + sys.wait(50) + end + mqttc:close() + mqttc = nil + end +) diff --git a/sdk/合宙/air780e/lua/main.lua b/sdk/合宙/air780e/lua/main.lua new file mode 100644 index 00000000..738a7895 --- /dev/null +++ b/sdk/合宙/air780e/lua/main.lua @@ -0,0 +1,31 @@ +--- 模块功能:lvgldemo +-- @module main +-- @author 杜兴杰 +-- @release 2023.03.15 + +-- LuaTools需要PROJECT和VERSION这两个信息 +PROJECT = "wumeiAir780" +VERSION = "0.0.1" + +log.info("main", PROJECT, VERSION) + +-- sys库是标配 +_G.sys = require("sys") + + + +--添加硬狗防止程序卡死 +if wdt then + wdt.init(9000)--初始化watchdog设置为9s + sys.timerLoopStart(wdt.feed, 3000)--3s喂一次狗 +end + + +require "WuMeiTest" + +-- 用户代码已结束--------------------------------------------- +-- 结尾总是这一句 +sys.run() +-- sys.run()之后后面不要加任何语句!!!!! + + diff --git a/sdk/合宙/air780e/lua/mainAir724.lua b/sdk/合宙/air780e/lua/mainAir724.lua new file mode 100644 index 00000000..ad537ba4 --- /dev/null +++ b/sdk/合宙/air780e/lua/mainAir724.lua @@ -0,0 +1,65 @@ + +--必须在这个位置定义PROJECT和VERSION变量 +--PROJECT:ascii string类型,可以随便定义,只要不使用,就行 +--VERSION:ascii string类型,如果使用Luat物联云平台固件升级的功能,必须按照"X.X.X"定义,X表示1位数字;否则可随便定义 +PROJECT = "DTU" +VERSION = "1.0.0" + +--加载日志功能模块,并且设置日志输出等级 +--如果关闭调用log模块接口输出的日志,等级设置为log.LOG_SILENT即可 +require "log" +LOG_LEVEL = log.LOGLEVEL_TRACE +--[[ +如果使用UART输出日志,打开这行注释的代码"--log.openTrace(true,1,115200)"即可,根据自己的需求修改此接口的参数 +如果要彻底关闭脚本中的输出日志(包括调用log模块接口和Lua标准print接口输出的日志),执行log.openTrace(false,第二个参数跟调用openTrace接口打开日志的第二个参数相同),例如: +1、没有调用过sys.opntrace配置日志输出端口或者最后一次是调用log.openTrace(true,nil,921600)配置日志输出端口,此时要关闭输出日志,直接调用log.openTrace(false)即可 +2、最后一次是调用log.openTrace(true,1,115200)配置日志输出端口,此时要关闭输出日志,直接调用log.openTrace(false,1)即可 +--]] +--log.openTrace(true,1,115200) + +require "sys" + +require "net" +--每1分钟查询一次GSM信号强度 +--每1分钟查询一次基站信息 +net.startQueryAll(60000, 60000) + +--此处关闭RNDIS网卡功能 +--否则,模块通过USB连接电脑后,会在电脑的网络适配器中枚举一个RNDIS网卡,电脑默认使用此网卡上网,导致模块使用的sim卡流量流失 +--如果项目中需要打开此功能,把ril.request("AT+RNDISCALL=0,1")修改为ril.request("AT+RNDISCALL=1,1")即可 +--注意:core固件:V0030以及之后的版本、V3028以及之后的版本,才以稳定地支持此功能 +ril.request("AT+RNDISCALL=1,1") + +--加载控制台调试功能模块(此处代码配置的是uart2,波特率115200) +--此功能模块不是必须的,根据项目需求决定是否加载 +--使用时注意:控制台使用的uart不要和其他功能使用的uart冲突 +--使用说明参考demo/console下的《console功能使用说明.docx》 +--require "console" +--console.setup(2, 115200) + +--加载网络指示灯和LTE指示灯功能模块 +--根据自己的项目需求和硬件配置决定:1、是否加载此功能模块;2、配置指示灯引脚 +--合宙官方出售的Air720U开发板上的网络指示灯引脚为pio.P0_1,LTE指示灯引脚为pio.P0_4 +require "netLed" +pmd.ldoset(2,pmd.LDO_VLCD) +netLed.setup(true,pio.P0_1,pio.P0_4) +--网络指示灯功能模块中,默认配置了各种工作状态下指示灯的闪烁规律,参考netLed.lua中ledBlinkTime配置的默认值 +--如果默认值满足不了需求,此处调用netLed.updateBlinkTime去配置闪烁时长 +--LTE指示灯功能模块中,配置的是注册上4G网络,灯就常亮,其余任何状态灯都会熄灭 + +--加载错误日志管理功能模块【强烈建议打开此功能】 +--如下2行代码,只是简单的演示如何使用errDump功能,详情参考errDump的api +require "errDump" +errDump.request("udp://dev_msg1.openluat.com:12425", nil, true) + +--加载远程升级功能模块【强烈建议打开此功能,如果使用了阿里云的OTA功能,可以不打开此功能】 +--如下3行代码,只是简单的演示如何使用update功能,详情参考update的api以及demo/update +PRODUCT_KEY = "7wazHLKGOdfjrSoG5tXOr4uUg7D5wT9k" +--require "update" +--update.request() + +--加载MQTT功能测试模块 +require "WuMeiTest" +--启动系统框架 +sys.init(0, 0) +sys.run() diff --git a/sdk/合宙/air780e/lua/mainVscode.lua b/sdk/合宙/air780e/lua/mainVscode.lua new file mode 100644 index 00000000..f1ed9f0c --- /dev/null +++ b/sdk/合宙/air780e/lua/mainVscode.lua @@ -0,0 +1,10 @@ +PROJECT = 'test' +VERSION = '2.0.0' +require 'log' +LOG_LEVEL = log.LOGLEVEL_TRACE +require 'sys' +require "WuMeiTest" + + +sys.init(0, 0) +sys.run() diff --git a/springboot/.gitignore b/springboot/.gitignore index a9894fa7..0a02aec9 100644 --- a/springboot/.gitignore +++ b/springboot/.gitignore @@ -1,6 +1,6 @@ ###################################################################### # Build Tools - +.yml .gradle /build/ !gradle/wrapper/gradle-wrapper.jar diff --git a/springboot/README.md b/springboot/README.md index 61938265..5f27be08 100644 --- a/springboot/README.md +++ b/springboot/README.md @@ -1,86 +1,10 @@ -## 平台简介 - -若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 - -* 前端采用Vue、Element UI。 -* 后端采用Spring Boot、Spring Security、Redis & Jwt。 -* 权限认证使用Jwt,支持多终端认证系统。 -* 支持加载动态权限菜单,多方式轻松权限控制。 -* 高效率开发,使用代码生成器可以一键生成前后端代码。 -* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3),保持同步更新。 -* 提供了单应用版本[RuoYi-Vue-fast](https://github.com/yangzongzhuan/RuoYi-Vue-fast),Oracle版本[RuoYi-Vue-Oracle](https://github.com/yangzongzhuan/RuoYi-Vue-Oracle),保持同步更新。 -* 不分离版本,请移步[RuoYi](https://gitee.com/y_project/RuoYi),微服务版本,请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud) -* 特别鸣谢:[element](https://github.com/ElemeFE/element),[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin),[eladmin-web](https://github.com/elunez/eladmin-web)。 -* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)   -* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)   - -## 内置功能 - -1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 -2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 -3. 岗位管理:配置系统用户所属担任职务。 -4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。 -5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 -6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 -7. 参数管理:对系统动态配置常用参数。 -8. 通知公告:系统通知公告信息发布维护。 -9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 -10. 登录日志:系统登录日志记录查询包含登录异常。 -11. 在线用户:当前系统中活跃用户状态监控。 -12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 -13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 -14. 系统接口:根据业务代码自动生成相关的api接口文档。 -15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 -16. 缓存监控:对系统的缓存信息查询,命令统计等。 -17. 在线构建器:拖动表单元素生成相应的HTML代码。 -18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 - -## 在线体验 - -- admin/admin123 -- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。 - -演示地址:http://vue.ruoyi.vip -文档地址:http://doc.ruoyi.vip - -## 演示图 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -## 若依前后端分离交流群 - -QQ群: [![加入QQ群](https://img.shields.io/badge/已满-937441-blue.svg)](https://jq.qq.com/?_wv=1027&k=5bVB1og) [![加入QQ群](https://img.shields.io/badge/已满-887144332-blue.svg)](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [![加入QQ群](https://img.shields.io/badge/已满-180251782-blue.svg)](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [![加入QQ群](https://img.shields.io/badge/已满-104180207-blue.svg)](https://jq.qq.com/?_wv=1027&k=51G72yr) [![加入QQ群](https://img.shields.io/badge/已满-186866453-blue.svg)](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [![加入QQ群](https://img.shields.io/badge/已满-201396349-blue.svg)](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [![加入QQ群](https://img.shields.io/badge/已满-101456076-blue.svg)](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [![加入QQ群](https://img.shields.io/badge/101539465-blue.svg)](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) 点击按钮入群。 \ No newline at end of file +### 一、项目目录 +   fastbee-admin ------------- 主程序入口
+   fastbee-common ---------- 公共模块
+   fastbee-framework -------- 开发框架
+   fastbee-gateway ----------- 消息通道转发
+   fastbee-open-api ---------- 系统开放接口
+   fastbee-plugs --------------- 拓展插件
+   fastbee-protocol ------------ 编解码协议
+   fastbee-server --------------- 传输层服务端 (netty-mqtt)
+   fastbee-service -------------- 核心业务处理
diff --git a/springboot/bin/run.bat b/springboot/bin/run.bat index b0557be3..0de1f6f7 100644 --- a/springboot/bin/run.bat +++ b/springboot/bin/run.bat @@ -4,11 +4,11 @@ echo [��Ϣ] ʹ��Jar��������Web���̡� echo. cd %~dp0 -cd ../wumei-admin/target +cd ../fastbee-admin/target set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -java -jar %JAVA_OPTS% wumei-admin.jar +java -jar %JAVA_OPTS% fastbee-admin.jar cd bin pause \ No newline at end of file diff --git a/springboot/fastbee-admin/pom.xml b/springboot/fastbee-admin/pom.xml new file mode 100644 index 00000000..0cb74acd --- /dev/null +++ b/springboot/fastbee-admin/pom.xml @@ -0,0 +1,112 @@ + + + + fastbee + com.fastbee + 3.8.5 + + 4.0.0 + jar + fastbee-admin + + + web服务入口 + + + + + + + org.springframework.boot + spring-boot-devtools + true + + + + + io.springfox + springfox-boot-starter + + + + + io.swagger + swagger-models + 1.6.2 + + + + + mysql + mysql-connector-java + + + + + com.fastbee + fastbee-framework + + + + + com.fastbee + fastbee-quartz + + + + + com.fastbee + fastbee-generator + + + + + com.fastbee + fastbee-open-api + + + + com.fastbee + boot-strap + + + + com.fastbee + gateway-boot + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.1.1.RELEASE + + true + + + + + repackage + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.1.0 + + false + ${project.artifactId} + + + + ${project.artifactId} + + + diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/FastBeeApplication.java b/springboot/fastbee-admin/src/main/java/com/fastbee/FastBeeApplication.java new file mode 100644 index 00000000..945d7de5 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/FastBeeApplication.java @@ -0,0 +1,20 @@ +package com.fastbee; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +/** + * 启动程序 + * + * @author ruoyi + */ +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) +public class FastBeeApplication +{ + public static void main(String[] args) + { + // System.setProperty("spring.devtools.restart.enabled", "false"); + SpringApplication.run(FastBeeApplication.class, args); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/FastBeeServletInitializer.java b/springboot/fastbee-admin/src/main/java/com/fastbee/FastBeeServletInitializer.java new file mode 100644 index 00000000..76586e58 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/FastBeeServletInitializer.java @@ -0,0 +1,18 @@ +package com.fastbee; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * web容器中进行部署 + * + * @author ruoyi + */ +public class FastBeeServletInitializer extends SpringBootServletInitializer +{ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) + { + return application.sources(FastBeeApplication.class); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/common/CaptchaController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/common/CaptchaController.java new file mode 100644 index 00000000..bce23720 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/common/CaptchaController.java @@ -0,0 +1,99 @@ +package com.fastbee.web.controller.common; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.FastByteArrayOutputStream; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import com.google.code.kaptcha.Producer; +import com.fastbee.common.config.RuoYiConfig; +import com.fastbee.common.constant.CacheConstants; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.utils.sign.Base64; +import com.fastbee.common.utils.uuid.IdUtils; +import com.fastbee.system.service.ISysConfigService; + +/** + * 验证码操作处理 + * + * @author ruoyi + */ +@Api(tags = "验证码操作") +@RestController +public class CaptchaController +{ + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysConfigService configService; + /** + * 生成验证码 + */ + @ApiOperation("获取验证码") + @GetMapping("/captchaImage") + public AjaxResult getCode(HttpServletResponse response) throws IOException + { + AjaxResult ajax = AjaxResult.success(); + boolean captchaEnabled = configService.selectCaptchaEnabled(); + ajax.put("captchaEnabled", captchaEnabled); + if (!captchaEnabled) + { + return ajax; + } + + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(); + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + + String capStr = null, code = null; + BufferedImage image = null; + + // 生成验证码 + String captchaType = RuoYiConfig.getCaptchaType(); + if ("math".equals(captchaType)) + { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); + } + else if ("char".equals(captchaType)) + { + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); + } + + redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + try + { + ImageIO.write(image, "jpg", os); + } + catch (IOException e) + { + return AjaxResult.error(e.getMessage()); + } + + ajax.put("uuid", uuid); + ajax.put("img", Base64.encode(os.toByteArray())); + return ajax; + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/common/CommonController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/common/CommonController.java new file mode 100644 index 00000000..97ea214b --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/common/CommonController.java @@ -0,0 +1,171 @@ +package com.fastbee.web.controller.common; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import com.fastbee.common.config.RuoYiConfig; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.file.FileUploadUtils; +import com.fastbee.common.utils.file.FileUtils; +import com.fastbee.framework.config.ServerConfig; + +/** + * 通用请求处理 + * + * @author ruoyi + */ +@Api(tags = "通用请求处理") +@RestController +@RequestMapping("/common") +public class CommonController +{ + private static final Logger log = LoggerFactory.getLogger(CommonController.class); + + @Autowired + private ServerConfig serverConfig; + + private static final String FILE_DELIMETER = ","; + + /** + * 通用下载请求 + * + * @param fileName 文件名称 + * @param delete 是否删除 + */ + @ApiOperation("文件下载") + @GetMapping("/download") + public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) + { + try + { + if (!FileUtils.checkAllowDownload(fileName)) + { + throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName)); + } + String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); + String filePath = RuoYiConfig.getDownloadPath() + fileName; + + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, realFileName); + FileUtils.writeBytes(filePath, response.getOutputStream()); + if (delete) + { + FileUtils.deleteFile(filePath); + } + } + catch (Exception e) + { + log.error("下载文件失败", e); + } + } + + /** + * 通用上传请求(单个) + */ + @ApiOperation("单个文件上传") + @PostMapping("/upload") + public AjaxResult uploadFile(MultipartFile file) throws Exception + { + try + { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); + ajax.put("originalFilename", file.getOriginalFilename()); + return ajax; + } + catch (Exception e) + { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 通用上传请求(多个) + */ + @ApiOperation("多个文件上传") + @PostMapping("/uploads") + public AjaxResult uploadFiles(List files) throws Exception + { + try + { + // 上传文件路径 + String filePath = RuoYiConfig.getUploadPath(); + List urls = new ArrayList(); + List fileNames = new ArrayList(); + List newFileNames = new ArrayList(); + List originalFilenames = new ArrayList(); + for (MultipartFile file : files) + { + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + urls.add(url); + fileNames.add(fileName); + newFileNames.add(FileUtils.getName(fileName)); + originalFilenames.add(file.getOriginalFilename()); + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER)); + ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER)); + ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER)); + ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER)); + return ajax; + } + catch (Exception e) + { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 本地资源通用下载 + */ + @GetMapping("/download/resource") + @ApiOperation("本地资源通用下载") + public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response) + throws Exception + { + try + { + if (!FileUtils.checkAllowDownload(resource)) + { + throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource)); + } + // 本地资源路径 + String localPath = RuoYiConfig.getProfile(); + // 数据库资源地址 + String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX); + // 下载名称 + String downloadName = StringUtils.substringAfterLast(downloadPath, "/"); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, downloadName); + FileUtils.writeBytes(downloadPath, response.getOutputStream()); + } + catch (Exception e) + { + log.error("下载文件失败", e); + } + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/CacheController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/CacheController.java new file mode 100644 index 00000000..fe38cdd0 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/CacheController.java @@ -0,0 +1,131 @@ +package com.fastbee.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.constant.CacheConstants; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.system.domain.SysCache; + +/** + * 缓存监控 + * + * @author ruoyi + */ +@Api(tags = "缓存监控") +@RestController +@RequestMapping("/monitor/cache") +public class CacheController +{ + @Autowired + private RedisTemplate redisTemplate; + + private final static List caches = new ArrayList(); + { + caches.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息")); + caches.add(new SysCache(CacheConstants.SYS_CONFIG_KEY, "配置信息")); + caches.add(new SysCache(CacheConstants.SYS_DICT_KEY, "数据字典")); + caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码")); + caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交")); + caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理")); + caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数")); + } + + @ApiOperation("获取缓存信息") + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception + { + Properties info = (Properties) redisTemplate.execute((RedisCallback) connection -> connection.info()); + Properties commandStats = (Properties) redisTemplate.execute((RedisCallback) connection -> connection.info("commandstats")); + Object dbSize = redisTemplate.execute((RedisCallback) connection -> connection.dbSize()); + + Map result = new HashMap<>(3); + result.put("info", info); + result.put("dbSize", dbSize); + + List> pieList = new ArrayList<>(); + commandStats.stringPropertyNames().forEach(key -> { + Map data = new HashMap<>(2); + String property = commandStats.getProperty(key); + data.put("name", StringUtils.removeStart(key, "cmdstat_")); + data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); + pieList.add(data); + }); + result.put("commandStats", pieList); + return AjaxResult.success(result); + } + + @ApiOperation("缓存列表") + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getNames") + public AjaxResult cache() + { + return AjaxResult.success(caches); + } + + @ApiOperation("键名列表") + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getKeys/{cacheName}") + public AjaxResult getCacheKeys(@PathVariable String cacheName) + { + Set cacheKeys = redisTemplate.keys(cacheName + "*"); + return AjaxResult.success(cacheKeys); + } + + @ApiOperation("缓存内容") + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getValue/{cacheName}/{cacheKey}") + public AjaxResult getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) + { + String cacheValue = redisTemplate.opsForValue().get(cacheKey); + SysCache sysCache = new SysCache(cacheName, cacheKey, cacheValue); + return AjaxResult.success(sysCache); + } + + @ApiOperation("清理缓存名称") + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheName/{cacheName}") + public AjaxResult clearCacheName(@PathVariable String cacheName) + { + Collection cacheKeys = redisTemplate.keys(cacheName + "*"); + redisTemplate.delete(cacheKeys); + return AjaxResult.success(); + } + + @ApiOperation("清理缓存键名") + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheKey/{cacheKey}") + public AjaxResult clearCacheKey(@PathVariable String cacheKey) + { + redisTemplate.delete(cacheKey); + return AjaxResult.success(); + } + + @ApiOperation("清理所有缓存内容") + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheAll") + public AjaxResult clearCacheAll() + { + Collection cacheKeys = redisTemplate.keys("*"); + redisTemplate.delete(cacheKeys); + return AjaxResult.success(); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/ServerController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/ServerController.java new file mode 100644 index 00000000..27510ed8 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/ServerController.java @@ -0,0 +1,31 @@ +package com.fastbee.web.controller.monitor; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.framework.web.domain.Server; + +/** + * 服务器监控 + * + * @author ruoyi + */ +@Api(tags = "服务器监控") +@RestController +@RequestMapping("/monitor/server") +public class ServerController +{ + @ApiOperation("获取服务器信息") + @PreAuthorize("@ss.hasPermi('monitor:server:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception + { + Server server = new Server(); + server.copyTo(); + return AjaxResult.success(server); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/SysLogininforController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/SysLogininforController.java new file mode 100644 index 00000000..e2e0fc07 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/SysLogininforController.java @@ -0,0 +1,91 @@ +package com.fastbee.web.controller.monitor; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.framework.web.service.SysPasswordService; +import com.fastbee.system.domain.SysLogininfor; +import com.fastbee.system.service.ISysLogininforService; + +/** + * 系统访问记录 + * + * @author ruoyi + */ +@Api(tags = "日志管理:登录日志") +@RestController +@RequestMapping("/monitor/logininfor") +public class SysLogininforController extends BaseController +{ + @Autowired + private ISysLogininforService logininforService; + + @Autowired + private SysPasswordService passwordService; + + @ApiOperation("获取列表登录信息") + @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')") + @GetMapping("/list") + public TableDataInfo list(SysLogininfor logininfor) + { + startPage(); + List list = logininforService.selectLogininforList(logininfor); + return getDataTable(list); + } + + @ApiOperation("导出登录日志列表") + @Log(title = "登录日志", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('monitor:logininfor:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysLogininfor logininfor) + { + List list = logininforService.selectLogininforList(logininfor); + ExcelUtil util = new ExcelUtil(SysLogininfor.class); + util.exportExcel(response, list, "登录日志"); + } + + @ApiOperation("批量删除登录日志") + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{infoIds}") + public AjaxResult remove(@PathVariable Long[] infoIds) + { + return toAjax(logininforService.deleteLogininforByIds(infoIds)); + } + + @ApiOperation("清空登录日志信息") + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + logininforService.cleanLogininfor(); + return success(); + } + + @ApiOperation("账户解锁") + @PreAuthorize("@ss.hasPermi('monitor:logininfor:unlock')") + @Log(title = "账户解锁", businessType = BusinessType.OTHER) + @GetMapping("/unlock/{userName}") + public AjaxResult unlock(@PathVariable("userName") String userName) + { + passwordService.clearLoginRecordCache(userName); + return success(); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/SysOperlogController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/SysOperlogController.java new file mode 100644 index 00000000..d6ad7141 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/SysOperlogController.java @@ -0,0 +1,77 @@ +package com.fastbee.web.controller.monitor; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.system.domain.SysOperLog; +import com.fastbee.system.service.ISysOperLogService; + +/** + * 操作日志记录 + * + * @author ruoyi + */ +@Api(tags = "日志管理:操作日志") +@RestController +@RequestMapping("/monitor/operlog") +public class SysOperlogController extends BaseController +{ + @Autowired + private ISysOperLogService operLogService; + + @ApiOperation("获取操作日志列表") + @PreAuthorize("@ss.hasPermi('monitor:operlog:list')") + @GetMapping("/list") + public TableDataInfo list(SysOperLog operLog) + { + startPage(); + List list = operLogService.selectOperLogList(operLog); + return getDataTable(list); + } + + @ApiOperation("导出操作日志") + @Log(title = "操作日志", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('monitor:operlog:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysOperLog operLog) + { + List list = operLogService.selectOperLogList(operLog); + ExcelUtil util = new ExcelUtil(SysOperLog.class); + util.exportExcel(response, list, "操作日志"); + } + + @ApiOperation("批量删除操作日志") + @Log(title = "操作日志", businessType = BusinessType.DELETE) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/{operIds}") + public AjaxResult remove(@PathVariable Long[] operIds) + { + return toAjax(operLogService.deleteOperLogByIds(operIds)); + } + + @ApiOperation("清空操作日志") + @Log(title = "操作日志", businessType = BusinessType.CLEAN) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/clean") + public AjaxResult clean() + { + operLogService.cleanOperLog(); + return success(); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/SysUserOnlineController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/SysUserOnlineController.java new file mode 100644 index 00000000..68d132d6 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/monitor/SysUserOnlineController.java @@ -0,0 +1,98 @@ +package com.fastbee.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.constant.CacheConstants; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.system.domain.SysUserOnline; +import com.fastbee.system.service.ISysUserOnlineService; + +/** + * 在线用户监控 + * + * @author ruoyi + */ +@Api(tags = "在线用户监控") +@RestController +@RequestMapping("/monitor/online") +public class SysUserOnlineController extends BaseController +{ + @Autowired + private ISysUserOnlineService userOnlineService; + + @Autowired + private RedisCache redisCache; + + @ApiOperation("获取在线用户列表") + @PreAuthorize("@ss.hasPermi('monitor:online:list')") + @GetMapping("/list") + public TableDataInfo list(String ipaddr, String userName) + { + Collection keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*"); + List userOnlineList = new ArrayList(); + for (String key : keys) + { + LoginUser user = redisCache.getCacheObject(key); + if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) + { + if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) + { + userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user)); + } + } + else if (StringUtils.isNotEmpty(ipaddr)) + { + if (StringUtils.equals(ipaddr, user.getIpaddr())) + { + userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user)); + } + } + else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) + { + if (StringUtils.equals(userName, user.getUsername())) + { + userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user)); + } + } + else + { + userOnlineList.add(userOnlineService.loginUserToUserOnline(user)); + } + } + Collections.reverse(userOnlineList); + userOnlineList.removeAll(Collections.singleton(null)); + return getDataTable(userOnlineList); + } + + /** + * 强退用户 + */ + @ApiOperation("强制退出在线用户") + @PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')") + @Log(title = "在线用户", businessType = BusinessType.FORCE) + @DeleteMapping("/{tokenId}") + public AjaxResult forceLogout(@PathVariable String tokenId) + { + redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId); + return success(); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysConfigController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysConfigController.java new file mode 100644 index 00000000..c7e38561 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysConfigController.java @@ -0,0 +1,146 @@ +package com.fastbee.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.constant.UserConstants; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.system.domain.SysConfig; +import com.fastbee.system.service.ISysConfigService; + +/** + * 参数配置 信息操作处理 + * + * @author ruoyi + */ +@Api(tags = "参数设置") +@RestController +@RequestMapping("/system/config") +public class SysConfigController extends BaseController +{ + @Autowired + private ISysConfigService configService; + + /** + * 获取参数配置列表 + */ + @ApiOperation("获取参数配置列表") + @PreAuthorize("@ss.hasPermi('system:config:list')") + @GetMapping("/list") + public TableDataInfo list(SysConfig config) + { + startPage(); + List list = configService.selectConfigList(config); + return getDataTable(list); + } + + @ApiOperation("导出参数配置列表") + @Log(title = "参数管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:config:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysConfig config) + { + List list = configService.selectConfigList(config); + ExcelUtil util = new ExcelUtil(SysConfig.class); + util.exportExcel(response, list, "参数数据"); + } + + /** + * 根据参数编号获取详细信息 + */ + @ApiOperation("根据参数编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:config:query')") + @GetMapping(value = "/{configId}") + public AjaxResult getInfo(@PathVariable Long configId) + { + return success(configService.selectConfigById(configId)); + } + + /** + * 根据参数键名查询参数值 + */ + @ApiOperation("根据参数键名查询参数值") + @GetMapping(value = "/configKey/{configKey}") + public AjaxResult getConfigKey(@PathVariable String configKey) + { + return success(configService.selectConfigByKey(configKey)); + } + + /** + * 新增参数配置 + */ + @ApiOperation("新增参数配置") + @PreAuthorize("@ss.hasPermi('system:config:add')") + @Log(title = "参数管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysConfig config) + { + if (UserConstants.NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config))) + { + return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setCreateBy(getUsername()); + return toAjax(configService.insertConfig(config)); + } + + /** + * 修改参数配置 + */ + @ApiOperation("修改参数配置") + @PreAuthorize("@ss.hasPermi('system:config:edit')") + @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysConfig config) + { + if (UserConstants.NOT_UNIQUE.equals(configService.checkConfigKeyUnique(config))) + { + return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setUpdateBy(getUsername()); + return toAjax(configService.updateConfig(config)); + } + + /** + * 删除参数配置 + */ + @ApiOperation("批量删除参数配置") + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{configIds}") + public AjaxResult remove(@PathVariable Long[] configIds) + { + configService.deleteConfigByIds(configIds); + return success(); + } + + /** + * 刷新参数缓存 + */ + @ApiOperation("刷新参数缓存") + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + configService.resetConfigCache(); + return success(); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysDeptController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysDeptController.java new file mode 100644 index 00000000..25775982 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysDeptController.java @@ -0,0 +1,142 @@ +package com.fastbee.web.controller.system; + +import java.util.List; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.constant.UserConstants; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysDept; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.system.service.ISysDeptService; + +/** + * 部门信息 + * + * @author ruoyi + */ +@Api(tags = "部门管理") +@RestController +@RequestMapping("/system/dept") +public class SysDeptController extends BaseController +{ + @Autowired + private ISysDeptService deptService; + + /** + * 获取部门列表 + */ + @ApiOperation("获取部门列表") + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list") + public AjaxResult list(SysDept dept) + { + List depts = deptService.selectDeptList(dept); + return success(depts); + } + + /** + * 查询部门列表(排除节点) + */ + @ApiOperation("查询部门列表(排除节点)") + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list/exclude/{deptId}") + public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) + { + List depts = deptService.selectDeptList(new SysDept()); + depts.removeIf(d -> d.getDeptId().intValue() == deptId || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + "")); + return success(depts); + } + + /** + * 根据部门编号获取详细信息 + */ + @ApiOperation("根据部门编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:dept:query')") + @GetMapping(value = "/{deptId}") + public AjaxResult getInfo(@PathVariable Long deptId) + { + deptService.checkDeptDataScope(deptId); + return success(deptService.selectDeptById(deptId)); + } + + /** + * 新增部门 + */ + @ApiOperation("新增部门") + @PreAuthorize("@ss.hasPermi('system:dept:add')") + @Log(title = "部门管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDept dept) + { + if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) + { + return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + dept.setCreateBy(getUsername()); + return toAjax(deptService.insertDept(dept)); + } + + /** + * 修改部门 + */ + @ApiOperation("修改部门") + @PreAuthorize("@ss.hasPermi('system:dept:edit')") + @Log(title = "部门管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDept dept) + { + Long deptId = dept.getDeptId(); + deptService.checkDeptDataScope(deptId); + if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) + { + return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + else if (dept.getParentId().equals(deptId)) + { + return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己"); + } + else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) && deptService.selectNormalChildrenDeptById(deptId) > 0) + { + return error("该部门包含未停用的子部门!"); + } + dept.setUpdateBy(getUsername()); + return toAjax(deptService.updateDept(dept)); + } + + /** + * 删除部门 + */ + @ApiOperation("根据部门编号删除部门") + @PreAuthorize("@ss.hasPermi('system:dept:remove')") + @Log(title = "部门管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{deptId}") + public AjaxResult remove(@PathVariable Long deptId) + { + if (deptService.hasChildByDeptId(deptId)) + { + return warn("存在下级部门,不允许删除"); + } + if (deptService.checkDeptExistUser(deptId)) + { + return warn("部门存在用户,不允许删除"); + } + deptService.checkDeptDataScope(deptId); + return toAjax(deptService.deleteDeptById(deptId)); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysDictDataController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysDictDataController.java new file mode 100644 index 00000000..7fa03d78 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysDictDataController.java @@ -0,0 +1,121 @@ +package com.fastbee.web.controller.system; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysDictData; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.system.service.ISysDictDataService; +import com.fastbee.system.service.ISysDictTypeService; + +/** + * 数据字典信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/dict/data") +public class SysDictDataController extends BaseController +{ + @Autowired + private ISysDictDataService dictDataService; + + @Autowired + private ISysDictTypeService dictTypeService; + + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictData dictData) + { + startPage(); + List list = dictDataService.selectDictDataList(dictData); + return getDataTable(list); + } + + @Log(title = "字典数据", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:dict:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysDictData dictData) + { + List list = dictDataService.selectDictDataList(dictData); + ExcelUtil util = new ExcelUtil(SysDictData.class); + util.exportExcel(response, list, "字典数据"); + } + + /** + * 查询字典数据详细 + */ + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictCode}") + public AjaxResult getInfo(@PathVariable Long dictCode) + { + return success(dictDataService.selectDictDataById(dictCode)); + } + + /** + * 根据字典类型查询字典数据信息 + */ + @GetMapping(value = "/type/{dictType}") + public AjaxResult dictType(@PathVariable String dictType) + { + List data = dictTypeService.selectDictDataByType(dictType); + if (StringUtils.isNull(data)) + { + data = new ArrayList(); + } + return success(data); + } + + /** + * 新增字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典数据", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictData dict) + { + dict.setCreateBy(getUsername()); + return toAjax(dictDataService.insertDictData(dict)); + } + + /** + * 修改保存字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:edit')") + @Log(title = "字典数据", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictData dict) + { + dict.setUpdateBy(getUsername()); + return toAjax(dictDataService.updateDictData(dict)); + } + + /** + * 删除字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictCodes}") + public AjaxResult remove(@PathVariable Long[] dictCodes) + { + dictDataService.deleteDictDataByIds(dictCodes); + return success(); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysDictTypeController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysDictTypeController.java new file mode 100644 index 00000000..3ca77c31 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysDictTypeController.java @@ -0,0 +1,144 @@ +package com.fastbee.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.constant.UserConstants; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysDictType; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.system.service.ISysDictTypeService; + +/** + * 数据字典信息 + * + * @author ruoyi + */ +@Api(tags = "字典管理") +@RestController +@RequestMapping("/system/dict/type") +public class SysDictTypeController extends BaseController +{ + @Autowired + private ISysDictTypeService dictTypeService; + + @ApiOperation("获取字典分页列表") + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictType dictType) + { + startPage(); + List list = dictTypeService.selectDictTypeList(dictType); + return getDataTable(list); + } + + @ApiOperation("导出字典列表") + @Log(title = "字典类型", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:dict:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysDictType dictType) + { + List list = dictTypeService.selectDictTypeList(dictType); + ExcelUtil util = new ExcelUtil(SysDictType.class); + util.exportExcel(response, list, "字典类型"); + } + + /** + * 查询字典类型详细 + */ + @ApiOperation("查询字典类型详细") + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictId}") + public AjaxResult getInfo(@PathVariable Long dictId) + { + return success(dictTypeService.selectDictTypeById(dictId)); + } + + /** + * 新增字典类型 + */ + @ApiOperation("新增字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典类型", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictType dict) + { + if (UserConstants.NOT_UNIQUE.equals(dictTypeService.checkDictTypeUnique(dict))) + { + return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setCreateBy(getUsername()); + return toAjax(dictTypeService.insertDictType(dict)); + } + + /** + * 修改字典类型 + */ + @ApiOperation("新增字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:edit')") + @Log(title = "字典类型", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictType dict) + { + if (UserConstants.NOT_UNIQUE.equals(dictTypeService.checkDictTypeUnique(dict))) + { + return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setUpdateBy(getUsername()); + return toAjax(dictTypeService.updateDictType(dict)); + } + + /** + * 删除字典类型 + */ + @ApiOperation("删除字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictIds}") + public AjaxResult remove(@PathVariable Long[] dictIds) + { + dictTypeService.deleteDictTypeByIds(dictIds); + return success(); + } + + /** + * 刷新字典缓存 + */ + @ApiOperation("刷新字典缓存") + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + dictTypeService.resetDictCache(); + return success(); + } + + /** + * 获取字典选择框列表 + */ + @ApiOperation("获取字典选择框列表") + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List dictTypes = dictTypeService.selectDictTypeAll(); + return success(dictTypes); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysIndexController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysIndexController.java new file mode 100644 index 00000000..1920037e --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysIndexController.java @@ -0,0 +1,29 @@ +package com.fastbee.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.config.RuoYiConfig; +import com.fastbee.common.utils.StringUtils; + +/** + * 首页 + * + * @author ruoyi + */ +@RestController +public class SysIndexController +{ + /** 系统基础配置 */ + @Autowired + private RuoYiConfig ruoyiConfig; + + /** + * 访问首页,提示语 + */ + @RequestMapping("/") + public String index() + { + return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion()); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysLoginController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysLoginController.java new file mode 100644 index 00000000..a11308b7 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysLoginController.java @@ -0,0 +1,97 @@ +package com.fastbee.web.controller.system; + +import java.util.List; +import java.util.Set; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysMenu; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.domain.model.LoginBody; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.framework.web.service.SysLoginService; +import com.fastbee.framework.web.service.SysPermissionService; +import com.fastbee.system.service.ISysMenuService; + +/** + * 登录验证 + * + * @author ruoyi + */ +@Api(tags = "登录验证") +@RestController +public class SysLoginController +{ + @Autowired + private SysLoginService loginService; + + @Autowired + private ISysMenuService menuService; + + @Autowired + private SysPermissionService permissionService; + @Value("${server.broker.enabled}") + private Boolean enabled; + + /** + * 登录方法 + * + * @param loginBody 登录信息 + * @return 结果 + */ + @ApiOperation("用户登录") + @PostMapping("/login") + public AjaxResult login(@RequestBody LoginBody loginBody) + { + AjaxResult ajax = AjaxResult.success(); + // 生成令牌 + String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), + loginBody.getUuid()); + ajax.put(Constants.TOKEN, token); + return ajax; + } + + /** + * 获取用户信息 + * + * @return 用户信息 + */ + @ApiOperation("获取用户信息") + @GetMapping("getInfo") + public AjaxResult getInfo() + { + SysUser user = SecurityUtils.getLoginUser().getUser(); + // 角色集合 + Set roles = permissionService.getRolePermission(user); + // 权限集合 + Set permissions = permissionService.getMenuPermission(user); + AjaxResult ajax = AjaxResult.success(); + ajax.put("user", user); + ajax.put("roles", roles); + ajax.put("permissions", permissions); + ajax.put("mqtt",enabled); + return ajax; + } + + /** + * 获取路由信息 + * + * @return 路由信息 + */ + @ApiOperation("获取路由信息") + @GetMapping("getRouters") + public AjaxResult getRouters() + { + Long userId = SecurityUtils.getUserId(); + List menus = menuService.selectMenuTreeByUserId(userId); + return AjaxResult.success(menuService.buildMenus(menus)); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysMenuController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysMenuController.java new file mode 100644 index 00000000..f63493e0 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysMenuController.java @@ -0,0 +1,153 @@ +package com.fastbee.web.controller.system; + +import java.util.List; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.constant.UserConstants; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysMenu; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.system.service.ISysMenuService; + +/** + * 菜单信息 + * + * @author ruoyi + */ +@Api(tags = "菜单管理") +@RestController +@RequestMapping("/system/menu") +public class SysMenuController extends BaseController +{ + @Autowired + private ISysMenuService menuService; + + /** + * 获取菜单列表 + */ + @ApiOperation("获取菜单列表") + @PreAuthorize("@ss.hasPermi('system:menu:list')") + @GetMapping("/list") + public AjaxResult list(SysMenu menu) + { + List menus = menuService.selectMenuList(menu, getUserId()); + return success(menus); + } + + /** + * 根据菜单编号获取详细信息 + */ + @ApiOperation("根据菜单编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:menu:query')") + @GetMapping(value = "/{menuId}") + public AjaxResult getInfo(@PathVariable Long menuId) + { + return success(menuService.selectMenuById(menuId)); + } + + /** + * 获取菜单下拉树列表 + */ + @ApiOperation("获取菜单下拉树列表") + @GetMapping("/treeselect") + public AjaxResult treeselect(SysMenu menu) + { + List menus = menuService.selectMenuList(menu, getUserId()); + return success(menuService.buildMenuTreeSelect(menus)); + } + + /** + * 加载对应角色菜单列表树 + */ + @ApiOperation("加载对应角色菜单列表树") + @GetMapping(value = "/roleMenuTreeselect/{roleId}") + public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId) + { + List menus = menuService.selectMenuList(getUserId()); + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId)); + ajax.put("menus", menuService.buildMenuTreeSelect(menus)); + return ajax; + } + + /** + * 新增菜单 + */ + @ApiOperation("新增菜单") + @PreAuthorize("@ss.hasPermi('system:menu:add')") + @Log(title = "菜单管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysMenu menu) + { + if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + menu.setCreateBy(getUsername()); + return toAjax(menuService.insertMenu(menu)); + } + + /** + * 修改菜单 + */ + @ApiOperation("修改菜单") + @PreAuthorize("@ss.hasPermi('system:menu:edit')") + @Log(title = "菜单管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysMenu menu) + { + if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + else if (menu.getMenuId().equals(menu.getParentId())) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); + } + menu.setUpdateBy(getUsername()); + return toAjax(menuService.updateMenu(menu)); + } + + /** + * 删除菜单 + */ + @ApiOperation("删除菜单") + @PreAuthorize("@ss.hasPermi('system:menu:remove')") + @Log(title = "菜单管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{menuId}") + public AjaxResult remove(@PathVariable("menuId") Long menuId) + { + if (menuService.hasChildByMenuId(menuId)) + { + return warn("存在子菜单,不允许删除"); + } + if (menuService.checkMenuExistRole(menuId)) + { + return warn("菜单已分配,不允许删除"); + } + return toAjax(menuService.deleteMenuById(menuId)); + } +} \ No newline at end of file diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysNoticeController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysNoticeController.java new file mode 100644 index 00000000..da21a916 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysNoticeController.java @@ -0,0 +1,100 @@ +package com.fastbee.web.controller.system; + +import java.util.List; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.system.domain.SysNotice; +import com.fastbee.system.service.ISysNoticeService; + +/** + * 公告 信息操作处理 + * + * @author ruoyi + */ +@Api(tags = "通知公告") +@RestController +@RequestMapping("/system/notice") +public class SysNoticeController extends BaseController +{ + @Autowired + private ISysNoticeService noticeService; + + /** + * 获取通知公告列表 + */ + @ApiOperation("获取通知公告列表") + @PreAuthorize("@ss.hasPermi('system:notice:list')") + @GetMapping("/list") + public TableDataInfo list(SysNotice notice) + { + startPage(); + List list = noticeService.selectNoticeList(notice); + return getDataTable(list); + } + + /** + * 根据通知公告编号获取详细信息 + */ + @ApiOperation("根据通知公告编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:notice:query')") + @GetMapping(value = "/{noticeId}") + public AjaxResult getInfo(@PathVariable Long noticeId) + { + return success(noticeService.selectNoticeById(noticeId)); + } + + /** + * 新增通知公告 + */ + @ApiOperation("新增通知公告") + @PreAuthorize("@ss.hasPermi('system:notice:add')") + @Log(title = "通知公告", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysNotice notice) + { + notice.setCreateBy(getUsername()); + return toAjax(noticeService.insertNotice(notice)); + } + + /** + * 修改通知公告 + */ + @ApiOperation("修改通知公告") + @PreAuthorize("@ss.hasPermi('system:notice:edit')") + @Log(title = "通知公告", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysNotice notice) + { + notice.setUpdateBy(getUsername()); + return toAjax(noticeService.updateNotice(notice)); + } + + /** + * 删除通知公告 + */ + @ApiOperation("删除通知公告") + @PreAuthorize("@ss.hasPermi('system:notice:remove')") + @Log(title = "通知公告", businessType = BusinessType.DELETE) + @DeleteMapping("/{noticeIds}") + public AjaxResult remove(@PathVariable Long[] noticeIds) + { + return toAjax(noticeService.deleteNoticeByIds(noticeIds)); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysPostController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysPostController.java new file mode 100644 index 00000000..29b3f2c1 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysPostController.java @@ -0,0 +1,141 @@ +package com.fastbee.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.constant.UserConstants; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.system.domain.SysPost; +import com.fastbee.system.service.ISysPostService; + +/** + * 岗位信息操作处理 + * + * @author ruoyi + */ +@Api(tags = "岗位管理") +@RestController +@RequestMapping("/system/post") +public class SysPostController extends BaseController +{ + @Autowired + private ISysPostService postService; + + /** + * 获取岗位列表 + */ + @ApiOperation("获取岗位列表") + @PreAuthorize("@ss.hasPermi('system:post:list')") + @GetMapping("/list") + public TableDataInfo list(SysPost post) + { + startPage(); + List list = postService.selectPostList(post); + return getDataTable(list); + } + + @ApiOperation("导出岗位列表") + @Log(title = "岗位管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:post:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysPost post) + { + List list = postService.selectPostList(post); + ExcelUtil util = new ExcelUtil(SysPost.class); + util.exportExcel(response, list, "岗位数据"); + } + + /** + * 根据岗位编号获取详细信息 + */ + @ApiOperation("根据岗位编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:post:query')") + @GetMapping(value = "/{postId}") + public AjaxResult getInfo(@PathVariable Long postId) + { + return success(postService.selectPostById(postId)); + } + + /** + * 新增岗位 + */ + @ApiOperation("新增岗位") + @PreAuthorize("@ss.hasPermi('system:post:add')") + @Log(title = "岗位管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysPost post) + { + if (UserConstants.NOT_UNIQUE.equals(postService.checkPostNameUnique(post))) + { + return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (UserConstants.NOT_UNIQUE.equals(postService.checkPostCodeUnique(post))) + { + return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setCreateBy(getUsername()); + return toAjax(postService.insertPost(post)); + } + + /** + * 修改岗位 + */ + @ApiOperation("修改岗位") + @PreAuthorize("@ss.hasPermi('system:post:edit')") + @Log(title = "岗位管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysPost post) + { + if (UserConstants.NOT_UNIQUE.equals(postService.checkPostNameUnique(post))) + { + return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (UserConstants.NOT_UNIQUE.equals(postService.checkPostCodeUnique(post))) + { + return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setUpdateBy(getUsername()); + return toAjax(postService.updatePost(post)); + } + + /** + * 删除岗位 + */ + @ApiOperation("删除岗位") + @PreAuthorize("@ss.hasPermi('system:post:remove')") + @Log(title = "岗位管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{postIds}") + public AjaxResult remove(@PathVariable Long[] postIds) + { + return toAjax(postService.deletePostByIds(postIds)); + } + + /** + * 获取岗位选择框列表 + */ + @ApiOperation("获取岗位选择框列表") + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List posts = postService.selectPostAll(); + return success(posts); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysProfileController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysProfileController.java new file mode 100644 index 00000000..8b3a257f --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysProfileController.java @@ -0,0 +1,163 @@ +package com.fastbee.web.controller.system; + +import com.fastbee.common.annotation.Log; +import com.fastbee.common.config.RuoYiConfig; +import com.fastbee.common.constant.UserConstants; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.enums.SocialPlatformType; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.file.FileUploadUtils; +import com.fastbee.common.utils.file.MimeTypeUtils; +import com.fastbee.framework.web.service.TokenService; +import com.fastbee.iot.domain.UserSocialProfile; +import com.fastbee.iot.service.IUserSocialProfileService; +import com.fastbee.system.service.ISysUserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * 个人信息 业务处理 + * + * @author ruoyi + */ +@Api(tags = "个人中心") +@RestController +@RequestMapping("/system/user/profile") +public class SysProfileController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private TokenService tokenService; + + @Autowired + private IUserSocialProfileService iUserSocialProfileService; + + /** + * 个人信息 + */ + @ApiOperation("获取个人信息") + @GetMapping + public AjaxResult profile() + { + LoginUser loginUser = getLoginUser(); + SysUser user = loginUser.getUser(); + AjaxResult ajax = AjaxResult.success(user); + ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername())); + ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername())); + List socialProfileList = iUserSocialProfileService.selectUserSocialProfile(loginUser.getUserId()); + UserSocialProfile userSocialProfile = socialProfileList.stream().filter(s -> SocialPlatformType.listWechatPlatform.contains(s.getSourceClient()) && "1".equals(s.getStatus())).findFirst().orElse(null); + ajax.put("socialGroup", socialProfileList); + ajax.put("wxBind", userSocialProfile != null); + return ajax; + } + + /** + * 修改用户 + */ + @ApiOperation("修改个人信息") + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult updateProfile(@RequestBody SysUser user) + { + LoginUser loginUser = getLoginUser(); + SysUser sysUser = loginUser.getUser(); + user.setUserName(sysUser.getUserName()); + if (StringUtils.isNotEmpty(user.getPhonenumber()) + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) + { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) + { + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUserId(sysUser.getUserId()); + user.setPassword(null); + user.setAvatar(null); + user.setDeptId(null); + if (userService.updateUserProfile(user) > 0) + { + // 更新缓存用户信息 + sysUser.setNickName(user.getNickName()); + sysUser.setPhonenumber(user.getPhonenumber()); + sysUser.setEmail(user.getEmail()); + sysUser.setSex(user.getSex()); + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改个人信息异常,请联系管理员"); + } + + /** + * 重置密码 + */ + @ApiOperation("重置密码") + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping("/updatePwd") + public AjaxResult updatePwd(String oldPassword, String newPassword) + { + LoginUser loginUser = getLoginUser(); + String userName = loginUser.getUsername(); + String password = loginUser.getPassword(); + if (!SecurityUtils.matchesPassword(oldPassword, password)) + { + return error("修改密码失败,旧密码错误"); + } + if (SecurityUtils.matchesPassword(newPassword, password)) + { + return error("新密码不能与旧密码相同"); + } + if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0) + { + // 更新缓存用户密码 + loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword)); + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改密码异常,请联系管理员"); + } + + /** + * 头像上传 + */ + @ApiOperation("头像上传") + @Log(title = "用户头像", businessType = BusinessType.UPDATE) + @PostMapping("/avatar") + public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception + { + if (!file.isEmpty()) + { + LoginUser loginUser = getLoginUser(); + String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION); + if (userService.updateUserAvatar(loginUser.getUsername(), avatar)) + { + AjaxResult ajax = AjaxResult.success(); + ajax.put("imgUrl", avatar); + // 更新缓存用户头像 + loginUser.getUser().setAvatar(avatar); + tokenService.setLoginUser(loginUser); + return ajax; + } + } + return error("上传图片异常,请联系管理员"); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysRegisterController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysRegisterController.java new file mode 100644 index 00000000..2bb1c580 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysRegisterController.java @@ -0,0 +1,42 @@ +package com.fastbee.web.controller.system; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.model.RegisterBody; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.framework.web.service.SysRegisterService; +import com.fastbee.system.service.ISysConfigService; + +/** + * 注册验证 + * + * @author ruoyi + */ +@Api(tags = "注册账号") +@RestController +public class SysRegisterController extends BaseController +{ + @Autowired + private SysRegisterService registerService; + + @Autowired + private ISysConfigService configService; + + @ApiOperation("注册账号") + @PostMapping("/register") + public AjaxResult register(@RequestBody RegisterBody user) + { + if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) + { + return error("当前系统没有开启注册功能!"); + } + String msg = registerService.register(user); + return StringUtils.isEmpty(msg) ? success() : error(msg); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysRoleController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysRoleController.java new file mode 100644 index 00000000..0ddef4bf --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysRoleController.java @@ -0,0 +1,282 @@ +package com.fastbee.web.controller.system; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.constant.UserConstants; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysDept; +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.framework.web.service.SysPermissionService; +import com.fastbee.framework.web.service.TokenService; +import com.fastbee.system.domain.SysUserRole; +import com.fastbee.system.service.ISysDeptService; +import com.fastbee.system.service.ISysRoleService; +import com.fastbee.system.service.ISysUserService; + +/** + * 角色信息 + * + * @author ruoyi + */ +@Api(tags = "角色管理") +@RestController +@RequestMapping("/system/role") +public class SysRoleController extends BaseController +{ + @Autowired + private ISysRoleService roleService; + + @Autowired + private TokenService tokenService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysDeptService deptService; + + @ApiOperation("获取角色分页列表") + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/list") + public TableDataInfo list(SysRole role) + { + startPage(); + List list = roleService.selectRoleList(role); + return getDataTable(list); + } + + @ApiOperation("导出角色列表") + @Log(title = "角色管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:role:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysRole role) + { + List list = roleService.selectRoleList(role); + ExcelUtil util = new ExcelUtil(SysRole.class); + util.exportExcel(response, list, "角色数据"); + } + + /** + * 根据角色编号获取详细信息 + */ + @ApiOperation("根据角色编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/{roleId}") + public AjaxResult getInfo(@PathVariable Long roleId) + { + roleService.checkRoleDataScope(roleId); + return success(roleService.selectRoleById(roleId)); + } + + /** + * 新增角色 + */ + @ApiOperation("新增角色") + @PreAuthorize("@ss.hasPermi('system:role:add')") + @Log(title = "角色管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysRole role) + { + if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) + { + return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } + else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) + { + return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setCreateBy(getUsername()); + return toAjax(roleService.insertRole(role)); + + } + + /** + * 修改保存角色 + */ + @ApiOperation("修改角色") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) + { + return error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } + else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) + { + return error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setUpdateBy(getUsername()); + + if (roleService.updateRole(role) > 0) + { + // 更新缓存用户权限 + LoginUser loginUser = getLoginUser(); + if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin()) + { + loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser())); + loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName())); + tokenService.setLoginUser(loginUser); + } + return success(); + } + return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); + } + + /** + * 修改保存数据权限 + */ + @ApiOperation("修改保存数据权限") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/dataScope") + public AjaxResult dataScope(@RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + return toAjax(roleService.authDataScope(role)); + } + + /** + * 状态修改 + */ + @ApiOperation("修改角色状态") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + role.setUpdateBy(getUsername()); + return toAjax(roleService.updateRoleStatus(role)); + } + + /** + * 删除角色 + */ + @ApiOperation("删除角色") + @PreAuthorize("@ss.hasPermi('system:role:remove')") + @Log(title = "角色管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{roleIds}") + public AjaxResult remove(@PathVariable Long[] roleIds) + { + return toAjax(roleService.deleteRoleByIds(roleIds)); + } + + /** + * 获取角色选择框列表 + */ + @ApiOperation("获取角色选择框列表") + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + return success(roleService.selectRoleAll()); + } + + /** + * 查询已分配用户角色列表 + */ + @ApiOperation("查询已分配用户角色列表") + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/allocatedList") + public TableDataInfo allocatedList(SysUser user) + { + startPage(); + List list = userService.selectAllocatedList(user); + return getDataTable(list); + } + + /** + * 查询未分配用户角色列表 + */ + @ApiOperation("查询未分配用户角色列表") + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/unallocatedList") + public TableDataInfo unallocatedList(SysUser user) + { + startPage(); + List list = userService.selectUnallocatedList(user); + return getDataTable(list); + } + + /** + * 取消授权用户 + */ + @ApiOperation("取消授权用户") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancel") + public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole) + { + return toAjax(roleService.deleteAuthUser(userRole)); + } + + /** + * 批量取消授权用户 + */ + @ApiOperation("批量取消授权用户") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancelAll") + public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds) + { + return toAjax(roleService.deleteAuthUsers(roleId, userIds)); + } + + /** + * 批量选择用户授权 + */ + @ApiOperation("批量选择用户授权") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/selectAll") + public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds) + { + roleService.checkRoleDataScope(roleId); + return toAjax(roleService.insertAuthUsers(roleId, userIds)); + } + + /** + * 获取对应角色部门树列表 + */ + @ApiOperation("获取对应角色部门树列表") + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/deptTree/{roleId}") + public AjaxResult deptTree(@PathVariable("roleId") Long roleId) + { + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId)); + ajax.put("depts", deptService.selectDeptTreeList(new SysDept())); + return ajax; + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserController.java new file mode 100644 index 00000000..09c534c3 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserController.java @@ -0,0 +1,274 @@ +package com.fastbee.web.controller.system; + +import java.util.List; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.constant.UserConstants; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysDept; +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.system.service.ISysDeptService; +import com.fastbee.system.service.ISysPostService; +import com.fastbee.system.service.ISysRoleService; +import com.fastbee.system.service.ISysUserService; + +/** + * 用户信息 + * + * @author ruoyi + */ +@Api(tags = "用户管理") +@RestController +@RequestMapping("/system/user") +public class SysUserController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysDeptService deptService; + + @Autowired + private ISysPostService postService; + + /** + * 获取用户列表 + */ + @ApiOperation("获取用户分页列表") + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/list") + public TableDataInfo list(SysUser user) + { + startPage(); + List list = userService.selectUserList(user); + return getDataTable(list); + } + + @ApiOperation("导出用户列表") + @Log(title = "用户管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:user:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysUser user) + { + List list = userService.selectUserList(user); + ExcelUtil util = new ExcelUtil(SysUser.class); + util.exportExcel(response, list, "用户数据"); + } + + @ApiOperation("批量导入用户") + @Log(title = "用户管理", businessType = BusinessType.IMPORT) + @PreAuthorize("@ss.hasPermi('system:user:import')") + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception + { + ExcelUtil util = new ExcelUtil(SysUser.class); + List userList = util.importExcel(file.getInputStream()); + String operName = getUsername(); + String message = userService.importUser(userList, updateSupport, operName); + return success(message); + } + + + @ApiOperation("下载用户导入模板") + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) + { + ExcelUtil util = new ExcelUtil(SysUser.class); + util.importTemplateExcel(response, "用户数据"); + } + + /** + * 根据用户编号获取详细信息 + */ + @ApiOperation("根据用户编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping(value = { "/", "/{userId}" }) + public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId) + { + userService.checkUserDataScope(userId); + AjaxResult ajax = AjaxResult.success(); + List roles = roleService.selectRoleAll(); + ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + ajax.put("posts", postService.selectPostAll()); + if (StringUtils.isNotNull(userId)) + { + SysUser sysUser = userService.selectUserById(userId); + ajax.put(AjaxResult.DATA_TAG, sysUser); + ajax.put("postIds", postService.selectPostListByUserId(userId)); + ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList())); + } + return ajax; + } + + /** + * 新增用户 + */ + @ApiOperation("新增用户") + @PreAuthorize("@ss.hasPermi('system:user:add')") + @Log(title = "用户管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysUser user) + { + if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user))) + { + return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) + { + return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) + { + return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setCreateBy(getUsername()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + return toAjax(userService.insertUser(user)); + } + + /** + * 修改用户 + */ + @ApiOperation("修改用户") + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user))) + { + return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) + { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) + { + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUser(user)); + } + + /** + * 删除用户 + */ + @ApiOperation("删除用户") + @PreAuthorize("@ss.hasPermi('system:user:remove')") + @Log(title = "用户管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{userIds}") + public AjaxResult remove(@PathVariable Long[] userIds) + { + if (ArrayUtils.contains(userIds, getUserId())) + { + return error("当前用户不能删除"); + } + return toAjax(userService.deleteUserByIds(userIds)); + } + + /** + * 重置密码 + */ + @ApiOperation("重置用户密码") + @PreAuthorize("@ss.hasPermi('system:user:resetPwd')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/resetPwd") + public AjaxResult resetPwd(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + user.setUpdateBy(getUsername()); + return toAjax(userService.resetPwd(user)); + } + + /** + * 状态修改 + */ + @ApiOperation("修改用户状态") + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUserStatus(user)); + } + + /** + * 根据用户编号获取授权角色 + */ + @ApiOperation("根据用户编号获取授权角色") + @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping("/authRole/{userId}") + public AjaxResult authRole(@PathVariable("userId") Long userId) + { + AjaxResult ajax = AjaxResult.success(); + SysUser user = userService.selectUserById(userId); + List roles = roleService.selectRolesByUserId(userId); + ajax.put("user", user); + ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + return ajax; + } + + /** + * 用户授权角色 + */ + @ApiOperation("为用户授权角色") + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.GRANT) + @PutMapping("/authRole") + public AjaxResult insertAuthRole(Long userId, Long[] roleIds) + { + userService.checkUserDataScope(userId); + userService.insertUserAuth(userId, roleIds); + return success(); + } + + /** + * 获取部门树列表 + */ + @ApiOperation("获取部门树列表") + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/deptTree") + public AjaxResult deptTree(SysDept dept) + { + return success(deptService.selectDeptTreeList(dept)); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/tool/SwaggerController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/tool/SwaggerController.java new file mode 100644 index 00000000..94f0e3e4 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/tool/SwaggerController.java @@ -0,0 +1,24 @@ +package com.fastbee.web.controller.tool; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import com.fastbee.common.core.controller.BaseController; + +/** + * swagger 接口 + * + * @author ruoyi + */ +@Controller +@RequestMapping("/tool/swagger") +public class SwaggerController extends BaseController +{ + @PreAuthorize("@ss.hasPermi('tool:swagger:view')") + @GetMapping() + public String index() + { + return redirect("/swagger-ui.html"); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/tool/TestController.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/tool/TestController.java new file mode 100644 index 00000000..f05f972a --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/tool/TestController.java @@ -0,0 +1,185 @@ +package com.fastbee.web.controller.tool; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.R; +import com.fastbee.common.utils.StringUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import io.swagger.annotations.ApiOperation; + +import javax.annotation.Resource; + +/** + * swagger 用户测试方法 + * + * @author ruoyi + */ +@Api("swagger 用户测试方法") +@RestController +@RequestMapping("/test/user") +public class TestController extends BaseController +{ + private final static Map users = new LinkedHashMap(); + { + users.put(1, new UserEntity(1, "admin", "admin123", "15888888888")); + users.put(2, new UserEntity(2, "ry", "admin123", "15666666666")); + } + + @ApiOperation("获取用户列表") + @GetMapping("/list") + public R> userList() + { + List userList = new ArrayList(users.values()); + return R.ok(userList); + } + + @ApiOperation("获取用户详细") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class) + @GetMapping("/{userId}") + public R getUser(@PathVariable Integer userId) + { + if (!users.isEmpty() && users.containsKey(userId)) + { + return R.ok(users.get(userId)); + } + else + { + return R.fail("用户不存在"); + } + } + + @ApiOperation("新增用户") + @ApiImplicitParams({ + @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class), + @ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class), + @ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class) + }) + @PostMapping("/save") + public R save(UserEntity user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) + { + return R.fail("用户ID不能为空"); + } + users.put(user.getUserId(), user); + return R.ok(); + } + + @ApiOperation("更新用户") + @PutMapping("/update") + public R update(@RequestBody UserEntity user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) + { + return R.fail("用户ID不能为空"); + } + if (users.isEmpty() || !users.containsKey(user.getUserId())) + { + return R.fail("用户不存在"); + } + users.remove(user.getUserId()); + users.put(user.getUserId(), user); + return R.ok(); + } + + @ApiOperation("删除用户信息") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class) + @DeleteMapping("/{userId}") + public R delete(@PathVariable Integer userId) + { + if (!users.isEmpty() && users.containsKey(userId)) + { + users.remove(userId); + return R.ok(); + } + else + { + return R.fail("用户不存在"); + } + } +} + +@ApiModel(value = "UserEntity", description = "用户实体") +class UserEntity +{ + @ApiModelProperty("用户ID") + private Integer userId; + + @ApiModelProperty("用户名称") + private String username; + + @ApiModelProperty("用户密码") + private String password; + + @ApiModelProperty("用户手机") + private String mobile; + + public UserEntity() + { + + } + + public UserEntity(Integer userId, String username, String password, String mobile) + { + this.userId = userId; + this.username = username; + this.password = password; + this.mobile = mobile; + } + + public Integer getUserId() + { + return userId; + } + + public void setUserId(Integer userId) + { + this.userId = userId; + } + + 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 getMobile() + { + return mobile; + } + + public void setMobile(String mobile) + { + this.mobile = mobile; + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/tool/TestController2.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/tool/TestController2.java new file mode 100644 index 00000000..971bf220 --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/controller/tool/TestController2.java @@ -0,0 +1,44 @@ +package com.fastbee.web.controller.tool; + +import com.fastbee.common.annotation.Anonymous; +import com.fastbee.iot.mapper.DeviceMapper; +import com.fastbee.iot.model.DeviceRelateAlertLogVO; +import com.fastbee.iot.service.IDeviceService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 测试类 + * @author fastb + * @date 2023-09-13 11:42 + */ +@Anonymous +@RestController +@RequestMapping("/test2") +public class TestController2 { + + @Resource + private IDeviceService deviceService; + @Resource + private DeviceMapper deviceMapper; + + @GetMapping("/add") + public void add() + { + Set deviceNumbers = new HashSet<>(); + deviceNumbers.add("D1PGLPG58K88"); + deviceNumbers.add("D1F0L7P84D8Z"); + deviceNumbers.add("D1F0L7P84D8Z_2"); + List deviceRelateAlertLogVOList = deviceMapper.selectDeviceBySerialNumbers(deviceNumbers); + Map deviceRelateAlertLogVOMap = deviceRelateAlertLogVOList.stream().collect(Collectors.toMap(DeviceRelateAlertLogVO::getSerialNumber, Function.identity())); + } +} diff --git a/springboot/fastbee-admin/src/main/java/com/fastbee/web/core/config/SwaggerConfig.java b/springboot/fastbee-admin/src/main/java/com/fastbee/web/core/config/SwaggerConfig.java new file mode 100644 index 00000000..ab6bb7be --- /dev/null +++ b/springboot/fastbee-admin/src/main/java/com/fastbee/web/core/config/SwaggerConfig.java @@ -0,0 +1,125 @@ +package com.fastbee.web.core.config; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.fastbee.common.config.RuoYiConfig; +import io.swagger.annotations.ApiOperation; +import io.swagger.models.auth.In; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.AuthorizationScope; +import springfox.documentation.service.Contact; +import springfox.documentation.service.SecurityReference; +import springfox.documentation.service.SecurityScheme; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spi.service.contexts.SecurityContext; +import springfox.documentation.spring.web.plugins.Docket; + +/** + * Swagger2的接口配置 + * + * @author ruoyi + */ +@Configuration +public class SwaggerConfig +{ + /** 系统基础配置 */ + @Autowired + private RuoYiConfig ruoyiConfig; + + /** 是否开启swagger */ + @Value("${swagger.enabled}") + private boolean enabled; + + /** 设置请求的统一前缀 */ + @Value("${swagger.pathMapping}") + private String pathMapping; + + /** + * 创建API + */ + @Bean + public Docket createRestApi() + { + return new Docket(DocumentationType.OAS_30) + // 是否启用Swagger + .enable(enabled) + // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息) + .apiInfo(apiInfo()) + // 设置哪些接口暴露给Swagger展示 + .select() + // 扫描所有有注解的api,用这种方式更灵活 + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) + // 扫描指定包中的swagger注解 + // .apis(RequestHandlerSelectors.basePackage("com.fastbee.project.tool.swagger")) + // 扫描所有 .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build() + /* 设置安全模式,swagger可以设置访问token */ + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()) + .pathMapping(pathMapping); + } + + /** + * 安全模式,这里指定token通过Authorization头请求头传递 + */ + private List securitySchemes() + { + List apiKeyList = new ArrayList(); + apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); + return apiKeyList; + } + + /** + * 安全上下文 + */ + private List securityContexts() + { + List securityContexts = new ArrayList<>(); + securityContexts.add( + SecurityContext.builder() + .securityReferences(defaultAuth()) + .operationSelector(o -> o.requestMappingPattern().matches("/.*")) + .build()); + return securityContexts; + } + + /** + * 默认的安全上引用 + */ + private List defaultAuth() + { + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + List securityReferences = new ArrayList<>(); + securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); + return securityReferences; + } + + /** + * 添加摘要信息 + */ + private ApiInfo apiInfo() + { + // 用ApiInfoBuilder进行定制 + return new ApiInfoBuilder() + // 设置标题 + .title("FastBee物联网平台接口文档") + // 描述 + .description("描述:FastBee物联网平台") + // 作者信息 + .contact(new Contact(ruoyiConfig.getName(), null, null)) + // 版本 + .version("版本号:" + ruoyiConfig.getVersion()) + .build(); + } +} diff --git a/springboot/fastbee-admin/src/main/resources/META-INF/spring-devtools.properties b/springboot/fastbee-admin/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 00000000..37e7b580 --- /dev/null +++ b/springboot/fastbee-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/springboot/fastbee-admin/src/main/resources/application-dev.yml b/springboot/fastbee-admin/src/main/resources/application-dev.yml new file mode 100644 index 00000000..952efd5a --- /dev/null +++ b/springboot/fastbee-admin/src/main/resources/application-dev.yml @@ -0,0 +1,82 @@ +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://localhost/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: 123456 + # 从库数据源 + slave: + enabled: false # 从数据源开关/默认关闭 + url: + username: + password: + initialSize: 5 # 初始连接数 + minIdle: 10 # 最小连接池数量 + maxActive: 20 # 最大连接池数量 + maxWait: 60000 # 配置获取连接等待超时的时间 + timeBetweenEvictionRunsMillis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 # 配置一个连接在池中最大生存的时间,单位是毫秒 + validationQuery: SELECT 1 FROM DUAL # 配置检测连接是否有效 + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + webStatFilter: + enabled: true + statViewServlet: + enabled: true + # 设置白名单,不填则允许所有访问 + allow: + url-pattern: /druid/* + # 控制台管理用户名和密码 + login-username: fastbee + login-password: fastbee + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + # redis 配置 + redis: + host: localhost # 地址 + port: 6379 # 端口,默认为6379 + database: 1 # 数据库索引 + #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://localhost:1883 # mqtt连接tcp地址 + client-id: ${random.int} # 客户端Id,不能相同,采用随机数 ${random.value} + default-topic: test # 默认主题 + timeout: 30 # 超时时间 + keepalive: 30 # 保持连接 + clearSession: true # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) + +# 日志配置 +logging: + level: + com.fastbee: debug + org.springframework: warn + +# Swagger配置 +swagger: + enabled: true # 是否开启swagger + pathMapping: /dev-api # 请求前缀 diff --git a/springboot/fastbee-admin/src/main/resources/application-prod.yml b/springboot/fastbee-admin/src/main/resources/application-prod.yml new file mode 100644 index 00000000..24932e86 --- /dev/null +++ b/springboot/fastbee-admin/src/main/resources/application-prod.yml @@ -0,0 +1,83 @@ +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://177.7.0.11/fastbee?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: fastbee + # 从库数据源 + slave: + enabled: false # 从数据源开关/默认关闭 + url: + username: + password: + + initialSize: 5 # 初始连接数 + minIdle: 10 # 最小连接池数量 + maxActive: 20 # 最大连接池数量 + maxWait: 60000 # 配置获取连接等待超时的时间 + timeBetweenEvictionRunsMillis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 # 配置一个连接在池中最大生存的时间,单位是毫秒 + validationQuery: SELECT 1 FROM DUAL # 配置检测连接是否有效 + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + webStatFilter: + enabled: true + statViewServlet: + enabled: true + # 设置白名单,不填则允许所有访问 + allow: + url-pattern: /druid/* + # 控制台管理用户名和密码 + login-username: fastbee + login-password: fastbee + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + # 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 # 连接消息服务器地址 + client-id: ${random.int} # 客户端Id,不能相同,采用随机数 ${random.value} + default-topic: test # 默认主题 + timeout: 30 # 超时时间 + keepalive: 30 # 保持连接 + clearSession: true # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息) + +# 日志配置 +logging: + level: + com.fastbee: error + org.springframework: warn + +# Swagger配置 +swagger: + enabled: true # 是否开启swagger + pathMapping: /prod-api # 请求前缀 diff --git a/springboot/fastbee-admin/src/main/resources/application.yml b/springboot/fastbee-admin/src/main/resources/application.yml new file mode 100644 index 00000000..079f0bd6 --- /dev/null +++ b/springboot/fastbee-admin/src/main/resources/application.yml @@ -0,0 +1,98 @@ +# 项目相关配置 +fastbee: + name: fastbee # 名称 + version: 3.8.5 # 版本 + copyrightYear: 2023 # 版权年份 + demoEnabled: true # 实例演示开关 + # 文件路径,以uploadPath结尾 示例( Windows配置 D:/uploadPath,Linux配置 /uploadPath) + profile: /uploadPath + addressEnabled: true # 获取ip地址开关 + captchaType: math # 验证码类型 math 数组计算 char 字符验证 + +# 开发环境配置 +server: + port: 8080 # 服务器的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: + must-pass: false # 客户端连接是否需要密码 + enabled: true # mqttBroker类型选择, true: 基于netty的mqttBroker和webSocket false: emq的mqttBroker + broker-node: node1 + port: 1883 + websocket-port: 8083 + websocket-path: /mqtt + keep-alive: 70 # 默认的全部客户端心跳上传时间 + +# 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 + +#集群配置 +cluster: + enable: true + type: redis + + +# 用户配置 +user: + password: + maxRetryCount: 5 # 密码最大错误次数 + lockTime: 10 # 密码锁定时间(默认10分钟) + +# token配置 +token: + header: Authorization # 令牌自定义标识 + secret: abcdefghijklfastbeesmartrstuvwxyz # 令牌密钥 + expireTime: 1440 # 令牌有效期(默认30分钟)1440为一天 + +# 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/* # 匹配链接 diff --git a/springboot/fastbee-admin/src/main/resources/banner.txt b/springboot/fastbee-admin/src/main/resources/banner.txt new file mode 100644 index 00000000..b8ebf46d --- /dev/null +++ b/springboot/fastbee-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/springboot/fastbee-admin/src/main/resources/i18n/messages.properties b/springboot/fastbee-admin/src/main/resources/i18n/messages.properties new file mode 100644 index 00000000..4098fc92 --- /dev/null +++ b/springboot/fastbee-admin/src/main/resources/i18n/messages.properties @@ -0,0 +1,37 @@ +#错误消息 +not.null=* 必须填写 +user.jcaptcha.error=验证码错误 +user.jcaptcha.expire=验证码已失效 +user.not.exists=用户不存在/密码错误 +user.password.not.match=用户不存在/密码错误 +user.password.retry.limit.count=密码输入错误{0}次 +user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟 +user.password.delete=对不起,您的账号已被删除 +user.blocked=用户已封禁,请联系管理员 +role.blocked=角色已封禁,请联系管理员 +user.logout.success=退出成功 + +length.not.valid=长度必须在{min}到{max}个字符之间 + +user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头 +user.password.not.valid=* 5-50个字符 + +user.email.not.valid=邮箱格式错误 +user.mobile.phone.number.not.valid=手机号格式错误 +user.login.success=登录成功 +user.register.success=注册成功 +user.notfound=请重新登录 +user.forcelogout=管理员强制退出,请重新登录 +user.unknown.error=未知错误,请重新登录 + +##文件上传消息 +upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB! +upload.filename.exceed.length=上传的文件名最长{0}个字符 + +##权限 +no.permission=您没有数据的权限,请联系管理员添加权限 [{0}] +no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}] +no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}] +no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}] +no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}] +no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}] diff --git a/springboot/fastbee-admin/src/main/resources/logback.xml b/springboot/fastbee-admin/src/main/resources/logback.xml new file mode 100644 index 00000000..44322024 --- /dev/null +++ b/springboot/fastbee-admin/src/main/resources/logback.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + ${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 + + 30 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log.path}/sys-user.log + + + ${log.path}/sys-user.%d{yyyy-MM-dd}.log + + 30 + + + ${log.pattern} + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/springboot/fastbee-admin/src/main/resources/mybatis/mybatis-config.xml b/springboot/fastbee-admin/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 00000000..ac47c038 --- /dev/null +++ b/springboot/fastbee-admin/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/springboot/fastbee-common/pom.xml b/springboot/fastbee-common/pom.xml new file mode 100644 index 00000000..70ba5cb8 --- /dev/null +++ b/springboot/fastbee-common/pom.xml @@ -0,0 +1,197 @@ + + + + fastbee + com.fastbee + 3.8.5 + + 4.0.0 + + fastbee-common + + + 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.baomidou + dynamic-datasource-spring-boot-starter + 3.5.2 + + + + + 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 + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.5 + compile + + + + cn.hutool + hutool-all + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + com.google.guava + guava + + + + + + + + + com.alibaba + easyexcel-core + + + + + diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/Anonymous.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/Anonymous.java new file mode 100644 index 00000000..77d90ef0 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/Anonymous.java @@ -0,0 +1,19 @@ +package com.fastbee.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 匿名访问不鉴权注解 + * + * @author ruoyi + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Anonymous +{ +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/DataScope.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/DataScope.java new file mode 100644 index 00000000..ee5bb173 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/DataScope.java @@ -0,0 +1,33 @@ +package com.fastbee.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据权限过滤注解 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/DataSource.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/DataSource.java new file mode 100644 index 00000000..045786be --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/DataSource.java @@ -0,0 +1,28 @@ +package com.fastbee.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.fastbee.common.enums.DataSourceType; + +/** + * 自定义多数据源切换注解 + * + * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 + * + * @author ruoyi + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DataSource +{ + /** + * 切换数据源名称 + */ + public DataSourceType value() default DataSourceType.MASTER; +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/DictFormat.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/DictFormat.java new file mode 100644 index 00000000..5536474f --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/DictFormat.java @@ -0,0 +1,22 @@ +package com.fastbee.common.annotation; + +import java.lang.annotation.*; + +/** + * 字典格式化 + * + * 实现将字典数据的值,格式化成字典数据的标签 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface DictFormat { + + /** + * 例如说,SysDictTypeConstants、InfDictTypeConstants + * + * @return 字典类型 + */ + String value(); + +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/Excel.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/Excel.java new file mode 100644 index 00000000..157c8825 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/Excel.java @@ -0,0 +1,187 @@ +package com.fastbee.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.math.BigDecimal; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import com.fastbee.common.utils.poi.ExcelHandlerAdapter; + +/** + * 自定义导出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/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/Excels.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/Excels.java new file mode 100644 index 00000000..b258cd0d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/Excels.java @@ -0,0 +1,18 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/Log.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/Log.java new file mode 100644 index 00000000..2526dd45 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/Log.java @@ -0,0 +1,46 @@ +package com.fastbee.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.enums.OperatorType; + +/** + * 自定义操作日志记录注解 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/RateLimiter.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/RateLimiter.java new file mode 100644 index 00000000..a41daf17 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/RateLimiter.java @@ -0,0 +1,40 @@ +package com.fastbee.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.fastbee.common.constant.CacheConstants; +import com.fastbee.common.enums.LimitType; + +/** + * 限流注解 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/RepeatSubmit.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/RepeatSubmit.java new file mode 100644 index 00000000..1785e572 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/RepeatSubmit.java @@ -0,0 +1,31 @@ +package com.fastbee.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义注解防止表单重复提交 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/SysProtocol.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/SysProtocol.java new file mode 100644 index 00000000..19d4a71d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/annotation/SysProtocol.java @@ -0,0 +1,21 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/config/DeviceTask.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/config/DeviceTask.java new file mode 100644 index 00000000..46a96655 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/config/DeviceTask.java @@ -0,0 +1,104 @@ +package com.fastbee.common.config; + +import com.fastbee.common.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); + } + + /*组装线程池*/ + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/config/RuoYiConfig.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/config/RuoYiConfig.java new file mode 100644 index 00000000..f8e1cb77 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/config/RuoYiConfig.java @@ -0,0 +1,135 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/CacheConstants.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/CacheConstants.java new file mode 100644 index 00000000..0f85d649 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/CacheConstants.java @@ -0,0 +1,49 @@ +package com.fastbee.common.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 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:"; + + /** + * 防重提交 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:"; +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/Constants.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/Constants.java new file mode 100644 index 00000000..c3b5ed04 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/Constants.java @@ -0,0 +1,142 @@ +package com.fastbee.common.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" }; +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java new file mode 100644 index 00000000..d14fab2c --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java @@ -0,0 +1,252 @@ +package com.fastbee.common.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 MESSAGE_CONSUME_TASK = "messageConsumeTask"; + /*内部消费线程publish*/ + String MESSAGE_CONSUME_TASK_PUB = "messageConsumeTaskPub"; + /*内部消费线程Fetch*/ + String MESSAGE_CONSUME_TASK_FETCH = "messageConsumeTaskFetch"; + /*OTA升级延迟队列*/ + String DELAY_UPGRADE_TASK = "delayUpgradeTask"; + + } + + interface MQTT{ + //*上报平台前缀*//* + String UP_TOPIC_SUFFIX = "post"; + //*下发设备前缀*//* + String DOWN_TOPIC_SUFFIX = "get"; + + /*模拟设备后缀*/ + String PROPERTY_GET_SIMULATE = "simulate"; + + String PREDIX = "/+/+"; + + String DUP = "dup"; + String QOS = "qos"; + String RETAIN = "retain"; + String CLEAN_SESSION = "cleanSession"; + + /*集群方式*/ + String REDIS_CHANNEL = "redis"; + String ROCKET_MQ = "rocketmq"; + } + + /*集群,全局发布的消息类型*/ + 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{ + /*redis全局前缀*/ + String GLOBAL_PREFIX_KEY = "fastbee:"; + /*设备在线状态*/ + String DEVICE_STATUS_KEY = "device:status"; + /*在线设备列表*/ + 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:"; + + /**采集点变更记录缓存key*/ + String COLLECT_POINT_CHANGE = "collect:point:change:"; + /**属性下发回调*/ + String PROP_READ_STORE = "prop:read:store:"; + /**sip*/ + String RECORDINFO_KEY = "sip:recordinfo:"; + String DEVICEID_KEY = "sip:deviceid:"; + String STREAM_KEY = "sip:stream:"; + String SIP_CSEQ_PREFIX = "sip:CSEQ:"; + String DEFAULT_SIP_CONFIG = "sip:config"; + String DEFAULT_MEDIA_CONFIG = "sip:mediaconfig"; + + + /**当前连接数*/ + String MESSAGE_CONNECT_COUNT = "messages:connect:count"; + /**总保留消息*/ + String MESSAGE_RETAIN_TOTAL = "message:retain:total"; + + /**主题数*/ + String MESSAGE_TOPIC_TOTAL = "message:topic: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"; + + + // 物模型值命名空间:Key:TSLV:{productId}_{deviceNumber} HKey:{identity#V/identity#S/identity#M/identity#N} + /** + * v-值 + * s-影子值 + * m-是否为检测值 + * n-名称 + */ + String DEVICE_PRE_KEY = "TSLV:"; + + // 物模型命名空间:Key:TSL:{productId} + String TSL_PRE_KEY ="TSL:"; + + /**modbus缓存指令*/ + String POLL_MODBUS_KEY = "poll:modbus"; + + + } + + interface TOPIC{ + /*属性上报*/ + String PROP = "properties"; + //事件 + String EVENT = "events"; + //功能 + String FUNCTION = "functions"; + /*非OTA消息回复*/ + String MSG_REPLY = "message/reply"; + /*OTA升级回复*/ + String UPGRADE_REPLY = "upgrade/reply"; + String SUB_UPGRADE_REPLY = "sub/upgrade/reply"; + /*网关子设备结尾*/ + String SUB = "/sub"; + } + + interface PROTOCOL { + String ModbusRtu = "MODBUS-RTU"; + String YinErDa = "YinErDa"; + String JsonObject = "JSONOBJECT"; + String JsonArray = "JSON"; + String ModbusRtuPak = "MODBUS-RTU-PAK"; + String FlowMeter = "FlowMeter"; + String RJ45 = "RJ45"; + String ModbusToJson = "MODBUS-JSON"; + String ModbusToJsonFY = "MODBUS-JSON-FY"; + String JsonObject_ChenYi = "JSONOBJECT-CHENYI"; + + + } + +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/GenConstants.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/GenConstants.java new file mode 100644 index 00000000..028d3a1c --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/GenConstants.java @@ -0,0 +1,117 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/HttpStatus.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/HttpStatus.java new file mode 100644 index 00000000..e54d8790 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/HttpStatus.java @@ -0,0 +1,105 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/ProductAuthConstant.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/ProductAuthConstant.java new file mode 100644 index 00000000..fa487e44 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/ProductAuthConstant.java @@ -0,0 +1,41 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/ScheduleConstants.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/ScheduleConstants.java new file mode 100644 index 00000000..62d07f01 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/ScheduleConstants.java @@ -0,0 +1,50 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/SipConstants.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/SipConstants.java new file mode 100644 index 00000000..f304dc88 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/SipConstants.java @@ -0,0 +1,9 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/UserConstants.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/UserConstants.java new file mode 100644 index 00000000..ad6f4a3d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/constant/UserConstants.java @@ -0,0 +1,78 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/controller/BaseController.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/controller/BaseController.java new file mode 100644 index 00000000..b331ba3b --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/controller/BaseController.java @@ -0,0 +1,216 @@ +package com.fastbee.common.core.controller; + +import com.fastbee.common.constant.CacheConstants; +import com.fastbee.common.constant.HttpStatus; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.core.page.PageDomain; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.core.page.TableSupport; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.PageUtils; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.sql.SqlUtil; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +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("查询成功"); + rspData.setRows(list); + rspData.setTotal(new PageInfo(list).getTotal()); + 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(); + } + + /** + * 页面跳转 + */ + public String redirect(String url) + { + return StringUtils.format("redirect:{}", url); + } + + /** + * 获取用户缓存信息 + * 由于不同端不能获取最新用户信息,所以优先以用户id缓存key获取用户信息 + */ + public LoginUser getLoginUser() + { + LoginUser loginUser = SecurityUtils.getLoginUser(); + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/AjaxResult.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/AjaxResult.java new file mode 100644 index 00000000..9db8ee4e --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/AjaxResult.java @@ -0,0 +1,214 @@ +package com.fastbee.common.core.domain; + +import java.util.HashMap; +import com.fastbee.common.constant.HttpStatus; +import com.fastbee.common.utils.StringUtils; + +/** + * 操作消息提醒 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/BaseDO.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/BaseDO.java new file mode 100644 index 00000000..be3e6486 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/BaseDO.java @@ -0,0 +1,43 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/BaseEntity.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/BaseEntity.java new file mode 100644 index 00000000..3bc489d5 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/BaseEntity.java @@ -0,0 +1,126 @@ +package com.fastbee.common.core.domain; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.annotations.ApiModelProperty; + +/** + * Entity基类 + * + * @author ruoyi + */ +public class BaseEntity implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 搜索值 */ + @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("备注") + private String remark; + + /** 请求参数 */ + @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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/CommonResult.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/CommonResult.java new file mode 100644 index 00000000..0ce5b111 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/CommonResult.java @@ -0,0 +1,112 @@ +package com.fastbee.common.core.domain; + +import com.fastbee.common.enums.GlobalErrorCodeConstants; +import com.fastbee.common.exception.ErrorCode; +import com.fastbee.common.exception.ServiceException; +import com.fasterxml.jackson.annotation.JsonIgnore; +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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/PageParam.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/PageParam.java new file mode 100644 index 00000000..b1873e70 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/PageParam.java @@ -0,0 +1,25 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/PageResult.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/PageResult.java new file mode 100644 index 00000000..97b8b843 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/PageResult.java @@ -0,0 +1,42 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/R.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/R.java new file mode 100644 index 00000000..fe572876 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/R.java @@ -0,0 +1,115 @@ +package com.fastbee.common.core.domain; + +import java.io.Serializable; +import com.fastbee.common.constant.HttpStatus; + +/** + * 响应信息主体 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/SortingField.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/SortingField.java new file mode 100644 index 00000000..685d73ff --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/SortingField.java @@ -0,0 +1,56 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/TenantBaseDO.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/TenantBaseDO.java new file mode 100644 index 00000000..06ef2c62 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/TenantBaseDO.java @@ -0,0 +1,20 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/TreeEntity.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/TreeEntity.java new file mode 100644 index 00000000..11e3b667 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/TreeEntity.java @@ -0,0 +1,79 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/TreeSelect.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/TreeSelect.java new file mode 100644 index 00000000..cbb874b8 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/TreeSelect.java @@ -0,0 +1,77 @@ +package com.fastbee.common.core.domain; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fastbee.common.core.domain.entity.SysDept; +import com.fastbee.common.core.domain.entity.SysMenu; + +/** + * 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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysDept.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysDept.java new file mode 100644 index 00000000..d4883954 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysDept.java @@ -0,0 +1,219 @@ +package com.fastbee.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +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 com.fastbee.common.core.domain.BaseEntity; + +/** + * 部门表 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 */ + @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(); + + 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("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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysDictData.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysDictData.java new file mode 100644 index 00000000..8f3d1f54 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysDictData.java @@ -0,0 +1,189 @@ +package com.fastbee.common.core.domain.entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.annotation.Excel.ColumnType; +import com.fastbee.common.constant.UserConstants; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 字典数据表 sys_dict_data + * + * @author ruoyi + */ +@ApiModel(value = "SysDictData", description = "字典数据表 sys_dict_data") +public class SysDictData extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典编码 */ + @ApiModelProperty("字典编码") + @Excel(name = "字典编码", cellType = ColumnType.NUMERIC) + private Long dictCode; + + /** 字典排序 */ + @ApiModelProperty("字典排序") + @Excel(name = "字典排序", cellType = ColumnType.NUMERIC) + private Long dictSort; + + /** 字典标签 */ + @ApiModelProperty("字典标签") + @Excel(name = "字典标签") + private String dictLabel; + + /** 字典键值 */ + @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; + + 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; + } + + @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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysDictType.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysDictType.java new file mode 100644 index 00000000..eee07a37 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysDictType.java @@ -0,0 +1,104 @@ +package com.fastbee.common.core.domain.entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.annotation.Excel.ColumnType; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 字典类型表 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 = 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; + + 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; + } + + @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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysMenu.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysMenu.java new file mode 100644 index 00000000..feee0421 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysMenu.java @@ -0,0 +1,279 @@ +package com.fastbee.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +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 com.fastbee.common.core.domain.BaseEntity; + +/** + * 菜单权限表 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(); + + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysRole.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysRole.java new file mode 100644 index 00000000..1e4d23bf --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysRole.java @@ -0,0 +1,257 @@ +package com.fastbee.common.core.domain.entity; + +import java.util.Set; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.annotation.Excel.ColumnType; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 角色表 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 = 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; + + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysUser.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysUser.java new file mode 100644 index 00000000..6ccbf8e1 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/entity/SysUser.java @@ -0,0 +1,346 @@ +package com.fastbee.common.core.domain.entity; + +import java.util.Date; +import java.util.List; +import javax.validation.constraints.*; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.annotation.Excel.ColumnType; +import com.fastbee.common.annotation.Excel.Type; +import com.fastbee.common.annotation.Excels; +import com.fastbee.common.core.domain.BaseEntity; +import com.fastbee.common.xss.Xss; + +/** + * 用户对象 sys_user + * + * @author ruoyi + */ +@ApiModel(value = "SysUser", description = "用户对象 sys_user") +public class SysUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号") + private Long userId; + + /** 部门ID */ + @ApiModelProperty("部门ID") + @Excel(name = "部门编号", type = 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; + + /** 帐号状态(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 = Type.EXPORT) + private String loginIp; + + /** 最后登录时间 */ + @ApiModelProperty("最后登录时间") + @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) + private Date loginDate; + + /** 部门对象 */ + @ApiModelProperty("部门对象") + @Excels({ + @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT), + @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT) + }) + private SysDept dept; + + /** 角色对象 */ + @ApiModelProperty("角色对象") + private List roles; + + /** 角色组 */ + @ApiModelProperty("角色组") + private Long[] roleIds; + + /** 岗位组 */ + @ApiModelProperty("岗位组") + private Long[] postIds; + + /** 角色ID */ + @ApiModelProperty("角色ID") + private Long roleId; + + 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(); + } +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/BindLoginBody.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/BindLoginBody.java new file mode 100644 index 00000000..806c615d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/BindLoginBody.java @@ -0,0 +1,22 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/BindRegisterBody.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/BindRegisterBody.java new file mode 100644 index 00000000..5a4353d0 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/BindRegisterBody.java @@ -0,0 +1,21 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/LoginBody.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/LoginBody.java new file mode 100644 index 00000000..c2569882 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/LoginBody.java @@ -0,0 +1,82 @@ +package com.fastbee.common.core.domain.model; + +/** + * 用户登录对象 + * + * @author ruoyi + */ +public class LoginBody +{ + /** + * 用户名 + */ + private String username; + + /** + * 用户密码 + */ + private String password; + + /** + * 验证码 + */ + private String code; + + /** + * 唯一标识 + */ + private String uuid; + + /** + * 手机号 + */ + private String phonenumber; + + public String getPhonenumber() { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) { + this.phonenumber = phonenumber; + } + + 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 getCode() + { + return code; + } + + public void setCode(String code) + { + this.code = code; + } + + public String getUuid() + { + return uuid; + } + + public void setUuid(String uuid) + { + this.uuid = uuid; + } +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/LoginUser.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/LoginUser.java new file mode 100644 index 00000000..07324faa --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/LoginUser.java @@ -0,0 +1,266 @@ +package com.fastbee.common.core.domain.model; + +import java.util.Collection; +import java.util.Set; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import com.alibaba.fastjson2.annotation.JSONField; +import com.fastbee.common.core.domain.entity.SysUser; + +/** + * 登录用户身份权限 + * + * @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; + + 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, SysUser user, Set permissions) + { + this.userId = userId; + this.deptId = deptId; + this.user = user; + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/RegisterBody.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/RegisterBody.java new file mode 100644 index 00000000..750f0e2c --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/domain/model/RegisterBody.java @@ -0,0 +1,11 @@ +package com.fastbee.common.core.domain.model; + +/** + * 用户注册对象 + * + * @author ruoyi + */ +public class RegisterBody extends LoginBody +{ + +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/iot/response/DashDeviceTotalDto.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/iot/response/DashDeviceTotalDto.java new file mode 100644 index 00000000..53fd0041 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/iot/response/DashDeviceTotalDto.java @@ -0,0 +1,22 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/iot/response/DeCodeBo.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/iot/response/DeCodeBo.java new file mode 100644 index 00000000..87c57d9e --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/iot/response/DeCodeBo.java @@ -0,0 +1,26 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/iot/response/IdentityAndName.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/iot/response/IdentityAndName.java new file mode 100644 index 00000000..3ef267c7 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/iot/response/IdentityAndName.java @@ -0,0 +1,70 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReplyBo.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReplyBo.java new file mode 100644 index 00000000..21b0a62f --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReplyBo.java @@ -0,0 +1,17 @@ +package com.fastbee.common.core.mq; + +import lombok.Data; + +/** + * @author bill + */ +@Data +public class DeviceReplyBo { + + /*设备下发消息id*/ + private String messageId; + /*标识符*/ + private String id; + /**下发值*/ + private String value; +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReport.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReport.java new file mode 100644 index 00000000..83b6e9f9 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReport.java @@ -0,0 +1,104 @@ +package com.fastbee.common.core.mq; + +import com.fastbee.common.core.mq.message.SubDeviceMessage; +import com.fastbee.common.core.protocol.Message; +import com.fastbee.common.core.thingsModel.ThingsModelValuesInput; +import com.fastbee.common.enums.FunctionReplyStatus; +import com.fastbee.common.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; + /** + * 寄存器地址 + */ + private String hexAddress; + /** + * 物模型标识符 + */ + private String identifier; + /** + * 消息id + */ + private String messageId; + /** + * 设备主动上报的消息体 + * key 物模型Identifier + * value 物模型设备对应值 + */ + private ThingsModelValuesInput valuesInput; + /** + * 消息id或 消息流水号 + */ + private String serNo; + /** + * 值是否监控,如果监控表示需要历史存储,该值来自物模型 + */ + private Integer isMonitor; + /** ================网关子设备====================*/ + /** + * 网关子设备编号 + */ + private List subDeviceCodes; + /** + * 网关子设备消息 + */ + private List subDeviceMessages; + /** ================回调数据====================*/ + + /** + * 是否设备回复数据 + */ + private Boolean isReply = false; + + /** + * 设备回复消息 + */ + private String replyMessage; + /** + * 设备回复状态 + */ + private FunctionReplyStatus status; + /** + * 从机编号 + */ + private Integer slaveId; + /** + * 服务器类型 + */ + private ServerType serverType; + /** + * 寄存器地址 + */ + private int address; + + private String protocolCode; + + private Long userId; + private String userName; + private String deviceName; +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReportBo.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReportBo.java new file mode 100644 index 00000000..502d3cf6 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReportBo.java @@ -0,0 +1,74 @@ +package com.fastbee.common.core.mq; + +import com.fastbee.common.core.mq.message.PropRead; +import com.fastbee.common.core.thingsModel.ThingsModelValuesInput; +import com.fastbee.common.enums.FunctionReplyStatus; +import com.fastbee.common.enums.ServerType; +import com.fastbee.common.enums.ThingsModelType; +import io.netty.buffer.ByteBuf; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * 设备上报 + * @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 ThingsModelValuesInput valuesInput; + /*处理的消息服务类型*/ + private ServerType serverType; + private Integer slaveId; + + /** + * 是否设备回复数据 + */ + private Boolean isReply = false; + + /** + * 设备回复消息 + */ + private String replyMessage; + /** + * 设备回复状态 + */ + private FunctionReplyStatus status; + /** + * 寄存器地址 + */ + private int address; + + private String protocolCode; + +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceStatusBo.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceStatusBo.java new file mode 100644 index 00000000..160e7263 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceStatusBo.java @@ -0,0 +1,35 @@ +package com.fastbee.common.core.mq; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/InvokeReqDto.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/InvokeReqDto.java new file mode 100644 index 00000000..dfb23108 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/InvokeReqDto.java @@ -0,0 +1,62 @@ +package com.fastbee.common.core.mq; + +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.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 { + + @NotNull(message = "设备编号不能为空") + @ApiModelProperty(value = "设备编号") + private String serialNumber; + + @NotNull(message = "标识符不能为空") + @ApiModelProperty(value = "标识符") + private String identifier; + /**消息体*/ + @ApiModelProperty(value = "消息体") + private JSONObject value; + /**远程消息体*/ + @ApiModelProperty(value = "远程调用消息体") + private Map remoteCommand; + /**设备超时时间*/ + @ApiModelProperty(value = "设备超时响应时间,默认10s") + private Integer timeOut = 10; + + @ApiModelProperty(value = "下发物模型类型") + @NotNull + private Integer type; + + @ApiModelProperty(value = "是否是影子模式") + @NotNull + private Boolean isShadow; + + private String dataType; + + @NotNull(message = "产品id不能为空") + @ApiModelProperty(value = "产品id") + private Long productId; + /**从机编号*/ + private Integer slaveId; + /** + * 显示的值 + */ + private String showValue; + + /** + * 物模型名称 + */ + private String modelName; + + private Date timestamp = DateUtils.getNowDate(); +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/MQSendMessageBo.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/MQSendMessageBo.java new file mode 100644 index 00000000..d9b09779 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/MQSendMessageBo.java @@ -0,0 +1,51 @@ +package com.fastbee.common.core.mq; + +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.core.protocol.modbus.ModbusCode; +import com.fastbee.common.enums.ThingsModelType; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 服务(指令)下发对象 + * + * @author bill + */ +@Data +@NoArgsConstructor +public class MQSendMessageBo { + + /*设备编号*/ + private String serialNumber; + /*下发属性标识符*/ + private String identifier; + /*寄存器地址 10进制*/ + private String hexAddress; + /*topic*/ + private JSONObject command; + private String topicName; + /*产品ID*/ + private Long productId; + /*物模型类型 1=-属性,2-功能,3-事件,4-属性和功能*/ + private ThingsModelType type; + /*下发的数据*/ + private JSONObject value; + /*协议编号 例如:modbus-rtu*/ + private String protocolCode; + /*messageId生成放到调用接口的时候生成*/ + private String messageId; + /*流水号,针对某些协议没有消息流水号无法区分下发的消息和上报的消息是否对应*/ + private String serialNo; + /*从机id*/ + private Integer slaveId; + /**显示值*/ + private String showValue; + private String modelName; + private ModbusCode code; + /*是否是影子模式*/ + private Boolean isShadow; + /*传输协议*/ + private String transport; + + +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/MessageReplyBo.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/MessageReplyBo.java new file mode 100644 index 00000000..4b4a2eb0 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/MessageReplyBo.java @@ -0,0 +1,51 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/DeviceData.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/DeviceData.java new file mode 100644 index 00000000..8d5c092c --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/DeviceData.java @@ -0,0 +1,49 @@ +package com.fastbee.common.core.mq.message; + +import com.fastbee.common.core.protocol.Message; +import com.fastbee.common.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; + + /*消息类型 1.设备上报数据 2.设备回调数据*/ + private int messageType; + + /*下发数据model*/ + private DeviceDownMessage downMessage; + + private Object body; + /*MQTT OR 其他*/ + private int type; + + /*Modbus*/ + private ModbusCode code; + + private PropRead prop; + /*是否使用modbus客户端模拟测试*/ + private boolean isEnabledTest; + /**产品id*/ + private Long productId; +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/DeviceDownMessage.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/DeviceDownMessage.java new file mode 100644 index 00000000..3db2f571 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/DeviceDownMessage.java @@ -0,0 +1,69 @@ +package com.fastbee.common.core.mq.message; + +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.core.protocol.modbus.ModbusCode; +import com.fastbee.common.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; + /*网关设备编码*/ + String subSerialNumber; + /** + * true: 表示是一条发往网关子设备的指令 + * 默认是false + */ + Boolean subFlag = false; + /** + * 从机编号 + */ + private Integer slaveId; + private ModbusCode code; + private int count; + private int address; + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/DeviceFunctionMessage.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/DeviceFunctionMessage.java new file mode 100644 index 00000000..df0d5784 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/DeviceFunctionMessage.java @@ -0,0 +1,33 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/DeviceMessage.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/DeviceMessage.java new file mode 100644 index 00000000..9af912f5 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/DeviceMessage.java @@ -0,0 +1,20 @@ +package com.fastbee.common.core.mq.message; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 集群消息 + * @author bill + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class DeviceMessage { + + /*数据*/ + private T data; + + private int nodeId; +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/InstructionsMessage.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/InstructionsMessage.java new file mode 100644 index 00000000..78b3a0d0 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/InstructionsMessage.java @@ -0,0 +1,20 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/MqttBo.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/MqttBo.java new file mode 100644 index 00000000..311f0af7 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/MqttBo.java @@ -0,0 +1,26 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/PropRead.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/PropRead.java new file mode 100644 index 00000000..9f949e08 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/PropRead.java @@ -0,0 +1,39 @@ +package com.fastbee.common.core.mq.message; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/ProtocolDto.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/ProtocolDto.java new file mode 100644 index 00000000..8e45f222 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/ProtocolDto.java @@ -0,0 +1,21 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/SubDeviceMessage.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/SubDeviceMessage.java new file mode 100644 index 00000000..47940924 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/message/SubDeviceMessage.java @@ -0,0 +1,18 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaReplyMessage.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaReplyMessage.java new file mode 100644 index 00000000..0ac4408d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaReplyMessage.java @@ -0,0 +1,17 @@ +package com.fastbee.common.core.mq.ota; + +import lombok.Data; + +/** + * OTA升级回复model + * @author gsb + * @date 2022/10/24 17:20 + */ +@Data +public class OtaReplyMessage { + + private String messageId; + // 200成功 其他。。 + private int code; + private String msg; +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeBo.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeBo.java new file mode 100644 index 00000000..41f98343 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeBo.java @@ -0,0 +1,46 @@ +package com.fastbee.common.core.mq.ota; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * OTA远程升级 + * @author gsb + * @date 2022/10/10 10:22 + */ +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class OtaUpgradeBo { + + /**OTAId*/ + private Long otaId; + @NotNull(message = "上传地址为空") + private String otaUrl; + @NotNull(message = "固件版本号不能为空") + private String firmwareVersion; + private String firmwareName; + @NotNull(message = "流水号不能为空") + private String seqNo; + @NotNull(message = "产品ID不能为空") + private Long productId; + private String signType = "16md5"; + @NotNull(message = "签名不能为空") + private String signCode; + /*产品名称*/ + private String productName; + private String fileBase64; + private Integer pushType; + /*设备编码,逐个升级*/ + private String serialNumber; + private String deviceName; + /*任务ID*/ + private Long taskId; + /*消息id*/ + private String messageId; + /*平台描述消息*/ + private String msg; +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeDelayTask.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeDelayTask.java new file mode 100644 index 00000000..9142c2a8 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeDelayTask.java @@ -0,0 +1,54 @@ +package com.fastbee.common.core.mq.ota; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fastbee.common.utils.DateUtils; +import lombok.Data; +import org.springframework.lang.NonNull; + +import java.lang.reflect.Member; +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; + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/page/PageDomain.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/page/PageDomain.java new file mode 100644 index 00000000..6d2cd75d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/page/PageDomain.java @@ -0,0 +1,101 @@ +package com.fastbee.common.core.page; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/page/TableDataInfo.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/page/TableDataInfo.java new file mode 100644 index 00000000..d84cb2c0 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/page/TableDataInfo.java @@ -0,0 +1,85 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/page/TableSupport.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/page/TableSupport.java new file mode 100644 index 00000000..3bb552f4 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/page/TableSupport.java @@ -0,0 +1,56 @@ +package com.fastbee.common.core.page; + +import com.fastbee.common.core.text.Convert; +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/protocol/Message.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/protocol/Message.java new file mode 100644 index 00000000..741e1241 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/protocol/Message.java @@ -0,0 +1,33 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/protocol/modbus/ModbusCode.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/protocol/modbus/ModbusCode.java new file mode 100644 index 00000000..f7ffaaf6 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/protocol/modbus/ModbusCode.java @@ -0,0 +1,87 @@ +package com.fastbee.common.core.protocol.modbus; + +import com.fastbee.common.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), // 读线圈(读写位模式) + Read02("读离散量输入",(byte) 0x02), // 读离散量输入(位只读模式) + Read03("读保持寄存器",(byte) 0x03), // 读保持寄存器(字节读写模式) + Read04("读输入寄存器",(byte) 0x04), // 读输入寄存器(字节只读模式) + + Write05("写单个线圈(读写位模式)",(byte) 0x05), // 写单个线圈(读写位模式) + Write06("写多个线圈",(byte) 0x06), // 写单个保持寄存器 + Write0F("写多个线圈",(byte) 0x0F), // 写多个线圈 + Write10("写多个保持寄存器",(byte) 0x10) // 写多个保持寄存器 + ; + + private String desc; + private byte code; + + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisCache.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisCache.java new file mode 100644 index 00000000..aa2f01fb --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisCache.java @@ -0,0 +1,735 @@ +package com.fastbee.common.core.redis; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import org.apache.commons.lang3.BooleanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ScanOptions; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.data.redis.support.atomic.RedisAtomicLong; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.regex.Pattern.compile; + +/** + * spring redis 工具类 + * + * @author ruoyi + **/ +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +@Component +public class RedisCache { + @Autowired + public RedisTemplate redisTemplate; + + @Autowired + 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; + } + } + + /** + * 返回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) { + 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(id) || field.equals(id + "#V")) { + String valueStr = new String((byte[]) entry.getValue()); + JSONObject jsonObject = JSONObject.parseObject((String) JSON.parse(valueStr)); + String value = (String) jsonObject.get("value"); + if (ruleResult(operator, value, triggerValue)) { + mapList.put(key, value); + } + } + } + } + return mapList; + }); + } + + /** + * 根据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; + } + + /** + * 判断字符串是否为整数或小数 + */ + 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); + } + + + /** + * 删除Hash中的数据 + * + * @param key + * @param hkey + */ + public void delHashValue(final String key, final String hkey) { + HashOperations hashOperations = redisTemplate.opsForHash(); + hashOperations.delete(key, hkey); + } + +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java new file mode 100644 index 00000000..fbc745d1 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyBuilder.java @@ -0,0 +1,84 @@ +package com.fastbee.common.core.redis; + +import com.fastbee.common.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; + } + + /**录像缓存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; + } + + /**ipCSEQ缓存key*/ + public static String buildSipCSEQCacheKey(String CSEQ){ + return FastBeeConstant.REDIS.SIP_CSEQ_PREFIX + CSEQ; + } + + /**modbus指令缓存可以*/ + public static String buildModbusCacheKey(Long productId){ + return FastBeeConstant.REDIS.POLL_MODBUS_KEY + productId; + } + + /*缓存设备下发指令消息ID*/ + public static String buildDownMessageIdCacheKey(String serialNumber){ + return FastBeeConstant.REDIS.DEVICE_MESSAGE_ID; + } + +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyDefine.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyDefine.java new file mode 100644 index 00000000..d550069a --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyDefine.java @@ -0,0 +1,113 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyRegistry.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyRegistry.java new file mode 100644 index 00000000..4cd9f7a3 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/redis/RedisKeyRegistry.java @@ -0,0 +1,28 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/CharsetKit.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/CharsetKit.java new file mode 100644 index 00000000..eaf58bec --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/CharsetKit.java @@ -0,0 +1,86 @@ +package com.fastbee.common.core.text; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.fastbee.common.utils.StringUtils; + +/** + * 字符集工具类 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/Convert.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/Convert.java new file mode 100644 index 00000000..528b19b4 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/Convert.java @@ -0,0 +1,1000 @@ +package com.fastbee.common.core.text; + +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; +import com.fastbee.common.utils.StringUtils; +import org.apache.commons.lang3.ArrayUtils; + +/** + * 类型转换器 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/IntArrayValuable.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/IntArrayValuable.java new file mode 100644 index 00000000..60c22cd2 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/IntArrayValuable.java @@ -0,0 +1,13 @@ +package com.fastbee.common.core.text; + +/** + * 可生成 Int 数组的接口 + */ +public interface IntArrayValuable { + + /** + * @return int 数组 + */ + int[] array(); + +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/KeyValue.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/KeyValue.java new file mode 100644 index 00000000..39e98942 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/KeyValue.java @@ -0,0 +1,20 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/StrFormatter.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/StrFormatter.java new file mode 100644 index 00000000..5453375c --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/text/StrFormatter.java @@ -0,0 +1,92 @@ +package com.fastbee.common.core.text; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/NeuronModel.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/NeuronModel.java new file mode 100644 index 00000000..f5d877f5 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/NeuronModel.java @@ -0,0 +1,44 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/ThingsModelSimpleItem.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/ThingsModelSimpleItem.java new file mode 100644 index 00000000..a8127bdf --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/ThingsModelSimpleItem.java @@ -0,0 +1,109 @@ +package com.fastbee.common.core.thingsModel; + +import com.fastbee.common.utils.DateUtils; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; + +import java.util.Date; + +/** + * 物模型值的项 + * + * @author kerwincui + * @date 2021-12-16 + */ +@AllArgsConstructor +public class ThingsModelSimpleItem +{ + /** 物模型唯一标识符 */ + private String id; + + /** 物模型值 */ + private String value; + + /** + * 更新时间 + */ + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date ts; + + private Integer slaveId; + + /** 备注 **/ + private String remark; + + private String timestamp; + + private boolean isBit = false; + + public ThingsModelSimpleItem(String id, String value , String remark){ + this.id=id; + this.value=value; + this.remark=remark; + } + + public ThingsModelSimpleItem(String id, String value ,Integer slaveId, String remark){ + this.id=id; + this.value=value; + this.slaveId = slaveId; + this.remark=remark; + } + + public boolean isBit() { + return isBit; + } + + public void setBit(boolean bit) { + isBit = bit; + } + + public Integer getSlaveId() { + return slaveId; + } + + public void setSlaveId(Integer slaveId) { + this.slaveId = slaveId; + } + + 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 getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/ThingsModelValuesInput.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/ThingsModelValuesInput.java new file mode 100644 index 00000000..b40caf1e --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/core/thingsModel/ThingsModelValuesInput.java @@ -0,0 +1,76 @@ +package com.fastbee.common.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 String stringValue; + + /** 设备物模型值的集合 **/ + private List thingsModelSimpleItem; + + private Integer slaveId; + + public Integer getSlaveId() { + return slaveId; + } + + public void setSlaveId(Integer slaveId) { + this.slaveId = slaveId; + } + + 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 getStringValue() { + return stringValue; + } + + public void setStringValue(String stringValue) { + this.stringValue = stringValue; + } + + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/BusinessStatus.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/BusinessStatus.java new file mode 100644 index 00000000..4c5bb7fe --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/BusinessStatus.java @@ -0,0 +1,20 @@ +package com.fastbee.common.enums; + +/** + * 操作状态 + * + * @author ruoyi + * + */ +public enum BusinessStatus +{ + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/BusinessType.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/BusinessType.java new file mode 100644 index 00000000..01aca531 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/BusinessType.java @@ -0,0 +1,59 @@ +package com.fastbee.common.enums; + +/** + * 业务操作类型 + * + * @author ruoyi + */ +public enum BusinessType +{ + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空数据 + */ + CLEAN, +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/CommonStatusEnum.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/CommonStatusEnum.java new file mode 100644 index 00000000..111808f6 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/CommonStatusEnum.java @@ -0,0 +1,36 @@ +package com.fastbee.common.enums; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/DataEnum.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/DataEnum.java new file mode 100644 index 00000000..08d4e5be --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/DataEnum.java @@ -0,0 +1,37 @@ +package com.fastbee.common.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("boolean","布尔类型"), + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/DataSourceType.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/DataSourceType.java new file mode 100644 index 00000000..e1e40e16 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/DataSourceType.java @@ -0,0 +1,19 @@ +package com.fastbee.common.enums; + +/** + * 数据源 + * + * @author ruoyi + */ +public enum DataSourceType +{ + /** + * 主库 + */ + MASTER, + + /** + * 从库 + */ + SLAVE +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/DeviceStatus.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/DeviceStatus.java new file mode 100644 index 00000000..7528d752 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/DeviceStatus.java @@ -0,0 +1,36 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ExceptionCode.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ExceptionCode.java new file mode 100644 index 00000000..88a74025 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ExceptionCode.java @@ -0,0 +1,23 @@ +package com.fastbee.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Data; +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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/FunctionReplyStatus.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/FunctionReplyStatus.java new file mode 100644 index 00000000..26ebf530 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/FunctionReplyStatus.java @@ -0,0 +1,21 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/GlobalErrorCodeConstants.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/GlobalErrorCodeConstants.java new file mode 100644 index 00000000..db80a3b9 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/GlobalErrorCodeConstants.java @@ -0,0 +1,52 @@ +package com.fastbee.common.enums; + + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/HttpMethod.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/HttpMethod.java new file mode 100644 index 00000000..7e085a28 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/HttpMethod.java @@ -0,0 +1,36 @@ +package com.fastbee.common.enums; + +import java.util.HashMap; +import java.util.Map; +import org.springframework.lang.Nullable; + +/** + * 请求方式 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/IErrorCode.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/IErrorCode.java new file mode 100644 index 00000000..becbc41f --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/IErrorCode.java @@ -0,0 +1,14 @@ +package com.fastbee.common.enums; + +/** + * 常用API返回对象接口 + */ +public interface IErrorCode { + + /**返回码*/ + int getCode(); + + /**返回信息*/ + String getMessage(); + +} \ No newline at end of file diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/LimitType.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/LimitType.java new file mode 100644 index 00000000..82d8d484 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/LimitType.java @@ -0,0 +1,20 @@ +package com.fastbee.common.enums; + +/** + * 限流类型 + * + * @author ruoyi + */ + +public enum LimitType +{ + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ModbusDataType.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ModbusDataType.java new file mode 100644 index 00000000..e719614d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ModbusDataType.java @@ -0,0 +1,38 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/OTAUpgrade.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/OTAUpgrade.java new file mode 100644 index 00000000..b9d5871a --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/OTAUpgrade.java @@ -0,0 +1,37 @@ +package com.fastbee.common.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, "升级中","设备OTA升级中"), + SUCCESS(3, "成功","升级成功"), + FAILED(4, "失败","升级失败"), + STOP(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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/OperatorType.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/OperatorType.java new file mode 100644 index 00000000..934801b0 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/OperatorType.java @@ -0,0 +1,24 @@ +package com.fastbee.common.enums; + +/** + * 操作人类别 + * + * @author ruoyi + */ +public enum OperatorType +{ + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ResultCode.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ResultCode.java new file mode 100644 index 00000000..86b293c9 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ResultCode.java @@ -0,0 +1,37 @@ +package com.fastbee.common.enums; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ServerType.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ServerType.java new file mode 100644 index 00000000..dec6b955 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ServerType.java @@ -0,0 +1,36 @@ +package com.fastbee.common.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"), + 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; + } +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/SocialPlatformType.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/SocialPlatformType.java new file mode 100644 index 00000000..e419b49f --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/SocialPlatformType.java @@ -0,0 +1,68 @@ +package com.fastbee.common.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", "微信开放平台小程序"), + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ThingsModelType.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ThingsModelType.java new file mode 100644 index 00000000..ed0c3330 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/ThingsModelType.java @@ -0,0 +1,50 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/TopicType.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/TopicType.java new file mode 100644 index 00000000..3f64f2ae --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/TopicType.java @@ -0,0 +1,62 @@ +package com.fastbee.common.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", "订阅功能调用返回结果"), + FIRMWARE_UPGRADE_REPLY(0,9,"/upgrade/reply", "订阅设备OTA升级结果"), + + + /*** 通用设备订阅主题(平台下发)***/ + FUNCTION_GET(1,17,"/function/get", "发布功能"), + PROPERTY_GET(1,12,"/property/get" ,"发布设备属性读取"), + FIRMWARE_SET(1,14, "/upgrade/set","发布OTA升级"), + 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_FUNCTION_POST(3,21,"/function/post", "设备端发布功能"), + DEV_PROPERTY_POST(3,22,"/property/post", "设备端发布属性"), + + + /*** webSocket转发前端使用 ***/ + WS_SERVICE_INVOKE(2,16,"/ws/service", "WS服务调用"), + + + /*** 模拟设备使用 ***/ + PROPERTY_GET_SIMULATE(4,23,"/property/get/simulate" ,"发布属性读取"), + PROPERTY_SET_SIMULATE(4,13, "/property/set/simulate","发布属性写入"), + WS_SERVICE_INVOKE_SIMULATE(2,24,"/ws/post/simulate", "模拟设备WS推送"), + PROPERTY_POST_SIMULATE(2,25,"/property/simulate/post", "订阅属性"); + + Integer type; + Integer order; + String topicSuffix; + String msg; + +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/UserStatus.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/UserStatus.java new file mode 100644 index 00000000..7ea86c2d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/UserStatus.java @@ -0,0 +1,30 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/VerifyTypeEnum.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/VerifyTypeEnum.java new file mode 100644 index 00000000..74202c25 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/enums/VerifyTypeEnum.java @@ -0,0 +1,23 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/DemoModeException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/DemoModeException.java new file mode 100644 index 00000000..ca1872f2 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/DemoModeException.java @@ -0,0 +1,15 @@ +package com.fastbee.common.exception; + +/** + * 演示模式异常 + * + * @author ruoyi + */ +public class DemoModeException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public DemoModeException() + { + } +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/ErrorCode.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/ErrorCode.java new file mode 100644 index 00000000..5d145d73 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/ErrorCode.java @@ -0,0 +1,28 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/GlobalException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/GlobalException.java new file mode 100644 index 00000000..f6bf8e42 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/GlobalException.java @@ -0,0 +1,58 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/ServerException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/ServerException.java new file mode 100644 index 00000000..459fdbc0 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/ServerException.java @@ -0,0 +1,60 @@ +package com.fastbee.common.exception; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/ServiceException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/ServiceException.java new file mode 100644 index 00000000..2cab2144 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/ServiceException.java @@ -0,0 +1,80 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/ServiceExceptionUtil.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/ServiceExceptionUtil.java new file mode 100644 index 00000000..0fe5b4db --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/ServiceExceptionUtil.java @@ -0,0 +1,125 @@ +package com.fastbee.common.exception; + +import com.fastbee.common.enums.GlobalErrorCodeConstants; +import com.google.common.annotations.VisibleForTesting; +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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/UtilException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/UtilException.java new file mode 100644 index 00000000..c39a5337 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/UtilException.java @@ -0,0 +1,26 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/base/BaseException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/base/BaseException.java new file mode 100644 index 00000000..6f6005d4 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/base/BaseException.java @@ -0,0 +1,97 @@ +package com.fastbee.common.exception.base; + +import com.fastbee.common.utils.MessageUtils; +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/file/FileException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/file/FileException.java new file mode 100644 index 00000000..e7fbb650 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/file/FileException.java @@ -0,0 +1,19 @@ +package com.fastbee.common.exception.file; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/file/FileNameLengthLimitExceededException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 00000000..b2ad8db2 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,16 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/file/FileSizeLimitExceededException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 00000000..cca7413d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,16 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/file/InvalidExtensionException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/file/InvalidExtensionException.java new file mode 100644 index 00000000..0a75787b --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/file/InvalidExtensionException.java @@ -0,0 +1,81 @@ +package com.fastbee.common.exception.file; + +import java.util.Arrays; +import org.apache.commons.fileupload.FileUploadException; + +/** + * 文件上传 误异常类 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/iot/MqttAuthorizationException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/iot/MqttAuthorizationException.java new file mode 100644 index 00000000..d2644a50 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/iot/MqttAuthorizationException.java @@ -0,0 +1,17 @@ +package com.fastbee.common.exception.iot; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/iot/MqttClientUserNameOrPassException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/iot/MqttClientUserNameOrPassException.java new file mode 100644 index 00000000..683f7e39 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/iot/MqttClientUserNameOrPassException.java @@ -0,0 +1,17 @@ +package com.fastbee.common.exception.iot; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/job/TaskException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/job/TaskException.java new file mode 100644 index 00000000..60cb410f --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/job/TaskException.java @@ -0,0 +1,34 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/CaptchaException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/CaptchaException.java new file mode 100644 index 00000000..f8d1544d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/CaptchaException.java @@ -0,0 +1,16 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/CaptchaExpireException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/CaptchaExpireException.java new file mode 100644 index 00000000..fd6401d6 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/CaptchaExpireException.java @@ -0,0 +1,16 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/UserException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/UserException.java new file mode 100644 index 00000000..d1d180f6 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/UserException.java @@ -0,0 +1,18 @@ +package com.fastbee.common.exception.user; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/UserPasswordNotMatchException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/UserPasswordNotMatchException.java new file mode 100644 index 00000000..54333f1a --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/UserPasswordNotMatchException.java @@ -0,0 +1,16 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/UserPasswordRetryLimitExceedException.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/UserPasswordRetryLimitExceedException.java new file mode 100644 index 00000000..c5d1812d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/exception/user/UserPasswordRetryLimitExceedException.java @@ -0,0 +1,16 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/PropertyPreExcludeFilter.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/PropertyPreExcludeFilter.java new file mode 100644 index 00000000..e8fbcd5c --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/PropertyPreExcludeFilter.java @@ -0,0 +1,24 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/RepeatableFilter.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/RepeatableFilter.java new file mode 100644 index 00000000..ed8bdda6 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/RepeatableFilter.java @@ -0,0 +1,52 @@ +package com.fastbee.common.filter; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.springframework.http.MediaType; +import com.fastbee.common.utils.StringUtils; + +/** + * 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/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/RepeatedlyRequestWrapper.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 00000000..53429f37 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,76 @@ +package com.fastbee.common.filter; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import com.fastbee.common.utils.http.HttpHelper; +import com.fastbee.common.constant.Constants; + +/** + * 构建可重复读取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/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/XssFilter.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/XssFilter.java new file mode 100644 index 00000000..e5ec9238 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/XssFilter.java @@ -0,0 +1,75 @@ +package com.fastbee.common.filter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.enums.HttpMethod; + +/** + * 防止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/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/XssHttpServletRequestWrapper.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 00000000..a514cf91 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,111 @@ +package com.fastbee.common.filter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.html.EscapeUtil; + +/** + * 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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/Arith.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/Arith.java new file mode 100644 index 00000000..bea66b58 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/Arith.java @@ -0,0 +1,114 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/BeanMapUtilByReflect.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/BeanMapUtilByReflect.java new file mode 100644 index 00000000..aa6feabd --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/BeanMapUtilByReflect.java @@ -0,0 +1,73 @@ +package com.fastbee.common.utils; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/CaculateUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/CaculateUtils.java new file mode 100644 index 00000000..02795f5c --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/CaculateUtils.java @@ -0,0 +1,360 @@ +package com.fastbee.common.utils; + +import com.fastbee.common.exception.ServiceException; +import io.netty.buffer.ByteBufUtil; +import org.apache.commons.codec.binary.Hex; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +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(BigDecimal.valueOf(Double.parseDouble(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).stripTrailingZeros(); + 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(); + } + } + + /** + * 转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 void main(String[] args) throws IOException { + Map map = new HashMap<>(); + map.put("%s", "180"); + String caculate = caculateReplace("%s*2", map); + System.out.println(caculate); + System.out.println(execute("%s*10",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 = "41EE8000"; + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/DateUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/DateUtils.java new file mode 100644 index 00000000..1da888b3 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/DateUtils.java @@ -0,0 +1,233 @@ +package com.fastbee.common.utils; + +import java.lang.management.ManagementFactory; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.Random; + +import org.apache.commons.lang3.time.DateFormatUtils; + +/** + * 时间工具类 + * + * @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"}; + + /** + * 获取当前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 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); + + } +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/DictUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/DictUtils.java new file mode 100644 index 00000000..c849fad0 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/DictUtils.java @@ -0,0 +1,186 @@ +package com.fastbee.common.utils; + +import java.util.Collection; +import java.util.List; +import com.alibaba.fastjson2.JSONArray; +import com.fastbee.common.constant.CacheConstants; +import com.fastbee.common.core.domain.entity.SysDictData; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.utils.spring.SpringUtils; + +/** + * 字典工具类 + * + * @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 字典值 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue) + { + return getDictLabel(dictType, dictValue, SEPARATOR); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel) + { + return getDictValue(dictType, dictLabel, SEPARATOR); + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @param separator 分隔符 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue, String separator) + { + 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())) + { + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/DigestUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/DigestUtils.java new file mode 100644 index 00000000..2f8ff025 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/DigestUtils.java @@ -0,0 +1,75 @@ +package com.fastbee.common.utils; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/EncodeUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/EncodeUtils.java new file mode 100644 index 00000000..ce195ea8 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/EncodeUtils.java @@ -0,0 +1,157 @@ +package com.fastbee.common.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 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; + } + } +} + diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ExceptionUtil.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ExceptionUtil.java new file mode 100644 index 00000000..927ac860 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ExceptionUtil.java @@ -0,0 +1,39 @@ +package com.fastbee.common.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import org.apache.commons.lang3.exception.ExceptionUtils; + +/** + * 错误信息处理类。 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ExceptionUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ExceptionUtils.java new file mode 100644 index 00000000..7c355cb1 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ExceptionUtils.java @@ -0,0 +1,53 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/LogUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/LogUtils.java new file mode 100644 index 00000000..413031d7 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/LogUtils.java @@ -0,0 +1,18 @@ +package com.fastbee.common.utils; + +/** + * 处理并记录日志文件 + * + * @author ruoyi + */ +public class LogUtils +{ + public static String getBlock(Object msg) + { + if (msg == null) + { + msg = ""; + } + return "[" + msg.toString() + "]"; + } +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/MapUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/MapUtils.java new file mode 100644 index 00000000..e5951298 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/MapUtils.java @@ -0,0 +1,66 @@ +package com.fastbee.common.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import com.fastbee.common.core.text.KeyValue; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; + +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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/Md5Utils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/Md5Utils.java new file mode 100644 index 00000000..d2e3b2d5 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/Md5Utils.java @@ -0,0 +1,82 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/MessageUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/MessageUtils.java new file mode 100644 index 00000000..b28412f1 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/MessageUtils.java @@ -0,0 +1,26 @@ +package com.fastbee.common.utils; + +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import com.fastbee.common.utils.spring.SpringUtils; + +/** + * 获取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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/PageUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/PageUtils.java new file mode 100644 index 00000000..8de87f8d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/PageUtils.java @@ -0,0 +1,35 @@ +package com.fastbee.common.utils; + +import com.github.pagehelper.PageHelper; +import com.fastbee.common.core.page.PageDomain; +import com.fastbee.common.core.page.TableSupport; +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/SecurityUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/SecurityUtils.java new file mode 100644 index 00000000..dd3e1470 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/SecurityUtils.java @@ -0,0 +1,120 @@ +package com.fastbee.common.utils; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import com.fastbee.common.constant.HttpStatus; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.exception.ServiceException; + +/** + * 安全服务工具类 + * + * @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) + { + throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取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; + } +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ServletUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ServletUtils.java new file mode 100644 index 00000000..0eef316b --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ServletUtils.java @@ -0,0 +1,228 @@ +package com.fastbee.common.utils; + +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; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import cn.hutool.extra.servlet.ServletUtil; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.core.text.Convert; + +/** + * 客户端工具类 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/StringUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/StringUtils.java new file mode 100644 index 00000000..b6e6742e --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/StringUtils.java @@ -0,0 +1,674 @@ +package com.fastbee.common.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import org.springframework.util.AntPathMatcher; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.core.text.StrFormatter; + +/** + * 字符串工具类 + * + * @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; + } + + +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/Threads.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/Threads.java new file mode 100644 index 00000000..bdba4628 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/Threads.java @@ -0,0 +1,99 @@ +package com.fastbee.common.utils; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 线程相关工具类. + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ValidationUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ValidationUtils.java new file mode 100644 index 00000000..34cfd92a --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ValidationUtils.java @@ -0,0 +1,55 @@ +package com.fastbee.common.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$]*"); + + 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 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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/VerifyCodeUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/VerifyCodeUtils.java new file mode 100644 index 00000000..9b1de74b --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/VerifyCodeUtils.java @@ -0,0 +1,228 @@ +package com.fastbee.common.utils; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +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; +import javax.imageio.ImageIO; + +/** + * 验证码工具类 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/bean/BeanUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/bean/BeanUtils.java new file mode 100644 index 00000000..f0917602 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/bean/BeanUtils.java @@ -0,0 +1,110 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/bean/BeanValidators.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/bean/BeanValidators.java new file mode 100644 index 00000000..050a5ab4 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/bean/BeanValidators.java @@ -0,0 +1,24 @@ +package com.fastbee.common.utils.bean; + +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.Validator; + +/** + * 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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/collection/CollectionUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/collection/CollectionUtils.java new file mode 100644 index 00000000..6194100b --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/collection/CollectionUtils.java @@ -0,0 +1,232 @@ +package com.fastbee.common.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); + } +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/date/DateUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/date/DateUtils.java new file mode 100644 index 00000000..2b48cd92 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/date/DateUtils.java @@ -0,0 +1,173 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/date/LocalDateTimeUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/date/LocalDateTimeUtils.java new file mode 100644 index 00000000..a761c412 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/date/LocalDateTimeUtils.java @@ -0,0 +1,63 @@ +package com.fastbee.common.utils.date; + +import cn.hutool.core.date.LocalDateTimeUtil; + +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * 时间工具类,用于 {@link LocalDateTime} + * + * @author fastbee + */ +public class LocalDateTimeUtils { + + /** + * 空的 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); + } + +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileTypeUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileTypeUtils.java new file mode 100644 index 00000000..8b6cf08f --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileTypeUtils.java @@ -0,0 +1,76 @@ +package com.fastbee.common.utils.file; + +import java.io.File; +import org.apache.commons.lang3.StringUtils; + +/** + * 文件类型工具类 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileUploadUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileUploadUtils.java new file mode 100644 index 00000000..b5a7ed32 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileUploadUtils.java @@ -0,0 +1,232 @@ +package com.fastbee.common.utils.file; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Objects; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; +import com.fastbee.common.config.RuoYiConfig; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.exception.file.FileNameLengthLimitExceededException; +import com.fastbee.common.exception.file.FileSizeLimitExceededException; +import com.fastbee.common.exception.file.InvalidExtensionException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.uuid.Seq; + +/** + * 文件上传工具类 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileUtils.java new file mode 100644 index 00000000..3fdcb711 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileUtils.java @@ -0,0 +1,340 @@ +package com.fastbee.common.utils.file; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.IdUtil; +import lombok.SneakyThrows; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import com.fastbee.common.config.RuoYiConfig; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.uuid.IdUtils; +import org.apache.commons.io.FilenameUtils; + +/** + * 文件处理工具类 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/ImageUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/ImageUtils.java new file mode 100644 index 00000000..070a700f --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/ImageUtils.java @@ -0,0 +1,98 @@ +package com.fastbee.common.utils.file; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; +import org.apache.poi.util.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fastbee.common.config.RuoYiConfig; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.utils.StringUtils; + +/** + * 图片处理工具类 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/MimeTypeUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/MimeTypeUtils.java new file mode 100644 index 00000000..bfbad86b --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/file/MimeTypeUtils.java @@ -0,0 +1,59 @@ +package com.fastbee.common.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", + // 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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC16Utils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC16Utils.java new file mode 100644 index 00000000..cd0358b4 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC16Utils.java @@ -0,0 +1,128 @@ +package com.fastbee.common.utils.gateway; + + +import com.fastbee.common.utils.gateway.protocol.ByteUtils; + +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[] 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 = "01000002"; + 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); + } + + +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC8Utils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC8Utils.java new file mode 100644 index 00000000..c36e6dcd --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC8Utils.java @@ -0,0 +1,93 @@ +package com.fastbee.common.utils.gateway; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/mq/Topics.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/mq/Topics.java new file mode 100644 index 00000000..9eab9cf6 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/mq/Topics.java @@ -0,0 +1,16 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/mq/TopicsPost.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/mq/TopicsPost.java new file mode 100644 index 00000000..f9f31847 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/mq/TopicsPost.java @@ -0,0 +1,14 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/mq/TopicsUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/mq/TopicsUtils.java new file mode 100644 index 00000000..78f5b659 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/mq/TopicsUtils.java @@ -0,0 +1,306 @@ +package com.fastbee.common.utils.gateway.mq; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.mq.message.DeviceDownMessage; +import com.fastbee.common.enums.ThingsModelType; +import com.fastbee.common.enums.TopicType; +import com.fastbee.common.utils.collection.CollectionUtils; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + + +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 (com.fastbee.common.utils.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(); + } + } + + + /** + * 获取所有可订阅的主题 + * + * @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 (com.fastbee.common.utils.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[] values = topic.split("/"); + return values[2]; + } + + /** + * 获取topic 判断字段 name + **/ + public String parseTopicName(String topic) { + String[] values = topic.split("/"); + return values[3]; + } + + /** + * 获取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 (com.fastbee.common.utils.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; + } +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/protocol/ByteUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/protocol/ByteUtils.java new file mode 100644 index 00000000..796e4534 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/protocol/ByteUtils.java @@ -0,0 +1,958 @@ +package com.fastbee.common.utils.gateway.protocol; + + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/protocol/NettyUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/protocol/NettyUtils.java new file mode 100644 index 00000000..18c8e921 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/protocol/NettyUtils.java @@ -0,0 +1,22 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/html/EscapeUtil.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/html/EscapeUtil.java new file mode 100644 index 00000000..134127c3 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/html/EscapeUtil.java @@ -0,0 +1,167 @@ +package com.fastbee.common.utils.html; + +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/html/HTMLFilter.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/html/HTMLFilter.java new file mode 100644 index 00000000..d2e5c43b --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/html/HTMLFilter.java @@ -0,0 +1,570 @@ +package com.fastbee.common.utils.html; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/http/HttpHelper.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/http/HttpHelper.java new file mode 100644 index 00000000..719a60b2 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/http/HttpHelper.java @@ -0,0 +1,55 @@ +package com.fastbee.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import javax.servlet.ServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 通用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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/http/HttpUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/http/HttpUtils.java new file mode 100644 index 00000000..bc348841 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/http/HttpUtils.java @@ -0,0 +1,274 @@ +package com.fastbee.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +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; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.utils.StringUtils; + +/** + * 通用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.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(); + } + + 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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ip/AddressUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ip/AddressUtils.java new file mode 100644 index 00000000..fddcd76f --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ip/AddressUtils.java @@ -0,0 +1,56 @@ +package com.fastbee.common.utils.ip; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.config.RuoYiConfig; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.http.HttpUtils; + +/** + * 获取地址类 + * + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ip/IpUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ip/IpUtils.java new file mode 100644 index 00000000..6aea1506 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/ip/IpUtils.java @@ -0,0 +1,264 @@ +package com.fastbee.common.utils.ip; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import javax.servlet.http.HttpServletRequest; +import com.fastbee.common.utils.StringUtils; + +/** + * 获取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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/json/JsonUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/json/JsonUtils.java new file mode 100644 index 00000000..b5fd0cdb --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/json/JsonUtils.java @@ -0,0 +1,159 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/object/ObjectUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/object/ObjectUtils.java new file mode 100644 index 00000000..1fd4c1cc --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/object/ObjectUtils.java @@ -0,0 +1,63 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/poi/ExcelHandlerAdapter.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/poi/ExcelHandlerAdapter.java new file mode 100644 index 00000000..ca8f11d7 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/poi/ExcelHandlerAdapter.java @@ -0,0 +1,19 @@ +package com.fastbee.common.utils.poi; + +/** + * Excel数据格式处理适配器 + * + * @author ruoyi + */ +public interface ExcelHandlerAdapter +{ + /** + * 格式化 + * + * @param value 单元格数据值 + * @param args excel注解args参数组 + * + * @return 处理后的值 + */ + Object format(Object value, String[] args); +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/poi/ExcelUtil.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/poi/ExcelUtil.java new file mode 100644 index 00000000..45eca859 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/poi/ExcelUtil.java @@ -0,0 +1,1734 @@ +package com.fastbee.common.utils.poi; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.hssf.usermodel.HSSFPicture; +import org.apache.poi.hssf.usermodel.HSSFPictureData; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ooxml.POIXMLDocumentPart; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.usermodel.PictureData; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.util.IOUtils; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFDataValidation; +import org.apache.poi.xssf.usermodel.XSSFDrawing; +import org.apache.poi.xssf.usermodel.XSSFPicture; +import org.apache.poi.xssf.usermodel.XSSFShape; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.annotation.Excel.ColumnType; +import com.fastbee.common.annotation.Excel.Type; +import com.fastbee.common.annotation.Excels; +import com.fastbee.common.config.RuoYiConfig; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.text.Convert; +import com.fastbee.common.exception.UtilException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.DictUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.file.FileTypeUtils; +import com.fastbee.common.utils.file.FileUtils; +import com.fastbee.common.utils.file.ImageUtils; +import com.fastbee.common.utils.reflect.ReflectUtils; + +/** + * Excel相关处理 + * + * @author ruoyi + */ +public class ExcelUtil +{ + private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); + + public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; + + public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; + + /** + * Excel sheet最大行数,默认65536 + */ + public static final int sheetSize = 65536; + + /** + * 工作表名称 + */ + private String sheetName; + + /** + * 导出类型(EXPORT:导出数据;IMPORT:导入模板) + */ + private Type type; + + /** + * 工作薄对象 + */ + private Workbook wb; + + /** + * 工作表对象 + */ + private Sheet sheet; + + /** + * 样式列表 + */ + private Map styles; + + /** + * 导入导出数据列表 + */ + private List list; + + /** + * 注解列表 + */ + private List fields; + + /** + * 当前行号 + */ + private int rownum; + + /** + * 标题 + */ + private String title; + + /** + * 最大高度 + */ + private short maxHeight; + + /** + * 合并后最后行数 + */ + private int subMergedLastRowNum = 0; + + /** + * 合并后开始行数 + */ + private int subMergedFirstRowNum = 1; + + /** + * 对象的子列表方法 + */ + private Method subMethod; + + /** + * 对象的子列表属性 + */ + private List subFields; + + /** + * 统计列表 + */ + private Map statistics = new HashMap(); + + /** + * 数字格式 + */ + private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00"); + + /** + * 实体对象 + */ + public Class clazz; + + /** + * 需要排除列属性 + */ + public String[] excludeFields; + + public ExcelUtil(Class clazz) + { + this.clazz = clazz; + } + + /** + * 隐藏Excel中列属性 + * + * @param fields 列属性名 示例[单个"name"/多个"id","name"] + * @throws Exception + */ + public void hideColumn(String... fields) + { + this.excludeFields = fields; + } + + public void init(List list, String sheetName, String title, Type type) + { + if (list == null) + { + list = new ArrayList(); + } + this.list = list; + this.sheetName = sheetName; + this.type = type; + this.title = title; + createExcelField(); + createWorkbook(); + createTitle(); + createSubHead(); + } + + /** + * 创建excel第一行标题 + */ + public void createTitle() + { + if (StringUtils.isNotEmpty(title)) + { + subMergedFirstRowNum++; + subMergedLastRowNum++; + int titleLastCol = this.fields.size() - 1; + if (isSubList()) + { + titleLastCol = titleLastCol + subFields.size() - 1; + } + Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); + titleRow.setHeightInPoints(30); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellStyle(styles.get("title")); + titleCell.setCellValue(title); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), titleRow.getRowNum(), titleLastCol)); + } + } + + /** + * 创建对象的子列表名称 + */ + public void createSubHead() + { + if (isSubList()) + { + subMergedFirstRowNum++; + subMergedLastRowNum++; + Row subRow = sheet.createRow(rownum); + int excelNum = 0; + for (Object[] objects : fields) + { + Excel attr = (Excel) objects[1]; + Cell headCell1 = subRow.createCell(excelNum); + headCell1.setCellValue(attr.name()); + headCell1.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + excelNum++; + } + int headFirstRow = excelNum - 1; + int headLastRow = headFirstRow + subFields.size() - 1; + if (headLastRow > headFirstRow) + { + sheet.addMergedRegion(new CellRangeAddress(rownum, rownum, headFirstRow, headLastRow)); + } + rownum++; + } + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(InputStream is) throws Exception + { + return importExcel(is, 0); + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @param titleNum 标题占用行数 + * @return 转换后集合 + */ + public List importExcel(InputStream is, int titleNum) throws Exception + { + return importExcel(StringUtils.EMPTY, is, titleNum); + } + + /** + * 对excel表单指定表格索引名转换成list + * + * @param sheetName 表格索引名 + * @param titleNum 标题占用行数 + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(String sheetName, InputStream is, int titleNum) throws Exception + { + this.type = Type.IMPORT; + this.wb = WorkbookFactory.create(is); + List list = new ArrayList(); + // 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet + Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0); + if (sheet == null) + { + throw new IOException("文件sheet不存在"); + } + boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook); + Map pictures; + if (isXSSFWorkbook) + { + pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb); + } + else + { + pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb); + } + // 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1 + int rows = sheet.getLastRowNum(); + + if (rows > 0) + { + // 定义一个map用于存放excel列的序号和field. + Map cellMap = new HashMap(); + // 获取表头 + Row heard = sheet.getRow(titleNum); + for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) + { + Cell cell = heard.getCell(i); + if (StringUtils.isNotNull(cell)) + { + String value = this.getCellValue(heard, i).toString(); + cellMap.put(value, i); + } + else + { + cellMap.put(null, i); + } + } + // 有数据时才处理 得到类的所有field. + List fields = this.getFields(); + Map fieldsMap = new HashMap(); + for (Object[] objects : fields) + { + Excel attr = (Excel) objects[1]; + Integer column = cellMap.get(attr.name()); + if (column != null) + { + fieldsMap.put(column, objects); + } + } + for (int i = titleNum + 1; i <= rows; i++) + { + // 从第2行开始取数据,默认第一行是表头. + Row row = sheet.getRow(i); + // 判断当前行是否是空行 + if (isRowEmpty(row)) + { + continue; + } + T entity = null; + for (Map.Entry entry : fieldsMap.entrySet()) + { + Object val = this.getCellValue(row, entry.getKey()); + + // 如果不存在实例则新建. + entity = (entity == null ? clazz.newInstance() : entity); + // 从map中得到对应列的field. + Field field = (Field) entry.getValue()[0]; + Excel attr = (Excel) entry.getValue()[1]; + // 取得类型,并根据对象类型设置值. + Class fieldType = field.getType(); + if (String.class == fieldType) + { + String s = Convert.toStr(val); + if (StringUtils.endsWith(s, ".0")) + { + val = StringUtils.substringBefore(s, ".0"); + } + else + { + String dateFormat = field.getAnnotation(Excel.class).dateFormat(); + if (StringUtils.isNotEmpty(dateFormat)) + { + val = parseDateToStr(dateFormat, val); + } + else + { + val = Convert.toStr(val); + } + } + } + else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toInt(val); + } + else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toLong(val); + } + else if (Double.TYPE == fieldType || Double.class == fieldType) + { + val = Convert.toDouble(val); + } + else if (Float.TYPE == fieldType || Float.class == fieldType) + { + val = Convert.toFloat(val); + } + else if (BigDecimal.class == fieldType) + { + val = Convert.toBigDecimal(val); + } + else if (Date.class == fieldType) + { + if (val instanceof String) + { + val = DateUtils.parseDate(val); + } + else if (val instanceof Double) + { + val = DateUtil.getJavaDate((Double) val); + } + } + else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) + { + val = Convert.toBool(val, false); + } + if (StringUtils.isNotNull(fieldType)) + { + String propertyName = field.getName(); + if (StringUtils.isNotEmpty(attr.targetAttr())) + { + propertyName = field.getName() + "." + attr.targetAttr(); + } + else if (StringUtils.isNotEmpty(attr.readConverterExp())) + { + val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); + } + else if (StringUtils.isNotEmpty(attr.dictType())) + { + val = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + val = dataFormatHandlerAdapter(val, attr); + } + else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) + { + PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey()); + if (image == null) + { + val = ""; + } + else + { + byte[] data = image.getData(); + val = FileUtils.writeImportBytes(data); + } + } + ReflectUtils.invokeSetter(entity, propertyName, val); + } + } + list.add(entity); + } + } + return list; + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName) + { + return exportExcel(list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName, String title) + { + this.init(list, sheetName, title, Type.EXPORT); + return exportExcel(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName) + { + exportExcel(response, list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(list, sheetName, title, Type.EXPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName) + { + return importTemplateExcel(sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName, String title) + { + this.init(null, sheetName, title, Type.IMPORT); + return exportExcel(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName) + { + importTemplateExcel(response, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(null, sheetName, title, Type.IMPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public void exportExcel(HttpServletResponse response) + { + try + { + writeSheet(); + wb.write(response.getOutputStream()); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + } + finally + { + IOUtils.closeQuietly(wb); + } + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public AjaxResult exportExcel() + { + OutputStream out = null; + try + { + writeSheet(); + String filename = encodingFilename(sheetName); + out = new FileOutputStream(getAbsoluteFile(filename)); + wb.write(out); + return AjaxResult.success(filename); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + throw new UtilException("导出Excel失败,请联系网站管理员!"); + } + finally + { + IOUtils.closeQuietly(wb); + IOUtils.closeQuietly(out); + } + } + + /** + * 创建写入数据到Sheet + */ + public void writeSheet() + { + // 取出一共有多少个sheet. + int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize)); + for (int index = 0; index < sheetNo; index++) + { + createSheet(sheetNo, index); + + // 产生一行 + Row row = sheet.createRow(rownum); + int column = 0; + // 写入各个字段的列头名称 + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + this.createHeadCell(subExcel, row, column++); + } + } + else + { + this.createHeadCell(excel, row, column++); + } + } + if (Type.EXPORT.equals(type)) + { + fillExcelData(index, row); + addStatisticsRow(); + } + } + } + + /** + * 填充excel数据 + * + * @param index 序号 + * @param row 单元格行 + */ + @SuppressWarnings("unchecked") + public void fillExcelData(int index, Row row) + { + int startNo = index * sheetSize; + int endNo = Math.min(startNo + sheetSize, list.size()); + int rowNo = (1 + rownum) - startNo; + for (int i = startNo; i < endNo; i++) + { + rowNo = isSubList() ? (i > 1 ? rowNo + 1 : rowNo + i) : i + 1 + rownum - startNo; + row = sheet.createRow(rowNo); + // 得到导出对象. + T vo = (T) list.get(i); + Collection subList = null; + if (isSubList()) + { + if (isSubListValue(vo)) + { + subList = getListCellValue(vo); + subMergedLastRowNum = subMergedLastRowNum + subList.size(); + } + else + { + subMergedFirstRowNum++; + subMergedLastRowNum++; + } + } + int column = 0; + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType()) && StringUtils.isNotNull(subList)) + { + boolean subFirst = false; + for (Object obj : subList) + { + if (subFirst) + { + rowNo++; + row = sheet.createRow(rowNo); + } + List subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class); + int subIndex = 0; + for (Field subField : subFields) + { + if (subField.isAnnotationPresent(Excel.class)) + { + subField.setAccessible(true); + Excel attr = subField.getAnnotation(Excel.class); + this.addCell(attr, row, (T) obj, subField, column + subIndex); + } + subIndex++; + } + subFirst = true; + } + this.subMergedFirstRowNum = this.subMergedFirstRowNum + subList.size(); + } + else + { + this.addCell(excel, row, vo, field, column++); + } + } + } + } + + /** + * 创建表格样式 + * + * @param wb 工作薄对象 + * @return 样式列表 + */ + private Map createStyles(Workbook wb) + { + // 写入各条记录,每条记录对应excel表中的一行 + Map styles = new HashMap(); + CellStyle style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBold(true); + style.setFont(titleFont); + styles.put("title", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + style.setFont(dataFont); + styles.put("data", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font totalFont = wb.createFont(); + totalFont.setFontName("Arial"); + totalFont.setFontHeightInPoints((short) 10); + style.setFont(totalFont); + styles.put("total", style); + + styles.putAll(annotationHeaderStyles(wb, styles)); + + styles.putAll(annotationDataStyles(wb)); + + return styles; + } + + /** + * 根据Excel注解创建表格头样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationHeaderStyles(Workbook wb, Map styles) + { + Map headerStyles = new HashMap(); + for (Object[] os : fields) + { + Excel excel = (Excel) os[1]; + String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor()); + if (!headerStyles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(excel.headerBackgroundColor().index); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBold(true); + headerFont.setColor(excel.headerColor().index); + style.setFont(headerFont); + headerStyles.put(key, style); + } + } + return headerStyles; + } + + /** + * 根据Excel注解创建表格列样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationDataStyles(Workbook wb) + { + Map styles = new HashMap(); + for (Object[] os : fields) + { + Excel excel = (Excel) os[1]; + String key = StringUtils.format("data_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor()); + if (!styles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.setAlignment(excel.align()); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setFillForegroundColor(excel.backgroundColor().getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + dataFont.setColor(excel.color().index); + style.setFont(dataFont); + styles.put(key, style); + } + } + return styles; + } + + /** + * 创建单元格 + */ + public Cell createHeadCell(Excel attr, Row row, int column) + { + // 创建列 + Cell cell = row.createCell(column); + // 写入列信息 + cell.setCellValue(attr.name()); + setDataValidation(attr, row, column); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + if (isSubList()) + { + // 填充默认样式,防止合并单元格样式失效 + sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor()))); + if (attr.needMerge()) + { + sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column)); + } + } + return cell; + } + + /** + * 设置单元格信息 + * + * @param value 单元格值 + * @param attr 注解相关 + * @param cell 单元格信息 + */ + public void setCellVo(Object value, Excel attr, Cell cell) + { + if (ColumnType.STRING == attr.cellType()) + { + String cellValue = Convert.toStr(value); + // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。 + if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) + { + cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0"); + } + if (value instanceof Collection && StringUtils.equals("[]", cellValue)) + { + cellValue = StringUtils.EMPTY; + } + cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix()); + } + else if (ColumnType.NUMERIC == attr.cellType()) + { + if (StringUtils.isNotNull(value)) + { + cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value)); + } + } + else if (ColumnType.IMAGE == attr.cellType()) + { + ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1); + String imagePath = Convert.toStr(value); + if (StringUtils.isNotEmpty(imagePath)) + { + byte[] data = ImageUtils.getImage(imagePath); + getDrawingPatriarch(cell.getSheet()).createPicture(anchor, + cell.getSheet().getWorkbook().addPicture(data, getImageType(data))); + } + } + } + + /** + * 获取画布 + */ + public static Drawing getDrawingPatriarch(Sheet sheet) + { + if (sheet.getDrawingPatriarch() == null) + { + sheet.createDrawingPatriarch(); + } + return sheet.getDrawingPatriarch(); + } + + /** + * 获取图片类型,设置图片插入类型 + */ + public int getImageType(byte[] value) + { + String type = FileTypeUtils.getFileExtendName(value); + if ("JPG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_JPEG; + } + else if ("PNG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_PNG; + } + return Workbook.PICTURE_TYPE_JPEG; + } + + /** + * 创建表格样式 + */ + public void setDataValidation(Excel attr, Row row, int column) + { + if (attr.name().indexOf("注:") >= 0) + { + sheet.setColumnWidth(column, 6000); + } + else + { + // 设置列宽 + sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256)); + } + if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0) + { + if (attr.combo().length > 15 || StringUtils.join(attr.combo()).length() > 255) + { + // 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到 + setXSSFValidationWithHidden(sheet, attr.combo(), attr.prompt(), 1, 100, column, column); + } + else + { + // 提示信息或只能选择不能输入的列内容. + setPromptOrValidation(sheet, attr.combo(), attr.prompt(), 1, 100, column, column); + } + } + } + + /** + * 添加单元格 + */ + public Cell addCell(Excel attr, Row row, T vo, Field field, int column) + { + Cell cell = null; + try + { + // 设置行高 + row.setHeight(maxHeight); + // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列. + if (attr.isExport()) + { + // 创建cell + cell = row.createCell(column); + if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) + { + CellRangeAddress cellAddress = new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column); + sheet.addMergedRegion(cellAddress); + } + cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor()))); + + // 用于读取对象中的属性 + Object value = getTargetValue(vo, field, attr); + String dateFormat = attr.dateFormat(); + String readConverterExp = attr.readConverterExp(); + String separator = attr.separator(); + String dictType = attr.dictType(); + if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) + { + cell.setCellValue(parseDateToStr(dateFormat, value)); + } + else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) + { + cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator)); + } + else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) + { + cell.setCellValue(convertDictByExp(Convert.toStr(value), dictType, separator)); + } + else if (value instanceof BigDecimal && -1 != attr.scale()) + { + cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + cell.setCellValue(dataFormatHandlerAdapter(value, attr)); + } + else + { + // 设置列类型 + setCellVo(value, attr, cell); + } + addStatisticsData(column, Convert.toStr(value), attr); + } + } + catch (Exception e) + { + log.error("导出Excel失败{}", e); + } + return cell; + } + + /** + * 设置 POI XSSFSheet 单元格提示或选择框 + * + * @param sheet 表单 + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, + int firstCol, int endCol) + { + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1"); + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + sheet.addValidationData(dataValidation); + } + + /** + * 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框). + * + * @param sheet 要设置的sheet. + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) + { + String hideSheetName = "combo_" + firstCol + "_" + endCol; + Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 + for (int i = 0; i < textlist.length; i++) + { + hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); + } + // 创建名称,可被其他单元格引用 + Name name = wb.createName(); + name.setNameName(hideSheetName + "_data"); + name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); + DataValidationHelper helper = sheet.getDataValidationHelper(); + // 加载下拉列表内容 + DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data"); + // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + // 数据有效性对象 + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + + sheet.addValidationData(dataValidation); + // 设置hiddenSheet隐藏 + wb.setSheetHidden(wb.getSheetIndex(hideSheet), true); + } + + /** + * 解析导出值 0=男,1=女,2=未知 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String convertByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(","); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[0].equals(value)) + { + propertyString.append(itemArray[1] + separator); + break; + } + } + } + else + { + if (itemArray[0].equals(propertyValue)) + { + return itemArray[1]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 反向解析值 男=0,女=1,未知=2 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String reverseByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(","); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[1].equals(value)) + { + propertyString.append(itemArray[0] + separator); + break; + } + } + } + else + { + if (itemArray[1].equals(propertyValue)) + { + return itemArray[0]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 解析字典值 + * + * @param dictValue 字典值 + * @param dictType 字典类型 + * @param separator 分隔符 + * @return 字典标签 + */ + public static String convertDictByExp(String dictValue, String dictType, String separator) + { + return DictUtils.getDictLabel(dictType, dictValue, separator); + } + + /** + * 反向解析值字典值 + * + * @param dictLabel 字典标签 + * @param dictType 字典类型 + * @param separator 分隔符 + * @return 字典值 + */ + public static String reverseDictByExp(String dictLabel, String dictType, String separator) + { + return DictUtils.getDictValue(dictType, dictLabel, separator); + } + + /** + * 数据处理器 + * + * @param value 数据值 + * @param excel 数据注解 + * @return + */ + public String dataFormatHandlerAdapter(Object value, Excel excel) + { + try + { + Object instance = excel.handler().newInstance(); + Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class }); + value = formatMethod.invoke(instance, value, excel.args()); + } + catch (Exception e) + { + log.error("不能格式化数据 " + excel.handler(), e.getMessage()); + } + return Convert.toStr(value); + } + + /** + * 合计统计信息 + */ + private void addStatisticsData(Integer index, String text, Excel entity) + { + if (entity != null && entity.isStatistics()) + { + Double temp = 0D; + if (!statistics.containsKey(index)) + { + statistics.put(index, temp); + } + try + { + temp = Double.valueOf(text); + } + catch (NumberFormatException e) + { + } + statistics.put(index, statistics.get(index) + temp); + } + } + + /** + * 创建统计行 + */ + public void addStatisticsRow() + { + if (statistics.size() > 0) + { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + Set keys = statistics.keySet(); + Cell cell = row.createCell(0); + cell.setCellStyle(styles.get("total")); + cell.setCellValue("合计"); + + for (Integer key : keys) + { + cell = row.createCell(key); + cell.setCellStyle(styles.get("total")); + cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key))); + } + statistics.clear(); + } + } + + /** + * 编码文件名 + */ + public String encodingFilename(String filename) + { + filename = UUID.randomUUID().toString() + "_" + filename + ".xlsx"; + return filename; + } + + /** + * 获取下载路径 + * + * @param filename 文件名称 + */ + public String getAbsoluteFile(String filename) + { + String downloadPath = RuoYiConfig.getDownloadPath() + filename; + File desc = new File(downloadPath); + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + return downloadPath; + } + + /** + * 获取bean中的属性值 + * + * @param vo 实体对象 + * @param field 字段 + * @param excel 注解 + * @return 最终的属性值 + * @throws Exception + */ + private Object getTargetValue(T vo, Field field, Excel excel) throws Exception + { + Object o = field.get(vo); + if (StringUtils.isNotEmpty(excel.targetAttr())) + { + String target = excel.targetAttr(); + if (target.contains(".")) + { + String[] targets = target.split("[.]"); + for (String name : targets) + { + o = getValue(o, name); + } + } + else + { + o = getValue(o, target); + } + } + return o; + } + + /** + * 以类的属性的get方法方法形式获取值 + * + * @param o + * @param name + * @return value + * @throws Exception + */ + private Object getValue(Object o, String name) throws Exception + { + if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) + { + Class clazz = o.getClass(); + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + o = field.get(o); + } + return o; + } + + /** + * 得到所有定义字段 + */ + private void createExcelField() + { + this.fields = getFields(); + this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList()); + this.maxHeight = getRowHeight(); + } + + /** + * 获取字段注解信息 + */ + public List getFields() + { + List fields = new ArrayList(); + List tempFields = new ArrayList<>(); + tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); + tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); + for (Field field : tempFields) + { + if (!ArrayUtils.contains(this.excludeFields, field.getName())) + { + // 单注解 + if (field.isAnnotationPresent(Excel.class)) + { + Excel attr = field.getAnnotation(Excel.class); + if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) + { + field.setAccessible(true); + fields.add(new Object[] { field, attr }); + } + if (Collection.class.isAssignableFrom(field.getType())) + { + subMethod = getSubMethod(field.getName(), clazz); + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); + } + } + + // 多注解 + if (field.isAnnotationPresent(Excels.class)) + { + Excels attrs = field.getAnnotation(Excels.class); + Excel[] excels = attrs.value(); + for (Excel attr : excels) + { + if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) + { + field.setAccessible(true); + fields.add(new Object[] { field, attr }); + } + } + } + } + } + return fields; + } + + /** + * 根据注解获取最大行高 + */ + public short getRowHeight() + { + double maxHeight = 0; + for (Object[] os : this.fields) + { + Excel excel = (Excel) os[1]; + maxHeight = Math.max(maxHeight, excel.height()); + } + return (short) (maxHeight * 20); + } + + /** + * 创建一个工作簿 + */ + public void createWorkbook() + { + this.wb = new SXSSFWorkbook(500); + this.sheet = wb.createSheet(); + wb.setSheetName(0, sheetName); + this.styles = createStyles(wb); + } + + /** + * 创建工作表 + * + * @param sheetNo sheet数量 + * @param index 序号 + */ + public void createSheet(int sheetNo, int index) + { + // 设置工作表的名称. + if (sheetNo > 1 && index > 0) + { + this.sheet = wb.createSheet(); + this.createTitle(); + wb.setSheetName(index, sheetName + index); + } + } + + /** + * 获取单元格值 + * + * @param row 获取的行 + * @param column 获取单元格列号 + * @return 单元格值 + */ + public Object getCellValue(Row row, int column) + { + if (row == null) + { + return row; + } + Object val = ""; + try + { + Cell cell = row.getCell(column); + if (StringUtils.isNotNull(cell)) + { + if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) + { + val = cell.getNumericCellValue(); + if (DateUtil.isCellDateFormatted(cell)) + { + val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换 + } + else + { + if ((Double) val % 1 != 0) + { + val = new BigDecimal(val.toString()); + } + else + { + val = new DecimalFormat("0").format(val); + } + } + } + else if (cell.getCellType() == CellType.STRING) + { + val = cell.getStringCellValue(); + } + else if (cell.getCellType() == CellType.BOOLEAN) + { + val = cell.getBooleanCellValue(); + } + else if (cell.getCellType() == CellType.ERROR) + { + val = cell.getErrorCellValue(); + } + + } + } + catch (Exception e) + { + return val; + } + return val; + } + + /** + * 判断是否是空行 + * + * @param row 判断的行 + * @return + */ + private boolean isRowEmpty(Row row) + { + if (row == null) + { + return true; + } + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) + { + Cell cell = row.getCell(i); + if (cell != null && cell.getCellType() != CellType.BLANK) + { + return false; + } + } + return true; + } + + /** + * 获取Excel2003图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) + { + Map sheetIndexPicMap = new HashMap(); + List pictures = workbook.getAllPictures(); + if (!pictures.isEmpty()) + { + for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) + { + HSSFClientAnchor anchor = (HSSFClientAnchor) shape.getAnchor(); + if (shape instanceof HSSFPicture) + { + HSSFPicture pic = (HSSFPicture) shape; + int pictureIndex = pic.getPictureIndex() - 1; + HSSFPictureData picData = pictures.get(pictureIndex); + String picIndex = String.valueOf(anchor.getRow1()) + "_" + String.valueOf(anchor.getCol1()); + sheetIndexPicMap.put(picIndex, picData); + } + } + return sheetIndexPicMap; + } + else + { + return sheetIndexPicMap; + } + } + + /** + * 获取Excel2007图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) + { + Map sheetIndexPicMap = new HashMap(); + for (POIXMLDocumentPart dr : sheet.getRelations()) + { + if (dr instanceof XSSFDrawing) + { + XSSFDrawing drawing = (XSSFDrawing) dr; + List shapes = drawing.getShapes(); + for (XSSFShape shape : shapes) + { + if (shape instanceof XSSFPicture) + { + XSSFPicture pic = (XSSFPicture) shape; + XSSFClientAnchor anchor = pic.getPreferredSize(); + CTMarker ctMarker = anchor.getFrom(); + String picIndex = ctMarker.getRow() + "_" + ctMarker.getCol(); + sheetIndexPicMap.put(picIndex, pic.getPictureData()); + } + } + } + } + return sheetIndexPicMap; + } + + /** + * 格式化不同类型的日期对象 + * + * @param dateFormat 日期格式 + * @param val 被格式化的日期对象 + * @return 格式化后的日期字符 + */ + public String parseDateToStr(String dateFormat, Object val) + { + if (val == null) + { + return ""; + } + String str; + if (val instanceof Date) + { + str = DateUtils.parseDateToStr(dateFormat, (Date) val); + } + else if (val instanceof LocalDateTime) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val)); + } + else if (val instanceof LocalDate) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val)); + } + else + { + str = val.toString(); + } + return str; + } + + /** + * 是否有对象的子列表 + */ + public boolean isSubList() + { + return StringUtils.isNotNull(subFields) && subFields.size() > 0; + } + + /** + * 是否有对象的子列表,集合不为空 + */ + public boolean isSubListValue(T vo) + { + return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0; + } + + /** + * 获取集合的值 + */ + public Collection getListCellValue(Object obj) + { + Object value; + try + { + value = subMethod.invoke(obj, new Object[] {}); + } + catch (Exception e) + { + return new ArrayList(); + } + return (Collection) value; + } + + /** + * 获取对象的子列表方法 + * + * @param name 名称 + * @param pojoClass 类对象 + * @return 子列表方法 + */ + public Method getSubMethod(String name, Class pojoClass) + { + StringBuffer getMethodName = new StringBuffer("get"); + getMethodName.append(name.substring(0, 1).toUpperCase()); + getMethodName.append(name.substring(1)); + Method method = null; + try + { + method = pojoClass.getMethod(getMethodName.toString(), new Class[] {}); + } + catch (Exception e) + { + log.error("获取对象异常{}", e.getMessage()); + } + return method; + } +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/reflect/ReflectUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/reflect/ReflectUtils.java new file mode 100644 index 00000000..54eb6290 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/reflect/ReflectUtils.java @@ -0,0 +1,410 @@ +package com.fastbee.common.utils.reflect; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Date; +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 com.fastbee.common.core.text.Convert; +import com.fastbee.common.utils.DateUtils; + +/** + * 反射工具类. 提供调用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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/sign/Base64.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/sign/Base64.java new file mode 100644 index 00000000..ca1f4f6c --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/sign/Base64.java @@ -0,0 +1,291 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/sign/Md5Utils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/sign/Md5Utils.java new file mode 100644 index 00000000..ba4aab16 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/sign/Md5Utils.java @@ -0,0 +1,67 @@ +package com.fastbee.common.utils.sign; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/spring/SpringUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/spring/SpringUtils.java new file mode 100644 index 00000000..61138aaf --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/spring/SpringUtils.java @@ -0,0 +1,183 @@ +package com.fastbee.common.utils.spring; + +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 com.fastbee.common.utils.StringUtils; +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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/sql/SqlUtil.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/sql/SqlUtil.java new file mode 100644 index 00000000..ddf3bc52 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/sql/SqlUtil.java @@ -0,0 +1,61 @@ +package com.fastbee.common.utils.sql; + +import com.fastbee.common.exception.UtilException; +import com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/IdUtils.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/IdUtils.java new file mode 100644 index 00000000..10659b34 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/IdUtils.java @@ -0,0 +1,128 @@ +package com.fastbee.common.utils.uuid; + +import lombok.extern.slf4j.Slf4j; +import com.fastbee.common.utils.Md5Utils; +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); + } +} diff --git a/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/Seq.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/Seq.java new file mode 100644 index 00000000..7ecd061d --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/Seq.java @@ -0,0 +1,86 @@ +package com.fastbee.common.utils.uuid; + +import java.util.concurrent.atomic.AtomicInteger; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; + +/** + * @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/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/UUID.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/UUID.java new file mode 100644 index 00000000..304f63a9 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/utils/uuid/UUID.java @@ -0,0 +1,484 @@ +package com.fastbee.common.utils.uuid; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import com.fastbee.common.exception.UtilException; + +/** + * 提供通用唯一识别码(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/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatAppResult.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatAppResult.java new file mode 100644 index 00000000..931a23a0 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatAppResult.java @@ -0,0 +1,72 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatLoginBody.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatLoginBody.java new file mode 100644 index 00000000..60a47673 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatLoginBody.java @@ -0,0 +1,89 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatLoginResult.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatLoginResult.java new file mode 100644 index 00000000..08c09ca9 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatLoginResult.java @@ -0,0 +1,22 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatMiniProgramResult.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatMiniProgramResult.java new file mode 100644 index 00000000..7f7db8af --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatMiniProgramResult.java @@ -0,0 +1,42 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatPhoneInfo.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatPhoneInfo.java new file mode 100644 index 00000000..01283209 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatPhoneInfo.java @@ -0,0 +1,41 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatUserInfo.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatUserInfo.java new file mode 100644 index 00000000..48a06c90 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/wechat/WeChatUserInfo.java @@ -0,0 +1,80 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/xss/Xss.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/xss/Xss.java new file mode 100644 index 00000000..5f79e8a0 --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/xss/Xss.java @@ -0,0 +1,27 @@ +package com.fastbee.common.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/springboot/fastbee-common/src/main/java/com/fastbee/common/xss/XssValidator.java b/springboot/fastbee-common/src/main/java/com/fastbee/common/xss/XssValidator.java new file mode 100644 index 00000000..cf6a6dee --- /dev/null +++ b/springboot/fastbee-common/src/main/java/com/fastbee/common/xss/XssValidator.java @@ -0,0 +1,34 @@ +package com.fastbee.common.xss; + +import com.fastbee.common.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/springboot/fastbee-framework/pom.xml b/springboot/fastbee-framework/pom.xml new file mode 100644 index 00000000..67e35e98 --- /dev/null +++ b/springboot/fastbee-framework/pom.xml @@ -0,0 +1,64 @@ + + + + fastbee + com.fastbee + 3.8.5 + + 4.0.0 + + fastbee-framework + + + framework框架核心 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.fastbee + fastbee-system-service + + + + + com.alibaba + druid-spring-boot-starter + + + + + pro.fessional + kaptcha + + + javax.servlet-api + javax.servlet + + + + + + + com.github.oshi + oshi-core + + + + + \ No newline at end of file diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/DataScopeAspect.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/DataScopeAspect.java new file mode 100644 index 00000000..813d9d3f --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/DataScopeAspect.java @@ -0,0 +1,167 @@ +package com.fastbee.framework.aspectj; + +import java.util.ArrayList; +import java.util.List; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; +import com.fastbee.common.annotation.DataScope; +import com.fastbee.common.core.domain.BaseEntity; +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.core.text.Convert; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.framework.security.context.PermissionContextHolder; + +/** + * 数据过滤处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class DataScopeAspect +{ + /** + * 全部数据权限 + */ + public static final String DATA_SCOPE_ALL = "1"; + + /** + * 自定数据权限 + */ + public static final String DATA_SCOPE_CUSTOM = "2"; + + /** + * 部门数据权限 + */ + public static final String DATA_SCOPE_DEPT = "3"; + + /** + * 部门及以下数据权限 + */ + public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; + + /** + * 仅本人数据权限 + */ + public static final String DATA_SCOPE_SELF = "5"; + + /** + * 数据权限过滤关键字 + */ + public static final String DATA_SCOPE = "dataScope"; + + @Before("@annotation(controllerDataScope)") + public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable + { + clearDataScope(point); + handleDataScope(point, controllerDataScope); + } + + protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) + { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNotNull(loginUser)) + { + SysUser currentUser = loginUser.getUser(); + // 如果是超级管理员,则不过滤数据 + if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) + { + String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext()); + dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), + controllerDataScope.userAlias(), permission); + } + } + } + + /** + * 数据范围过滤 + * + * @param joinPoint 切点 + * @param user 用户 + * @param deptAlias 部门别名 + * @param userAlias 用户别名 + * @param permission 权限字符 + */ + public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, String permission) + { + StringBuilder sqlString = new StringBuilder(); + List conditions = new ArrayList(); + + for (SysRole role : user.getRoles()) + { + String dataScope = role.getDataScope(); + if (!DATA_SCOPE_CUSTOM.equals(dataScope) && conditions.contains(dataScope)) + { + continue; + } + if (StringUtils.isNotEmpty(permission) && StringUtils.isNotEmpty(role.getPermissions()) + && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) + { + continue; + } + if (DATA_SCOPE_ALL.equals(dataScope)) + { + sqlString = new StringBuilder(); + break; + } + else if (DATA_SCOPE_CUSTOM.equals(dataScope)) + { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, + role.getRoleId())); + } + else if (DATA_SCOPE_DEPT.equals(dataScope)) + { + sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); + } + else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) + { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", + deptAlias, user.getDeptId(), user.getDeptId())); + } + else if (DATA_SCOPE_SELF.equals(dataScope)) + { + if (StringUtils.isNotBlank(userAlias)) + { + sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); + } + else + { + // 数据权限为仅本人且没有userAlias别名不查询任何数据 + sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); + } + } + conditions.add(dataScope); + } + + if (StringUtils.isNotBlank(sqlString.toString())) + { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) + { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); + } + } + } + + /** + * 拼接权限sql前先清空params.dataScope参数防止注入 + */ + private void clearDataScope(final JoinPoint joinPoint) + { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) + { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, ""); + } + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/DataSourceAspect.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/DataSourceAspect.java new file mode 100644 index 00000000..9a31e44b --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/DataSourceAspect.java @@ -0,0 +1,72 @@ +package com.fastbee.framework.aspectj; + +import java.util.Objects; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import com.fastbee.common.annotation.DataSource; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.framework.datasource.DynamicDataSourceContextHolder; + +/** + * 多数据源处理 + * + * @author ruoyi + */ +@Aspect +@Order(1) +@Component +public class DataSourceAspect +{ + protected Logger logger = LoggerFactory.getLogger(getClass()); + + @Pointcut("@annotation(com.fastbee.common.annotation.DataSource)" + + "|| @within(com.fastbee.common.annotation.DataSource)") + public void dsPointCut() + { + + } + + @Around("dsPointCut()") + public Object around(ProceedingJoinPoint point) throws Throwable + { + DataSource dataSource = getDataSource(point); + + if (StringUtils.isNotNull(dataSource)) + { + DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); + } + + try + { + return point.proceed(); + } + finally + { + // 销毁数据源 在执行方法之后 + DynamicDataSourceContextHolder.clearDataSourceType(); + } + } + + /** + * 获取需要切换的数据源 + */ + public DataSource getDataSource(ProceedingJoinPoint point) + { + MethodSignature signature = (MethodSignature) point.getSignature(); + DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); + if (Objects.nonNull(dataSource)) + { + return dataSource; + } + + return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/LogAspect.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/LogAspect.java new file mode 100644 index 00000000..bd03f3f7 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/LogAspect.java @@ -0,0 +1,227 @@ +package com.fastbee.framework.aspectj; + +import java.util.Collection; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; +import com.alibaba.fastjson2.JSON; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.enums.BusinessStatus; +import com.fastbee.common.enums.HttpMethod; +import com.fastbee.common.filter.PropertyPreExcludeFilter; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.common.utils.ServletUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.ip.IpUtils; +import com.fastbee.framework.manager.AsyncManager; +import com.fastbee.framework.manager.factory.AsyncFactory; +import com.fastbee.system.domain.SysOperLog; + +/** + * 操作日志记录处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class LogAspect +{ + private static final Logger log = LoggerFactory.getLogger(LogAspect.class); + + /** 排除敏感属性字段 */ + public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) + { + handleLog(joinPoint, controllerLog, null, jsonResult); + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) + { + handleLog(joinPoint, controllerLog, e, null); + } + + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) + { + try + { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + + // *========数据库日志=========*// + SysOperLog operLog = new SysOperLog(); + operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); + // 请求的地址 + String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); + operLog.setOperIp(ip); + operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + if (loginUser != null) + { + operLog.setOperName(loginUser.getUsername()); + } + + if (e != null) + { + operLog.setStatus(BusinessStatus.FAIL.ordinal()); + operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); + } + // 设置方法名称 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operLog.setMethod(className + "." + methodName + "()"); + // 设置请求方式 + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + // 处理设置注解上的参数 + getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); + // 保存数据库 + AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); + } + catch (Exception exp) + { + // 记录本地异常日志 + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } + } + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param log 日志 + * @param operLog 操作日志 + * @throws Exception + */ + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception + { + // 设置action动作 + operLog.setBusinessType(log.businessType().ordinal()); + // 设置标题 + operLog.setTitle(log.title()); + // 设置操作人类别 + operLog.setOperatorType(log.operatorType().ordinal()); + // 是否需要保存request,参数和值 + if (log.isSaveRequestData()) + { + // 获取参数的信息,传入到数据库中。 + setRequestValue(joinPoint, operLog); + } + // 是否需要保存response,参数和值 + if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) + { + operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operLog 操作日志 + * @throws Exception 异常 + */ + private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception + { + String requestMethod = operLog.getRequestMethod(); + if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) + { + String params = argsArrayToString(joinPoint.getArgs()); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } + else + { + Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); + operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter()), 0, 2000)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray) + { + String params = ""; + if (paramsArray != null && paramsArray.length > 0) + { + for (Object o : paramsArray) + { + if (StringUtils.isNotNull(o) && !isFilterObject(o)) + { + try + { + String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter()); + params += jsonObj.toString() + " "; + } + catch (Exception e) + { + } + } + } + } + return params.trim(); + } + + /** + * 忽略敏感属性 + */ + public PropertyPreExcludeFilter excludePropertyPreFilter() + { + return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) + { + Class clazz = o.getClass(); + if (clazz.isArray()) + { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } + else if (Collection.class.isAssignableFrom(clazz)) + { + Collection collection = (Collection) o; + for (Object value : collection) + { + return value instanceof MultipartFile; + } + } + else if (Map.class.isAssignableFrom(clazz)) + { + Map map = (Map) o; + for (Object value : map.entrySet()) + { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/RateLimiterAspect.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/RateLimiterAspect.java new file mode 100644 index 00000000..caed1bd1 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/RateLimiterAspect.java @@ -0,0 +1,90 @@ +package com.fastbee.framework.aspectj; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; +import com.fastbee.common.annotation.RateLimiter; +import com.fastbee.common.enums.LimitType; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.ServletUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.ip.IpUtils; + +/** + * 限流处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class RateLimiterAspect +{ + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + private RedisTemplate redisTemplate; + + private RedisScript limitScript; + + @Autowired + public void setRedisTemplate1(RedisTemplate redisTemplate) + { + this.redisTemplate = redisTemplate; + } + + @Autowired + public void setLimitScript(RedisScript limitScript) + { + this.limitScript = limitScript; + } + + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable + { + int time = rateLimiter.time(); + int count = rateLimiter.count(); + + String combineKey = getCombineKey(rateLimiter, point); + List keys = Collections.singletonList(combineKey); + try + { + Long number = redisTemplate.execute(limitScript, keys, count, time); + if (StringUtils.isNull(number) || number.intValue() > count) + { + throw new ServiceException("访问过于频繁,请稍候再试"); + } + log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey); + } + catch (ServiceException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException("服务器限流异常,请稍候再试"); + } + } + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) + { + StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); + if (rateLimiter.limitType() == LimitType.IP) + { + stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-"); + } + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + stringBuffer.append(targetClass.getName()).append("-").append(method.getName()); + return stringBuffer.toString(); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/ApplicationConfig.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/ApplicationConfig.java new file mode 100644 index 00000000..3157801a --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/ApplicationConfig.java @@ -0,0 +1,30 @@ +package com.fastbee.framework.config; + +import java.util.TimeZone; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * 程序注解配置 + * + * @author ruoyi + */ +@Configuration +// 表示通过aop框架暴露该代理对象,AopContext能够访问 +@EnableAspectJAutoProxy(exposeProxy = true) +// 指定要扫描的Mapper类的包的路径 +@MapperScan("com.fastbee.**.mapper") +public class ApplicationConfig +{ + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() + { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/CaptchaConfig.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/CaptchaConfig.java new file mode 100644 index 00000000..26f4d0f0 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/CaptchaConfig.java @@ -0,0 +1,83 @@ +package com.fastbee.framework.config; + +import java.util.Properties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import static com.google.code.kaptcha.Constants.*; + +/** + * 验证码配置 + * + * @author ruoyi + */ +@Configuration +public class CaptchaConfig +{ + @Bean(name = "captchaProducer") + public DefaultKaptcha getKaptchaBean() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + + @Bean(name = "captchaProducerMath") + public DefaultKaptcha getKaptchaBeanMath() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.fastbee.framework.config.KaptchaTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/DruidConfig.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/DruidConfig.java new file mode 100644 index 00000000..6614ed63 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/DruidConfig.java @@ -0,0 +1,126 @@ +package com.fastbee.framework.config; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.sql.DataSource; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; +import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import com.fastbee.common.enums.DataSourceType; +import com.fastbee.common.utils.spring.SpringUtils; +import com.fastbee.framework.config.properties.DruidProperties; +import com.fastbee.framework.datasource.DynamicDataSource; + +/** + * druid 配置多数据源 + * + * @author ruoyi + */ +@Configuration +public class DruidConfig +{ + @Bean + @ConfigurationProperties("spring.datasource.druid.master") + public DataSource masterDataSource(DruidProperties druidProperties) + { + DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); + return druidProperties.dataSource(dataSource); + } + + @Bean + @ConfigurationProperties("spring.datasource.druid.slave") + @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true") + public DataSource slaveDataSource(DruidProperties druidProperties) + { + DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); + return druidProperties.dataSource(dataSource); + } + + @Bean(name = "dynamicDataSource") + @Primary + public DynamicDataSource dataSource(DataSource masterDataSource) + { + Map targetDataSources = new HashMap<>(); + targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); + setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource"); + return new DynamicDataSource(masterDataSource, targetDataSources); + } + + /** + * 设置数据源 + * + * @param targetDataSources 备选数据源集合 + * @param sourceName 数据源名称 + * @param beanName bean名称 + */ + public void setDataSource(Map targetDataSources, String sourceName, String beanName) + { + try + { + DataSource dataSource = SpringUtils.getBean(beanName); + targetDataSources.put(sourceName, dataSource); + } + catch (Exception e) + { + } + } + + /** + * 去除监控页面底部的广告 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true") + public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) + { + // 获取web监控页面的参数 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + // 提取common.js的配置路径 + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + final String filePath = "support/http/resources/js/common.js"; + // 创建filter进行过滤 + Filter filter = new Filter() + { + @Override + public void init(javax.servlet.FilterConfig filterConfig) throws ServletException + { + } + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + chain.doFilter(request, response); + // 重置缓冲区,响应头不会被重置 + response.resetBuffer(); + // 获取common.js + String text = Utils.readFromResource(filePath); + // 正则替换banner, 除去底部的广告信息 + text = text.replaceAll("
", ""); + text = text.replaceAll("powered.*?shrek.wang", ""); + response.getWriter().write(text); + } + @Override + public void destroy() + { + } + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/FastJson2JsonRedisSerializer.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 00000000..43f1889d --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,48 @@ +package com.fastbee.framework.config; + +import java.nio.charset.Charset; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; + +/** + * Redis使用FastJson序列化 + * + * @author ruoyi + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer +{ + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + private Class clazz; + + public FastJson2JsonRedisSerializer(Class clazz) + { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) + { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length <= 0) + { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + + return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/FilterConfig.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/FilterConfig.java new file mode 100644 index 00000000..8e0e76ef --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/FilterConfig.java @@ -0,0 +1,58 @@ +package com.fastbee.framework.config; + +import java.util.HashMap; +import java.util.Map; +import javax.servlet.DispatcherType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.fastbee.common.filter.RepeatableFilter; +import com.fastbee.common.filter.XssFilter; +import com.fastbee.common.utils.StringUtils; + +/** + * Filter配置 + * + * @author ruoyi + */ +@Configuration +public class FilterConfig +{ + @Value("${xss.excludes}") + private String excludes; + + @Value("${xss.urlPatterns}") + private String urlPatterns; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") + public FilterRegistrationBean xssFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new XssFilter()); + registration.addUrlPatterns(StringUtils.split(urlPatterns, ",")); + registration.setName("xssFilter"); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + Map initParameters = new HashMap(); + initParameters.put("excludes", excludes); + registration.setInitParameters(initParameters); + return registration; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public FilterRegistrationBean someFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new RepeatableFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + return registration; + } + +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/KaptchaTextCreator.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/KaptchaTextCreator.java new file mode 100644 index 00000000..a468e30c --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/KaptchaTextCreator.java @@ -0,0 +1,68 @@ +package com.fastbee.framework.config; + +import java.util.Random; +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +/** + * 验证码文本生成器 + * + * @author ruoyi + */ +public class KaptchaTextCreator extends DefaultTextCreator +{ + private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); + + @Override + public String getText() + { + Integer result = 0; + Random random = new Random(); + int x = random.nextInt(10); + int y = random.nextInt(10); + StringBuilder suChinese = new StringBuilder(); + int randomoperands = random.nextInt(3); + if (randomoperands == 0) + { + result = x * y; + suChinese.append(CNUMBERS[x]); + suChinese.append("*"); + suChinese.append(CNUMBERS[y]); + } + else if (randomoperands == 1) + { + if ((x != 0) && y % x == 0) + { + result = y / x; + suChinese.append(CNUMBERS[y]); + suChinese.append("/"); + suChinese.append(CNUMBERS[x]); + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + } + else + { + if (x >= y) + { + result = x - y; + suChinese.append(CNUMBERS[x]); + suChinese.append("-"); + suChinese.append(CNUMBERS[y]); + } + else + { + result = y - x; + suChinese.append(CNUMBERS[y]); + suChinese.append("-"); + suChinese.append(CNUMBERS[x]); + } + } + suChinese.append("=?@" + result); + return suChinese.toString(); + } +} \ No newline at end of file diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/MyBatisConfig.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/MyBatisConfig.java new file mode 100644 index 00000000..a1c902a6 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/MyBatisConfig.java @@ -0,0 +1,177 @@ +package com.fastbee.framework.config; + +import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import com.fastbee.common.utils.StringUtils; +import org.apache.ibatis.io.VFS; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.env.Environment; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.util.ClassUtils; + +import javax.sql.DataSource; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +/** + * Mybatis支持*匹配扫描包 + * + * @author ruoyi + */ +@Configuration +public class MyBatisConfig +{ + @Autowired + private Environment env; + + static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; + + public static String setTypeAliasesPackage(String typeAliasesPackage) + { + ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver(); + MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver); + List allResult = new ArrayList(); + try + { + for (String aliasesPackage : typeAliasesPackage.split(",")) + { + List result = new ArrayList(); + aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN; + Resource[] resources = resolver.getResources(aliasesPackage); + if (resources != null && resources.length > 0) + { + MetadataReader metadataReader = null; + for (Resource resource : resources) + { + if (resource.isReadable()) + { + metadataReader = metadataReaderFactory.getMetadataReader(resource); + try + { + result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName()); + } + catch (ClassNotFoundException e) + { + e.printStackTrace(); + } + } + } + } + if (result.size() > 0) + { + HashSet hashResult = new HashSet(result); + allResult.addAll(hashResult); + } + } + if (allResult.size() > 0) + { + typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0])); + } + else + { + throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包"); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + return typeAliasesPackage; + } + + public Resource[] resolveMapperLocations(String[] mapperLocations) + { + ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); + List resources = new ArrayList(); + if (mapperLocations != null) + { + for (String mapperLocation : mapperLocations) + { + try + { + Resource[] mappers = resourceResolver.getResources(mapperLocation); + resources.addAll(Arrays.asList(mappers)); + } + catch (IOException e) + { + // ignore + } + } + } + return resources.toArray(new Resource[resources.size()]); + } + + /** + * mybatis 配置 + */ +// @Bean(name = "mysqlSessionFactory") +// @Primary +// public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception +// { +// String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage"); +// String mapperLocations = env.getProperty("mybatis.mapperLocations"); +// String configLocation = env.getProperty("mybatis.configLocation"); +// typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage); +// VFS.addImplClass(SpringBootVFS.class); +// +// final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); +// sessionFactory.setDataSource(dataSource); +// sessionFactory.setTypeAliasesPackage(typeAliasesPackage); +// sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ","))); +// sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); +// return sessionFactory.getObject(); +// } + + /** + * mybatis-plus 配置:把 SqlSessionFactoryBean 换成 MybatisSqlSessionFactoryBean就行 + * @param dataSource 数据源 + * @return + */ + @Bean(name = "mysqlSessionFactory") + @Primary + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception + { + String typeAliasesPackage = env.getProperty("mybatis-plus.typeAliasesPackage"); + String mapperLocations = env.getProperty("mybatis-plus.mapperLocations"); + String configLocation = env.getProperty("mybatis-plus.configLocation"); + typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage); + VFS.addImplClass(SpringBootVFS.class); + + final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + sessionFactory.setTypeAliasesPackage(typeAliasesPackage); + sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ","))); + sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); + return sessionFactory.getObject(); + } + + @Bean(name = "mysqlTransactionManager") + @Primary + public DataSourceTransactionManager mysqlTransactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + @Bean(name = "mysqlSqlSessionTemplate") + @Primary + public SqlSessionTemplate mysqlSqlSessionTemplate(@Qualifier("mysqlSessionFactory") SqlSessionFactory sqlSessionFactory) { + return new SqlSessionTemplate(sqlSessionFactory); + } + +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/RedisConfig.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/RedisConfig.java new file mode 100644 index 00000000..909d32d9 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/RedisConfig.java @@ -0,0 +1,69 @@ +package com.fastbee.framework.config; + +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @author ruoyi + */ +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport +{ + @Bean + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) + { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } + + @Bean + public DefaultRedisScript limitScript() + { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(limitScriptText()); + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 限流脚本 + */ + private String limitScriptText() + { + return "local key = KEYS[1]\n" + + "local count = tonumber(ARGV[1])\n" + + "local time = tonumber(ARGV[2])\n" + + "local current = redis.call('get', key);\n" + + "if current and tonumber(current) > count then\n" + + " return tonumber(current);\n" + + "end\n" + + "current = redis.call('incr', key)\n" + + "if tonumber(current) == 1 then\n" + + " redis.call('expire', key, time)\n" + + "end\n" + + "return tonumber(current);"; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/ResourcesConfig.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/ResourcesConfig.java new file mode 100644 index 00000000..76e98745 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/ResourcesConfig.java @@ -0,0 +1,73 @@ +package com.fastbee.framework.config; + +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import com.fastbee.common.config.RuoYiConfig; +import com.fastbee.common.constant.Constants; +import com.fastbee.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 通用配置 + * + * @author ruoyi + */ +@Configuration +public class ResourcesConfig implements WebMvcConfigurer +{ + @Autowired + private RepeatSubmitInterceptor repeatSubmitInterceptor; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) + { + /** 本地文件上传路径 */ + registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**") + .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/"); + + /** swagger配置 */ + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") + .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());; + } + + /** + * 自定义拦截规则 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) + { + registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); + } + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter() + { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // 设置访问源地址 + config.addAllowedOriginPattern("*"); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 有效期 1800秒 + config.setMaxAge(1800L); + // 添加映射路径,拦截一切请求 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + // 返回新的CorsFilter + return new CorsFilter(source); + } +} \ No newline at end of file diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/SecurityConfig.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/SecurityConfig.java new file mode 100644 index 00000000..8281994c --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/SecurityConfig.java @@ -0,0 +1,152 @@ +package com.fastbee.framework.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.filter.CorsFilter; +import com.fastbee.framework.config.properties.PermitAllUrlProperties; +import com.fastbee.framework.security.filter.JwtAuthenticationTokenFilter; +import com.fastbee.framework.security.handle.AuthenticationEntryPointImpl; +import com.fastbee.framework.security.handle.LogoutSuccessHandlerImpl; + +/** + * spring security配置 + * + * @author ruoyi + */ +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter +{ + /** + * 自定义用户认证逻辑 + */ + @Autowired + private UserDetailsService userDetailsService; + + /** + * 认证失败处理类 + */ + @Autowired + private AuthenticationEntryPointImpl unauthorizedHandler; + + /** + * 退出处理类 + */ + @Autowired + private LogoutSuccessHandlerImpl logoutSuccessHandler; + + /** + * token认证过滤器 + */ + @Autowired + private JwtAuthenticationTokenFilter authenticationTokenFilter; + + /** + * 跨域过滤器 + */ + @Autowired + private CorsFilter corsFilter; + + /** + * 允许匿名访问的地址 + */ + @Autowired + private PermitAllUrlProperties permitAllUrl; + + /** + * 解决 无法直接注入 AuthenticationManager + * + * @return + * @throws Exception + */ + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception + { + return super.authenticationManagerBean(); + } + + /** + * anyRequest | 匹配所有请求路径 + * access | SpringEl表达式结果为true时可以访问 + * anonymous | 匿名可以访问 + * denyAll | 用户不能访问 + * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) + * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 + * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 + * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 + * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 + * hasRole | 如果有参数,参数表示角色,则其角色可以访问 + * permitAll | 用户可以任意访问 + * rememberMe | 允许通过remember-me登录的用户访问 + * authenticated | 用户登录后可访问 + */ + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception + { + // 注解标记允许匿名访问的url + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests(); + permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll()); + + httpSecurity + // CSRF禁用,因为不使用session + .csrf().disable() + // 禁用HTTP响应标头 + .headers().cacheControl().disable().and() + // 认证失败处理类 + .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() + // 基于token,所以不需要session + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + // 过滤请求 + .authorizeRequests() + // 对于登录login 注册register 验证码captchaImage 允许匿名访问 + .antMatchers("/login", "/register", "/captchaImage","/iot/tool/register","/iot/tool/ntp","/iot/tool/download", + "/iot/tool/mqtt/auth","/iot/tool/mqtt/authv5","/iot/tool/mqtt/webhook","/iot/tool/mqtt/webhookv5","/auth/**/**", + "/wechat/mobileLogin", "/wechat/miniLogin", "/wechat/wxBind/callback").permitAll() + .antMatchers("/zlmhook/**").permitAll() + .antMatchers("/goview/sys/login","/goview/project/getData").permitAll() + // 静态资源,可匿名访问 + .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() + .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() + // 除上面外的所有请求全部需要鉴权认证 + .anyRequest().authenticated() + .and() + .headers().frameOptions().disable(); + // 添加Logout filter + httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); + // 添加JWT filter + httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); + // 添加CORS filter + httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); + httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); + } + + /** + * 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() + { + return new BCryptPasswordEncoder(); + } + + /** + * 身份认证接口 + */ + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception + { + auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/ServerConfig.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/ServerConfig.java new file mode 100644 index 00000000..a07d6e0a --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/ServerConfig.java @@ -0,0 +1,32 @@ +package com.fastbee.framework.config; + +import javax.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import com.fastbee.common.utils.ServletUtils; + +/** + * 服务相关配置 + * + * @author ruoyi + */ +@Component +public class ServerConfig +{ + /** + * 获取完整的请求路径,包括:域名,端口,上下文访问路径 + * + * @return 服务地址 + */ + public String getUrl() + { + HttpServletRequest request = ServletUtils.getRequest(); + return getDomain(request); + } + + public static String getDomain(HttpServletRequest request) + { + StringBuffer url = request.getRequestURL(); + String contextPath = request.getServletContext().getContextPath(); + return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/ThreadPoolConfig.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/ThreadPoolConfig.java new file mode 100644 index 00000000..8560f0f6 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/ThreadPoolConfig.java @@ -0,0 +1,63 @@ +package com.fastbee.framework.config; + +import com.fastbee.common.utils.Threads; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 线程池配置 + * + * @author ruoyi + **/ +@Configuration +public class ThreadPoolConfig +{ + // 核心线程池大小 + private int corePoolSize = 50; + + // 最大可创建的线程数 + private int maxPoolSize = 200; + + // 队列最大长度 + private int queueCapacity = 1000; + + // 线程池维护线程所允许的空闲时间 + private int keepAliveSeconds = 300; + + @Bean(name = "threadPoolTaskExecutor") + public ThreadPoolTaskExecutor threadPoolTaskExecutor() + { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setMaxPoolSize(maxPoolSize); + executor.setCorePoolSize(corePoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setKeepAliveSeconds(keepAliveSeconds); + // 线程池对拒绝任务(无线程可用)的处理策略 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + return executor; + } + + /** + * 执行周期性或定时任务 + */ + @Bean(name = "scheduledExecutorService") + protected ScheduledExecutorService scheduledExecutorService() + { + return new ScheduledThreadPoolExecutor(corePoolSize, + new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), + new ThreadPoolExecutor.CallerRunsPolicy()) + { + @Override + protected void afterExecute(Runnable r, Throwable t) + { + super.afterExecute(r, t); + Threads.printException(r, t); + } + }; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/properties/DruidProperties.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/properties/DruidProperties.java new file mode 100644 index 00000000..057403ec --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/properties/DruidProperties.java @@ -0,0 +1,77 @@ +package com.fastbee.framework.config.properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import com.alibaba.druid.pool.DruidDataSource; + +/** + * druid 配置属性 + * + * @author ruoyi + */ +@Configuration +public class DruidProperties +{ + @Value("${spring.datasource.druid.initialSize}") + private int initialSize; + + @Value("${spring.datasource.druid.minIdle}") + private int minIdle; + + @Value("${spring.datasource.druid.maxActive}") + private int maxActive; + + @Value("${spring.datasource.druid.maxWait}") + private int maxWait; + + @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") + private int timeBetweenEvictionRunsMillis; + + @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") + private int minEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") + private int maxEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.validationQuery}") + private String validationQuery; + + @Value("${spring.datasource.druid.testWhileIdle}") + private boolean testWhileIdle; + + @Value("${spring.datasource.druid.testOnBorrow}") + private boolean testOnBorrow; + + @Value("${spring.datasource.druid.testOnReturn}") + private boolean testOnReturn; + + public DruidDataSource dataSource(DruidDataSource datasource) + { + /** 配置初始化大小、最小、最大 */ + datasource.setInitialSize(initialSize); + datasource.setMaxActive(maxActive); + datasource.setMinIdle(minIdle); + + /** 配置获取连接等待超时的时间 */ + datasource.setMaxWait(maxWait); + + /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ + datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + + /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ + datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); + + /** + * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 + */ + datasource.setValidationQuery(validationQuery); + /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */ + datasource.setTestWhileIdle(testWhileIdle); + /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnBorrow(testOnBorrow); + /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnReturn(testOnReturn); + return datasource; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/properties/PermitAllUrlProperties.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/properties/PermitAllUrlProperties.java new file mode 100644 index 00000000..71baae88 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/config/properties/PermitAllUrlProperties.java @@ -0,0 +1,72 @@ +package com.fastbee.framework.config.properties; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; +import org.apache.commons.lang3.RegExUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import com.fastbee.common.annotation.Anonymous; + +/** + * 设置Anonymous注解允许匿名访问的url + * + * @author ruoyi + */ +@Configuration +public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware +{ + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); + + private ApplicationContext applicationContext; + + private List urls = new ArrayList<>(); + + public String ASTERISK = "*"; + + @Override + public void afterPropertiesSet() + { + RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); + Map map = mapping.getHandlerMethods(); + + map.keySet().forEach(info -> { + HandlerMethod handlerMethod = map.get(info); + + // 获取方法上边的注解 替代path variable 为 * + Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class); + Optional.ofNullable(method).ifPresent(anonymous -> info.getPatternsCondition().getPatterns() + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + + // 获取类上边的注解, 替代path variable 为 * + Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); + Optional.ofNullable(controller).ifPresent(anonymous -> info.getPatternsCondition().getPatterns() + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + }); + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException + { + this.applicationContext = context; + } + + public List getUrls() + { + return urls; + } + + public void setUrls(List urls) + { + this.urls = urls; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/datasource/DynamicDataSource.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/datasource/DynamicDataSource.java new file mode 100644 index 00000000..2b8a56f5 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/datasource/DynamicDataSource.java @@ -0,0 +1,26 @@ +package com.fastbee.framework.datasource; + +import java.util.Map; +import javax.sql.DataSource; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * 动态数据源 + * + * @author ruoyi + */ +public class DynamicDataSource extends AbstractRoutingDataSource +{ + public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) + { + super.setDefaultTargetDataSource(defaultTargetDataSource); + super.setTargetDataSources(targetDataSources); + super.afterPropertiesSet(); + } + + @Override + protected Object determineCurrentLookupKey() + { + return DynamicDataSourceContextHolder.getDataSourceType(); + } +} \ No newline at end of file diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/datasource/DynamicDataSourceContextHolder.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/datasource/DynamicDataSourceContextHolder.java new file mode 100644 index 00000000..04e5877f --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/datasource/DynamicDataSourceContextHolder.java @@ -0,0 +1,45 @@ +package com.fastbee.framework.datasource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 数据源切换处理 + * + * @author ruoyi + */ +public class DynamicDataSourceContextHolder +{ + public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); + + /** + * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, + * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 + */ + private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 设置数据源的变量 + */ + public static void setDataSourceType(String dsType) + { + log.info("切换到{}数据源", dsType); + CONTEXT_HOLDER.set(dsType); + } + + /** + * 获得数据源的变量 + */ + public static String getDataSourceType() + { + return CONTEXT_HOLDER.get(); + } + + /** + * 清空数据源变量 + */ + public static void clearDataSourceType() + { + CONTEXT_HOLDER.remove(); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/RepeatSubmitInterceptor.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/RepeatSubmitInterceptor.java new file mode 100644 index 00000000..7ce4bc2b --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/RepeatSubmitInterceptor.java @@ -0,0 +1,55 @@ +package com.fastbee.framework.interceptor; + +import java.lang.reflect.Method; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import com.alibaba.fastjson2.JSON; +import com.fastbee.common.annotation.RepeatSubmit; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.utils.ServletUtils; + +/** + * 防止重复提交拦截器 + * + * @author ruoyi + */ +@Component +public abstract class RepeatSubmitInterceptor implements HandlerInterceptor +{ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception + { + if (handler instanceof HandlerMethod) + { + HandlerMethod handlerMethod = (HandlerMethod) handler; + Method method = handlerMethod.getMethod(); + RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); + if (annotation != null) + { + if (this.isRepeatSubmit(request, annotation)) + { + AjaxResult ajaxResult = AjaxResult.error(annotation.message()); + ServletUtils.renderString(response, JSON.toJSONString(ajaxResult)); + return false; + } + } + return true; + } + else + { + return true; + } + } + + /** + * 验证是否重复提交由子类实现具体的防重复提交的规则 + * + * @param request + * @return + * @throws Exception + */ + public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/impl/SameUrlDataInterceptor.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/impl/SameUrlDataInterceptor.java new file mode 100644 index 00000000..fabf4f9e --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/impl/SameUrlDataInterceptor.java @@ -0,0 +1,110 @@ +package com.fastbee.framework.interceptor.impl; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.fastbee.common.annotation.RepeatSubmit; +import com.fastbee.common.constant.CacheConstants; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.filter.RepeatedlyRequestWrapper; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.http.HttpHelper; +import com.fastbee.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 判断请求url和数据是否和上一次相同, + * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 + * + * @author ruoyi + */ +@Component +public class SameUrlDataInterceptor extends RepeatSubmitInterceptor +{ + public final String REPEAT_PARAMS = "repeatParams"; + + public final String REPEAT_TIME = "repeatTime"; + + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + @Autowired + private RedisCache redisCache; + + @SuppressWarnings("unchecked") + @Override + public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) + { + String nowParams = ""; + if (request instanceof RepeatedlyRequestWrapper) + { + RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; + nowParams = HttpHelper.getBodyString(repeatedlyRequest); + } + + // body参数为空,获取Parameter的数据 + if (StringUtils.isEmpty(nowParams)) + { + nowParams = JSON.toJSONString(request.getParameterMap()); + } + Map nowDataMap = new HashMap(); + nowDataMap.put(REPEAT_PARAMS, nowParams); + nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); + + // 请求地址(作为存放cache的key值) + String url = request.getRequestURI(); + + // 唯一值(没有消息头则使用请求地址) + String submitKey = StringUtils.trimToEmpty(request.getHeader(header)); + + // 唯一标识(指定key + url + 消息头) + String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey; + + Object sessionObj = redisCache.getCacheObject(cacheRepeatKey); + if (sessionObj != null) + { + Map sessionMap = (Map) sessionObj; + if (sessionMap.containsKey(url)) + { + Map preDataMap = (Map) sessionMap.get(url); + if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) + { + return true; + } + } + } + Map cacheMap = new HashMap(); + cacheMap.put(url, nowDataMap); + redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS); + return false; + } + + /** + * 判断参数是否相同 + */ + private boolean compareParams(Map nowMap, Map preMap) + { + String nowParams = (String) nowMap.get(REPEAT_PARAMS); + String preParams = (String) preMap.get(REPEAT_PARAMS); + return nowParams.equals(preParams); + } + + /** + * 判断两次间隔时间 + */ + private boolean compareTime(Map nowMap, Map preMap, int interval) + { + long time1 = (Long) nowMap.get(REPEAT_TIME); + long time2 = (Long) preMap.get(REPEAT_TIME); + if ((time1 - time2) < interval) + { + return true; + } + return false; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/manager/AsyncManager.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/manager/AsyncManager.java new file mode 100644 index 00000000..12d73e51 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/manager/AsyncManager.java @@ -0,0 +1,55 @@ +package com.fastbee.framework.manager; + +import java.util.TimerTask; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import com.fastbee.common.utils.Threads; +import com.fastbee.common.utils.spring.SpringUtils; + +/** + * 异步任务管理器 + * + * @author ruoyi + */ +public class AsyncManager +{ + /** + * 操作延迟10毫秒 + */ + private final int OPERATE_DELAY_TIME = 10; + + /** + * 异步操作任务调度线程池 + */ + private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); + + /** + * 单例模式 + */ + private AsyncManager(){} + + private static AsyncManager me = new AsyncManager(); + + public static AsyncManager me() + { + return me; + } + + /** + * 执行任务 + * + * @param task 任务 + */ + public void execute(TimerTask task) + { + executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); + } + + /** + * 停止任务线程池 + */ + public void shutdown() + { + Threads.shutdownAndAwaitTermination(executor); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/manager/ShutdownManager.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/manager/ShutdownManager.java new file mode 100644 index 00000000..a4e6efdd --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/manager/ShutdownManager.java @@ -0,0 +1,39 @@ +package com.fastbee.framework.manager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import javax.annotation.PreDestroy; + +/** + * 确保应用退出时能关闭后台线程 + * + * @author ruoyi + */ +@Component +public class ShutdownManager +{ + private static final Logger logger = LoggerFactory.getLogger("sys-user"); + + @PreDestroy + public void destroy() + { + shutdownAsyncManager(); + } + + /** + * 停止异步执行任务 + */ + private void shutdownAsyncManager() + { + try + { + logger.info("====关闭后台任务任务线程池===="); + AsyncManager.me().shutdown(); + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + } + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/manager/factory/AsyncFactory.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/manager/factory/AsyncFactory.java new file mode 100644 index 00000000..34bd8098 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/manager/factory/AsyncFactory.java @@ -0,0 +1,102 @@ +package com.fastbee.framework.manager.factory; + +import java.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.utils.LogUtils; +import com.fastbee.common.utils.ServletUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.ip.AddressUtils; +import com.fastbee.common.utils.ip.IpUtils; +import com.fastbee.common.utils.spring.SpringUtils; +import com.fastbee.system.domain.SysLogininfor; +import com.fastbee.system.domain.SysOperLog; +import com.fastbee.system.service.ISysLogininforService; +import com.fastbee.system.service.ISysOperLogService; +import eu.bitwalker.useragentutils.UserAgent; + +/** + * 异步工厂(产生任务用) + * + * @author ruoyi + */ +public class AsyncFactory +{ + private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息 + * @param args 列表 + * @return 任务task + */ + public static TimerTask recordLogininfor(final String username, final String status, final String message, + final Object... args) + { + final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + final String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); + return new TimerTask() + { + @Override + public void run() + { + String address = AddressUtils.getRealAddressByIP(ip); + StringBuilder s = new StringBuilder(); + s.append(LogUtils.getBlock(ip)); + s.append(address); + s.append(LogUtils.getBlock(username)); + s.append(LogUtils.getBlock(status)); + s.append(LogUtils.getBlock(message)); + // 打印信息到日志 + sys_user_logger.info(s.toString(), args); + // 获取客户端操作系统 + String os = userAgent.getOperatingSystem().getName(); + // 获取客户端浏览器 + String browser = userAgent.getBrowser().getName(); + // 封装对象 + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setUserName(username); + logininfor.setIpaddr(ip); + logininfor.setLoginLocation(address); + logininfor.setBrowser(browser); + logininfor.setOs(os); + logininfor.setMsg(message); + // 日志状态 + if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) + { + logininfor.setStatus(Constants.SUCCESS); + } + else if (Constants.LOGIN_FAIL.equals(status)) + { + logininfor.setStatus(Constants.FAIL); + } + // 插入数据 + SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor); + } + }; + } + + /** + * 操作日志记录 + * + * @param operLog 操作日志信息 + * @return 任务task + */ + public static TimerTask recordOper(final SysOperLog operLog) + { + return new TimerTask() + { + @Override + public void run() + { + // 远程查询操作地点 + operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp())); + SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog); + } + }; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/mybatis/LambdaQueryWrapperX.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/mybatis/LambdaQueryWrapperX.java new file mode 100644 index 00000000..bfa330f7 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/mybatis/LambdaQueryWrapperX.java @@ -0,0 +1,136 @@ +package com.fastbee.framework.mybatis; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.util.StringUtils; + +import java.util.Collection; + +/** + * 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能: + *

+ * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。 + * + * @param 数据类型 + */ +public class LambdaQueryWrapperX extends LambdaQueryWrapper { + + public LambdaQueryWrapperX likeIfPresent(SFunction column, String val) { + if (StringUtils.hasText(val)) { + return (LambdaQueryWrapperX) super.like(column, val); + } + return this; + } + + public LambdaQueryWrapperX inIfPresent(SFunction column, Collection values) { + if (!CollectionUtils.isEmpty(values)) { + return (LambdaQueryWrapperX) super.in(column, values); + } + return this; + } + + public LambdaQueryWrapperX inIfPresent(SFunction column, Object... values) { + if (!ArrayUtil.isEmpty(values)) { + return (LambdaQueryWrapperX) super.in(column, values); + } + return this; + } + + public LambdaQueryWrapperX eqIfPresent(SFunction column, Object val) { + if (ObjectUtil.isNotEmpty(val)) { + return (LambdaQueryWrapperX) super.eq(column, val); + } + return this; + } + + public LambdaQueryWrapperX neIfPresent(SFunction column, Object val) { + if (ObjectUtil.isNotEmpty(val)) { + return (LambdaQueryWrapperX) super.ne(column, val); + } + return this; + } + + public LambdaQueryWrapperX gtIfPresent(SFunction column, Object val) { + if (val != null) { + return (LambdaQueryWrapperX) super.gt(column, val); + } + return this; + } + + public LambdaQueryWrapperX geIfPresent(SFunction column, Object val) { + if (val != null) { + return (LambdaQueryWrapperX) super.ge(column, val); + } + return this; + } + + public LambdaQueryWrapperX ltIfPresent(SFunction column, Object val) { + if (val != null) { + return (LambdaQueryWrapperX) super.lt(column, val); + } + return this; + } + + public LambdaQueryWrapperX leIfPresent(SFunction column, Object val) { + if (val != null) { + return (LambdaQueryWrapperX) super.le(column, val); + } + return this; + } + + public LambdaQueryWrapperX betweenIfPresent(SFunction column, Object val1, Object val2) { + if (val1 != null && val2 != null) { + return (LambdaQueryWrapperX) super.between(column, val1, val2); + } + if (val1 != null) { + return (LambdaQueryWrapperX) ge(column, val1); + } + if (val2 != null) { + return (LambdaQueryWrapperX) le(column, val2); + } + return this; + } + + public LambdaQueryWrapperX betweenIfPresent(SFunction column, Object[] values) { + Object val1 = ArrayUtils.get(values, 0); + Object val2 = ArrayUtils.get(values, 1); + return betweenIfPresent(column, val1, val2); + } + + // ========== 重写父类方法,方便链式调用 ========== + + @Override + public LambdaQueryWrapperX eq(boolean condition, SFunction column, Object val) { + super.eq(condition, column, val); + return this; + } + + @Override + public LambdaQueryWrapperX eq(SFunction column, Object val) { + super.eq(column, val); + return this; + } + + @Override + public LambdaQueryWrapperX orderByDesc(SFunction column) { + super.orderByDesc(true, column); + return this; + } + + @Override + public LambdaQueryWrapperX last(String lastSql) { + super.last(lastSql); + return this; + } + + @Override + public LambdaQueryWrapperX in(SFunction column, Collection coll) { + super.in(column, coll); + return this; + } + +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/mybatis/QueryWrapperX.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/mybatis/QueryWrapperX.java new file mode 100644 index 00000000..44ba9f7d --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/mybatis/QueryWrapperX.java @@ -0,0 +1,140 @@ +package com.fastbee.framework.mybatis; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.ArrayUtils; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.Collection; + +/** + * 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能: + * + * 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。 + * + * @param 数据类型 + */ +public class QueryWrapperX extends QueryWrapper { + + public QueryWrapperX likeIfPresent(String column, String val) { + if (StringUtils.hasText(val)) { + return (QueryWrapperX) super.like(column, val); + } + return this; + } + + public QueryWrapperX inIfPresent(String column, Collection values) { + if (!CollectionUtils.isEmpty(values)) { + return (QueryWrapperX) super.in(column, values); + } + return this; + } + + public QueryWrapperX inIfPresent(String column, Object... values) { + if (!ArrayUtils.isEmpty(values)) { + return (QueryWrapperX) super.in(column, values); + } + return this; + } + + public QueryWrapperX eqIfPresent(String column, Object val) { + if (val != null) { + return (QueryWrapperX) super.eq(column, val); + } + return this; + } + + public QueryWrapperX neIfPresent(String column, Object val) { + if (val != null) { + return (QueryWrapperX) super.ne(column, val); + } + return this; + } + + public QueryWrapperX gtIfPresent(String column, Object val) { + if (val != null) { + return (QueryWrapperX) super.gt(column, val); + } + return this; + } + + public QueryWrapperX geIfPresent(String column, Object val) { + if (val != null) { + return (QueryWrapperX) super.ge(column, val); + } + return this; + } + + public QueryWrapperX ltIfPresent(String column, Object val) { + if (val != null) { + return (QueryWrapperX) super.lt(column, val); + } + return this; + } + + public QueryWrapperX leIfPresent(String column, Object val) { + if (val != null) { + return (QueryWrapperX) super.le(column, val); + } + return this; + } + + public QueryWrapperX betweenIfPresent(String column, Object val1, Object val2) { + if (val1 != null && val2 != null) { + return (QueryWrapperX) super.between(column, val1, val2); + } + if (val1 != null) { + return (QueryWrapperX) ge(column, val1); + } + if (val2 != null) { + return (QueryWrapperX) le(column, val2); + } + return this; + } + + public QueryWrapperX betweenIfPresent(String column, Object[] values) { + if (values!= null && values.length != 0 && values[0] != null && values[1] != null) { + return (QueryWrapperX) super.between(column, values[0], values[1]); + } + if (values!= null && values.length != 0 && values[0] != null) { + return (QueryWrapperX) ge(column, values[0]); + } + if (values!= null && values.length != 0 && values[1] != null) { + return (QueryWrapperX) le(column, values[1]); + } + return this; + } + + // ========== 重写父类方法,方便链式调用 ========== + + @Override + public QueryWrapperX eq(boolean condition, String column, Object val) { + super.eq(condition, column, val); + return this; + } + + @Override + public QueryWrapperX eq(String column, Object val) { + super.eq(column, val); + return this; + } + + @Override + public QueryWrapperX orderByDesc(String column) { + super.orderByDesc(true, column); + return this; + } + + @Override + public QueryWrapperX last(String lastSql) { + super.last(lastSql); + return this; + } + + @Override + public QueryWrapperX in(String column, Collection coll) { + super.in(column, coll); + return this; + } + +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/mybatis/mapper/BaseMapperX.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/mybatis/mapper/BaseMapperX.java new file mode 100644 index 00000000..e6252387 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/mybatis/mapper/BaseMapperX.java @@ -0,0 +1,112 @@ +package com.fastbee.framework.mybatis.mapper; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.baomidou.mybatisplus.extension.toolkit.Db; +import com.fastbee.common.core.domain.PageParam; +import com.fastbee.common.core.domain.PageResult; +import com.fastbee.framework.mybatis.utils.MyBatisUtils; +import org.apache.ibatis.annotations.Param; + +import java.util.Collection; +import java.util.List; + +/** + * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力 + *

+ * 为什么继承 MPJBaseMapper 接口?支持 MyBatis Plus 多表 Join 的能力。 + */ +public interface BaseMapperX extends BaseMapper { + + default PageResult selectPage(PageParam pageParam, @Param("ew") Wrapper queryWrapper) { + // MyBatis Plus 查询 + IPage mpPage = MyBatisUtils.buildPage(pageParam); + selectPage(mpPage, queryWrapper); + // 转换返回 + return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); + } + + default T selectOne(String field, Object value) { + return selectOne(new QueryWrapper().eq(field, value)); + } + + default T selectOne(SFunction field, Object value) { + return selectOne(new LambdaQueryWrapper().eq(field, value)); + } + + default T selectOne(String field1, Object value1, String field2, Object value2) { + return selectOne(new QueryWrapper().eq(field1, value1).eq(field2, value2)); + } + + default T selectOne(SFunction field1, Object value1, SFunction field2, Object value2) { + return selectOne(new LambdaQueryWrapper().eq(field1, value1).eq(field2, value2)); + } + + default Long selectCount() { + return selectCount(new QueryWrapper()); + } + + default Long selectCount(String field, Object value) { + return selectCount(new QueryWrapper().eq(field, value)); + } + + default Long selectCount(SFunction field, Object value) { + return selectCount(new LambdaQueryWrapper().eq(field, value)); + } + + default List selectList() { + return selectList(new QueryWrapper<>()); + } + + default List selectList(String field, Object value) { + return selectList(new QueryWrapper().eq(field, value)); + } + + default List selectList(SFunction field, Object value) { + return selectList(new LambdaQueryWrapper().eq(field, value)); + } + + default List selectList(String field, Collection values) { + return selectList(new QueryWrapper().in(field, values)); + } + + default List selectList(SFunction field, Collection values) { + return selectList(new LambdaQueryWrapper().in(field, values)); + } + + default List selectList(SFunction leField, SFunction geField, Object value) { + return selectList(new LambdaQueryWrapper().le(leField, value).ge(geField, value)); + } + + /** + * 批量插入,适合大量数据插入 + * + * @param entities 实体们 + */ + default void insertBatch(Collection entities) { + Db.saveBatch(entities); + } + + /** + * 批量插入,适合大量数据插入 + * + * @param entities 实体们 + * @param size 插入数量 Db.saveBatch 默认为 1000 + */ + default void insertBatch(Collection entities, int size) { + Db.saveBatch(entities, size); + } + + default void updateBatch(T update) { + update(update, new QueryWrapper<>()); + } + + default void updateBatch(Collection entities, int size) { + Db.updateBatchById(entities, size); + } + +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/mybatis/utils/MyBatisUtils.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/mybatis/utils/MyBatisUtils.java new file mode 100644 index 00000000..9493e76a --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/mybatis/utils/MyBatisUtils.java @@ -0,0 +1,88 @@ +package com.fastbee.framework.mybatis.utils; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.fastbee.common.core.domain.PageParam; +import com.fastbee.common.core.domain.SortingField; +import net.sf.jsqlparser.expression.Alias; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * MyBatis 工具类 + */ +public class MyBatisUtils { + + private static final String MYSQL_ESCAPE_CHARACTER = "`"; + + public static Page buildPage(PageParam pageParam) { + return buildPage(pageParam, null); + } + + public static Page buildPage(PageParam pageParam, Collection sortingFields) { + // 页码 + 数量 + Page page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize()); + // 排序字段 + if (!CollectionUtil.isEmpty(sortingFields)) { + page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder()) ? + OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField())) + .collect(Collectors.toList())); + } + return page; + } + + /** + * 将拦截器添加到链中 + * 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置 + * + * @param interceptor 链 + * @param inner 拦截器 + * @param index 位置 + */ + public static void addInterceptor(MybatisPlusInterceptor interceptor, InnerInterceptor inner, int index) { + List inners = new ArrayList<>(interceptor.getInterceptors()); + inners.add(index, inner); + interceptor.setInterceptors(inners); + } + + /** + * 获得 Table 对应的表名 + * + * 兼容 MySQL 转义表名 `t_xxx` + * + * @param table 表 + * @return 去除转移字符后的表名 + */ + public static String getTableName(Table table) { + String tableName = table.getName(); + if (tableName.startsWith(MYSQL_ESCAPE_CHARACTER) && tableName.endsWith(MYSQL_ESCAPE_CHARACTER)) { + tableName = tableName.substring(1, tableName.length() - 1); + } + return tableName; + } + + /** + * 构建 Column 对象 + * + * @param tableName 表名 + * @param tableAlias 别名 + * @param column 字段名 + * @return Column 对象 + */ + public static Column buildColumn(String tableName, Alias tableAlias, String column) { + if (tableAlias != null) { + tableName = tableAlias.getName(); + } + return new Column(tableName + StringPool.DOT + column); + } + +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/context/AuthenticationContextHolder.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/context/AuthenticationContextHolder.java new file mode 100644 index 00000000..1fb2ed6b --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/context/AuthenticationContextHolder.java @@ -0,0 +1,28 @@ +package com.fastbee.framework.security.context; + +import org.springframework.security.core.Authentication; + +/** + * 身份验证信息 + * + * @author ruoyi + */ +public class AuthenticationContextHolder +{ + private static final ThreadLocal contextHolder = new ThreadLocal<>(); + + public static Authentication getContext() + { + return contextHolder.get(); + } + + public static void setContext(Authentication context) + { + contextHolder.set(context); + } + + public static void clearContext() + { + contextHolder.remove(); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/context/PermissionContextHolder.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/context/PermissionContextHolder.java new file mode 100644 index 00000000..85a95b22 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/context/PermissionContextHolder.java @@ -0,0 +1,27 @@ +package com.fastbee.framework.security.context; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import com.fastbee.common.core.text.Convert; + +/** + * 权限信息 + * + * @author ruoyi + */ +public class PermissionContextHolder +{ + private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT"; + + public static void setContext(String permission) + { + RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission, + RequestAttributes.SCOPE_REQUEST); + } + + public static String getContext() + { + return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES, + RequestAttributes.SCOPE_REQUEST)); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/filter/JwtAuthenticationTokenFilter.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 00000000..6d190782 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,44 @@ +package com.fastbee.framework.security.filter; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.framework.web.service.TokenService; + +/** + * token过滤器 验证token有效性 + * + * @author ruoyi + */ +@Component +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter +{ + @Autowired + private TokenService tokenService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) + { + tokenService.verifyToken(loginUser); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + chain.doFilter(request, response); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/handle/AuthenticationEntryPointImpl.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/handle/AuthenticationEntryPointImpl.java new file mode 100644 index 00000000..de193326 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/handle/AuthenticationEntryPointImpl.java @@ -0,0 +1,40 @@ +package com.fastbee.framework.security.handle; + +import java.io.IOException; +import java.io.Serializable; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.fastbee.common.constant.HttpStatus; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.utils.ServletUtils; +import com.fastbee.common.utils.StringUtils; + +/** + * 认证失败处理类 返回未授权 + * + * @author ruoyi + */ +@Component +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable +{ + private static final long serialVersionUID = -8970718410437077606L; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) + throws IOException + { + int code = HttpStatus.UNAUTHORIZED; + String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg))); + } + + + public static boolean isAjaxRequest(HttpServletRequest request) { + String ajaxFlag = request.getHeader("X-Requested-With"); + return ajaxFlag != null && "XMLHttpRequest".equals(ajaxFlag); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/handle/LogoutSuccessHandlerImpl.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/handle/LogoutSuccessHandlerImpl.java new file mode 100644 index 00000000..65ad50f5 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -0,0 +1,52 @@ +package com.fastbee.framework.security.handle; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import com.alibaba.fastjson2.JSON; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.utils.ServletUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.framework.manager.AsyncManager; +import com.fastbee.framework.manager.factory.AsyncFactory; +import com.fastbee.framework.web.service.TokenService; + +/** + * 自定义退出处理类 返回成功 + * + * @author ruoyi + */ +@Configuration +public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler +{ + @Autowired + private TokenService tokenService; + + /** + * 退出处理 + * + * @return + */ + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser)) + { + String userName = loginUser.getUsername(); + // 删除用户缓存记录 + tokenService.delLoginUser(loginUser.getToken()); + // 记录用户退出日志 + AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功")); + } + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success("退出成功"))); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/Server.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/Server.java new file mode 100644 index 00000000..b9173e00 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/Server.java @@ -0,0 +1,240 @@ +package com.fastbee.framework.web.domain; + +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import com.fastbee.common.utils.Arith; +import com.fastbee.common.utils.ip.IpUtils; +import com.fastbee.framework.web.domain.server.Cpu; +import com.fastbee.framework.web.domain.server.Jvm; +import com.fastbee.framework.web.domain.server.Mem; +import com.fastbee.framework.web.domain.server.Sys; +import com.fastbee.framework.web.domain.server.SysFile; +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.CentralProcessor.TickType; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.software.os.FileSystem; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.Util; + +/** + * 服务器相关信息 + * + * @author ruoyi + */ +public class Server +{ + private static final int OSHI_WAIT_SECOND = 1000; + + /** + * CPU相关信息 + */ + private Cpu cpu = new Cpu(); + + /** + * 內存相关信息 + */ + private Mem mem = new Mem(); + + /** + * JVM相关信息 + */ + private Jvm jvm = new Jvm(); + + /** + * 服务器相关信息 + */ + private Sys sys = new Sys(); + + /** + * 磁盘相关信息 + */ + private List sysFiles = new LinkedList(); + + public Cpu getCpu() + { + return cpu; + } + + public void setCpu(Cpu cpu) + { + this.cpu = cpu; + } + + public Mem getMem() + { + return mem; + } + + public void setMem(Mem mem) + { + this.mem = mem; + } + + public Jvm getJvm() + { + return jvm; + } + + public void setJvm(Jvm jvm) + { + this.jvm = jvm; + } + + public Sys getSys() + { + return sys; + } + + public void setSys(Sys sys) + { + this.sys = sys; + } + + public List getSysFiles() + { + return sysFiles; + } + + public void setSysFiles(List sysFiles) + { + this.sysFiles = sysFiles; + } + + public void copyTo() throws Exception + { + SystemInfo si = new SystemInfo(); + HardwareAbstractionLayer hal = si.getHardware(); + + setCpuInfo(hal.getProcessor()); + + setMemInfo(hal.getMemory()); + + setSysInfo(); + + setJvmInfo(); + + setSysFiles(si.getOperatingSystem()); + } + + /** + * 设置CPU信息 + */ + private void setCpuInfo(CentralProcessor processor) + { + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + Util.sleep(OSHI_WAIT_SECOND); + long[] ticks = processor.getSystemCpuLoadTicks(); + long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; + long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; + long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; + long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; + long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; + long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; + long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; + long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; + long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; + cpu.setCpuNum(processor.getLogicalProcessorCount()); + cpu.setTotal(totalCpu); + cpu.setSys(cSys); + cpu.setUsed(user); + cpu.setWait(iowait); + cpu.setFree(idle); + } + + /** + * 设置内存信息 + */ + private void setMemInfo(GlobalMemory memory) + { + mem.setTotal(memory.getTotal()); + mem.setUsed(memory.getTotal() - memory.getAvailable()); + mem.setFree(memory.getAvailable()); + } + + /** + * 设置服务器信息 + */ + private void setSysInfo() + { + Properties props = System.getProperties(); + sys.setComputerName(IpUtils.getHostName()); + sys.setComputerIp(IpUtils.getHostIp()); + sys.setOsName(props.getProperty("os.name")); + sys.setOsArch(props.getProperty("os.arch")); + sys.setUserDir(props.getProperty("user.dir")); + } + + /** + * 设置Java虚拟机 + */ + private void setJvmInfo() throws UnknownHostException + { + Properties props = System.getProperties(); + jvm.setTotal(Runtime.getRuntime().totalMemory()); + jvm.setMax(Runtime.getRuntime().maxMemory()); + jvm.setFree(Runtime.getRuntime().freeMemory()); + jvm.setVersion(props.getProperty("java.version")); + jvm.setHome(props.getProperty("java.home")); + } + + /** + * 设置磁盘信息 + */ + private void setSysFiles(OperatingSystem os) + { + FileSystem fileSystem = os.getFileSystem(); + List fsArray = fileSystem.getFileStores(); + for (OSFileStore fs : fsArray) + { + long free = fs.getUsableSpace(); + long total = fs.getTotalSpace(); + long used = total - free; + SysFile sysFile = new SysFile(); + sysFile.setDirName(fs.getMount()); + sysFile.setSysTypeName(fs.getType()); + sysFile.setTypeName(fs.getName()); + sysFile.setTotal(convertFileSize(total)); + sysFile.setFree(convertFileSize(free)); + sysFile.setUsed(convertFileSize(used)); + sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100)); + sysFiles.add(sysFile); + } + } + + /** + * 字节转换 + * + * @param size 字节大小 + * @return 转换后值 + */ + public String convertFileSize(long size) + { + long kb = 1024; + long mb = kb * 1024; + long gb = mb * 1024; + if (size >= gb) + { + return String.format("%.1f GB", (float) size / gb); + } + else if (size >= mb) + { + float f = (float) size / mb; + return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); + } + else if (size >= kb) + { + float f = (float) size / kb; + return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); + } + else + { + return String.format("%d B", size); + } + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/Cpu.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/Cpu.java new file mode 100644 index 00000000..705b2825 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/Cpu.java @@ -0,0 +1,101 @@ +package com.fastbee.framework.web.domain.server; + +import com.fastbee.common.utils.Arith; + +/** + * CPU相关信息 + * + * @author ruoyi + */ +public class Cpu +{ + /** + * 核心数 + */ + private int cpuNum; + + /** + * CPU总的使用率 + */ + private double total; + + /** + * CPU系统使用率 + */ + private double sys; + + /** + * CPU用户使用率 + */ + private double used; + + /** + * CPU当前等待率 + */ + private double wait; + + /** + * CPU当前空闲率 + */ + private double free; + + public int getCpuNum() + { + return cpuNum; + } + + public void setCpuNum(int cpuNum) + { + this.cpuNum = cpuNum; + } + + public double getTotal() + { + return Arith.round(Arith.mul(total, 100), 2); + } + + public void setTotal(double total) + { + this.total = total; + } + + public double getSys() + { + return Arith.round(Arith.mul(sys / total, 100), 2); + } + + public void setSys(double sys) + { + this.sys = sys; + } + + public double getUsed() + { + return Arith.round(Arith.mul(used / total, 100), 2); + } + + public void setUsed(double used) + { + this.used = used; + } + + public double getWait() + { + return Arith.round(Arith.mul(wait / total, 100), 2); + } + + public void setWait(double wait) + { + this.wait = wait; + } + + public double getFree() + { + return Arith.round(Arith.mul(free / total, 100), 2); + } + + public void setFree(double free) + { + this.free = free; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/Jvm.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/Jvm.java new file mode 100644 index 00000000..5d45a7b5 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/Jvm.java @@ -0,0 +1,130 @@ +package com.fastbee.framework.web.domain.server; + +import java.lang.management.ManagementFactory; +import com.fastbee.common.utils.Arith; +import com.fastbee.common.utils.DateUtils; + +/** + * JVM相关信息 + * + * @author ruoyi + */ +public class Jvm +{ + /** + * 当前JVM占用的内存总数(M) + */ + private double total; + + /** + * JVM最大可用内存总数(M) + */ + private double max; + + /** + * JVM空闲内存(M) + */ + private double free; + + /** + * JDK版本 + */ + private String version; + + /** + * JDK路径 + */ + private String home; + + public double getTotal() + { + return Arith.div(total, (1024 * 1024), 2); + } + + public void setTotal(double total) + { + this.total = total; + } + + public double getMax() + { + return Arith.div(max, (1024 * 1024), 2); + } + + public void setMax(double max) + { + this.max = max; + } + + public double getFree() + { + return Arith.div(free, (1024 * 1024), 2); + } + + public void setFree(double free) + { + this.free = free; + } + + public double getUsed() + { + return Arith.div(total - free, (1024 * 1024), 2); + } + + public double getUsage() + { + return Arith.mul(Arith.div(total - free, total, 4), 100); + } + + /** + * 获取JDK名称 + */ + public String getName() + { + return ManagementFactory.getRuntimeMXBean().getVmName(); + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getHome() + { + return home; + } + + public void setHome(String home) + { + this.home = home; + } + + /** + * JDK启动时间 + */ + public String getStartTime() + { + return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate()); + } + + /** + * JDK运行时间 + */ + public String getRunTime() + { + return DateUtils.getDatePoor(DateUtils.getNowDate(), DateUtils.getServerStartDate()); + } + + /** + * 运行参数 + */ + public String getInputArgs() + { + return ManagementFactory.getRuntimeMXBean().getInputArguments().toString(); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/Mem.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/Mem.java new file mode 100644 index 00000000..44074dfa --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/Mem.java @@ -0,0 +1,61 @@ +package com.fastbee.framework.web.domain.server; + +import com.fastbee.common.utils.Arith; + +/** + * 內存相关信息 + * + * @author ruoyi + */ +public class Mem +{ + /** + * 内存总量 + */ + private double total; + + /** + * 已用内存 + */ + private double used; + + /** + * 剩余内存 + */ + private double free; + + public double getTotal() + { + return Arith.div(total, (1024 * 1024 * 1024), 2); + } + + public void setTotal(long total) + { + this.total = total; + } + + public double getUsed() + { + return Arith.div(used, (1024 * 1024 * 1024), 2); + } + + public void setUsed(long used) + { + this.used = used; + } + + public double getFree() + { + return Arith.div(free, (1024 * 1024 * 1024), 2); + } + + public void setFree(long free) + { + this.free = free; + } + + public double getUsage() + { + return Arith.mul(Arith.div(used, total, 4), 100); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/Sys.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/Sys.java new file mode 100644 index 00000000..c7b2f257 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/Sys.java @@ -0,0 +1,84 @@ +package com.fastbee.framework.web.domain.server; + +/** + * 系统相关信息 + * + * @author ruoyi + */ +public class Sys +{ + /** + * 服务器名称 + */ + private String computerName; + + /** + * 服务器Ip + */ + private String computerIp; + + /** + * 项目路径 + */ + private String userDir; + + /** + * 操作系统 + */ + private String osName; + + /** + * 系统架构 + */ + private String osArch; + + public String getComputerName() + { + return computerName; + } + + public void setComputerName(String computerName) + { + this.computerName = computerName; + } + + public String getComputerIp() + { + return computerIp; + } + + public void setComputerIp(String computerIp) + { + this.computerIp = computerIp; + } + + public String getUserDir() + { + return userDir; + } + + public void setUserDir(String userDir) + { + this.userDir = userDir; + } + + public String getOsName() + { + return osName; + } + + public void setOsName(String osName) + { + this.osName = osName; + } + + public String getOsArch() + { + return osArch; + } + + public void setOsArch(String osArch) + { + this.osArch = osArch; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/SysFile.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/SysFile.java new file mode 100644 index 00000000..80edd964 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/domain/server/SysFile.java @@ -0,0 +1,114 @@ +package com.fastbee.framework.web.domain.server; + +/** + * 系统文件相关信息 + * + * @author ruoyi + */ +public class SysFile +{ + /** + * 盘符路径 + */ + private String dirName; + + /** + * 盘符类型 + */ + private String sysTypeName; + + /** + * 文件类型 + */ + private String typeName; + + /** + * 总大小 + */ + private String total; + + /** + * 剩余大小 + */ + private String free; + + /** + * 已经使用量 + */ + private String used; + + /** + * 资源的使用率 + */ + private double usage; + + public String getDirName() + { + return dirName; + } + + public void setDirName(String dirName) + { + this.dirName = dirName; + } + + public String getSysTypeName() + { + return sysTypeName; + } + + public void setSysTypeName(String sysTypeName) + { + this.sysTypeName = sysTypeName; + } + + public String getTypeName() + { + return typeName; + } + + public void setTypeName(String typeName) + { + this.typeName = typeName; + } + + public String getTotal() + { + return total; + } + + public void setTotal(String total) + { + this.total = total; + } + + public String getFree() + { + return free; + } + + public void setFree(String free) + { + this.free = free; + } + + public String getUsed() + { + return used; + } + + public void setUsed(String used) + { + this.used = used; + } + + public double getUsage() + { + return usage; + } + + public void setUsage(double usage) + { + this.usage = usage; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/exception/GlobalExceptionHandler.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..a848827f --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/exception/GlobalExceptionHandler.java @@ -0,0 +1,114 @@ +package com.fastbee.framework.web.exception; + +import javax.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import com.fastbee.common.constant.HttpStatus; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.exception.DemoModeException; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.StringUtils; + +/** + * 全局异常处理器 + * + * @author ruoyi + */ +@RestControllerAdvice +public class GlobalExceptionHandler +{ + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 权限校验异常 + */ + @ExceptionHandler(AccessDeniedException.class) + public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权"); + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return AjaxResult.error(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) + { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public AjaxResult handleException(Exception e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public AjaxResult handleBindException(BindException e) + { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) + { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 演示模式异常 + */ + @ExceptionHandler(DemoModeException.class) + public AjaxResult handleDemoModeException(DemoModeException e) + { + return AjaxResult.error("演示模式,不允许操作"); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/PermissionService.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/PermissionService.java new file mode 100644 index 00000000..16061890 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/PermissionService.java @@ -0,0 +1,168 @@ +package com.fastbee.framework.web.service; + +import java.util.Set; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.framework.security.context.PermissionContextHolder; + +/** + * RuoYi首创 自定义权限实现,ss取自SpringSecurity首字母 + * + * @author ruoyi + */ +@Service("ss") +public class PermissionService +{ + /** 所有权限标识 */ + private static final String ALL_PERMISSION = "*:*:*"; + + /** 管理员角色权限标识 */ + private static final String SUPER_ADMIN = "admin"; + + private static final String ROLE_DELIMETER = ","; + + private static final String PERMISSION_DELIMETER = ","; + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermi(String permission) + { + if (StringUtils.isEmpty(permission)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + PermissionContextHolder.setContext(permission); + return hasPermissions(loginUser.getPermissions(), permission); + } + + /** + * 验证用户是否不具备某权限,与 hasPermi逻辑相反 + * + * @param permission 权限字符串 + * @return 用户是否不具备某权限 + */ + public boolean lacksPermi(String permission) + { + return hasPermi(permission) != true; + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + public boolean hasAnyPermi(String permissions) + { + if (StringUtils.isEmpty(permissions)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + PermissionContextHolder.setContext(permissions); + Set authorities = loginUser.getPermissions(); + for (String permission : permissions.split(PERMISSION_DELIMETER)) + { + if (permission != null && hasPermissions(authorities, permission)) + { + return true; + } + } + return false; + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色字符串 + * @return 用户是否具备某角色 + */ + public boolean hasRole(String role) + { + if (StringUtils.isEmpty(role)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) + { + return false; + } + for (SysRole sysRole : loginUser.getUser().getRoles()) + { + String roleKey = sysRole.getRoleKey(); + if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) + { + return true; + } + } + return false; + } + + /** + * 验证用户是否不具备某角色,与 isRole逻辑相反。 + * + * @param role 角色名称 + * @return 用户是否不具备某角色 + */ + public boolean lacksRole(String role) + { + return hasRole(role) != true; + } + + /** + * 验证用户是否具有以下任意一个角色 + * + * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + public boolean hasAnyRoles(String roles) + { + if (StringUtils.isEmpty(roles)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) + { + return false; + } + for (String role : roles.split(ROLE_DELIMETER)) + { + if (hasRole(role)) + { + return true; + } + } + return false; + } + + /** + * 判断是否包含权限 + * + * @param permissions 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + private boolean hasPermissions(Set permissions, String permission) + { + return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/SysLoginService.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/SysLoginService.java new file mode 100644 index 00000000..7a37687d --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/SysLoginService.java @@ -0,0 +1,217 @@ +package com.fastbee.framework.web.service; + +import com.fastbee.common.constant.CacheConstants; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.enums.UserStatus; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.exception.user.CaptchaException; +import com.fastbee.common.exception.user.CaptchaExpireException; +import com.fastbee.common.exception.user.UserPasswordNotMatchException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.MessageUtils; +import com.fastbee.common.utils.ServletUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.ip.IpUtils; +import com.fastbee.framework.manager.AsyncManager; +import com.fastbee.framework.manager.factory.AsyncFactory; +import com.fastbee.framework.security.context.AuthenticationContextHolder; +import com.fastbee.system.service.ISysConfigService; +import com.fastbee.system.service.ISysUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 登录校验方法 + * + * @author ruoyi + */ +@Component +public class SysLoginService +{ + @Autowired + private TokenService tokenService; + + @Resource + private AuthenticationManager authenticationManager; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + @Autowired + private UserDetailsServiceImpl userDetailsServiceImpl; + + @Resource + private SysPasswordService passwordService; + + /** + * 登录验证 + * + * @param username 用户名 + * @param password 密码 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public String login(String username, String password, String code, String uuid) + { + boolean captchaEnabled = configService.selectCaptchaEnabled(); + // 验证码开关 + if (captchaEnabled) + { + validateCaptcha(username, code, uuid); + } + // 用户验证 + Authentication authentication = null; + try + { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); + AuthenticationContextHolder.setContext(authenticationToken); + // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername + authentication = authenticationManager.authenticate(authenticationToken); + } + catch (Exception e) + { + if (e instanceof BadCredentialsException) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); + throw new ServiceException(e.getMessage()); + } + } + finally + { + AuthenticationContextHolder.clearContext(); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + recordLoginInfo(loginUser.getUserId()); + // 生成token + return tokenService.createToken(loginUser); + } + + /** + * 第三方验证后,调用登录方法 + * @param username 用户名 + * @param password 密码 + * @return token + */ + public String socialLogin(String username, String password){ + // 用户验证 + Authentication authentication = null; + try + { + // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername + authentication = authenticationManager + .authenticate(new UsernamePasswordAuthenticationToken(username, password)); + } + catch (Exception e) + { + if (e instanceof BadCredentialsException) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); + throw new ServiceException(e.getMessage()); + } + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + recordLoginInfo(loginUser.getUserId()); + // 生成token + return tokenService.createToken(loginUser); + } + + /** + * 三方跳转登录认证方法 + * @param username 系统用户名 + * @param encodePwd 系统用户密码 + * @return + */ + public String redirectLogin(String username,String encodePwd){ +// UserDetails userDetails=userDetailsServiceImpl.loadUserByUsername(username); + SysUser user = userService.selectUserByUserName(username); + if (StringUtils.isNull(user)) + { + throw new ServiceException("登录用户:" + username + " 不存在"); + } + else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + throw new ServiceException("对不起,您的账号:" + username + " 已被删除"); + } + else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + throw new ServiceException("对不起,您的账号:" + username + " 已停用"); + } + // 重写验证方法 + passwordService.socialValidate(user, encodePwd); + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + UserDetails userDetails = userDetailsServiceImpl.createLoginUser(user); + LoginUser loginUser = (LoginUser) userDetails; + recordLoginInfo(loginUser.getUserId()); + // 生成token + return tokenService.createToken(loginUser); + + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + redisCache.deleteObject(verifyKey); + if (captcha == null) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); + throw new CaptchaException(); + } + } + + /** + * 记录登录信息 + * + * @param userId 用户ID + */ + public void recordLoginInfo(Long userId) + { + SysUser sysUser = new SysUser(); + sysUser.setUserId(userId); + sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest())); + sysUser.setLoginDate(DateUtils.getNowDate()); + userService.updateUserProfile(sysUser); + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/SysPasswordService.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/SysPasswordService.java new file mode 100644 index 00000000..493dd6af --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/SysPasswordService.java @@ -0,0 +1,126 @@ +package com.fastbee.framework.web.service; + +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import com.fastbee.common.constant.CacheConstants; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.exception.user.UserPasswordNotMatchException; +import com.fastbee.common.exception.user.UserPasswordRetryLimitExceedException; +import com.fastbee.common.utils.MessageUtils; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.framework.manager.AsyncManager; +import com.fastbee.framework.manager.factory.AsyncFactory; +import com.fastbee.framework.security.context.AuthenticationContextHolder; + +/** + * 登录密码方法 + * + * @author ruoyi + */ +@Component +public class SysPasswordService +{ + @Autowired + private RedisCache redisCache; + + @Value(value = "${user.password.maxRetryCount}") + private int maxRetryCount; + + @Value(value = "${user.password.lockTime}") + private int lockTime; + + /** + * 登录账户密码错误次数缓存键名 + * + * @param username 用户名 + * @return 缓存键key + */ + private String getCacheKey(String username) + { + return CacheConstants.PWD_ERR_CNT_KEY + username; + } + + public void validate(SysUser user) + { + Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext(); + String username = usernamePasswordAuthenticationToken.getName(); + String password = usernamePasswordAuthenticationToken.getCredentials().toString(); + + Integer retryCount = redisCache.getCacheObject(getCacheKey(username)); + + if (retryCount == null) + { + retryCount = 0; + } + + if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, + MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount, lockTime))); + throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime); + } + + if (!matches(user, password)) + { + retryCount = retryCount + 1; + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, + MessageUtils.message("user.password.retry.limit.count", retryCount))); + redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES); + throw new UserPasswordNotMatchException(); + } + else + { + clearLoginRecordCache(username); + } + } + + public void socialValidate(SysUser user, String encodePwd) + { + String username = user.getUserName(); + String password = user.getPassword(); + + Integer retryCount = redisCache.getCacheObject(getCacheKey(username)); + + if (retryCount == null) + { + retryCount = 0; + } + + if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, + MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount, lockTime))); + throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime); + } + + if(!password.equals(encodePwd)){ + retryCount = retryCount + 1; + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, + MessageUtils.message("user.password.retry.limit.count", retryCount))); + redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES); + throw new UserPasswordNotMatchException(); + } + else + { + clearLoginRecordCache(username); + } + } + + public boolean matches(SysUser user, String rawPassword) + { + return SecurityUtils.matchesPassword(rawPassword, user.getPassword()); + } + + public void clearLoginRecordCache(String loginName) + { + if (redisCache.hasKey(getCacheKey(loginName))) + { + redisCache.deleteObject(getCacheKey(loginName)); + } + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/SysPermissionService.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/SysPermissionService.java new file mode 100644 index 00000000..1010a7bc --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/SysPermissionService.java @@ -0,0 +1,82 @@ +package com.fastbee.framework.web.service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.system.service.ISysMenuService; +import com.fastbee.system.service.ISysRoleService; + +/** + * 用户权限处理 + * + * @author ruoyi + */ +@Component +public class SysPermissionService +{ + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysMenuService menuService; + + /** + * 获取角色数据权限 + * + * @param user 用户信息 + * @return 角色权限信息 + */ + public Set getRolePermission(SysUser user) + { + Set roles = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + roles.add("admin"); + } + else + { + roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId())); + } + return roles; + } + + /** + * 获取菜单数据权限 + * + * @param user 用户信息 + * @return 菜单权限信息 + */ + public Set getMenuPermission(SysUser user) + { + Set perms = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + perms.add("*:*:*"); + } + else + { + List roles = user.getRoles(); + if (!roles.isEmpty() && roles.size() > 1) + { + // 多角色设置permissions属性,以便数据权限匹配权限 + for (SysRole role : roles) + { + Set rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId()); + role.setPermissions(rolePerms); + perms.addAll(rolePerms); + } + } + else + { + perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); + } + } + return perms; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/SysRegisterService.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/SysRegisterService.java new file mode 100644 index 00000000..dcf3255b --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/SysRegisterService.java @@ -0,0 +1,115 @@ +package com.fastbee.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.fastbee.common.constant.CacheConstants; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.constant.UserConstants; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.domain.model.RegisterBody; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.exception.user.CaptchaException; +import com.fastbee.common.exception.user.CaptchaExpireException; +import com.fastbee.common.utils.MessageUtils; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.framework.manager.AsyncManager; +import com.fastbee.framework.manager.factory.AsyncFactory; +import com.fastbee.system.service.ISysConfigService; +import com.fastbee.system.service.ISysUserService; + +/** + * 注册校验方法 + * + * @author ruoyi + */ +@Component +public class SysRegisterService +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + @Autowired + private RedisCache redisCache; + + /** + * 注册 + */ + public String register(RegisterBody registerBody) + { + String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(); + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + + // 验证码开关 + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + validateCaptcha(username, registerBody.getCode(), registerBody.getUuid()); + } + + if (StringUtils.isEmpty(username)) + { + msg = "用户名不能为空"; + } + else if (StringUtils.isEmpty(password)) + { + msg = "用户密码不能为空"; + } + else if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + msg = "账户长度必须在2到20个字符之间"; + } + else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + msg = "密码长度必须在5到20个字符之间"; + } + else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(sysUser))) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + } + else + { + sysUser.setNickName(username); + sysUser.setPassword(SecurityUtils.encryptPassword(password)); + boolean regFlag = userService.registerUser(sysUser); + if (!regFlag) + { + msg = "注册失败,请联系系统管理人员"; + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"))); + } + } + return msg; + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + redisCache.deleteObject(verifyKey); + if (captcha == null) + { + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + throw new CaptchaException(); + } + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/TokenService.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/TokenService.java new file mode 100644 index 00000000..73f8b8f0 --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/TokenService.java @@ -0,0 +1,270 @@ +package com.fastbee.framework.web.service; + +import com.fastbee.common.constant.CacheConstants; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.utils.ServletUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.ip.AddressUtils; +import com.fastbee.common.utils.ip.IpUtils; +import com.fastbee.common.utils.uuid.IdUtils; +import eu.bitwalker.useragentutils.UserAgent; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * token验证处理 + * + * @author ruoyi + */ +@Component +public class TokenService +{ + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + // 令牌秘钥 + @Value("${token.secret}") + private String secret; + + // 令牌有效期(默认30分钟) + @Value("${token.expireTime}") + private int expireTime; + + protected static final long MILLIS_SECOND = 1000; + + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L; + + @Autowired + private RedisCache redisCache; + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(HttpServletRequest request) + { + // 获取请求携带的令牌 + String token = getToken(request); + if (StringUtils.isNotEmpty(token)) + { + try + { + Claims claims = parseToken(token); + // 解析对应的权限以及用户信息 + String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); + String userKey = getTokenKey(uuid); + LoginUser user = redisCache.getCacheObject(userKey); + return user; + } + catch (Exception e) + { + } + } + return null; + } + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUserByToken(String token) { + if (StringUtils.isNotEmpty(token)) { + try { + Claims claims = parseToken(token); + // 解析对应的权限以及用户信息 + String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); + String userKey = getTokenKey(uuid); + LoginUser user = redisCache.getCacheObject(userKey); + return user; + } catch (Exception e) { + } + } + return null; + } + + /** + * 根据用户id获取用户身份信息 + * 由于多端登录,根据token获取的用户信息不一样,所以增加一个根据用户id获取用户信息的缓存key,以后多端需要获取用户最新信息就用这个方法吧 + * @return 用户信息 + */ + public LoginUser getLoginUserByUserId(Long userId) { + if (userId != null) { + try { + String userKey = getUserIdKey(userId); + return redisCache.getCacheObject(userKey); + } catch (Exception e) { + } + } + return null; + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser(LoginUser loginUser) + { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) + { + refreshToken(loginUser); + } + } + + /** + * 删除用户身份信息 + */ + public void delLoginUser(String token) + { + if (StringUtils.isNotEmpty(token)) + { + String userKey = getTokenKey(token); + redisCache.deleteObject(userKey); + } + } + + /** + * 创建令牌 + * + * @param loginUser 用户信息 + * @return 令牌 + */ + public String createToken(LoginUser loginUser) + { + String token = IdUtils.fastUUID(); + loginUser.setToken(token); + setUserAgent(loginUser); + refreshToken(loginUser); + + Map claims = new HashMap<>(); + claims.put(Constants.LOGIN_USER_KEY, token); + return createToken(claims); + } + + /** + * 验证令牌有效期,相差不足20分钟,自动刷新缓存 + * + * @param loginUser + * @return 令牌 + */ + public void verifyToken(LoginUser loginUser) + { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) + { + refreshToken(loginUser); + } + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken(LoginUser loginUser) + { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + String userKey = getTokenKey(loginUser.getToken()); + redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + // 使用token作为用户信息缓存key,多端不能同步最新信息,需要重新登录,因此添加一个使用用户id作为缓存key + String userIdKey = getUserIdKey(loginUser.getUserId()); + redisCache.setCacheObject(userIdKey, loginUser, expireTime, TimeUnit.MINUTES); + } + + /** + * 设置用户代理信息 + * + * @param loginUser 登录信息 + */ + public void setUserAgent(LoginUser loginUser) + { + UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); + loginUser.setIpaddr(ip); + loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); + loginUser.setBrowser(userAgent.getBrowser().getName()); + loginUser.setOs(userAgent.getOperatingSystem().getName()); + } + + /** + * 从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + private String createToken(Map claims) + { + String token = Jwts.builder() + .setClaims(claims) + .signWith(SignatureAlgorithm.HS512, secret).compact(); + return token; + } + + /** + * 从令牌中获取数据声明 + * + * @param token 令牌 + * @return 数据声明 + */ + private Claims parseToken(String token) + { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } + + /** + * 从令牌中获取用户名 + * + * @param token 令牌 + * @return 用户名 + */ + public String getUsernameFromToken(String token) + { + Claims claims = parseToken(token); + return claims.getSubject(); + } + + /** + * 获取请求token + * + * @param request + * @return token + */ + private String getToken(HttpServletRequest request) + { + String token = request.getHeader(header); + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) + { + token = token.replace(Constants.TOKEN_PREFIX, ""); + } + return token; + } + + private String getTokenKey(String uuid) + { + return CacheConstants.LOGIN_TOKEN_KEY + uuid; + } + + private String getUserIdKey(Long userId) { + return CacheConstants.LOGIN_USERID_KEY + userId; + } +} diff --git a/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/UserDetailsServiceImpl.java b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/UserDetailsServiceImpl.java new file mode 100644 index 00000000..01c56c4b --- /dev/null +++ b/springboot/fastbee-framework/src/main/java/com/fastbee/framework/web/service/UserDetailsServiceImpl.java @@ -0,0 +1,65 @@ +package com.fastbee.framework.web.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.enums.UserStatus; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.system.service.ISysUserService; + +/** + * 用户验证处理 + * + * @author ruoyi + */ +@Service +public class UserDetailsServiceImpl implements UserDetailsService +{ + private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); + + @Autowired + private ISysUserService userService; + + @Autowired + private SysPasswordService passwordService; + + @Autowired + private SysPermissionService permissionService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException + { + SysUser user = userService.selectUserByUserName(username); + if (StringUtils.isNull(user)) + { + log.info("登录用户:{} 不存在.", username); + throw new ServiceException("登录用户:" + username + " 不存在"); + } + else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + log.info("登录用户:{} 已被删除.", username); + throw new ServiceException("对不起,您的账号:" + username + " 已被删除"); + } + else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + log.info("登录用户:{} 已被停用.", username); + throw new ServiceException("对不起,您的账号:" + username + " 已停用"); + } + + passwordService.validate(user); + + return createLoginUser(user); + } + + public UserDetails createLoginUser(SysUser user) + { + return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + } +} diff --git a/springboot/fastbee-gateway/fastbee-mq/pom.xml b/springboot/fastbee-gateway/fastbee-mq/pom.xml new file mode 100644 index 00000000..edb31127 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + fastbee-gateway + com.fastbee + 3.8.5 + + fastbee-mq + + + + + com.fastbee + fastbee-protocol-collect + + + + cn.hutool + hutool-all + + + + + + + diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/config/MqConfig.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/config/MqConfig.java new file mode 100644 index 00000000..50198a1a --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/config/MqConfig.java @@ -0,0 +1,17 @@ +package com.fastbee.mq.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Configuration; + +/** + * mq集群配置 + * @author gsb + * @date 2022/10/10 8:27 + */ +@Configuration +@ConditionalOnExpression("${cluster.enable:false}") +public class MqConfig { + + + +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/model/ReportDataBo.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/model/ReportDataBo.java new file mode 100644 index 00000000..a9af6826 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/model/ReportDataBo.java @@ -0,0 +1,40 @@ +package com.fastbee.mq.model; + +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; +import lombok.Data; + +import java.util.List; + +/** + * 上报数据模型bo + * @author bill + */ +@Data +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 Long userId; + private String userName; + private String deviceName; + +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/mqttClient/MqttClientConfig.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/mqttClient/MqttClientConfig.java new file mode 100644 index 00000000..c9264879 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/mqttClient/MqttClientConfig.java @@ -0,0 +1,58 @@ +package com.fastbee.mq.mqttClient; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** mqtt配置信息*/ + +@Data +@Component +@ConfigurationProperties(prefix = "spring.mqtt") +public class MqttClientConfig { + + /** + * 用户名 + */ + private String username; + /** + * 密码 + */ + private String password; + /** + * 连接地址 + */ + private String hostUrl; + /** + * 客户Id + */ + private String clientId; + /** + * 默认连接话题 + */ + private String defaultTopic; + /** + * 超时时间 + */ + private int timeout; + /** + * 保持连接数 + */ + private int keepalive; + + /**是否清除session*/ + private boolean clearSession; + /**是否共享订阅*/ + private boolean isShared; + /**分组共享订阅*/ + private boolean isSharedGroup; + + /** + * true: 使用netty搭建的mqttBroker false: 使用emq + */ + @Value("${server.broker.enabled}") + private Boolean enabled; + +} + diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/mqttClient/MqttService.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/mqttClient/MqttService.java new file mode 100644 index 00000000..71eacdb7 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/mqttClient/MqttService.java @@ -0,0 +1,68 @@ +package com.fastbee.mq.mqttClient; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.mq.DeviceReportBo; +import com.fastbee.common.enums.ServerType; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.gateway.mq.TopicsPost; +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import com.fastbee.mq.redischannel.producer.MessageProducer; +import com.fastbee.mq.service.IDeviceReportMessageService; +import com.fastbee.mq.service.IMessagePublishService; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.MqttAsyncClient; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Arrays; + + +@Component +@Slf4j +public class MqttService { + + @Resource + private TopicsUtils topicsUtils; + @Resource + private IDeviceReportMessageService deviceReportMessageService; + + + + public void subscribe(MqttAsyncClient client) throws MqttException { + TopicsPost allPost = topicsUtils.getAllPost(); + client.subscribe(allPost.getTopics(), allPost.getQos()); + log.info("mqtt监控主题,{}", Arrays.asList(allPost.getTopics())); + } + + /** + * 消息回调方法 + * + * @param topic 主题 + * @param mqttMessage 消息体 + */ + public void subscribeCallback(String topic, MqttMessage mqttMessage) { + + String message = new String(mqttMessage.getPayload()); + log.info("接收消息主题 : " + topic); + log.info("接收消息Qos : " + mqttMessage.getQos()); + log.info("接收消息内容 : " + message); + String serialNumber = topicsUtils.parseSerialNumber(topic); + Long productId = topicsUtils.parseProductId(topic); + String name = topicsUtils.parseTopicName(topic); + DeviceReportBo reportBo = DeviceReportBo.builder() + .serialNumber(serialNumber) + .productId(productId) + .data(mqttMessage.getPayload()) + .platformDate(DateUtils.getNowDate()) + .topicName(topic) + .serverType(ServerType.MQTT) + .build(); + if (name.startsWith("property")) { + deviceReportMessageService.parseReportMsg(reportBo); + } + } + + +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/mqttClient/PubMqttCallBack.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/mqttClient/PubMqttCallBack.java new file mode 100644 index 00000000..12a0e552 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/mqttClient/PubMqttCallBack.java @@ -0,0 +1,117 @@ +package com.fastbee.mq.mqttClient; + + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.*; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * mqtt客户端回调 + */ +@Slf4j +@Component +@Data +@NoArgsConstructor +public class PubMqttCallBack implements MqttCallbackExtended { + + + /** + * mqtt客户端 + */ + private MqttAsyncClient client; + /** + * 创建客户端参数 + */ + private MqttConnectOptions options; + + @Resource + private MqttService mqttService; + + private Boolean enabled; + + + public PubMqttCallBack(MqttAsyncClient client, MqttConnectOptions options,Boolean enabled) { + this.client = client; + this.options = options; + this.enabled = enabled; + } + + /** + * mqtt客户端连接 + * + * @param cause 错误 + */ + @Override + public void connectionLost(Throwable cause) { + + // 连接丢失后,一般在这里面进行重连 + log.debug("=>mqtt 连接丢失", cause); + int count = 1; + // int sleepTime = 0; + boolean willConnect = true; + while (willConnect) { + try { + Thread.sleep(1000); + log.debug("=>连接[{}]断开,尝试重连第{}次", this.client.getServerURI(), count++); + this.client.connect(this.options); + log.debug("=>重连成功"); + willConnect = false; + } catch (Exception e) { + log.error("=>重连异常", e); + } + } + } + + /** + * 客户端订阅主题回调消息 + * + * @param topic 主题 + * @param message 消息 + */ + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + // subscribe后得到的消息会执行到这里面 + try { + mqttService.subscribeCallback(topic, message); + } catch (Exception e) { + log.warn("mqtt 订阅消息异常", e); + } + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + + } + + + /** + * 监听mqtt连接消息 + */ + @Override + public void connectComplete(boolean reconnect, String serverURI) { + log.info("MQTT内部客户端已经连接!"); + System.out.print("" + + " * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * \n" + + " * _⚲_⚲_ ______ _ ____ * \n" + + " * | / \\ | | ____| | | | _ \\ * \n" + + " * | | | ● | | | | |__ __ _ ___| |_ | |_) | ___ ___ * \n" + + " * | \\ / | | __/ _` / __| __| | _ < / _ \\/ _ \\ * \n" + + " * \\ / | | | (_| \\__ \\ |_ | |_) | __/ __/ * \n" + + " * V |_| \\__,_|___/\\__| |____/ \\___|\\___| * \n" + + " * * \n"+ + " * * * * * * * * * * * * FastBee物联网平台[✔启动成功] * * * * * * * * * * * * \n"); + + //连接后订阅, enable为false表示使用emq + if (!enabled) { + try { + mqttService.subscribe(client); + } catch (MqttException e) { + log.error("=>订阅主题失败 error={}", e.getMessage()); + } + } + } +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/mqttClient/PubMqttClient.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/mqttClient/PubMqttClient.java new file mode 100644 index 00000000..92505e75 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/mqttClient/PubMqttClient.java @@ -0,0 +1,225 @@ +package com.fastbee.mq.mqttClient; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.enums.FunctionReplyStatus; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.iot.domain.FunctionLog; +import com.fastbee.iot.service.IFunctionLogService; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 发布服务mqtt客户端 + */ +@Component +@Slf4j +public class PubMqttClient { + + @Resource + private MqttClientConfig mqttConfig; + @Resource(name = "pubMqttCallBack") + private PubMqttCallBack mqttCallBack; + /** + * 连接配置 + */ + private MqttConnectOptions options; + /** + * MQTT异步客户端 + */ + private MqttAsyncClient client; + /** + * 是否连接标记 + */ + private boolean isConnected = false; + @Resource + private RedisCache redisCache; + @Resource + private IFunctionLogService functionLogService; + + /** + * 启动MQTT客户端 + */ + public synchronized void initialize() { + + try { + setOptions(); + createClient(); + while (!client.isConnected()) { + IMqttToken token = client.connect(options); + if(token != null && token.isComplete()) { + log.debug("=>内部MQTT客户端启动成功"); + this.isConnected = true; + break; + } + log.debug("=>内部mqtt客户端连接中..."); + Thread.sleep(20000); + } + } catch (MqttException ex) { + log.error("=>MQTT客户端初始化异常", ex); + } catch (Exception e) { + log.error("=>连接MQTT服务器异常", e); + this.isConnected = false; + } + + } + + public boolean isConnected() { + return this.isConnected; + } + + private void createClient() { + try { + if (client == null) { + /*host为主机名,clientId是连接MQTT的客户端ID*/ + client = new MqttAsyncClient(mqttConfig.getHostUrl(), getClientId(), new MemoryPersistence()); + //设置回调函数 + client.setCallback(mqttCallBack); + mqttCallBack.setClient(client); + mqttCallBack.setOptions(this.options); + mqttCallBack.setEnabled(mqttConfig.getEnabled()); + } + } catch (Exception e) { + log.error("=>mqtt客户端创建错误"); + } + } + + /** + * 设置连接属性 + */ + private void setOptions() { + + if (options != null) { + options = null; + } + options = new MqttConnectOptions(); + options.setConnectionTimeout(mqttConfig.getTimeout()); + options.setKeepAliveInterval(mqttConfig.getKeepalive()); + options.setUserName(mqttConfig.getUsername()); + options.setPassword(mqttConfig.getPassword().toCharArray()); + //设置自动重新连接 + options.setAutomaticReconnect(true); + /*设置为false,断开连接,不清除session,重连后还是原来的session + 保留订阅的主题,能接收离线期间的消息*/ + options.setCleanSession(true); + } + + /** + * 断开与mqtt的连接 + */ + public synchronized void disconnect() { + //判断客户端是否null 是否连接 + if (client != null && client.isConnected()) { + try { + IMqttToken token = client.disconnect(); + token.waitForCompletion(); + } catch (MqttException e) { + log.error("=>断开mqtt连接发生错误 message={}", e.getMessage()); + throw new ServiceException("断开mqtt连接发生错误" + e.getMessage()); + } + } + client = null; + } + + /** + * 重新连接MQTT + */ + public synchronized void refresh() { + disconnect(); + initialize(); + } + + /** + * 拼接客户端id + */ + public final String getClientId() { + return FastBeeConstant.SERVER.WM_PREFIX + System.currentTimeMillis(); + } + + /** + * 发布qos=1,非持久化 + */ + public void publish(String topic, byte[] pushMessage, FunctionLog log) { + try { + redisCache.incr2(FastBeeConstant.REDIS.MESSAGE_SEND_TOTAL, -1L); + redisCache.incr2(FastBeeConstant.REDIS.MESSAGE_SEND_TODAY, 60 * 60 * 24); + publish(pushMessage, topic, false, 0); + if (null != log) { + //存储服务下发成功 + log.setResultMsg(FunctionReplyStatus.NORELY.getMessage()); + log.setResultCode(FunctionReplyStatus.NORELY.getCode()); + functionLogService.insertFunctionLog(log); + } + } catch (Exception e) { + if (null != log) { + //服务下发失败存储 + log.setResultMsg(FunctionReplyStatus.FAIl.getMessage() + "原因: " + e.getMessage()); + log.setResultCode(FunctionReplyStatus.FAIl.getCode()); + functionLogService.insertFunctionLog(log); + } + } + } + + /** + * 发布主题 + * + * @param message payload消息体 + * @param topic 主题 + * @param retained 是否保留消息 + * @param qos 消息质量 + * Qos1:消息发送一次,不确保 + * Qos2:至少分发一次,服务器确保接收消息进行确认 + * Qos3:只分发一次,确保消息送达和只传递一次 + */ + public void publish(byte[] message, String topic, boolean retained, int qos) { + //设置mqtt消息 + MqttMessage mqttMessage = new MqttMessage(); + mqttMessage.setQos(qos); + mqttMessage.setRetained(retained); + mqttMessage.setPayload(message); + + IMqttDeliveryToken token; + try { + token = client.publish(topic, mqttMessage); + token.waitForCompletion(); + } catch (MqttPersistenceException e) { + log.error("=>发布主题时发生错误 topic={},message={}", topic, e.getMessage()); + throw new ServiceException(e.getMessage()); + } catch (MqttException ex) { + throw new ServiceException(ex.getMessage()); + } + } + + + /** + * 发布 + * + * @param qos 连接方式 + * @param retained 是否保留 + * @param topic 主题 + * @param pushMessage 消息体 + */ + public void publish(int qos, boolean retained, String topic, String pushMessage) { + redisCache.incr2(FastBeeConstant.REDIS.MESSAGE_SEND_TOTAL, -1L); + redisCache.incr2(FastBeeConstant.REDIS.MESSAGE_SEND_TODAY, 60 * 60 * 24); + log.info("发布主题[{}],发布消息[{}]" + topic,pushMessage); + MqttMessage message = new MqttMessage(); + message.setQos(qos); + message.setRetained(retained); + message.setPayload(pushMessage.getBytes()); + + try { + IMqttDeliveryToken token = client.publish(topic, message); + token.waitForCompletion(); + } catch (MqttPersistenceException e) { + e.printStackTrace(); + } catch (MqttException e) { + log.error("=>发布主题时发生错误 topic={},message={}", topic, e.getMessage()); + } + } + +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/config/RedisConsumeConfig.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/config/RedisConsumeConfig.java new file mode 100644 index 00000000..b2c626b8 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/config/RedisConsumeConfig.java @@ -0,0 +1,57 @@ +package com.fastbee.mq.redischannel.config; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.mq.redischannel.consumer.RedisChannelConsume; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.listener.PatternTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; + +/** + * redisChannel配置 + * @author gsb + * @date 2022/10/10 8:57 + */ +@Configuration +@EnableCaching +@Slf4j +public class RedisConsumeConfig { + + @Bean + @ConditionalOnProperty(prefix ="cluster", name = "type" ,havingValue = FastBeeConstant.MQTT.REDIS_CHANNEL,matchIfMissing = true) + RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, + MessageListenerAdapter listenerAdapter) { + + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + // 可以添加多个 messageListener,配置不同的交换机 + container.addMessageListener(listenerAdapter, new PatternTopic(FastBeeConstant.CHANNEL.DEVICE_STATUS)); + container.addMessageListener(listenerAdapter, new PatternTopic(FastBeeConstant.CHANNEL.PROP_READ)); + container.addMessageListener(listenerAdapter, new PatternTopic(FastBeeConstant.CHANNEL.FUNCTION_INVOKE)); + container.addMessageListener(listenerAdapter,new PatternTopic(FastBeeConstant.CHANNEL.UPGRADE)); + return container; + } + + /**配置消息监听类 默认监听方法onMessage*/ + @Bean + @ConditionalOnProperty(prefix ="cluster", name = "type" ,havingValue = FastBeeConstant.MQTT.REDIS_CHANNEL,matchIfMissing = true) + MessageListenerAdapter listenerAdapter(RedisChannelConsume consume){ + return new MessageListenerAdapter(consume,"onMessage"); + } + + @Bean + @ConditionalOnProperty(prefix ="cluster", name = "type" ,havingValue = FastBeeConstant.MQTT.REDIS_CHANNEL,matchIfMissing = true) + StringRedisTemplate template(RedisConnectionFactory connectionFactory){ + return new StringRedisTemplate(connectionFactory); + } + + + + +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/consumer/DeviceOtherMsgConsumer.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/consumer/DeviceOtherMsgConsumer.java new file mode 100644 index 00000000..be32c0ad --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/consumer/DeviceOtherMsgConsumer.java @@ -0,0 +1,34 @@ +package com.fastbee.mq.redischannel.consumer; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.mq.DeviceReportBo; +import com.fastbee.mq.service.impl.DeviceOtherMsgHandler; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * @author gsb + * @date 2023/2/27 14:33 + */ +@Slf4j +@Component +public class DeviceOtherMsgConsumer { + + @Resource + private DeviceOtherMsgHandler otherMsgHandler; + + @Async(FastBeeConstant.TASK.DEVICE_OTHER_TASK) + public void consume(DeviceReportBo bo){ + try { + //处理emq订阅的非 property/post 属性上报的消息 ,因为其他消息量小,放在一起处理 + otherMsgHandler.messageHandler(bo); + }catch (Exception e){ + log.error("=>设备其他消息处理出错",e); + } + } + +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/consumer/RedisChannelConsume.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/consumer/RedisChannelConsume.java new file mode 100644 index 00000000..0374d507 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/consumer/RedisChannelConsume.java @@ -0,0 +1,33 @@ +package com.fastbee.mq.redischannel.consumer; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.stereotype.Component; + +/** + * redisChannel消息监听 + * + * @author gsb + * @date 2022/10/10 9:17 + */ +@Component +@Slf4j +public class RedisChannelConsume implements MessageListener { + + /** + * 监听推送消息 + */ + @Override + public void onMessage(Message message, byte[] pattern) { + try { + /*获取channel*/ + String channel = new String(message.getChannel()); + /*获取消息*/ + String body = new String(message.getBody()); + + } catch (Exception e) { + log.error("=>redisChannel处理消息异常,e", e); + } + } +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/listen/DeviceOtherListen.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/listen/DeviceOtherListen.java new file mode 100644 index 00000000..706c5c94 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/listen/DeviceOtherListen.java @@ -0,0 +1,35 @@ +package com.fastbee.mq.redischannel.listen; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.mq.DeviceReportBo; +import com.fastbee.mq.redischannel.consumer.DeviceOtherMsgConsumer; +import com.fastbee.mq.redischannel.queue.DeviceOtherQueue; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * @author gsb + * @date 2023/2/28 10:02 + */ +@Slf4j +@Component +public class DeviceOtherListen { + + @Resource + private DeviceOtherMsgConsumer otherMsgConsumer; + + @Async(FastBeeConstant.TASK.DEVICE_OTHER_TASK) + public void listen(){ + while (true){ + try { + DeviceReportBo reportBo = DeviceOtherQueue.take(); + otherMsgConsumer.consume(reportBo); + }catch (Exception e){ + log.error("=>emq数据转发异常"); + } + } + } +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/producer/MessageProducer.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/producer/MessageProducer.java new file mode 100644 index 00000000..53828925 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/producer/MessageProducer.java @@ -0,0 +1,20 @@ +package com.fastbee.mq.redischannel.producer; + +import com.fastbee.common.core.mq.DeviceReportBo; +import com.fastbee.common.core.mq.DeviceStatusBo; +import com.fastbee.common.core.mq.MQSendMessageBo; +import com.fastbee.common.core.mq.ota.OtaUpgradeBo; +import com.fastbee.common.core.mq.message.DeviceDownMessage; +import com.fastbee.mq.redischannel.queue.*; + +/** + *设备消息生产者 ,设备的消息发送通道 + * @author bill + */ +public class MessageProducer { + + public static void sendOtherMsg(DeviceReportBo bo){ + DeviceOtherQueue.offer(bo); + } + +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/queue/DeviceOtherQueue.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/queue/DeviceOtherQueue.java new file mode 100644 index 00000000..e93d1d82 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/redischannel/queue/DeviceOtherQueue.java @@ -0,0 +1,25 @@ +package com.fastbee.mq.redischannel.queue; + +import com.fastbee.common.core.mq.DeviceReportBo; +import lombok.SneakyThrows; + +import java.util.concurrent.LinkedBlockingQueue; + +/** + * @author gsb + * @date 2022/10/10 10:13 + */ +public class DeviceOtherQueue { + + private static final LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); + + /*元素加入队列,最后*/ + public static void offer(DeviceReportBo dto){ + queue.offer(dto); + } + /*取出队列元素 先进先出*/ + @SneakyThrows + public static DeviceReportBo take(){ + return queue.take(); + } +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IDataHandler.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IDataHandler.java new file mode 100644 index 00000000..254b821b --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IDataHandler.java @@ -0,0 +1,32 @@ +package com.fastbee.mq.service; + +import com.fastbee.mq.model.ReportDataBo; + +/** + * 客户端上报数据处理方法集合 + * @author bill + */ +public interface IDataHandler { + + /** + * 上报属性或功能处理 + * + * @param bo 上报数据模型 + */ + public void reportData(ReportDataBo bo); + + + /** + * 上报事件 + * + * @param bo 上报数据模型 + */ + public void reportEvent(ReportDataBo bo); + + /** + * 上报设备信息 + * @param bo 上报数据模型 + */ + public void reportDevice(ReportDataBo bo); + +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IDeviceReportMessageService.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IDeviceReportMessageService.java new file mode 100644 index 00000000..d2f780df --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IDeviceReportMessageService.java @@ -0,0 +1,35 @@ +package com.fastbee.mq.service; + +import com.fastbee.common.core.mq.DeviceReport; +import com.fastbee.common.core.mq.DeviceReportBo; +import com.fastbee.iot.domain.Device; + +/** + * 处理设备上报数据解析 + * @author gsb + * @date 2022/10/10 13:48 + */ +public interface IDeviceReportMessageService { + + /** + * 处理设备主动上报数据 + * @param bo + */ + public void parseReportMsg(DeviceReportBo bo); + + /** + * 构建消息 + * @param bo + */ + public Device buildReport(DeviceReportBo bo); + + + /** + * 处理设备主动上报属性 + * + * @param topicName + * @param message + */ + public void handlerReportMessage(DeviceReport message, String topicName); + +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IFunctionInvoke.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IFunctionInvoke.java new file mode 100644 index 00000000..b43c645f --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IFunctionInvoke.java @@ -0,0 +1,21 @@ +package com.fastbee.mq.service; + +import com.fastbee.common.core.mq.InvokeReqDto; + +import java.util.Map; + +/** + * 设备指令下发接口 + * @author gsb + * @date 2022/12/5 11:03 + */ +public interface IFunctionInvoke { + + + /** + * 服务调用,设备不响应 + * @param reqDto 服务下发对象 + * @return 消息id messageId + */ + public String invokeNoReply(InvokeReqDto reqDto); +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IMessagePublishService.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IMessagePublishService.java new file mode 100644 index 00000000..36675bd4 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IMessagePublishService.java @@ -0,0 +1,19 @@ +package com.fastbee.mq.service; + +import com.fastbee.common.core.mq.message.DeviceMessage; + +/** + * 设备消息推送mq + * @author bill + */ +public interface IMessagePublishService { + + + /** + * 发布消息到mq + * @param message 设备消息 + * @param channel 推送channel + */ + public void publish(Object message,String channel); + +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IMqttMessagePublish.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IMqttMessagePublish.java new file mode 100644 index 00000000..14f033c3 --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/IMqttMessagePublish.java @@ -0,0 +1,78 @@ +package com.fastbee.mq.service; + +import com.fastbee.common.core.mq.DeviceReportBo; +import com.fastbee.common.core.mq.MQSendMessageBo; +import com.fastbee.common.core.mq.message.DeviceDownMessage; +import com.fastbee.common.core.mq.message.InstructionsMessage; +import com.fastbee.common.core.mq.ota.OtaUpgradeBo; +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; +import com.fastbee.common.enums.TopicType; +import com.fastbee.iot.domain.Device; +import com.fastbee.mq.model.ReportDataBo; + +import java.util.List; + +public interface IMqttMessagePublish { + + /** + * 下发数据编码 + */ + InstructionsMessage buildMessage(DeviceDownMessage downMessage, TopicType type); + + /** + * 服务(指令)下发 + */ + public void funcSend(MQSendMessageBo bo); + + /** + * OTA升级下发 + */ + public void upGradeOTA(OtaUpgradeBo bo); + + public void sendFunctionMessage(DeviceReportBo bo); + + + /** + * 1.发布设备状态 + */ + public void publishStatus(Long productId, String deviceNum, int deviceStatus, int isShadow, int rssi); + + + /** + * 2.发布设备信息 + */ + public void publishInfo(Long productId, String deviceNum); + + + /** + * 3.发布时钟同步信息 + * + * @param bo 数据模型 + */ + public void publishNtp(ReportDataBo bo); + + + /** + * 4.发布属性 + * delay 延时,秒为单位 + */ + public void publishProperty(Long productId, String deviceNum, List thingsList, int delay); + + + /** + * 5.发布功能 + * delay 延时,秒为单位 + */ + public void publishFunction(Long productId, String deviceNum, List thingsList, int delay); + + + /** + * 设备数据同步 + * + * @param deviceNumber 设备编号 + * @return 设备 + */ + public Device deviceSynchronization(String deviceNumber); + + +} \ No newline at end of file diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/impl/DeviceOtherMsgHandler.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/impl/DeviceOtherMsgHandler.java new file mode 100644 index 00000000..b8091d3e --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/impl/DeviceOtherMsgHandler.java @@ -0,0 +1,95 @@ +package com.fastbee.mq.service.impl; + +import com.fastbee.common.core.mq.DeviceReportBo; +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import com.fastbee.mq.model.ReportDataBo; +import com.fastbee.mq.service.IDataHandler; +import com.fastbee.mq.service.IMqttMessagePublish; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * @author gsb + * @date 2023/2/27 14:42 + */ +@Component +@Slf4j +public class DeviceOtherMsgHandler { + + @Resource + private TopicsUtils topicsUtils; + @Resource + private IDataHandler dataHandler; + @Resource + private IMqttMessagePublish messagePublish; + + /** + * 非属性消息消息处理入口 + * @param bo + */ + public void messageHandler(DeviceReportBo bo){ + String type = ""; + String name = topicsUtils.parseTopicName(bo.getTopicName()); + ReportDataBo data = this.buildReportData(bo); + switch (name) { + case "info": + dataHandler.reportDevice(data); + break; + case "ntp": + messagePublish.publishNtp(data); + break; + // 接收 property/get 模拟设备数据 + case "property": + type = topicsUtils.parseTopicName4(bo.getTopicName()); + break; + case "function": + data.setShadow(false); + data.setType(2); + data.setRuleEngine(true); + dataHandler.reportData(data); + break; + case "event": + data.setType(3); + data.setRuleEngine(true); + dataHandler.reportEvent(data); + break; + case "property-offline": + data.setShadow(true); + data.setType(1); + dataHandler.reportData(data); + break; + case "function-offline": + data.setShadow(true); + data.setType(2); + dataHandler.reportData(data); + break; + case "property-online": + break; + case "function-online": + type = topicsUtils.parseTopicName4(bo.getTopicName()); + if (type.equals("get")) { + log.info("function-online:{}",bo); + //处理功能下发 + messagePublish.sendFunctionMessage(bo); + } + break; + } + } + + /**组装数据*/ + private ReportDataBo buildReportData(DeviceReportBo bo){ + String message = new String(bo.getData()); + log.info("收到设备信息[{}]",message); + Long productId = topicsUtils.parseProductId(bo.getTopicName()); + ReportDataBo dataBo = new ReportDataBo(); + dataBo.setMessage(message); + dataBo.setProductId(productId); + dataBo.setSerialNumber(bo.getSerialNumber()); + dataBo.setRuleEngine(false); + return dataBo; + } + +} diff --git a/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/impl/FunctionInvokeImpl.java b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/impl/FunctionInvokeImpl.java new file mode 100644 index 00000000..970a9cdf --- /dev/null +++ b/springboot/fastbee-gateway/fastbee-mq/src/main/java/com/fastbee/mq/service/impl/FunctionInvokeImpl.java @@ -0,0 +1,55 @@ +package com.fastbee.mq.service.impl; + +import com.fastbee.common.core.mq.DeviceReplyBo; +import com.fastbee.common.core.mq.InvokeReqDto; +import com.fastbee.common.core.mq.MQSendMessageBo; +import com.fastbee.common.core.mq.MessageReplyBo; +import com.fastbee.common.core.protocol.modbus.ModbusCode; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.core.redis.RedisKeyBuilder; +import com.fastbee.common.enums.ThingsModelType; +import com.fastbee.common.utils.bean.BeanUtils; +import com.fastbee.iot.util.SnowflakeIdWorker; +import com.fastbee.mq.redischannel.producer.MessageProducer; +import com.fastbee.mq.service.IFunctionInvoke; +import com.fastbee.mq.service.IMqttMessagePublish; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @author gsb + * @date 2022/12/5 11:34 + */ +@Slf4j +@Service +public class FunctionInvokeImpl implements IFunctionInvoke { + + + @Resource + private IMqttMessagePublish mqttMessagePublish; + private SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(2); + + + /** + * 服务调用,设备不响应 + * @param reqDto 服务下发对象 + * @return 消息id messageId + */ + @Override + public String invokeNoReply(InvokeReqDto reqDto){ + log.debug("=>下发指令请求:[{}]",reqDto); + MQSendMessageBo bo = new MQSendMessageBo(); + BeanUtils.copyBeanProp(bo,reqDto); + long id = snowflakeIdWorker.nextId(); + String messageId = id+""; + bo.setMessageId(messageId+""); + bo.setType(ThingsModelType.getType(reqDto.getType())); + mqttMessagePublish.funcSend(bo); + return messageId; + } +} diff --git a/springboot/fastbee-gateway/gateway-boot/pom.xml b/springboot/fastbee-gateway/gateway-boot/pom.xml new file mode 100644 index 00000000..2df7ffbe --- /dev/null +++ b/springboot/fastbee-gateway/gateway-boot/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + fastbee-gateway + com.fastbee + 3.8.5 + + gateway-boot + + 网关模块 + + + + + com.fastbee + fastbee-mq + + + + + + diff --git a/springboot/fastbee-gateway/gateway-boot/src/main/java/com/fastbee/gateway/boot/start/StartBoot.java b/springboot/fastbee-gateway/gateway-boot/src/main/java/com/fastbee/gateway/boot/start/StartBoot.java new file mode 100644 index 00000000..99d250bd --- /dev/null +++ b/springboot/fastbee-gateway/gateway-boot/src/main/java/com/fastbee/gateway/boot/start/StartBoot.java @@ -0,0 +1,42 @@ +package com.fastbee.gateway.boot.start; + +import com.fastbee.mq.mqttClient.PubMqttClient; +import com.fastbee.mq.redischannel.listen.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 启动类 + * + * @author bill + */ +@Component +@Slf4j +@Order(2) +public class StartBoot implements ApplicationRunner { + + + @Autowired + private PubMqttClient mqttClient; + @Resource + private DeviceOtherListen otherListen; + + + @Override + public void run(ApplicationArguments args) throws Exception { + try { + otherListen.listen(); + /*启动内部客户端,用来下发客户端服务*/ + mqttClient.initialize(); + log.info("=>设备监听队列启动成功"); + } catch (Exception e) { + log.error("=>客户端启动失败:{}", e.getMessage(),e); + } + } +} diff --git a/springboot/fastbee-gateway/pom.xml b/springboot/fastbee-gateway/pom.xml new file mode 100644 index 00000000..67bf0333 --- /dev/null +++ b/springboot/fastbee-gateway/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + fastbee + com.fastbee + 3.8.5 + + + pom + + + gateway-boot + fastbee-mq + + + fastbee-gateway + fastbee-gateway + + + + com.fastbee + fastbee-common + + + + com.fastbee + fastbee-iot-service + + + + + + + diff --git a/springboot/fastbee-open-api/pom.xml b/springboot/fastbee-open-api/pom.xml new file mode 100644 index 00000000..6e04ab1f --- /dev/null +++ b/springboot/fastbee-open-api/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + fastbee + com.fastbee + 3.8.5 + + + + fastbee-open-api + controller层接口 + + + + com.fastbee + mqtt-broker + + + + + + + diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/AuthResourceController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/AuthResourceController.java new file mode 100644 index 00000000..58ead906 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/AuthResourceController.java @@ -0,0 +1,25 @@ +package com.fastbee.data.controller; + +import com.fastbee.common.core.controller.BaseController; +import org.springframework.web.bind.annotation.*; + +/** + * 设备告警Controller + * + * @author kerwincui + * @date 2022-01-13 + */ +@RestController +@RequestMapping("/oauth/resource") +public class AuthResourceController extends BaseController +{ + /** + * 查询设备告警列表 + */ + @GetMapping("/product") + public String findAll() { + return "查询产品列表成功!"; + } + + +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/CategoryController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/CategoryController.java new file mode 100644 index 00000000..e0415769 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/CategoryController.java @@ -0,0 +1,128 @@ +package com.fastbee.data.controller; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.iot.model.IdAndName; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.iot.domain.Category; +import com.fastbee.iot.service.ICategoryService; +import com.fastbee.common.utils.poi.ExcelUtil; + +/** + * 产品分类Controller + * + * @author kerwincui + * @date 2021-12-16 + */ +@Api(tags = "产品分类") +@RestController +@RequestMapping("/iot/category") +public class CategoryController extends BaseController +{ + @Autowired + private ICategoryService categoryService; + + /** + * 查询产品分类列表 + */ + @PreAuthorize("@ss.hasPermi('iot:category:list')") + @GetMapping("/list") + @ApiOperation("分类分页列表") + public TableDataInfo list(Category category) + { + startPage(); + return getDataTable(categoryService.selectCategoryList(category)); + } + + /** + * 查询产品简短分类列表 + */ + @PreAuthorize("@ss.hasPermi('iot:category:list')") + @GetMapping("/shortlist") + @ApiOperation("分类简短列表") + public AjaxResult shortlist() + { + return AjaxResult.success(categoryService.selectCategoryShortList()); + } + + /** + * 导出产品分类列表 + */ + @PreAuthorize("@ss.hasPermi('iot:category:export')") + @Log(title = "产品分类", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ApiOperation("导出分类") + public void export(HttpServletResponse response, Category category) + { + List list = categoryService.selectCategoryList(category); + ExcelUtil util = new ExcelUtil(Category.class); + util.exportExcel(response, list, "产品分类数据"); + } + + /** + * 获取产品分类详细信息 + */ + @ApiOperation("获取分类详情") + @PreAuthorize("@ss.hasPermi('iot:category:query')") + @GetMapping(value = "/{categoryId}") + public AjaxResult getInfo(@PathVariable("categoryId") Long categoryId) + { + return AjaxResult.success(categoryService.selectCategoryByCategoryId(categoryId)); + } + + /** + * 新增产品分类 + */ + @PreAuthorize("@ss.hasPermi('iot:category:add')") + @Log(title = "产品分类", businessType = BusinessType.INSERT) + @PostMapping + @ApiOperation("添加分类") + public AjaxResult add(@RequestBody Category category) + { + return toAjax(categoryService.insertCategory(category)); + } + + /** + * 修改产品分类 + */ + @PreAuthorize("@ss.hasPermi('iot:category:edit')") + @Log(title = "产品分类", businessType = BusinessType.UPDATE) + @PutMapping + @ApiOperation("修改分类") + public AjaxResult edit(@RequestBody Category category) + { + return toAjax(categoryService.updateCategory(category)); + } + + /** + * 删除产品分类 + */ + @PreAuthorize("@ss.hasPermi('iot:category:remove')") + @Log(title = "产品分类", businessType = BusinessType.DELETE) + @DeleteMapping("/{categoryIds}") + @ApiOperation("批量删除分类") + public AjaxResult remove(@PathVariable Long[] categoryIds) + { + return categoryService.deleteCategoryByCategoryIds(categoryIds); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/DeviceController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/DeviceController.java new file mode 100644 index 00000000..29b20e5d --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/DeviceController.java @@ -0,0 +1,267 @@ +package com.fastbee.data.controller; + +import com.fastbee.common.annotation.Anonymous; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.model.DeviceRelateUserInput; +import com.fastbee.iot.service.IDeviceService; +import com.fastbee.mq.service.IMqttMessagePublish; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 设备Controller + * + * @author kerwincui + * @date 2021-12-16 + */ +@Api(tags = "设备管理") +@RestController +@RequestMapping("/iot/device") +public class DeviceController extends BaseController +{ + @Autowired + private IDeviceService deviceService; + + // @Lazy + @Autowired + private IMqttMessagePublish messagePublish; + + /** + * 查询设备列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/list") + @ApiOperation("设备分页列表") + public TableDataInfo list(Device device) + { + startPage(); + return getDataTable(deviceService.selectDeviceList(device)); + } + + /** + * 查询未分配授权码设备列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/unAuthlist") + @ApiOperation("设备分页列表") + public TableDataInfo unAuthlist(Device device) + { + startPage(); + return getDataTable(deviceService.selectUnAuthDeviceList(device)); + } + + /** + * 查询分组可添加设备 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/listByGroup") + @ApiOperation("查询分组可添加设备分页列表") + public TableDataInfo listByGroup(Device device) + { + startPage(); + return getDataTable(deviceService.selectDeviceListByGroup(device)); + } + + /** + * 查询设备简短列表,主页列表数据 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/shortList") + @ApiOperation("设备分页简短列表") + public TableDataInfo shortList(Device device) + { + startPage(); + return getDataTable(deviceService.selectDeviceShortList(device)); + } + + /** + * 查询所有设备简短列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/all") + @ApiOperation("查询所有设备简短列表") + public TableDataInfo allShortList() + { + return getDataTable(deviceService.selectAllDeviceShortList()); + } + + /** + * 导出设备列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:export')") + @Log(title = "设备", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ApiOperation("导出设备") + public void export(HttpServletResponse response, Device device) + { + List list = deviceService.selectDeviceList(device); + ExcelUtil util = new ExcelUtil(Device.class); + util.exportExcel(response, list, "设备数据"); + } + + /** + * 获取设备详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/{deviceId}") + @ApiOperation("获取设备详情") + public AjaxResult getInfo(@PathVariable("deviceId") Long deviceId) + { + return AjaxResult.success(deviceService.selectDeviceByDeviceId(deviceId)); + } + + /** + * 设备数据同步 + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/synchronization/{serialNumber}") + @ApiOperation("设备数据同步") + public AjaxResult deviceSynchronization(@PathVariable("serialNumber") String serialNumber) + { + return AjaxResult.success(messagePublish.deviceSynchronization(serialNumber)); + } + + /** + * 根据设备编号详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/getDeviceBySerialNumber/{serialNumber}") + @ApiOperation("根据设备编号获取设备详情") + public AjaxResult getInfoBySerialNumber(@PathVariable("serialNumber") String serialNumber) + { + return AjaxResult.success(deviceService.selectDeviceBySerialNumber(serialNumber)); + } + + /** + * 获取设备统计信息 + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/statistic") + @ApiOperation("获取设备统计信息") + public AjaxResult getDeviceStatistic() + { + return AjaxResult.success(deviceService.selectDeviceStatistic()); + } + + /** + * 获取设备详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/runningStatus") + @ApiOperation("获取设备详情和运行状态") + public AjaxResult getRunningStatusInfo(Long deviceId, Integer slaveId) + { + return AjaxResult.success(deviceService.selectDeviceRunningStatusByDeviceId(deviceId,slaveId)); + } + + /** + * 新增设备 + */ + @PreAuthorize("@ss.hasPermi('iot:device:add')") + @Log(title = "添加设备", businessType = BusinessType.INSERT) + @PostMapping + @ApiOperation("添加设备") + public AjaxResult add(@RequestBody Device device) + { + return AjaxResult.success(deviceService.insertDevice(device)); + } + + /** + * 设备关联用户 + */ + @PreAuthorize("@ss.hasPermi('iot:device:add')") + @Log(title = "设备关联用户", businessType = BusinessType.UPDATE) + @PostMapping("/relateUser") + @ApiOperation("设备关联用户") + public AjaxResult relateUser(@RequestBody DeviceRelateUserInput deviceRelateUserInput) + { + if(deviceRelateUserInput.getUserId()==0 || deviceRelateUserInput.getUserId()==null){ + return AjaxResult.error("用户ID不能为空"); + } + if(deviceRelateUserInput.getDeviceNumberAndProductIds()==null || deviceRelateUserInput.getDeviceNumberAndProductIds().size()==0){ + return AjaxResult.error("设备编号和产品ID不能为空"); + } + return deviceService.deviceRelateUser(deviceRelateUserInput); + } + + /** + * 修改设备 + */ + @PreAuthorize("@ss.hasPermi('iot:device:edit')") + @Log(title = "修改设备", businessType = BusinessType.UPDATE) + @PutMapping + @ApiOperation("修改设备") + public AjaxResult edit(@RequestBody Device device) + { + return deviceService.updateDevice(device); + } + + /** + * 重置设备状态 + */ + @PreAuthorize("@ss.hasPermi('iot:device:edit')") + @Log(title = "重置设备状态", businessType = BusinessType.UPDATE) + @PutMapping("/reset/{serialNumber}") + @ApiOperation("重置设备状态") + public AjaxResult resetDeviceStatus(@PathVariable String serialNumber) + { + Device device=new Device(); + device.setSerialNumber(serialNumber); + return toAjax(deviceService.resetDeviceStatus(device.getSerialNumber())); + } + + /** + * 删除设备 + */ + @PreAuthorize("@ss.hasPermi('iot:device:remove')") + @Log(title = "删除设备", businessType = BusinessType.DELETE) + @DeleteMapping("/{deviceIds}") + @ApiOperation("批量删除设备") + public AjaxResult remove(@PathVariable Long[] deviceIds) throws SchedulerException { + return toAjax(deviceService.deleteDeviceByDeviceId(deviceIds[0])); + } + + /** + * 生成设备编号 + */ + @PreAuthorize("@ss.hasPermi('iot:device:edit')") + @GetMapping("/generator") + @ApiOperation("生成设备编号") + public AjaxResult generatorDeviceNum(Integer type){ + return AjaxResult.success("操作成功",deviceService.generationDeviceNum(type)); + } + + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping("/gwDevCount") + @ApiOperation("子设备数量") + public AjaxResult getGwDevCount(String gwDevCode){ + return AjaxResult.success(deviceService.getSubDeviceCount(gwDevCode)); + } + + /** + * 获取设备MQTT连接参数 + * @param deviceId 设备主键id + * @return + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping("/getMqttConnectData") + @ApiOperation("获取设备MQTT连接参数") + public AjaxResult getMqttConnectData(Long deviceId){ + return AjaxResult.success(deviceService.getMqttConnectData(deviceId)); + } + +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/DeviceJobController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/DeviceJobController.java new file mode 100644 index 00000000..b1360538 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/DeviceJobController.java @@ -0,0 +1,147 @@ +package com.fastbee.data.controller; + +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.exception.job.TaskException; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.iot.domain.DeviceJob; +import com.fastbee.iot.service.IDeviceJobService; +import com.fastbee.quartz.util.CronUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 调度任务信息操作处理 + * + * @author kerwincui + */ +@Api(tags = "调度任务信息操作处理模块") +@RestController +@RequestMapping("/iot/job") +public class DeviceJobController extends BaseController +{ + @Autowired + private IDeviceJobService jobService; + + /** + * 查询定时任务列表 + */ + @ApiOperation("查询定时任务列表") + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/list") + public TableDataInfo list(DeviceJob deviceJob) + { + startPage(); + List list = jobService.selectJobList(deviceJob); + return getDataTable(list); + } + + /** + * 导出定时任务列表 + */ + @ApiOperation("导出定时任务列表") + @PreAuthorize("@ss.hasPermi('iot:device:export')") + @Log(title = "定时任务", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, DeviceJob deviceJob) + { + List list = jobService.selectJobList(deviceJob); + ExcelUtil util = new ExcelUtil(DeviceJob.class); + util.exportExcel(response, list, "定时任务"); + } + + /** + * 获取定时任务详细信息 + */ + @ApiOperation("获取定时任务详细信息") + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/{jobId}") + public AjaxResult getInfo(@PathVariable("jobId") Long jobId) + { + return AjaxResult.success(jobService.selectJobById(jobId)); + } + + /** + * 新增定时任务 + */ + @ApiOperation("新增定时任务") + @PreAuthorize("@ss.hasPermi('iot:device:add')") + @Log(title = "定时任务", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody DeviceJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + job.setCreateBy(getUsername()); + return toAjax(jobService.insertJob(job)); + } + + /** + * 修改定时任务 + */ + @ApiOperation("修改定时任务") + @PreAuthorize("@ss.hasPermi('iot:device:edit')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody DeviceJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + job.setUpdateBy(getUsername()); + return toAjax(jobService.updateJob(job)); + } + + /** + * 定时任务状态修改 + */ + @ApiOperation("定时任务状态修改") + @PreAuthorize("@ss.hasPermi('iot:device:edit')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody DeviceJob job) throws SchedulerException + { + DeviceJob newJob = jobService.selectJobById(job.getJobId()); + newJob.setStatus(job.getStatus()); + return toAjax(jobService.changeStatus(newJob)); + } + + /** + * 定时任务立即执行一次 + */ + @ApiOperation("定时任务立即执行一次") + @PreAuthorize("@ss.hasPermi('iot:device:edit')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/run") + public AjaxResult run(@RequestBody DeviceJob job) throws SchedulerException + { + jobService.run(job); + return AjaxResult.success(); + } + + /** + * 删除定时任务 + */ + @ApiOperation("删除定时任务") + @PreAuthorize("@ss.hasPermi('iot:device:remove')") + @Log(title = "定时任务", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobIds}") + public AjaxResult remove(@PathVariable Long[] jobIds) throws SchedulerException, TaskException + { + jobService.deleteJobByIds(jobIds); + return AjaxResult.success(); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/DeviceLogController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/DeviceLogController.java new file mode 100644 index 00000000..4db3b514 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/DeviceLogController.java @@ -0,0 +1,49 @@ +package com.fastbee.data.controller; + +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.iot.domain.DeviceLog; +import com.fastbee.iot.model.HistoryModel; +import com.fastbee.iot.model.MonitorModel; +import com.fastbee.iot.service.IDeviceLogService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; + +/** + * 设备日志Controller + * + * @author kerwincui + * @date 2022-01-13 + */ +@Api(tags = "设备日志模块") +@RestController +@RequestMapping("/iot/deviceLog") +public class DeviceLogController extends BaseController +{ + @Autowired + private IDeviceLogService deviceLogService; + + /** + * 查询设备的监测数据 + */ + @ApiOperation("查询设备的监测数据") + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/monitor") + public TableDataInfo monitorList(DeviceLog deviceLog) + { + List list = deviceLogService.selectMonitorList(deviceLog); + return getDataTable(list); + } + +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/DeviceUserController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/DeviceUserController.java new file mode 100644 index 00000000..c183b25f --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/DeviceUserController.java @@ -0,0 +1,136 @@ +package com.fastbee.data.controller; + +import java.util.List; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.iot.domain.DeviceUser; +import com.fastbee.iot.service.IDeviceUserService; +import com.fastbee.common.core.page.TableDataInfo; + +/** + * 设备用户Controller + * + * @author kerwincui + * @date 2021-12-16 + */ +@Api(tags = "设备用户") +@RestController +@RequestMapping("/iot/deviceUser") +public class DeviceUserController extends BaseController +{ + @Autowired + private IDeviceUserService deviceUserService; + + /** + * 查询设备用户列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/list") + @ApiOperation("设备用户分页列表") + public TableDataInfo list(DeviceUser deviceUser) + { + startPage(); + List list = deviceUserService.selectDeviceUserList(deviceUser); + return getDataTable(list); + } + + /** + * 获取设备分享用户信息 + */ + @GetMapping("/shareUser") + public AjaxResult userList(DeviceUser user) + { + return AjaxResult.success(deviceUserService.selectShareUser(user)); + } + + /** + * 获取设备用户详细信息 根据deviceId 查询的话可能会查出多个 + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/{deviceId}") + @ApiOperation("获取设备用户详情") + public AjaxResult getInfo(@PathVariable("deviceId") Long deviceId) + { + return AjaxResult.success(deviceUserService.selectDeviceUserByDeviceId(deviceId)); + } + + /** + * 获取设备用户详细信息 双主键 device_id 和 user_id + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/{deviceId}/{userId}") + @ApiOperation("获取设备用户详情,根据用户id 和 设备id") + public AjaxResult getInfo(@PathVariable("deviceId") Long deviceId, @PathVariable("userId") Long userId) + { + return AjaxResult.success(deviceUserService.selectDeviceUserByDeviceIdAndUserId(deviceId, userId)); + } + + /** + * 新增设备用户 + */ + @PreAuthorize("@ss.hasPermi('iot:device:share')") + @Log(title = "设备用户", businessType = BusinessType.INSERT) + @PostMapping + @ApiOperation("添加设备用户") + public AjaxResult add(@RequestBody DeviceUser deviceUser) + { + return toAjax(deviceUserService.insertDeviceUser(deviceUser)); + } + + /** + * 新增多个设备用户 + */ + @PreAuthorize("@ss.hasPermi('iot:device:share')") + @Log(title = "设备用户", businessType = BusinessType.INSERT) + @PostMapping("/addDeviceUsers") + @ApiOperation("批量添加设备用户") + public AjaxResult addDeviceUsers(@RequestBody List deviceUsers) + { + return toAjax(deviceUserService.insertDeviceUserList(deviceUsers)); + } + + /** + * 修改设备用户 + */ + @ApiOperation("修改设备用户") + @PreAuthorize("@ss.hasPermi('iot:device:edit')") + @Log(title = "设备用户", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody DeviceUser deviceUser) + { + return toAjax(deviceUserService.updateDeviceUser(deviceUser)); + } + + + /** + * 删除设备用户 + */ + @ApiOperation("删除设备用户") + @PreAuthorize("@ss.hasPermi('iot:device:remove')") + @Log(title = "设备用户", businessType = BusinessType.DELETE) + @DeleteMapping + public AjaxResult remove(@RequestBody DeviceUser deviceUser) + { + int count=deviceUserService.deleteDeviceUser(deviceUser); + if(count==0){ + return AjaxResult.error("设备所有者不能删除"); + }else{ + return AjaxResult.success(); + } + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/EventLogController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/EventLogController.java new file mode 100644 index 00000000..b539097e --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/EventLogController.java @@ -0,0 +1,114 @@ +package com.fastbee.data.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.fastbee.iot.domain.EventLog; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.iot.service.IEventLogService; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.common.core.page.TableDataInfo; + +/** + * 事件日志Controller + * + * @author kerwincui + * @date 2023-03-28 + */ +@Api(tags = "事件日志") +@RestController +@RequestMapping("/iot/event") +public class EventLogController extends BaseController +{ + @Autowired + private IEventLogService eventLogService; + + /** + * 查询事件日志列表 + */ + @ApiOperation("查询事件日志列表") + @PreAuthorize("@ss.hasPermi('iot:event:list')") + @GetMapping("/list") + public TableDataInfo list(EventLog eventLog) + { + startPage(); + List list = eventLogService.selectEventLogList(eventLog); + return getDataTable(list); + } + + /** + * 导出事件日志列表 + */ + @ApiOperation("导出事件日志列表") + @PreAuthorize("@ss.hasPermi('iot:event:export')") + @Log(title = "事件日志", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, EventLog eventLog) + { + List list = eventLogService.selectEventLogList(eventLog); + ExcelUtil util = new ExcelUtil(EventLog.class); + util.exportExcel(response, list, "事件日志数据"); + } + + /** + * 获取事件日志详细信息 + */ + @ApiOperation("获取事件日志详细信息") + @PreAuthorize("@ss.hasPermi('iot:event:query')") + @GetMapping(value = "/{logId}") + public AjaxResult getInfo(@PathVariable("logId") Long logId) + { + return success(eventLogService.selectEventLogByLogId(logId)); + } + + /** + * 新增事件日志 + */ + @ApiOperation("新增事件日志") + @PreAuthorize("@ss.hasPermi('iot:event:add')") + @Log(title = "事件日志", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody EventLog eventLog) + { + return toAjax(eventLogService.insertEventLog(eventLog)); + } + + /** + * 修改事件日志 + */ + @ApiOperation("修改事件日志") + @PreAuthorize("@ss.hasPermi('iot:event:edit')") + @Log(title = "事件日志", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody EventLog eventLog) + { + return toAjax(eventLogService.updateEventLog(eventLog)); + } + + /** + * 删除事件日志 + */ + @ApiOperation("删除事件日志") + @PreAuthorize("@ss.hasPermi('iot:event:remove')") + @Log(title = "事件日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{logIds}") + public AjaxResult remove(@PathVariable Long[] logIds) + { + return toAjax(eventLogService.deleteEventLogByLogIds(logIds)); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/FunctionLogController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/FunctionLogController.java new file mode 100644 index 00000000..f6d8e682 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/FunctionLogController.java @@ -0,0 +1,114 @@ +package com.fastbee.data.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.iot.domain.FunctionLog; +import com.fastbee.iot.service.IFunctionLogService; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.common.core.page.TableDataInfo; + +/** + * 设备服务下发日志Controller + * + * @author kerwincui + * @date 2022-10-22 + */ +@Api(tags = "设备服务下发日志") +@RestController +@RequestMapping("/iot/log") +public class FunctionLogController extends BaseController +{ + @Autowired + private IFunctionLogService functionLogService; + + /** + * 查询设备服务下发日志列表 + */ + @ApiOperation("查询设备服务下发日志列表") + @PreAuthorize("@ss.hasPermi('iot:log:list')") + @GetMapping("/list") + public TableDataInfo list(FunctionLog functionLog) + { + startPage(); + List list = functionLogService.selectFunctionLogList(functionLog); + return getDataTable(list); + } + + /** + * 导出设备服务下发日志列表 + */ + @ApiOperation("导出设备服务下发日志列表") + @PreAuthorize("@ss.hasPermi('iot:log:export')") + @Log(title = "设备服务下发日志", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, FunctionLog functionLog) + { + List list = functionLogService.selectFunctionLogList(functionLog); + ExcelUtil util = new ExcelUtil(FunctionLog.class); + util.exportExcel(response, list, "设备服务下发日志数据"); + } + + /** + * 获取设备服务下发日志详细信息 + */ + @ApiOperation("获取设备服务下发日志详细信息") + @PreAuthorize("@ss.hasPermi('iot:log:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) + { + return AjaxResult.success(functionLogService.selectFunctionLogById(id)); + } + + /** + * 新增设备服务下发日志 + */ + @ApiOperation("新增设备服务下发日志") + @PreAuthorize("@ss.hasPermi('iot:log:add')") + @Log(title = "设备服务下发日志", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody FunctionLog functionLog) + { + return toAjax(functionLogService.insertFunctionLog(functionLog)); + } + + /** + * 修改设备服务下发日志 + */ + @ApiOperation("修改设备服务下发日志") + @PreAuthorize("@ss.hasPermi('iot:log:edit')") + @Log(title = "设备服务下发日志", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody FunctionLog functionLog) + { + return toAjax(functionLogService.updateFunctionLog(functionLog)); + } + + /** + * 删除设备服务下发日志 + */ + @ApiOperation("删除设备服务下发日志") + @PreAuthorize("@ss.hasPermi('iot:log:remove')") + @Log(title = "设备服务下发日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) + { + return toAjax(functionLogService.deleteFunctionLogByIds(ids)); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/GroupController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/GroupController.java new file mode 100644 index 00000000..68c314a9 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/GroupController.java @@ -0,0 +1,140 @@ +package com.fastbee.data.controller; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.fastbee.iot.domain.DeviceGroup; +import com.fastbee.iot.model.DeviceGroupInput; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.iot.domain.Group; +import com.fastbee.iot.service.IGroupService; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.common.core.page.TableDataInfo; + +/** + * 设备分组Controller + * + * @author kerwincui + * @date 2021-12-16 + */ +@Api(tags = "设备分组") +@RestController +@RequestMapping("/iot/group") +public class GroupController extends BaseController +{ + @Autowired + private IGroupService groupService; + + /** + * 查询设备分组列表 + */ + @PreAuthorize("@ss.hasPermi('iot:group:list')") + @GetMapping("/list") + @ApiOperation("分组分页列表") + public TableDataInfo list(Group group) + { + startPage(); + return getDataTable(groupService.selectGroupList(group)); + } + + /** + * 导出设备分组列表 + */ + @PreAuthorize("@ss.hasPermi('iot:group:export')") + @Log(title = "分组", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ApiOperation("导出分组") + public void export(HttpServletResponse response, Group group) + { + List list = groupService.selectGroupList(group); + ExcelUtil util = new ExcelUtil(Group.class); + util.exportExcel(response, list, "设备分组数据"); + } + + /** + * 获取设备分组详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:group:query')") + @GetMapping(value = "/{groupId}") + @ApiOperation("获取分组详情") + public AjaxResult getInfo(@PathVariable("groupId") Long groupId) + { + return AjaxResult.success(groupService.selectGroupByGroupId(groupId)); + } + + /** + * 获取分组下的所有关联设备ID数组 + */ + @PreAuthorize("@ss.hasPermi('iot:group:query')") + @GetMapping(value = "/getDeviceIds/{groupId}") + @ApiOperation("获取分组下的所有关联设备ID数组") + public AjaxResult getDeviceIds(@PathVariable("groupId") Long groupId) + { + return AjaxResult.success(groupService.selectDeviceIdsByGroupId(groupId)); + } + + /** + * 新增设备分组 + */ + @PreAuthorize("@ss.hasPermi('iot:group:add')") + @Log(title = "分组", businessType = BusinessType.INSERT) + @PostMapping + @ApiOperation("添加分组") + public AjaxResult add(@RequestBody Group group) + { + return toAjax(groupService.insertGroup(group)); + } + + /** + * 更新分组下的关联设备 + * @param input + * @return + */ + @PreAuthorize("@ss.hasPermi('iot:group:edit')") + @Log(title = "设备分组", businessType = BusinessType.UPDATE) + @PutMapping("/updateDeviceGroups") + @ApiOperation("更新分组下的关联设备") + public AjaxResult updateDeviceGroups(@RequestBody DeviceGroupInput input){ + return toAjax(groupService.updateDeviceGroups(input)); + } + + /** + * 修改设备分组 + */ + @PreAuthorize("@ss.hasPermi('iot:group:edit')") + @Log(title = "分组", businessType = BusinessType.UPDATE) + @PutMapping + @ApiOperation("修改分组") + public AjaxResult edit(@RequestBody Group group) + { + return toAjax(groupService.updateGroup(group)); + } + + /** + * 删除设备分组 + */ + @PreAuthorize("@ss.hasPermi('iot:group:remove')") + @Log(title = "分组", businessType = BusinessType.DELETE) + @DeleteMapping("/{groupIds}") + @ApiOperation("批量删除设备分组") + public AjaxResult remove(@PathVariable Long[] groupIds) + { + return toAjax(groupService.deleteGroupByGroupIds(groupIds)); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/NewsCategoryController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/NewsCategoryController.java new file mode 100644 index 00000000..f06a148f --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/NewsCategoryController.java @@ -0,0 +1,126 @@ +package com.fastbee.data.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.fastbee.iot.model.IdAndName; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.iot.domain.NewsCategory; +import com.fastbee.iot.service.INewsCategoryService; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.common.core.page.TableDataInfo; + +/** + * 新闻分类Controller + * + * @author kerwincui + * @date 2022-04-09 + */ +@Api(tags = "新闻分类") +@RestController +@RequestMapping("/iot/newsCategory") +public class NewsCategoryController extends BaseController +{ + @Autowired + private INewsCategoryService newsCategoryService; + + /** + * 查询新闻分类列表 + */ + @PreAuthorize("@ss.hasPermi('iot:newsCategory:list')") + @GetMapping("/list") + @ApiOperation("新闻分类分页列表") + public TableDataInfo list(NewsCategory newsCategory) + { + startPage(); + List list = newsCategoryService.selectNewsCategoryList(newsCategory); + return getDataTable(list); + } + + /** + * 查询新闻分类简短列表 + */ + @PreAuthorize("@ss.hasPermi('iot:news:list')") + @GetMapping("/newsCategoryShortList") + @ApiOperation("分类简短列表") + public AjaxResult newsCategoryShortList() + { + List list = newsCategoryService.selectNewsCategoryShortList(); + return AjaxResult.success(list); + } + + /** + * 导出新闻分类列表 + */ + @PreAuthorize("@ss.hasPermi('iot:newsCategory:export')") + @Log(title = "新闻分类", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, NewsCategory newsCategory) + { + List list = newsCategoryService.selectNewsCategoryList(newsCategory); + ExcelUtil util = new ExcelUtil(NewsCategory.class); + util.exportExcel(response, list, "新闻分类数据"); + } + + /** + * 获取新闻分类详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:newsCategory:query')") + @GetMapping(value = "/{categoryId}") + @ApiOperation("新闻分类详情") + public AjaxResult getInfo(@PathVariable("categoryId") Long categoryId) + { + return AjaxResult.success(newsCategoryService.selectNewsCategoryByCategoryId(categoryId)); + } + + /** + * 新增新闻分类 + */ + @PreAuthorize("@ss.hasPermi('iot:newsCategory:add')") + @Log(title = "新闻分类", businessType = BusinessType.INSERT) + @PostMapping + @ApiOperation("添加新闻分类") + public AjaxResult add(@RequestBody NewsCategory newsCategory) + { + return toAjax(newsCategoryService.insertNewsCategory(newsCategory)); + } + + /** + * 修改新闻分类 + */ + @PreAuthorize("@ss.hasPermi('iot:newsCategory:edit')") + @Log(title = "新闻分类", businessType = BusinessType.UPDATE) + @PutMapping + @ApiOperation("修改新闻分类") + public AjaxResult edit(@RequestBody NewsCategory newsCategory) + { + return toAjax(newsCategoryService.updateNewsCategory(newsCategory)); + } + + /** + * 删除新闻分类 + */ + @PreAuthorize("@ss.hasPermi('iot:newsCategory:remove')") + @Log(title = "新闻分类", businessType = BusinessType.DELETE) + @DeleteMapping("/{categoryIds}") + @ApiOperation("删除新闻分类") + public AjaxResult remove(@PathVariable Long[] categoryIds) + { + return newsCategoryService.deleteNewsCategoryByCategoryIds(categoryIds); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/NewsController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/NewsController.java new file mode 100644 index 00000000..5009d09c --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/NewsController.java @@ -0,0 +1,141 @@ +package com.fastbee.data.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.fastbee.iot.model.CategoryNews; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.iot.domain.News; +import com.fastbee.iot.service.INewsService; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.common.core.page.TableDataInfo; + +/** + * 新闻资讯Controller + * + * @author kerwincui + * @date 2022-04-09 + */ +@Api(tags = "新闻资讯") +@RestController +@RequestMapping("/iot/news") +public class NewsController extends BaseController +{ + @Autowired + private INewsService newsService; + + /** + * 查询新闻资讯列表 + */ + @PreAuthorize("@ss.hasPermi('iot:news:list')") + @GetMapping("/list") + @ApiOperation("新闻分页列表") + public TableDataInfo list(News news) + { + startPage(); + List list = newsService.selectNewsList(news); + return getDataTable(list); + } + + /** + * 查询轮播的新闻资讯 + */ + @PreAuthorize("@ss.hasPermi('iot:news:list')") + @GetMapping("/bannerList") + @ApiOperation("轮播新闻列表") + public AjaxResult bannerList() + { + News news=new News(); + news.setIsBanner(1); + news.setStatus(1); + List list = newsService.selectNewsList(news); + return AjaxResult.success(list); + } + + /** + * 查询置顶的新闻资讯 + */ + @PreAuthorize("@ss.hasPermi('iot:news:list')") + @GetMapping("/topList") + @ApiOperation("置顶新闻列表") + public AjaxResult topList() + { + List list = newsService.selectTopNewsList(); + return AjaxResult.success(list); + } + + /** + * 导出新闻资讯列表 + */ + @PreAuthorize("@ss.hasPermi('iot:news:export')") + @Log(title = "新闻资讯", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, News news) + { + List list = newsService.selectNewsList(news); + ExcelUtil util = new ExcelUtil(News.class); + util.exportExcel(response, list, "新闻资讯数据"); + } + + /** + * 获取新闻资讯详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:news:query')") + @GetMapping(value = "/{newsId}") + @ApiOperation("新闻详情") + public AjaxResult getInfo(@PathVariable("newsId") Long newsId) + { + return AjaxResult.success(newsService.selectNewsByNewsId(newsId)); + } + + /** + * 新增新闻资讯 + */ + @PreAuthorize("@ss.hasPermi('iot:news:add')") + @Log(title = "新闻资讯", businessType = BusinessType.INSERT) + @PostMapping + @ApiOperation("添加新闻资讯") + public AjaxResult add(@RequestBody News news) + { + return toAjax(newsService.insertNews(news)); + } + + /** + * 修改新闻资讯 + */ + @PreAuthorize("@ss.hasPermi('iot:news:edit')") + @Log(title = "新闻资讯", businessType = BusinessType.UPDATE) + @PutMapping + @ApiOperation("修改新闻资讯") + public AjaxResult edit(@RequestBody News news) + { + return toAjax(newsService.updateNews(news)); + } + + /** + * 删除新闻资讯 + */ + @PreAuthorize("@ss.hasPermi('iot:news:remove')") + @Log(title = "新闻资讯", businessType = BusinessType.DELETE) + @DeleteMapping("/{newsIds}") + @ApiOperation("删除新闻资讯") + public AjaxResult remove(@PathVariable Long[] newsIds) + { + return toAjax(newsService.deleteNewsByNewsIds(newsIds)); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/OauthClientDetailsController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/OauthClientDetailsController.java new file mode 100644 index 00000000..fa31282e --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/OauthClientDetailsController.java @@ -0,0 +1,114 @@ +package com.fastbee.data.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.iot.domain.OauthClientDetails; +import com.fastbee.iot.service.IOauthClientDetailsService; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.common.core.page.TableDataInfo; + +/** + * 云云对接Controller + * + * @author kerwincui + * @date 2022-02-07 + */ +@Api(tags = "云云对接") +@RestController +@RequestMapping("/iot/clientDetails") +public class OauthClientDetailsController extends BaseController +{ + @Autowired + private IOauthClientDetailsService oauthClientDetailsService; + + /** + * 查询云云对接列表 + */ + @ApiOperation("查询云云对接列表") + @PreAuthorize("@ss.hasPermi('iot:clientDetails:list')") + @GetMapping("/list") + public TableDataInfo list(OauthClientDetails oauthClientDetails) + { + startPage(); + List list = oauthClientDetailsService.selectOauthClientDetailsList(oauthClientDetails); + return getDataTable(list); + } + + /** + * 导出云云对接列表 + */ + @ApiOperation("导出云云对接列表") + @PreAuthorize("@ss.hasPermi('iot:clientDetails:export')") + @Log(title = "云云对接", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, OauthClientDetails oauthClientDetails) + { + List list = oauthClientDetailsService.selectOauthClientDetailsList(oauthClientDetails); + ExcelUtil util = new ExcelUtil(OauthClientDetails.class); + util.exportExcel(response, list, "云云对接数据"); + } + + /** + * 获取云云对接详细信息 + */ + @ApiOperation("获取云云对接详细信息") + @PreAuthorize("@ss.hasPermi('iot:clientDetails:query')") + @GetMapping(value = "/{clientId}") + public AjaxResult getInfo(@PathVariable("clientId") String clientId) + { + return AjaxResult.success(oauthClientDetailsService.selectOauthClientDetailsByClientId(clientId)); + } + + /** + * 新增云云对接 + */ + @ApiOperation("新增云云对接") + @PreAuthorize("@ss.hasPermi('iot:clientDetails:add')") + @Log(title = "云云对接", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody OauthClientDetails oauthClientDetails) + { + return toAjax(oauthClientDetailsService.insertOauthClientDetails(oauthClientDetails)); + } + + /** + * 修改云云对接 + */ + @ApiOperation("修改云云对接") + @PreAuthorize("@ss.hasPermi('iot:clientDetails:edit')") + @Log(title = "云云对接", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody OauthClientDetails oauthClientDetails) + { + return toAjax(oauthClientDetailsService.updateOauthClientDetails(oauthClientDetails)); + } + + /** + * 修改云云对接 + */ + @ApiOperation("修改云云对接") + @PreAuthorize("@ss.hasPermi('iot:clientDetails:remove')") + @Log(title = "云云对接", businessType = BusinessType.DELETE) + @DeleteMapping("/{clientIds}") + public AjaxResult remove(@PathVariable String[] clientIds) + { + return toAjax(oauthClientDetailsService.deleteOauthClientDetailsByClientIds(clientIds)); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ProductAuthorizeController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ProductAuthorizeController.java new file mode 100644 index 00000000..8cb063e6 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ProductAuthorizeController.java @@ -0,0 +1,121 @@ +package com.fastbee.data.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.fastbee.iot.model.ProductAuthorizeVO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.poi.ss.formula.functions.T; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.iot.domain.ProductAuthorize; +import com.fastbee.iot.service.IProductAuthorizeService; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.common.core.page.TableDataInfo; + +/** + * 产品授权码Controller + * + * @author kami + * @date 2022-04-11 + */ +@Api(tags = "产品授权码") +@RestController +@RequestMapping("/iot/authorize") +public class ProductAuthorizeController extends BaseController +{ + @Autowired + private IProductAuthorizeService productAuthorizeService; + + /** + * 查询产品授权码列表 + */ + @ApiOperation("查询产品授权码列表") + @PreAuthorize("@ss.hasPermi('iot:product:list')") + @GetMapping("/list") + public TableDataInfo list(ProductAuthorize productAuthorize) + { + startPage(); + List list = productAuthorizeService.selectProductAuthorizeList(productAuthorize); + return getDataTable(list); + } + + /** + * 导出产品授权码列表 + */ + @ApiOperation("导出产品授权码列表") + @PreAuthorize("@ss.hasPermi('iot:authorize:export')") + @Log(title = "产品授权码", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, ProductAuthorize productAuthorize) + { + List list = productAuthorizeService.selectProductAuthorizeList(productAuthorize); + ExcelUtil util = new ExcelUtil(ProductAuthorize.class); + util.exportExcel(response, list, "产品授权码数据"); + } + + /** + * 获取产品授权码详细信息 + */ + @ApiOperation("获取产品授权码详细信息") + @PreAuthorize("@ss.hasPermi('iot:authorize:query')") + @GetMapping(value = "/{authorizeId}") + public AjaxResult getInfo(@PathVariable("authorizeId") Long authorizeId) + { + return AjaxResult.success(productAuthorizeService.selectProductAuthorizeByAuthorizeId(authorizeId)); + } + + /** + * 新增产品授权码 + */ + @ApiOperation("新增产品授权码") + @PreAuthorize("@ss.hasPermi('iot:authorize:add')") + @Log(title = "产品授权码", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody ProductAuthorize productAuthorize) + { + return toAjax(productAuthorizeService.insertProductAuthorize(productAuthorize)); + } + + /** + * 根据数量批量新增产品授权码 + */ + @ApiOperation("根据数量批量新增产品授权码") + @PreAuthorize("@ss.hasPermi('iot:authorize:add')") + @Log(title = "根据数量批量新增产品授权码", businessType = BusinessType.INSERT) + @PostMapping("addProductAuthorizeByNum") + public AjaxResult addProductAuthorizeByNum(@RequestBody ProductAuthorizeVO productAuthorizeVO) + { + return toAjax(productAuthorizeService.addProductAuthorizeByNum(productAuthorizeVO)); + } + + /** + * 修改产品授权码 + */ + @ApiOperation("修改产品授权码") + @PreAuthorize("@ss.hasPermi('iot:authorize:edit')") + @Log(title = "产品授权码", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody ProductAuthorize productAuthorize) + { + return toAjax(productAuthorizeService.updateProductAuthorize(productAuthorize)); + } + + /** + * 删除产品授权码 + */ + @ApiOperation("删除产品授权码") + @PreAuthorize("@ss.hasPermi('iot:authorize:remove')") + @Log(title = "产品授权码", businessType = BusinessType.DELETE) + @DeleteMapping("/{authorizeIds}") + public AjaxResult remove(@PathVariable Long[] authorizeIds) + { + return toAjax(productAuthorizeService.deleteProductAuthorizeByAuthorizeIds(authorizeIds)); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ProductController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ProductController.java new file mode 100644 index 00000000..08f82d55 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ProductController.java @@ -0,0 +1,154 @@ +package com.fastbee.data.controller; + +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.iot.domain.Product; +import com.fastbee.iot.model.ChangeProductStatusModel; +import com.fastbee.iot.model.IdAndName; +import com.fastbee.iot.service.IProductService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 产品Controller + * + * @author kerwincui + * @date 2021-12-16 + */ +@Api(tags = "产品管理") +@RestController +@RequestMapping("/iot/product") +public class ProductController extends BaseController +{ + @Autowired + private IProductService productService; + + /** + * 查询产品列表 + */ + @GetMapping("/list") + @ApiOperation("产品分页列表") + public TableDataInfo list(Product product) + { + startPage(); + return getDataTable(productService.selectProductList(product)); + } + + /** + * 查询产品简短列表 + */ + @PreAuthorize("@ss.hasPermi('iot:product:list')") + @GetMapping("/shortList") + @ApiOperation("产品简短列表") + public AjaxResult shortList(Product product) + { + + List list = productService.selectProductShortList(); + return AjaxResult.success(list); + } + + /** + * 导出产品列表 + */ + @PreAuthorize("@ss.hasPermi('iot:product:export')") + @Log(title = "产品", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ApiOperation("导出产品") + public void export(HttpServletResponse response, Product product) + { + List list = productService.selectProductList(product); + ExcelUtil util = new ExcelUtil(Product.class); + util.exportExcel(response, list, "产品数据"); + } + + /** + * 获取产品详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:product:query')") + @GetMapping(value = "/{productId}") + @ApiOperation("获取产品详情") + public AjaxResult getInfo(@PathVariable("productId") Long productId) + { + return AjaxResult.success(productService.selectProductByProductId(productId)); + } + + /** + * 新增产品 + */ + @PreAuthorize("@ss.hasPermi('iot:product:add')") + @Log(title = "添加产品", businessType = BusinessType.INSERT) + @PostMapping + @ApiOperation("添加产品") + public AjaxResult add(@RequestBody Product product) + { + return AjaxResult.success(productService.insertProduct(product)); + } + + /** + * 修改产品 + */ + @PreAuthorize("@ss.hasPermi('iot:product:edit')") + @Log(title = "修改产品", businessType = BusinessType.UPDATE) + @PutMapping + @ApiOperation("修改产品") + public AjaxResult edit(@RequestBody Product product) + { + return toAjax(productService.updateProduct(product)); + } + + /** + * 获取产品下面的设备数量 + */ + @PreAuthorize("@ss.hasPermi('iot:product:query')") + @GetMapping("/deviceCount/{productId}") + @ApiOperation("获取产品下面的设备数量") + public AjaxResult deviceCount(@PathVariable Long productId) + { + return AjaxResult.success(productService.selectDeviceCountByProductId(productId)); + } + + /** + * 发布产品 + */ + @PreAuthorize("@ss.hasPermi('iot:product:add')") + @Log(title = "更新产品状态", businessType = BusinessType.UPDATE) + @PutMapping("/status") + @ApiOperation("更新产品状态") + public AjaxResult changeProductStatus(@RequestBody ChangeProductStatusModel model) + { + return productService.changeProductStatus(model); + } + + /** + * 删除产品 + */ + @PreAuthorize("@ss.hasPermi('iot:product:remove')") + @Log(title = "产品", businessType = BusinessType.DELETE) + @DeleteMapping("/{productIds}") + @ApiOperation("批量删除产品") + public AjaxResult remove(@PathVariable Long[] productIds) + { + return productService.deleteProductByProductIds(productIds); + } + + + /** + * 查询采集点模板关联的所有产品 + */ + @PreAuthorize("@ss.hasPermi('iot:product:list')") + @GetMapping("/queryByTemplateId") + @ApiOperation("查询采集点模板id关联的所有产品") + public AjaxResult queryByTemplateId(@RequestParam Long templateId){ + return AjaxResult.success(productService.selectByTempleId(templateId)); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/SocialLoginController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/SocialLoginController.java new file mode 100644 index 00000000..f54f2f95 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/SocialLoginController.java @@ -0,0 +1,104 @@ +package com.fastbee.data.controller; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.model.BindLoginBody; +import com.fastbee.common.core.domain.model.BindRegisterBody; +import com.fastbee.iot.service.ISocialLoginService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import me.zhyd.oauth.model.AuthCallback; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 第三方登录接口Controller + * + * @author json + * @date 2022-04-12 + */ +@Api(tags = "第三方登录接口") +@RestController +@RequestMapping("/auth") +public class SocialLoginController { + + @Autowired + private ISocialLoginService iSocialLoginService; + + + @GetMapping("/render/{source}") + @ApiOperation("微信登录跳转二维码") + @ApiImplicitParam(name = "source", value = "登录类型", required = true, dataType = "String", paramType = "path", dataTypeClass = String.class) + public void renderAuth(HttpServletResponse httpServletResponse, HttpServletRequest httpServletRequest, @PathVariable String source) throws IOException { + // 生成授权页面 + httpServletResponse.sendRedirect(iSocialLoginService.renderAuth(source, httpServletRequest)); + } + + @GetMapping("/callback/{source}") + @ApiOperation("微信登录扫码回调api") + @ApiImplicitParam(name = "source", value = "平台来源", required = true, dataType = "String", paramType = "path", dataTypeClass = String.class) + public void login(@PathVariable("source") String source, AuthCallback authCallback, HttpServletResponse httpServletResponse, HttpServletRequest httpServletRequest) throws IOException { + //回调接口 + httpServletResponse.sendRedirect(iSocialLoginService.callback(source, authCallback, httpServletRequest)); + } + + @GetMapping("/checkBindId/{bindId}") + @ApiOperation("检查bindId") + @ApiImplicitParam(name = "bindId", value = "绑定ID", required = true, dataType = "String", paramType = "path", dataTypeClass = String.class) + public AjaxResult checkBindId(@PathVariable String bindId) { + return iSocialLoginService.checkBindId(bindId); + } + + @GetMapping("/getErrorMsg/{errorId}") + @ApiOperation("获取errorMsg") + @ApiImplicitParam(name = "errorId", value = "错误提示ID", required = true, dataType = "String", paramType = "path", dataTypeClass = String.class) + public AjaxResult getErrorMsg(@PathVariable String errorId) { + return iSocialLoginService.getErrorMsg(errorId); + } + + /** + * 已经绑定账户,跳转登录接口 + * + * @param loginId + * @return + */ + @GetMapping("/login/{loginId}") + @ApiOperation("跳转登录api") + @ApiImplicitParam(name = "loginId", value = "登录Id", required = true, dataType = "String", paramType = "path", dataTypeClass = String.class) + public AjaxResult socialLogin(@PathVariable String loginId) { + // 生成授权页面 + return iSocialLoginService.socialLogin(loginId); + } + + /** + * 登录方法 + * + * @param bindLoginBody 绑定登录信息 + * @return 结果 + */ + @ApiOperation("绑定登录方法") + @PostMapping("/bind/login") + public AjaxResult bindLogin(@RequestBody BindLoginBody bindLoginBody) { + return iSocialLoginService.bindLogin(bindLoginBody); + } + + /** + * 注册绑定接口 + * + * @param bindRegisterBody 注册信息 + * @return 注册绑定信息 + */ + @ApiOperation("注册绑定") + @PostMapping("/bind/register") + public AjaxResult bindRegister(@RequestBody BindRegisterBody bindRegisterBody) { + return iSocialLoginService.bindRegister(bindRegisterBody); + } + +} + + + diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/SocialPlatformController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/SocialPlatformController.java new file mode 100644 index 00000000..873935ad --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/SocialPlatformController.java @@ -0,0 +1,109 @@ +package com.fastbee.data.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.iot.domain.SocialPlatform; +import com.fastbee.iot.service.ISocialPlatformService; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.common.core.page.TableDataInfo; + +/** + * 第三方登录平台控制Controller + * + * @author kerwincui + * @date 2022-04-11 + */ +@Api(tags = "第三方登录平台") +@RestController +@RequestMapping("/iot/platform") +public class SocialPlatformController extends BaseController { + @Autowired + private ISocialPlatformService socialPlatformService; + + /** + * 查询第三方登录平台控制列表 + */ + @PreAuthorize("@ss.hasPermi('iot:platform:list')") + @GetMapping("/list") + @ApiOperation("第三方登录平台分页列表") + public TableDataInfo list(SocialPlatform socialPlatform) { + startPage(); + List list = socialPlatformService.selectSocialPlatformList(socialPlatform); + return getDataTable(list); + } + + /** + * 导出第三方登录平台控制列表 + */ + @ApiOperation("导出第三方登录平台控制列表") + @PreAuthorize("@ss.hasPermi('iot:platform:export')") + @Log(title = "第三方登录平台控制", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SocialPlatform socialPlatform) { + List list = socialPlatformService.selectSocialPlatformList(socialPlatform); + ExcelUtil util = new ExcelUtil(SocialPlatform.class); + util.exportExcel(response, list, "第三方登录平台控制数据"); + } + + /** + * 获取第三方登录平台控制详细信息 + */ + @ApiOperation("获取第三方登录平台控制详细信息") + @PreAuthorize("@ss.hasPermi('iot:platform:query')") + @GetMapping(value = "/{socialPlatformId}") + public AjaxResult getInfo(@PathVariable("socialPlatformId") Long socialPlatformId) { + return AjaxResult.success(socialPlatformService.selectSocialPlatformBySocialPlatformId(socialPlatformId)); + } + + /** + * 新增第三方登录平台控制 + */ + @ApiOperation("新增第三方登录平台控制") + @PreAuthorize("@ss.hasPermi('iot:platform:add')") + @Log(title = "第三方登录平台控制", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SocialPlatform socialPlatform) { + socialPlatform.setCreateBy(getUsername()); + return toAjax(socialPlatformService.insertSocialPlatform(socialPlatform)); + } + + /** + * 修改第三方登录平台控制 + */ + @ApiOperation("修改第三方登录平台控制") + @PreAuthorize("@ss.hasPermi('iot:platform:edit')") + @Log(title = "第三方登录平台控制", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SocialPlatform socialPlatform) { + socialPlatform.setUpdateBy(getUsername()); + return toAjax(socialPlatformService.updateSocialPlatform(socialPlatform)); + } + + /** + * 删除第三方登录平台控制 + */ + @ApiOperation("删除第三方登录平台控制") + @PreAuthorize("@ss.hasPermi('iot:platform:remove')") + @Log(title = "第三方登录平台控制", businessType = BusinessType.DELETE) + @DeleteMapping("/{socialPlatformIds}") + public AjaxResult remove(@PathVariable Long[] socialPlatformIds) { + return toAjax(socialPlatformService.deleteSocialPlatformBySocialPlatformIds(socialPlatformIds)); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ThingsModelController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ThingsModelController.java new file mode 100644 index 00000000..7f2b6eaa --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ThingsModelController.java @@ -0,0 +1,180 @@ +package com.fastbee.data.controller; + +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.iot.domain.ThingsModel; +import com.fastbee.iot.model.ImportThingsModelInput; +import com.fastbee.iot.model.ThingsModelPerm; +import com.fastbee.iot.model.varTemp.SyncModel; +import com.fastbee.iot.service.IThingsModelService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.Arrays; +import java.util.List; + +/** + * 物模型Controller + * + * @author kerwincui + * @date 2021-12-16 + */ +@RestController +@RequestMapping("/iot/model") +@Api(tags="产品物模型") +public class ThingsModelController extends BaseController +{ + @Autowired + private IThingsModelService thingsModelService; + + /** + * 查询物模型列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/list") + @ApiOperation("产品物模型分页列表") + public TableDataInfo list(ThingsModel thingsModel) + { + startPage(); + List list = thingsModelService.selectThingsModelList(thingsModel); + return getDataTable(list); + } + + /** + * 查询物模型对应设备分享权限 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/permList/{productId}") + @ApiOperation("查询物模型对应设备分享权限") + public AjaxResult permList(@PathVariable Long productId) + { + List list = thingsModelService.selectThingsModelPermList(productId); + return AjaxResult.success(list); + } + + /** + * 获取物模型详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/{modelId}") + @ApiOperation("获取产品物模型详情") + public AjaxResult getInfo(@PathVariable("modelId") Long modelId) + { + return AjaxResult.success(thingsModelService.selectThingsModelByModelId(modelId)); + } + + /** + * 新增物模型 + */ + @PreAuthorize("@ss.hasPermi('iot:device:add')") + @Log(title = "物模型", businessType = BusinessType.INSERT) + @PostMapping + @ApiOperation("添加产品物模型") + public AjaxResult add(@RequestBody ThingsModel thingsModel) + { + int result=thingsModelService.insertThingsModel(thingsModel); + if(result==1){ + return AjaxResult.success(); + }else if(result==2){ + return AjaxResult.error("产品下的标识符不能重复"); + }else{ + return AjaxResult.error(); + } + } + + @Log(title = "导入物模型",businessType = BusinessType.INSERT) + @PostMapping("/import") + @ApiOperation("导入通用物模型") + public AjaxResult ImportByTemplateIds(@RequestBody ImportThingsModelInput input){ + int repeatCount=thingsModelService.importByTemplateIds(input); + if(repeatCount==0){ + return AjaxResult.success("数据导入成功"); + }else{ + return AjaxResult.success(repeatCount+"条数据未导入,标识符重复"); + } + } + + /** + * 修改物模型 + */ + @PreAuthorize("@ss.hasPermi('iot:device:edit')") + @Log(title = "物模型", businessType = BusinessType.UPDATE) + @PutMapping + @ApiOperation("修改产品物模型") + public AjaxResult edit(@RequestBody ThingsModel thingsModel) + { + int result=thingsModelService.updateThingsModel(thingsModel); + if(result==1){ + return AjaxResult.success(); + }else if(result==2){ + return AjaxResult.error("产品下的标识符不能重复"); + }else{ + return AjaxResult.error(); + } + } + + /** + * 删除物模型 + */ + @PreAuthorize("@ss.hasPermi('iot:device:remove')") + @Log(title = "物模型", businessType = BusinessType.DELETE) + @DeleteMapping("/{modelIds}") + @ApiOperation("批量删除产品物模型") + public AjaxResult remove(@PathVariable Long[] modelIds) + { + return toAjax(thingsModelService.deleteThingsModelByModelIds(modelIds)); + } + + /** + * 获取缓存的JSON物模型 + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/cache/{productId}") + @ApiOperation("获取缓存的JSON物模型") + public AjaxResult getCacheThingsModelByProductId(@PathVariable("productId") Long productId) + { + return AjaxResult.success("操作成功",thingsModelService.getCacheThingsModelByProductId(productId)); + } + + @ApiOperation(value = "物模型导入模板") + @PostMapping("/temp") + public void temp(HttpServletResponse response){ + ExcelUtil excelUtil = new ExcelUtil<>(ThingsModel.class); + excelUtil.importTemplateExcel(response,"采集点"); + } + + + /** + * 导入采集点 + */ + @PreAuthorize("@ss.hasPermi('iot:point:import')") + @ApiOperation(value = "采集点导入") + @PostMapping(value = "/importData") + public AjaxResult importData(MultipartFile file, Integer tempSlaveId) throws Exception{ + ExcelUtil excelUtil = new ExcelUtil<>(ThingsModel.class); + List list = excelUtil.importExcel(file.getInputStream()); + String result = thingsModelService.importData(list, tempSlaveId); + return AjaxResult.success(result); + } + + @PreAuthorize("@ss.hasPermi('iot:device:edit')") + @Log(title = "物模型", businessType = BusinessType.UPDATE) + @PostMapping("/synchron") + @ApiOperation("同步采集点模板至产品物模型") + public AjaxResult synchron(@RequestBody SyncModel model) + { + thingsModelService.synchronizeVarTempToProduct(model.getProductIds(), model.getTemplateId()); + return AjaxResult.success(); + } + +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ThingsModelTemplateController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ThingsModelTemplateController.java new file mode 100644 index 00000000..74cf2c9e --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ThingsModelTemplateController.java @@ -0,0 +1,147 @@ +package com.fastbee.data.controller; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.iot.domain.ThingsModel; +import com.fastbee.iot.domain.VarTemp; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.iot.domain.ThingsModelTemplate; +import com.fastbee.iot.service.IThingsModelTemplateService; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.common.core.page.TableDataInfo; +import org.springframework.web.multipart.MultipartFile; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; + +/** + * 通用物模型Controller + * + * @author kerwincui + * @date 2021-12-16 + */ +@RestController +@RequestMapping("/iot/template") +@Api(tags = "通用物模型") +public class ThingsModelTemplateController extends BaseController { + @Autowired + private IThingsModelTemplateService thingsModelTemplateService; + + /** + * 查询通用物模型列表 + */ + @PreAuthorize("@ss.hasPermi('iot:template:list')") + @GetMapping("/list") + @ApiOperation("通用物模型分页列表") + public TableDataInfo list(ThingsModelTemplate thingsModelTemplate) { + startPage(); + return getDataTable(thingsModelTemplateService.selectThingsModelTemplateList(thingsModelTemplate)); + } + + /** + * 导出通用物模型列表 + */ + @PreAuthorize("@ss.hasPermi('iot:template:export')") + @Log(title = "通用物模型", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ApiOperation("导出通用物模型") + public void export(HttpServletResponse response, ThingsModelTemplate thingsModelTemplate) { + List list = thingsModelTemplateService.selectThingsModelTemplateList(thingsModelTemplate); + ExcelUtil util = new ExcelUtil(ThingsModelTemplate.class); + util.exportExcel(response, list, "通用物模型数据"); + } + + /** + * 获取通用物模型详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:template:query')") + @GetMapping(value = "/{templateId}") + @ApiOperation("获取通用物模型详情") + public AjaxResult getInfo(@PathVariable("templateId") Long templateId) { + return AjaxResult.success(thingsModelTemplateService.selectThingsModelTemplateByTemplateId(templateId)); + } + + /** + * 新增通用物模型 + */ + @PreAuthorize("@ss.hasPermi('iot:template:add')") + @Log(title = "通用物模型", businessType = BusinessType.INSERT) + @PostMapping + @ApiOperation("添加通用物模型") + public AjaxResult add(@RequestBody ThingsModelTemplate thingsModelTemplate) { + return toAjax(thingsModelTemplateService.insertThingsModelTemplate(thingsModelTemplate)); + } + + /** + * 修改通用物模型 + */ + @PreAuthorize("@ss.hasPermi('iot:template:edit')") + @Log(title = "通用物模型", businessType = BusinessType.UPDATE) + @PutMapping + @ApiOperation("修改通用物模型") + public AjaxResult edit(@RequestBody ThingsModelTemplate thingsModelTemplate) { + return toAjax(thingsModelTemplateService.updateThingsModelTemplate(thingsModelTemplate)); + } + + /** + * 删除通用物模型 + */ + @PreAuthorize("@ss.hasPermi('iot:template:remove')") + @Log(title = "通用物模型", businessType = BusinessType.DELETE) + @DeleteMapping("/{templateIds}") + @ApiOperation("批量删除通用物模型") + public AjaxResult remove(@PathVariable Long[] templateIds) { + return toAjax(thingsModelTemplateService.deleteThingsModelTemplateByTemplateIds(templateIds)); + } + + @ApiOperation(value = "物模型导入模板") + @PostMapping("/temp") + public void temp(HttpServletResponse response) { + ExcelUtil excelUtil = new ExcelUtil<>(ThingsModelTemplate.class); + excelUtil.importTemplateExcel(response, "采集点"); + } + + + /** + * 导入采集点 + */ + @PreAuthorize("@ss.hasPermi('iot:template:add')") + @ApiOperation(value = "采集点导入") + @PostMapping(value = "/importData") + public AjaxResult importData(MultipartFile file, String tempSlaveId) throws Exception { + ExcelUtil excelUtil = new ExcelUtil<>(ThingsModelTemplate.class); + List list = excelUtil.importExcel(file.getInputStream()); + String result = thingsModelTemplateService.importData(list, tempSlaveId); + return AjaxResult.success(result); + } + + /** + * 根据模板id查询采集点数据 + */ + @ApiOperation("根据模板id查询采集点数据") + @PreAuthorize("@ss.hasPermi('iot:template:query')") + @GetMapping("/getPoints") + public TableDataInfo getPoints(VarTemp varTemp) { + startPage(); + List result = thingsModelTemplateService.selectAllByTemplateId(varTemp.getTemplateId()); + return getDataTable(result); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ToolController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ToolController.java new file mode 100644 index 00000000..fa85fcb6 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/ToolController.java @@ -0,0 +1,450 @@ +package com.fastbee.data.controller; + +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.ProtocolDeCodeService; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.config.RuoYiConfig; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.iot.response.DeCodeBo; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.core.protocol.modbus.ModbusCode; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.exception.file.FileNameLengthLimitExceededException; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.file.FileUploadUtils; +import com.fastbee.common.utils.file.FileUtils; +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.model.*; +import com.fastbee.iot.model.ThingsModels.ThingsModelShadow; +import com.fastbee.iot.service.IDeviceService; +import com.fastbee.iot.service.IToolService; +import com.fastbee.iot.util.VelocityInitializer; +import com.fastbee.iot.util.VelocityUtils; +import com.fastbee.mq.model.ReportDataBo; +import com.fastbee.mq.mqttClient.MqttClientConfig; +import com.fastbee.mq.mqttClient.PubMqttClient; +import com.fastbee.mq.service.IMqttMessagePublish; +import com.fastbee.mqtt.model.PushMessageBo; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.netty.buffer.ByteBufUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.io.IOUtils; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import static com.fastbee.common.utils.file.FileUploadUtils.getExtension; + +/** + * 产品分类Controller + * + * @author kerwincui + * @date 2021-12-16 + */ +@Api(tags = "工具相关") +@RestController +@RequestMapping("/iot/tool") +public class ToolController extends BaseController { + private static final Logger log = LoggerFactory.getLogger(ToolController.class); + + @Autowired + private IDeviceService deviceService; + @Autowired + private IMqttMessagePublish messagePublish; + @Autowired + private MqttClientConfig mqttConfig; + @Autowired + private IToolService toolService; + // 令牌秘钥 + @Value("${token.secret}") + private String secret; + + @Resource + private ProtocolDeCodeService deCodeService; + @Resource + private PubMqttClient mqttClient; + + /** + * 用户注册 + */ + @ApiOperation("用户注册") + @PostMapping("/register") + public AjaxResult register(@RequestBody RegisterUserInput user) { + String msg = toolService.register(user); + return StringUtils.isEmpty(msg) ? success() : error(msg); + } + + /** + * 获取用户列表 + */ + @ApiOperation("获取用户列表") + @GetMapping("/userList") + public TableDataInfo list(SysUser user) + { + startPage(); + List list = toolService.selectUserList(user); + return getDataTable(list); + } + + @ApiOperation("mqtt认证") + @PostMapping("/mqtt/auth") + public ResponseEntity mqttAuth(@RequestParam String clientid, @RequestParam String username, @RequestParam String password) throws Exception { + if (clientid.startsWith("server")) { + // 服务端认证:配置的账号密码认证 + if (mqttConfig.getUsername().equals(username) && mqttConfig.getPassword().equals(password)) { + log.info("-----------服务端mqtt认证成功,clientId:" + clientid + "---------------"); + return ResponseEntity.ok().body("ok"); + } else { + return toolService.returnUnauthorized(new MqttAuthenticationModel(clientid, username, password), "mqtt账号和密码与认证服务器配置不匹配"); + } + } else if (clientid.startsWith("web") || clientid.startsWith("phone")) { + // web端和移动端认证:token认证 + String token = password; + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) { + token = token.replace(Constants.TOKEN_PREFIX, ""); + } + try { + Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + log.info("-----------移动端/Web端mqtt认证成功,clientId:" + clientid + "---------------"); + return ResponseEntity.ok().body("ok"); + } catch (Exception ex) { + return toolService.returnUnauthorized(new MqttAuthenticationModel(clientid, username, password), ex.getMessage()); + } + } else { + // 替换为整合之后的接口 + return toolService.clientAuth(clientid, username, password); + } + } + @ApiOperation("mqtt认证") + @PostMapping("/mqtt/authv5") + public ResponseEntity mqttAuthv5(@RequestBody JSONObject json) throws Exception { + log.info("-----------auth-json:" + json + "---------------"); + String clientid = json.getString("clientid"); + String username = json.getString("username"); + String password = json.getString("password"); + String peerhost = json.getString("peerhost"); // 源IP地址 + JSONObject ret = new JSONObject(); + ret.put("is_superuser", false); + if (clientid.startsWith("server")) { + // 服务端认证:配置的账号密码认证 + if (mqttConfig.getUsername().equals(username) && mqttConfig.getPassword().equals(password)) { + log.info("-----------服务端mqtt认证成功,clientId:" + clientid + "---------------"); + ret.put("result", "allow"); + return ResponseEntity.ok().body(ret); + } else { + ret.put("result", "deny"); + return ResponseEntity.ok().body(ret); + } + } else if (clientid.startsWith("web") || clientid.startsWith("phone")) { + // web端和移动端认证:token认证 + String token = password; + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) { + token = token.replace(Constants.TOKEN_PREFIX, ""); + } + try { + Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + log.info("-----------移动端/Web端mqtt认证成功,clientId:" + clientid + "---------------"); + ret.put("result", "allow"); + return ResponseEntity.ok().body(ret); + } catch (Exception ex) { + ret.put("result", "deny"); + return ResponseEntity.ok().body(ret); + } + } else { + // 替换为整合之后的接口 + return toolService.clientAuthv5(clientid, username, password); + } + } + + @ApiOperation("mqtt钩子处理") + @PostMapping("/mqtt/webhook") + public void webHookProcess(@RequestBody MqttClientConnectModel model) { + try { + System.out.println("webhook:" + model.getAction()); + // 过滤服务端、web端和手机端 + if (model.getClientid().startsWith("server") || model.getClientid().startsWith("web") || model.getClientid().startsWith("phone")) { + return; + } + // 设备端认证:加密认证(E)和简单认证(S,配置的账号密码认证) + String[] clientArray = model.getClientid().split("&"); + String authType = clientArray[0]; + String deviceNumber = clientArray[1]; + Long productId = Long.valueOf(clientArray[2]); + Long userId = Long.valueOf(clientArray[3]); + + Device device = deviceService.selectShortDeviceBySerialNumber(deviceNumber); + ReportDataBo ruleBo = new ReportDataBo(); + ruleBo.setProductId(device.getProductId()); + ruleBo.setSerialNumber(device.getSerialNumber()); + // 设备状态(1-未激活,2-禁用,3-在线,4-离线) + if (model.getAction().equals("client_disconnected")) { + device.setStatus(4); + deviceService.updateDeviceStatusAndLocation(device, ""); + // 设备掉线后发布设备状态 + messagePublish.publishStatus(device.getProductId(), device.getSerialNumber(), 4, device.getIsShadow(),device.getRssi()); + // 清空保留消息,上线后发布新的属性功能保留消息 + messagePublish.publishProperty(device.getProductId(), device.getSerialNumber(), null,0); + messagePublish.publishFunction(device.getProductId(), device.getSerialNumber(), null,0); + + } else if (model.getAction().equals("client_connected")) { + device.setStatus(3); + deviceService.updateDeviceStatusAndLocation(device, model.getIpaddress()); + // 设备上线后发布设备状态 + messagePublish.publishStatus(device.getProductId(), device.getSerialNumber(), 3, device.getIsShadow(),device.getRssi()); + // 影子模式,发布属性和功能 + if (device.getIsShadow() == 1) { + ThingsModelShadow shadow = deviceService.getDeviceShadowThingsModel(device); + if (shadow.getProperties().size() > 0) { + messagePublish.publishProperty(device.getProductId(), device.getSerialNumber(), shadow.getProperties(),3); + } + if (shadow.getFunctions().size() > 0) { + messagePublish.publishFunction(device.getProductId(), device.getSerialNumber(), shadow.getFunctions(),3); + } + } + + } + + } catch (Exception ex) { + ex.printStackTrace(); + log.error("发生错误:" + ex.getMessage()); + } + } + + @ApiOperation("mqtt钩子处理") + @PostMapping("/mqtt/webhookv5") + public void webHookProcessv5(@RequestBody JSONObject json) { + try { + String clientid = json.getString("clientid"); + String event = json.getString("event"); + String peername = json.getString("peername"); // 类似 127.0.0.111.11.11.11:8812 + String ipAddress=peername; + if(peername.indexOf(":")!=-1){ + ipAddress = peername.substring(0,peername.indexOf(":")); + } + log.info("webhook:" + event + ",clientid:" + clientid); + // 过滤服务端、web端和手机端 + if (clientid.startsWith("server") || clientid.startsWith("web") || clientid.startsWith("phone")) { + return; + } + // 设备端认证:加密认证(E)和简单认证(S,配置的账号密码认证) + String[] clientArray = clientid.split("&"); + String deviceNumber = clientArray[1]; + + Device device = deviceService.selectShortDeviceBySerialNumber(deviceNumber); + ReportDataBo ruleBo = new ReportDataBo(); + ruleBo.setProductId(device.getProductId()); + ruleBo.setSerialNumber(device.getSerialNumber()); + // 设备状态(1-未激活,2-禁用,3-在线,4-离线) + if (event.equals("client.disconnected")) { + device.setStatus(4); + deviceService.updateDeviceStatusAndLocation(device, ""); + // 设备掉线后发布设备状态 + messagePublish.publishStatus(device.getProductId(), device.getSerialNumber(), 4, device.getIsShadow(),device.getRssi()); + // 清空保留消息,上线后发布新的属性功能保留消息 + messagePublish.publishProperty(device.getProductId(), device.getSerialNumber(), null,0); + messagePublish.publishFunction(device.getProductId(), device.getSerialNumber(), null,0); + } else if (event.equals("client.connected")) { + device.setStatus(3); + deviceService.updateDeviceStatusAndLocation(device, ipAddress); + // 设备掉线后发布设备状态 + messagePublish.publishStatus(device.getProductId(), device.getSerialNumber(), 3, device.getIsShadow(),device.getRssi()); + } else if(event.equals("session.subscribed")){ + // 影子模式,发布属性和功能 + if (device.getIsShadow() == 1) { + ThingsModelShadow shadow = deviceService.getDeviceShadowThingsModel(device); + if (shadow.getProperties().size() > 0) { + messagePublish.publishProperty(device.getProductId(), device.getSerialNumber(), shadow.getProperties(),3); + } + if (shadow.getFunctions().size() > 0) { + messagePublish.publishFunction(device.getProductId(), device.getSerialNumber(), shadow.getFunctions(),3); + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + log.error("发生错误:" + ex.getMessage()); + } + } + + + @ApiOperation("获取NTP时间") + @GetMapping("/ntp") + public JSONObject ntp(@RequestParam Long deviceSendTime) { + JSONObject ntpJson = new JSONObject(); + ntpJson.put("deviceSendTime", deviceSendTime); + ntpJson.put("serverRecvTime", System.currentTimeMillis()); + ntpJson.put("serverSendTime", System.currentTimeMillis()); + return ntpJson; + } + + /** + * 文件上传 + */ + @PostMapping("/upload") + @ApiOperation("文件上传") + public AjaxResult uploadFile(MultipartFile file) throws Exception { + try { + String filePath = RuoYiConfig.getProfile(); + // 文件名长度限制 + int fileNamelength = file.getOriginalFilename().length(); + if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { + throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); + } + // 文件类型限制 + // assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + + // 获取文件名和文件类型 + String fileName = file.getOriginalFilename(); + String extension = getExtension(file); + //设置日期格式 + SimpleDateFormat df = new SimpleDateFormat("yyyy-MMdd-HHmmss"); + fileName = "/iot/" + getLoginUser().getUserId().toString() + "/" + df.format(new Date()) + "." + extension; + //创建目录 + File desc = new File(filePath + File.separator + fileName); + if (!desc.exists()) { + if (!desc.getParentFile().exists()) { + desc.getParentFile().mkdirs(); + } + } + // 存储文件 + file.transferTo(desc); + + String url = "/profile" + fileName; + AjaxResult ajax = AjaxResult.success(); + ajax.put("fileName", url); + ajax.put("url", url); + return ajax; + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 下载文件 + */ + @ApiOperation("文件下载") + @GetMapping("/download") + public void download(String fileName, HttpServletResponse response, HttpServletRequest request) { + try { +// if (!FileUtils.checkAllowDownload(fileName)) { +// throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName)); +// } + String filePath = RuoYiConfig.getProfile(); + // 资源地址 + String downloadPath = filePath + fileName.replace("/profile", ""); + // 下载名称 + String downloadName = StringUtils.substringAfterLast(downloadPath, "/"); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, downloadName); + FileUtils.writeBytes(downloadPath, response.getOutputStream()); + } catch (Exception e) { + log.error("下载文件失败", e); + } + } + + /** + * 批量生成代码 + */ + @Log(title = "SDK生成", businessType = BusinessType.GENCODE) + @GetMapping("/genSdk") + @ApiOperation("生成SDK") + public void genSdk(HttpServletResponse response, int deviceChip) throws IOException { + byte[] data = downloadCode(deviceChip); + genSdk(response, data); + } + + /** + * 生成zip文件 + */ + private void genSdk(HttpServletResponse response, byte[] data) throws IOException { + response.reset(); + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); + response.setHeader("Content-Disposition", "attachment; filename=\"fastbee.zip\""); + response.addHeader("Content-Length", "" + data.length); + response.setContentType("application/octet-stream; charset=UTF-8"); + IOUtils.write(data, response.getOutputStream()); + } + + /** + * 批量生成代码(下载方式) + * + * @param deviceChip + * @return 数据 + */ + public byte[] downloadCode(int deviceChip) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); +// generatorCode(deviceChip, zip); + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 查询表信息并生成代码 + */ + private void generatorCode(int deviceChip, ZipOutputStream zip) { + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(deviceChip); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(""); + for (String template : templates) { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try { + // 添加到zip + zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template))); + IOUtils.write(sw.toString(), zip, Constants.UTF8); + IOUtils.closeQuietly(sw); + zip.flush(); + zip.closeEntry(); + } catch (IOException e) { + System.out.println("渲染模板失败"); + } + } + } + + @GetMapping("/getTopics") + @ApiOperation("获取所有下发的topic") + public AjaxResult getTopics(Boolean isSimulate){ + return AjaxResult.success(TopicsUtils.getAllGet(isSimulate)); + } + + @GetMapping("/decode") + @ApiOperation("指令编码") + public AjaxResult decode(DeCodeBo bo){ + return AjaxResult.success(deCodeService.protocolDeCode(bo)); + } + +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/UserSocialController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/UserSocialController.java new file mode 100644 index 00000000..b0fd433d --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/UserSocialController.java @@ -0,0 +1,69 @@ +package com.fastbee.data.controller; + +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.iot.service.IUserSocialProfileService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 第三方登录平台控制Controller + * + * @author json + * @date 2022-04-24 + */ +@Api(tags = "用户社交账户api") +@RestController +@RequestMapping("/iot/social/") +public class UserSocialController extends BaseController { + + @Autowired + private IUserSocialProfileService iUserSocialProfileService; + + + /** + * 绑定 + * + * @param bindId 绑定id + * @return + */ + @GetMapping("/bindId/{bindId}") + @ApiOperation("绑定api") + @ApiImplicitParam(name = "bindId", value = "绑定bindId", required = true, dataType = "String", paramType = "path", dataTypeClass = String.class) + public AjaxResult bindUser(@PathVariable String bindId) { + return iUserSocialProfileService.bindUser(bindId, getUserId()); + } + + + /** + * 绑定 + * + * @param platform 绑定类型 + * @return + */ + @GetMapping("/bind/{platform}") + @ApiOperation("绑定跳转api") + @ApiImplicitParam(name = "platform", value = "绑定platform", required = true, dataType = "String", paramType = "path", dataTypeClass = String.class) + public AjaxResult bind(@PathVariable String platform) { + return iUserSocialProfileService.bindSocialAccount(platform); + } + + /** + * 解绑 + * + * @param socialUserId 用户社交平台Id + * @return + */ + @GetMapping("/unbind/{socialUserId}") + @ApiOperation("解绑api") + @ApiImplicitParam(name = "socialUserId", value = "绑定socialId", required = true, dataType = "Long", paramType = "path", dataTypeClass = Long.class) + public AjaxResult unbind(@PathVariable Long socialUserId) { + return iUserSocialProfileService.unbindSocialAccount(socialUserId, getUserId()); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/dashBoard/DashBoardController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/dashBoard/DashBoardController.java new file mode 100644 index 00000000..9ff74d14 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/dashBoard/DashBoardController.java @@ -0,0 +1,72 @@ +package com.fastbee.data.controller.dashBoard; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.iot.model.dashBoard.DashMqttMetrics; +import com.fastbee.iot.model.dashBoard.DashMqttStat; +import com.fastbee.mqtt.manager.ClientManager; +import com.fastbee.mqtt.manager.RetainMsgManager; +import com.fastbee.base.service.ISessionStore; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * 首页面板和大屏数据 + * + * @author gsb + * @date 2023/3/27 17:00 + */ +@Api(tags = "首页面板和大屏数据") +@RestController +@RequestMapping("/bashBoard") +public class DashBoardController { + + @Resource + private RedisCache redisCache; + @Resource + private ISessionStore sessionStore; + + @GetMapping("/stats") + @ApiOperation("mqtt状态数据") + public AjaxResult stats() { + DashMqttStat stat = DashMqttStat.builder() + .connection_count(sessionStore.getSessionMap().size()) + .connection_total(getTotal(FastBeeConstant.REDIS.MESSAGE_CONNECT_TOTAL)) + .subscription_count(ClientManager.topicMap.size()) + .subscription_total(getTotal(FastBeeConstant.REDIS.MESSAGE_SUBSCRIBE_TOTAL)) + .retain_count(RetainMsgManager.getSize()) + .retain_total(getTotal(FastBeeConstant.REDIS.MESSAGE_RETAIN_TOTAL)) + .session_count(sessionStore.getSessionMap().size()) + .session_total(getTotal(FastBeeConstant.REDIS.MESSAGE_CONNECT_TOTAL)) + .build(); + return AjaxResult.success(stat); + + } + + + @GetMapping("/metrics") + @ApiOperation("mqtt统计") + public AjaxResult metrics() { + DashMqttMetrics metrics = DashMqttMetrics.builder() + .send_total(getTotal(FastBeeConstant.REDIS.MESSAGE_SEND_TOTAL)) + .receive_total(getTotal(FastBeeConstant.REDIS.MESSAGE_RECEIVE_TOTAL)) + .auth_total(getTotal(FastBeeConstant.REDIS.MESSAGE_AUTH_TOTAL)) + .connect_total(getTotal(FastBeeConstant.REDIS.MESSAGE_CONNECT_TOTAL)) + .subscribe_total(getTotal(FastBeeConstant.REDIS.MESSAGE_SUBSCRIBE_TOTAL)) + .today_received(getTotal(FastBeeConstant.REDIS.MESSAGE_RECEIVE_TODAY)) + .today_send(getTotal(FastBeeConstant.REDIS.MESSAGE_SEND_TODAY)) + .build(); + return AjaxResult.success(metrics); + } + + + public Integer getTotal(String key) { + return redisCache.getCacheObject(key) == null ? 0 : redisCache.getCacheObject(key); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/netty/NettyManagerController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/netty/NettyManagerController.java new file mode 100644 index 00000000..529fb8c3 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/netty/NettyManagerController.java @@ -0,0 +1,121 @@ +package com.fastbee.data.controller.netty; + +import com.fastbee.base.service.ISessionStore; +import com.fastbee.base.session.Session; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.gateway.mq.Topics; +import com.fastbee.mqtt.manager.ClientManager; +import com.fastbee.mqtt.manager.SessionManger; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * NETTY客户端可视化 + * + * @author gsb + * @date 2023/3/23 14:51 + */ +@RestController +@RequestMapping("/iot/mqtt") +@Api(tags = "Netty可视化") +@Slf4j +public class NettyManagerController extends BaseController { + + @Resource + private ISessionStore sessionStore; + + /** + * 客户端管理列表 + * @param pageSize 分页大小 + * @param pageNum 页数 + * @param clientId 查询客户端id + * @param serverCode 服务端类型编码 MQTT/TCP/UDP + * @param isClient 是否只显示设备端 1-是/0或null-否 + * @return 客户端列表 + */ + @GetMapping("/clients") + @ApiOperation("netty客户端列表") + public AjaxResult clients(Integer pageSize, Integer pageNum, String clientId, String serverCode,Integer isClient) { + List list = new ArrayList<>(); + if (StringUtils.isEmpty(clientId)) { + ConcurrentHashMap sourceMap = sessionStore.getSessionMap(); + ConcurrentHashMap selectMap = new ConcurrentHashMap<>(); + sourceMap.forEach((key, value) -> { + if (serverCode.equals(value.getServerType().getCode())) { + if (null != isClient && isClient == 1){ + if (!key.startsWith("server") && !key.startsWith("web") && !key.startsWith("phone") && !key.startsWith("test")){ + selectMap.put(key,value); + } + }else { + selectMap.put(key, value); + } + } + }); + Map sessionMap = sessionStore.listPage(selectMap, pageSize, pageNum); + List result = new ArrayList<>(sessionMap.values()); + for (Session session : result) { + Map topicMap = ClientManager.clientTopicMap.get(session.getClientId()); + if (topicMap != null) { + List topicsList = new ArrayList<>(); + topicMap.keySet().forEach(topic -> { + Topics tBo = new Topics(); + tBo.setTopicName(topic); + topicsList.add(tBo); + }); + session.setTopics(topicsList); + session.setTopicCount(topicMap.size()); + } else { + session.setTopicCount(0); + } + list.add(session); + } + return AjaxResult.success(list, selectMap.size()); + } else { + List result = new ArrayList<>(); + Session session = sessionStore.getSession(clientId); + if (session != null) { + Map topicMap = ClientManager.clientTopicMap.get(session.getClientId()); + if (topicMap != null) { + List topicsList = new ArrayList<>(); + topicMap.keySet().forEach(topic -> { + Topics tBo = new Topics(); + tBo.setTopicName(topic); + topicsList.add(tBo); + }); + session.setTopics(topicsList); + session.setTopicCount(topicMap.size()); + } else { + session.setTopicCount(0); + } + if (serverCode.equals(session.getServerType().getCode())) { + result.add(session); + } + } + return AjaxResult.success(result, result.size()); + } + + } + + @GetMapping("/client/out") + @ApiOperation("netty客户端踢出") + public AjaxResult clientOut(String clientId){ + Session session = sessionStore.getSession(clientId); + Optional.ofNullable(session).orElseThrow(() -> new SecurityException("客户端不存在")); + SessionManger.removeClient(clientId); + return AjaxResult.success(); + } + +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/protocol/ProtocolController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/protocol/ProtocolController.java new file mode 100644 index 00000000..a1011fe1 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/protocol/ProtocolController.java @@ -0,0 +1,114 @@ +package com.fastbee.data.controller.protocol; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.iot.domain.Protocol; +import com.fastbee.iot.service.IProtocolService; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.common.core.page.TableDataInfo; + +/** + * 协议Controller + * + * @author kerwincui + * @date 2022-12-07 + */ +@Api(tags = "协议") +@RestController +@RequestMapping("/iot/protocol") +public class ProtocolController extends BaseController +{ + @Autowired + private IProtocolService protocolService; + + /** + * 查询协议列表 + */ + @ApiOperation("查询协议列表") + @PreAuthorize("@ss.hasPermi('iot:protocol:list')") + @GetMapping("/list") + public TableDataInfo list(Protocol protocol) + { + startPage(); + List list = protocolService.selectProtocolList(protocol); + return getDataTable(list); + } + + /** + * 导出协议列表 + */ + @ApiOperation("导出协议列表") + @PreAuthorize("@ss.hasPermi('iot:protocol:export')") + @Log(title = "协议", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, Protocol protocol) + { + List list = protocolService.selectProtocolList(protocol); + ExcelUtil util = new ExcelUtil(Protocol.class); + util.exportExcel(response, list, "协议数据"); + } + + /** + * 获取协议详细信息 + */ + @ApiOperation("获取协议详细信息") + @PreAuthorize("@ss.hasPermi('iot:protocol:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) + { + return AjaxResult.success(protocolService.selectProtocolById(id)); + } + + /** + * 新增协议 + */ + @ApiOperation("新增协议") + @PreAuthorize("@ss.hasPermi('iot:protocol:add')") + @Log(title = "协议", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody Protocol protocol) + { + return toAjax(protocolService.insertProtocol(protocol)); + } + + /** + * 修改协议 + */ + @ApiOperation("修改协议") + @PreAuthorize("@ss.hasPermi('iot:protocol:edit')") + @Log(title = "协议", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody Protocol protocol) + { + return toAjax(protocolService.updateProtocol(protocol)); + } + + /** + * 删除协议 + */ + @ApiOperation("删除协议") + @PreAuthorize("@ss.hasPermi('iot:protocol:remove')") + @Log(title = "协议", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) + { + return toAjax(protocolService.deleteProtocolByIds(ids)); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/runtime/DeviceRuntimeController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/runtime/DeviceRuntimeController.java new file mode 100644 index 00000000..9b0b07eb --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/runtime/DeviceRuntimeController.java @@ -0,0 +1,119 @@ +package com.fastbee.data.controller.runtime; + +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.mq.InvokeReqDto; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.enums.ThingsModelType; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.iot.domain.DeviceLog; +import com.fastbee.iot.domain.FunctionLog; +import com.fastbee.iot.service.IDeviceRuntimeService; +import com.fastbee.mq.service.IFunctionInvoke; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; +import java.util.Map; + +/** + * 设备运行时数据controller + * + * @author gsb + * @date 2022/12/5 11:52 + */ +@RestController +@RequestMapping("/iot/runtime") +@Api(tags = "设备运行数据") +public class DeviceRuntimeController extends BaseController { + + @Autowired + private IFunctionInvoke functionInvoke; + @Resource + private IDeviceRuntimeService runtimeService; + + /** + * 服务下发 + * 例如modbus 格式如下 + * + * @see InvokeReqDto#getRemoteCommand() + * key = 寄存器地址 + * value = 寄存器地址值 + *

+ * 其他协议 key = identifier + * value = 值 + * { + * "serialNumber": "860061060282358", + * "productId": "2", + * "identifier": "temp", + * "remoteCommand": { + * "4": "4" + * } + * } + */ + @PostMapping("/service/invoke") + @PreAuthorize("@ss.hasPermi('iot:service:invoke')") + @ApiOperation(value = "服务下发", httpMethod = "POST", response = AjaxResult.class, notes = "服务下发") + public AjaxResult invoke(@Valid @RequestBody InvokeReqDto reqDto) { + reqDto.setValue(new JSONObject(reqDto.getRemoteCommand())); + String messageId = functionInvoke.invokeNoReply(reqDto); + return AjaxResult.success(messageId); + } + + + /** + * 根据messageId查询服务回执 + */ + @GetMapping(value = "fun/get") + //@PreAuthorize("@ss.hasPermi('iot:service:get')") + @ApiOperation(value = "根据messageId查询服务回执", httpMethod = "GET", response = AjaxResult.class, notes = "根据messageId查询服务回执") + public AjaxResult reply(String serialNumber, String messageId) { + if (StringUtils.isEmpty(messageId) || StringUtils.isEmpty(serialNumber)) { + throw new ServiceException("消息id为空"); + } + // TODO - 根据消息id查询 + //DeviceTdReq req = new DeviceTdReq(); + //req.setImei(serialNumber); + //req.setMessageId(messageId); + //DeviceTdData data = deviceTdService.selectReplyMsg(req); + //return toAjax(data) + return AjaxResult.success(); + } + + + /** + * 实时状态 + * @param serialNumber 设备类型 + * @param type 物模型类型 + * @return 结果 + */ + @GetMapping(value = "/runState") + @ApiOperation(value = "实时状态") + public AjaxResult runState(String serialNumber, Integer type,Long productId,Integer slaveId){ + ThingsModelType modelType = ThingsModelType.getType(type); + List logList = runtimeService.runtimeBySerialNumber(serialNumber, modelType,productId,slaveId); + return AjaxResult.success(logList); + } + + /** + * 设备服务下发日志 + */ + @GetMapping(value = "/funcLog") + @ApiOperation(value = "设备服务下发日志") + public TableDataInfo funcLog(String serialNumber){ + startPage(); + List logList = runtimeService.runtimeReply(serialNumber); + return getDataTable(logList); + } + + + +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/wechat/WeChatController.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/wechat/WeChatController.java new file mode 100644 index 00000000..01d68fa0 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/controller/wechat/WeChatController.java @@ -0,0 +1,127 @@ +package com.fastbee.data.controller.wechat; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.wechat.WeChatLoginBody; +import com.fastbee.common.wechat.WeChatLoginResult; +import com.fastbee.iot.wechat.WeChatService; +import com.fastbee.iot.wechat.vo.WxBindReqVO; +import com.fastbee.iot.wechat.vo.WxCancelBindReqVO; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 微信相关控制器 + * @author fastb + * @date 2023-07-31 11:29 + */ +@Api(tags = "微信相关模块") +@RestController +@RequestMapping("/wechat") +public class WeChatController { + + @Resource + private WeChatService weChatService; + + /** + * 移动应用微信登录 + * @param weChatLoginBody 微信登录参数 + * @return 登录结果 + */ + @ApiOperation("移动应用微信登录") + @PostMapping("/mobileLogin") + public AjaxResult mobileLogin(@RequestBody WeChatLoginBody weChatLoginBody) { + return AjaxResult.success(weChatService.mobileLogin(weChatLoginBody)); + } + + /** + * 小程序微信登录 + * @param weChatLoginBody 微信登录参数 + * @return 登录结果 + */ + @ApiOperation("小程序微信登录") + @PostMapping("/miniLogin") + public AjaxResult miniLogin(@RequestBody WeChatLoginBody weChatLoginBody) { + WeChatLoginResult weChatLoginResult = weChatService.miniLogin(weChatLoginBody); + return AjaxResult.success(weChatLoginResult); + } + + /** + * 小程序、移动应用微信绑定 + * @param wxBindReqVO 微信绑定传参类型 + * @return 结果 + */ + @ApiOperation("小程序、移动应用微信绑定") + @PostMapping("/bind") + public AjaxResult bind(@RequestBody WxBindReqVO wxBindReqVO) { + if (StringUtils.isEmpty(wxBindReqVO.getSourceClient())) { + throw new ServiceException("请传入验证方式"); + } + return weChatService.bind(wxBindReqVO); + } + + /** + * 取消所有微信绑定 + * @param wxCancelBindReqVO 微信解绑传参类型 + * @return 结果 + */ + @ApiOperation("取消所有微信绑定") + @PostMapping("/cancelBind") + public AjaxResult cancelBind(@RequestBody WxCancelBindReqVO wxCancelBindReqVO) { + if (wxCancelBindReqVO.getVerifyType() == null) { + throw new ServiceException("请传入验证方式"); + } + return weChatService.cancelBind(wxCancelBindReqVO); + } + + /** + * 网站应用获取微信绑定二维码信息 + * @return 二维码信息 + */ + @ApiOperation("网站应用获取微信绑定二维码信息") + @GetMapping("/getWxBindQr") + public AjaxResult getWxBindQr(HttpServletRequest httpServletRequest) { + // 返回二维码信息 + return weChatService.getWxBindQr(httpServletRequest); + } + + /** + * 网站应用内微信扫码绑定回调接口 + * @param code 用户凭证 + * @param state 时间戳 + * @param httpServletRequest 请求信息 + * @param httpServletResponse 响应信息 + */ + @ApiOperation("网站应用内微信扫码绑定回调地址") + @GetMapping("/wxBind/callback") + public void wxBindCallback(String code, String state, String wxBindId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException { + //回调接口 + httpServletResponse.sendRedirect(weChatService.wxBindCallback(code, state, wxBindId, httpServletRequest, httpServletResponse)); + } + + /** + * 网站应用获取微信绑定结果信息 + * @param wxBindMsgId 微信绑定结果信息id + * @return msg + */ + @ApiOperation("网站应用获取微信绑定结果信息") + @GetMapping("/getWxBindMsg") + public AjaxResult getWxBindMsg(String wxBindMsgId) { + if (StringUtils.isEmpty(wxBindMsgId)) { + return AjaxResult.error("请传入wxBindMsgId"); + } + // 返回二维码信息 + return weChatService.getWxBindMsg(wxBindMsgId); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/AbstractQuartzJob.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/AbstractQuartzJob.java new file mode 100644 index 00000000..65763689 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/AbstractQuartzJob.java @@ -0,0 +1,108 @@ +package com.fastbee.data.quartz; + +import com.fastbee.common.constant.Constants; +import com.fastbee.common.constant.ScheduleConstants; +import com.fastbee.common.utils.ExceptionUtil; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.bean.BeanUtils; +import com.fastbee.common.utils.spring.SpringUtils; +import com.fastbee.iot.domain.DeviceJob; +import com.fastbee.quartz.domain.SysJobLog; +import com.fastbee.quartz.service.ISysJobLogService; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; + +/** + * 抽象quartz调用 + * + * @author ruoyi + */ +public abstract class AbstractQuartzJob implements Job +{ + private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); + + /** + * 线程本地变量 + */ + private static ThreadLocal threadLocal = new ThreadLocal<>(); + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException + { + DeviceJob deviceJob = new DeviceJob(); + BeanUtils.copyBeanProp(deviceJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES)); + try + { + before(context, deviceJob); + if (deviceJob != null) + { + doExecute(context, deviceJob); + } + after(context, deviceJob, null); + } + catch (Exception e) + { + log.error("任务执行异常 - :", e); + after(context, deviceJob, e); + } + } + + /** + * 执行前 + * + * @param context 工作执行上下文对象 + * @param deviceJob 系统计划任务 + */ + protected void before(JobExecutionContext context, DeviceJob deviceJob) + { + threadLocal.set(new Date()); + } + + /** + * 执行后 + * + * @param context 工作执行上下文对象 + * @param deviceJob 系统计划任务 + */ + protected void after(JobExecutionContext context, DeviceJob deviceJob, Exception e) + { + Date startTime = threadLocal.get(); + threadLocal.remove(); + + final SysJobLog sysJobLog = new SysJobLog(); + sysJobLog.setJobName(deviceJob.getJobName()); + sysJobLog.setJobGroup(deviceJob.getJobGroup()); + sysJobLog.setInvokeTarget(deviceJob.getDeviceName()); + sysJobLog.setStartTime(startTime); + sysJobLog.setStopTime(new Date()); + long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime(); + sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒"); + if (e != null) + { + sysJobLog.setStatus(Constants.FAIL); + String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000); + sysJobLog.setExceptionInfo(errorMsg); + } + else + { + sysJobLog.setStatus(Constants.SUCCESS); + } + + // 写入数据库当中 + SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); + } + + /** + * 执行方法,由子类重载 + * + * @param context 工作执行上下文对象 + * @param deviceJob 系统计划任务 + * @throws Exception 执行过程中的异常 + */ + protected abstract void doExecute(JobExecutionContext context, DeviceJob deviceJob) throws Exception; +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/CronUtils.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/CronUtils.java new file mode 100644 index 00000000..0fe80a16 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/CronUtils.java @@ -0,0 +1,64 @@ +package com.fastbee.data.quartz; + +import org.quartz.CronExpression; + +import java.text.ParseException; +import java.util.Date; + +/** + * cron表达式工具类 + * + * @author ruoyi + * + */ +public class CronUtils +{ + /** + * 返回一个布尔值代表一个给定的Cron表达式的有效性 + * + * @param cronExpression Cron表达式 + * @return boolean 表达式是否有效 + */ + public static boolean isValid(String cronExpression) + { + return CronExpression.isValidExpression(cronExpression); + } + + /** + * 返回一个字符串值,表示该消息无效Cron表达式给出有效性 + * + * @param cronExpression Cron表达式 + * @return String 无效时返回表达式错误描述,如果有效返回null + */ + public static String getInvalidMessage(String cronExpression) + { + try + { + new CronExpression(cronExpression); + return null; + } + catch (ParseException pe) + { + return pe.getMessage(); + } + } + + /** + * 返回下一个执行时间根据给定的Cron表达式 + * + * @param cronExpression Cron表达式 + * @return Date 下次Cron表达式执行时间 + */ + public static Date getNextExecution(String cronExpression) + { + try + { + CronExpression cron = new CronExpression(cronExpression); + return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis())); + } + catch (ParseException e) + { + throw new IllegalArgumentException(e.getMessage()); + } + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/JobInvokeUtil.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/JobInvokeUtil.java new file mode 100644 index 00000000..be53c89b --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/JobInvokeUtil.java @@ -0,0 +1,77 @@ +package com.fastbee.data.quartz; + +import com.alibaba.fastjson2.JSON; +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; +import com.fastbee.common.utils.spring.SpringUtils; +import com.fastbee.iot.domain.DeviceJob; +import com.fastbee.iot.model.Action; +import com.fastbee.mq.service.IMqttMessagePublish; + +import java.util.ArrayList; +import java.util.List; + +/** + * 任务执行工具 + * + * @author kerwincui + */ +public class JobInvokeUtil { + + /**获取消息推送接口*/ + private static IMqttMessagePublish messagePublish = SpringUtils.getBean(IMqttMessagePublish.class); + + + /** + * 执行方法 + * + * @param deviceJob 系统任务 + */ + public static void invokeMethod(DeviceJob deviceJob) throws Exception { + if (deviceJob.getJobType() == 1) { + System.out.println("------------------------执行定时任务-----------------------------"); + List actions = JSON.parseArray(deviceJob.getActions(), Action.class); + List propertys = new ArrayList<>(); + List functions = new ArrayList<>(); + for (int i = 0; i < actions.size(); i++) { + ThingsModelSimpleItem model = new ThingsModelSimpleItem(); + model.setId(actions.get(i).getId()); + model.setValue(actions.get(i).getValue()); + model.setRemark("设备定时"); + if (actions.get(i).getType() == 1) { + propertys.add(model); + } else if (actions.get(i).getType() == 2) { + functions.add(model); + } + } + // 发布属性 + if (propertys.size() > 0) { + messagePublish.publishProperty(deviceJob.getProductId(), deviceJob.getSerialNumber(), propertys, 0); + } + // 发布功能 + if (functions.size() > 0) { + messagePublish.publishFunction(deviceJob.getProductId(), deviceJob.getSerialNumber(), functions, 0); + } + + } else if (deviceJob.getJobType() == 2) { + + } else if (deviceJob.getJobType() == 3) { + System.out.println("------------------------定时执行场景联动-----------------------------"); + List actions = JSON.parseArray(deviceJob.getActions(), Action.class); + for (int i = 0; i < actions.size(); i++) { + ThingsModelSimpleItem model = new ThingsModelSimpleItem(); + model.setId(actions.get(i).getId()); + model.setValue(actions.get(i).getValue()); + model.setRemark("场景联动定时触发"); + if (actions.get(i).getType() == 1) { + List propertys = new ArrayList<>(); + propertys.add(model); + messagePublish.publishProperty(actions.get(i).getProductId(), actions.get(i).getSerialNumber(), propertys, 0); + } else if (actions.get(i).getType() == 2) { + List functions = new ArrayList<>(); + functions.add(model); + messagePublish.publishFunction(actions.get(i).getProductId(), actions.get(i).getSerialNumber(), functions, 0); + } + } + } + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/QuartzDisallowConcurrentExecution.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/QuartzDisallowConcurrentExecution.java new file mode 100644 index 00000000..d284aa94 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/QuartzDisallowConcurrentExecution.java @@ -0,0 +1,21 @@ +package com.fastbee.data.quartz; + +import com.fastbee.iot.domain.DeviceJob; +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; + +/** + * 定时任务处理(禁止并发执行) + * + * @author ruoyi + * + */ +@DisallowConcurrentExecution +public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, DeviceJob deviceJob) throws Exception + { + JobInvokeUtil.invokeMethod(deviceJob); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/QuartzJobExecution.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/QuartzJobExecution.java new file mode 100644 index 00000000..69086a33 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/QuartzJobExecution.java @@ -0,0 +1,19 @@ +package com.fastbee.data.quartz; + +import com.fastbee.iot.domain.DeviceJob; +import org.quartz.JobExecutionContext; + +/** + * 定时任务处理(允许并发执行) + * + * @author ruoyi + * + */ +public class QuartzJobExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, DeviceJob deviceJob) throws Exception + { + JobInvokeUtil.invokeMethod(deviceJob); + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/ScheduleUtils.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/ScheduleUtils.java new file mode 100644 index 00000000..00b47e8f --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/quartz/ScheduleUtils.java @@ -0,0 +1,105 @@ +package com.fastbee.data.quartz; + +import com.fastbee.common.constant.ScheduleConstants; +import com.fastbee.common.exception.job.TaskException; +import com.fastbee.common.exception.job.TaskException.Code; +import com.fastbee.iot.domain.DeviceJob; +import org.quartz.*; + +/** + * 定时任务工具类 + * + * @author ruoyi + * + */ +public class ScheduleUtils +{ + /** + * 得到quartz任务类 + * + * @param deviceJob 执行计划 + * @return 具体执行任务类 + */ + private static Class getQuartzJobClass(DeviceJob deviceJob) + { + boolean isConcurrent = "0".equals(deviceJob.getConcurrent()); + return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class; + } + + /** + * 构建任务触发对象 + */ + public static TriggerKey getTriggerKey(Long jobId, String jobGroup) + { + return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 构建任务键对象 + */ + public static JobKey getJobKey(Long jobId, String jobGroup) + { + return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 创建定时任务 + */ + public static void createScheduleJob(Scheduler scheduler, DeviceJob job) throws SchedulerException, TaskException + { + Class jobClass = getQuartzJobClass(job); + // 1.创建任务 + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build(); + + // 表达式调度构建器 + CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); + cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder); + + // 2.创建触发器, 按新的cronExpression表达式构建一个新的trigger + CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup)) + .withSchedule(cronScheduleBuilder).build(); + + // 放入参数,运行时的方法可以获取 + jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job); + + // 判断是否存在 + if (scheduler.checkExists(getJobKey(jobId, jobGroup))) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(getJobKey(jobId, jobGroup)); + } + + // 3.任务和触发器添加到调度器 + scheduler.scheduleJob(jobDetail, trigger); + + // 暂停任务 + if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + } + + /** + * 设置定时任务策略 + */ + public static CronScheduleBuilder handleCronScheduleMisfirePolicy(DeviceJob job, CronScheduleBuilder cb) + throws TaskException + { + switch (job.getMisfirePolicy()) + { + case ScheduleConstants.MISFIRE_DEFAULT: + return cb; + case ScheduleConstants.MISFIRE_IGNORE_MISFIRES: + return cb.withMisfireHandlingInstructionIgnoreMisfires(); + case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED: + return cb.withMisfireHandlingInstructionFireAndProceed(); + case ScheduleConstants.MISFIRE_DO_NOTHING: + return cb.withMisfireHandlingInstructionDoNothing(); + default: + throw new TaskException("The task misfire policy '" + job.getMisfirePolicy() + + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR); + } + } +} diff --git a/springboot/fastbee-open-api/src/main/java/com/fastbee/data/service/impl/DeviceJobServiceImpl.java b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/service/impl/DeviceJobServiceImpl.java new file mode 100644 index 00000000..227b0183 --- /dev/null +++ b/springboot/fastbee-open-api/src/main/java/com/fastbee/data/service/impl/DeviceJobServiceImpl.java @@ -0,0 +1,334 @@ +package com.fastbee.data.service.impl; + +import com.fastbee.common.constant.ScheduleConstants; +import com.fastbee.common.exception.job.TaskException; +import com.fastbee.iot.domain.DeviceJob; +import com.fastbee.iot.mapper.DeviceJobMapper; +import com.fastbee.iot.service.IDeviceJobService; +import com.fastbee.iot.service.IDeviceService; +import com.fastbee.iot.service.cache.IDeviceCache; +import com.fastbee.data.quartz.CronUtils; +import com.fastbee.data.quartz.ScheduleUtils; +import com.fastbee.quartz.domain.SysJob; +import com.fastbee.quartz.mapper.SysJobMapper; +import lombok.extern.slf4j.Slf4j; +import org.quartz.JobDataMap; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.List; + +/** + * 定时任务调度信息 服务层 + * + * @author kerwincui + */ +@Service +@Slf4j +public class DeviceJobServiceImpl implements IDeviceJobService +{ + @Autowired + private Scheduler scheduler; + + @Autowired + private DeviceJobMapper jobMapper; + + @Autowired + private SysJobMapper sysJobMapper; + + + /** + * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据) + */ + @PostConstruct + public void init() throws SchedulerException, TaskException + { + scheduler.clear(); + // 设备定时任务 + List jobList = jobMapper.selectJobAll(); + for (DeviceJob deviceJob : jobList) + { + ScheduleUtils.createScheduleJob(scheduler, deviceJob); + } + + // 系统定时任务 + List sysJobList = sysJobMapper.selectJobAll(); + for (SysJob job : sysJobList) + { + com.fastbee.quartz.util.ScheduleUtils.createScheduleJob(scheduler, job); + } + } + + /** + * 获取quartz调度器的计划任务列表 + * + * @param job 调度信息 + * @return + */ + @Override + public List selectJobList(DeviceJob job) + { + return jobMapper.selectJobList(job); + } + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + @Override + public DeviceJob selectJobById(Long jobId) + { + return jobMapper.selectJobById(jobId); + } + + /** + * 暂停任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int pauseJob(DeviceJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 恢复任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int resumeJob(DeviceJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.NORMAL.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteJob(DeviceJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + int rows = jobMapper.deleteJobById(jobId); + if (rows > 0) + { + scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteJobByIds(Long[] jobIds) throws SchedulerException + { + for (Long jobId : jobIds) + { + DeviceJob job = jobMapper.selectJobById(jobId); + deleteJob(job); + } + } + + /** + * 根据设备Ids批量删除调度信息 + * + * @param deviceIds 需要删除数据的设备Ids + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteJobByDeviceIds(Long[] deviceIds) throws SchedulerException + { + // 查出所有job + List deviceJobs=jobMapper.selectShortJobListByDeviceIds(deviceIds); + // 批量删除job + int rows=jobMapper.deleteJobByDeviceIds(deviceIds); + // 批量删除调度器 + for(DeviceJob job:deviceJobs){ + scheduler.deleteJob(ScheduleUtils.getJobKey(job.getJobId(), job.getJobGroup())); + } + } + + /** + * 根据告警Ids批量删除调度信息 + * + * @param alertIds 需要删除数据的告警Ids + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteJobByAlertIds(Long[] alertIds) throws SchedulerException + { + // 查出所有job + List deviceJobs=jobMapper.selectShortJobListByAlertIds(alertIds); + // 批量删除job + int rows=jobMapper.deleteJobByAlertIds(alertIds); + // 批量删除调度器 + for(DeviceJob job:deviceJobs){ + scheduler.deleteJob(ScheduleUtils.getJobKey(job.getJobId(), job.getJobGroup())); + } + } + + /** + * 根据场景联动Ids批量删除调度信息 + * + * @param sceneIds 需要删除数据的场景Ids + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteJobBySceneIds(Long[] sceneIds) throws SchedulerException + { + // 查出所有job + List deviceJobs=jobMapper.selectShortJobListBySceneIds(sceneIds); + // 批量删除job + int rows=jobMapper.deleteJobBySceneIds(sceneIds); + // 批量删除调度器 + for(DeviceJob job:deviceJobs){ + scheduler.deleteJob(ScheduleUtils.getJobKey(job.getJobId(), job.getJobGroup())); + } + } + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int changeStatus(DeviceJob job) throws SchedulerException + { + int rows = 0; + String status = job.getStatus(); + if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) + { + rows = resumeJob(job); + } + else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) + { + rows = pauseJob(job); + } + return rows; + } + + /** + * 立即运行任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void run(DeviceJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + DeviceJob properties = selectJobById(job.getJobId()); + // 参数 + JobDataMap dataMap = new JobDataMap(); + dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties); + scheduler.triggerJob(ScheduleUtils.getJobKey(jobId, jobGroup), dataMap); + } + + /** + * 新增任务 + * + * @param deviceJob 调度信息 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int insertJob(DeviceJob deviceJob) throws SchedulerException, TaskException + { + int rows = jobMapper.insertJob(deviceJob); + if (rows > 0) + { + ScheduleUtils.createScheduleJob(scheduler, deviceJob); + } + return rows; + } + + /** + * 更新任务的时间表达式 + * + * @param deviceJob 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateJob(DeviceJob deviceJob) throws SchedulerException, TaskException + { + DeviceJob properties = selectJobById(deviceJob.getJobId()); + int rows = jobMapper.updateJob(deviceJob); + if (rows > 0) + { + updateSchedulerJob(deviceJob, properties.getJobGroup()); + } + return rows; + } + + /** + * 更新任务 + * + * @param deviceJob 任务对象 + * @param jobGroup 任务组名 + */ + public void updateSchedulerJob(DeviceJob deviceJob, String jobGroup) throws SchedulerException, TaskException + { + Long jobId = deviceJob.getJobId(); + // 判断是否存在 + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + if (scheduler.checkExists(jobKey)) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(jobKey); + } + ScheduleUtils.createScheduleJob(scheduler, deviceJob); + } + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + @Override + public boolean checkCronExpressionIsValid(String cronExpression) + { + return CronUtils.isValid(cronExpression); + } +} diff --git a/springboot/fastbee-plugs/fastbee-generator/pom.xml b/springboot/fastbee-plugs/fastbee-generator/pom.xml new file mode 100644 index 00000000..4242b221 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/pom.xml @@ -0,0 +1,39 @@ + + + + fastbee-plugs + com.fastbee + 3.8.5 + + 4.0.0 + + fastbee-generator + + + generator代码生成 + + + + + + + org.apache.velocity + velocity-engine-core + + + + + commons-collections + commons-collections + + + + + com.fastbee + fastbee-common + + + + + \ No newline at end of file diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/config/GenConfig.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/config/GenConfig.java new file mode 100644 index 00000000..e8f2fedc --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/config/GenConfig.java @@ -0,0 +1,73 @@ +package com.fastbee.generator.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; + +/** + * 读取代码生成相关配置 + * + * @author ruoyi + */ +@Component +@ConfigurationProperties(prefix = "gen") +@PropertySource(value = { "classpath:generator.yml" }) +public class GenConfig +{ + /** 作者 */ + public static String author; + + /** 生成包路径 */ + public static String packageName; + + /** 自动去除表前缀,默认是false */ + public static boolean autoRemovePre; + + /** 表前缀(类名不会包含表前缀) */ + public static String tablePrefix; + + public static String getAuthor() + { + return author; + } + + @Value("${author}") + public void setAuthor(String author) + { + GenConfig.author = author; + } + + public static String getPackageName() + { + return packageName; + } + + @Value("${packageName}") + public void setPackageName(String packageName) + { + GenConfig.packageName = packageName; + } + + public static boolean getAutoRemovePre() + { + return autoRemovePre; + } + + @Value("${autoRemovePre}") + public void setAutoRemovePre(boolean autoRemovePre) + { + GenConfig.autoRemovePre = autoRemovePre; + } + + public static String getTablePrefix() + { + return tablePrefix; + } + + @Value("${tablePrefix}") + public void setTablePrefix(String tablePrefix) + { + GenConfig.tablePrefix = tablePrefix; + } +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/controller/GenController.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/controller/GenController.java new file mode 100644 index 00000000..c30a3ebf --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/controller/GenController.java @@ -0,0 +1,230 @@ +package com.fastbee.generator.controller; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.core.text.Convert; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.generator.domain.GenTable; +import com.fastbee.generator.domain.GenTableColumn; +import com.fastbee.generator.service.IGenTableColumnService; +import com.fastbee.generator.service.IGenTableService; + +/** + * 代码生成 操作处理 + * + * @author ruoyi + */ +@Api(tags = "代码生成模块") +@RestController +@RequestMapping("/tool/gen") +public class GenController extends BaseController +{ + @Autowired + private IGenTableService genTableService; + + @Autowired + private IGenTableColumnService genTableColumnService; + + /** + * 查询代码生成列表 + */ + @ApiOperation("查询代码生成列表") + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/list") + public TableDataInfo genList(GenTable genTable) + { + startPage(); + List list = genTableService.selectGenTableList(genTable); + return getDataTable(list); + } + + /** + * 修改代码生成业务 + */ + @ApiOperation("修改代码生成业务") + @PreAuthorize("@ss.hasPermi('tool:gen:query')") + @GetMapping(value = "/{tableId}") + public AjaxResult getInfo(@PathVariable Long tableId) + { + GenTable table = genTableService.selectGenTableById(tableId); + List tables = genTableService.selectGenTableAll(); + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + Map map = new HashMap(); + map.put("info", table); + map.put("rows", list); + map.put("tables", tables); + return success(map); + } + + /** + * 查询数据库列表 + */ + @ApiOperation("查询数据库列表") + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/db/list") + public TableDataInfo dataList(GenTable genTable) + { + startPage(); + List list = genTableService.selectDbTableList(genTable); + return getDataTable(list); + } + + /** + * 查询数据表字段列表 + */ + @ApiOperation("查询数据表字段列表") + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping(value = "/column/{tableId}") + public TableDataInfo columnList(Long tableId) + { + TableDataInfo dataInfo = new TableDataInfo(); + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + dataInfo.setRows(list); + dataInfo.setTotal(list.size()); + return dataInfo; + } + + /** + * 导入表结构(保存) + */ + @ApiOperation("导入表结构") + @PreAuthorize("@ss.hasPermi('tool:gen:import')") + @Log(title = "代码生成", businessType = BusinessType.IMPORT) + @PostMapping("/importTable") + public AjaxResult importTableSave(String tables) + { + String[] tableNames = Convert.toStrArray(tables); + // 查询表信息 + List tableList = genTableService.selectDbTableListByNames(tableNames); + genTableService.importGenTable(tableList); + return success(); + } + + /** + * 修改保存代码生成业务 + */ + @ApiOperation("修改保存代码生成业务") + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult editSave(@Validated @RequestBody GenTable genTable) + { + genTableService.validateEdit(genTable); + genTableService.updateGenTable(genTable); + return success(); + } + + /** + * 删除代码生成 + */ + @ApiOperation("删除代码生成") + @PreAuthorize("@ss.hasPermi('tool:gen:remove')") + @Log(title = "代码生成", businessType = BusinessType.DELETE) + @DeleteMapping("/{tableIds}") + public AjaxResult remove(@PathVariable Long[] tableIds) + { + genTableService.deleteGenTableByIds(tableIds); + return success(); + } + + /** + * 预览代码 + */ + @ApiOperation("预览代码") + @PreAuthorize("@ss.hasPermi('tool:gen:preview')") + @GetMapping("/preview/{tableId}") + public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException + { + Map dataMap = genTableService.previewCode(tableId); + return success(dataMap); + } + + /** + * 生成代码(下载方式) + */ + @ApiOperation("生成代码(下载方式)") + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/download/{tableName}") + public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException + { + byte[] data = genTableService.downloadCode(tableName); + genCode(response, data); + } + + /** + * 生成代码(自定义路径) + */ + @ApiOperation("生成代码(自定义路径)") + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/genCode/{tableName}") + public AjaxResult genCode(@PathVariable("tableName") String tableName) + { + genTableService.generatorCode(tableName); + return success(); + } + + /** + * 同步数据库 + */ + @ApiOperation("同步数据库") + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @GetMapping("/synchDb/{tableName}") + public AjaxResult synchDb(@PathVariable("tableName") String tableName) + { + genTableService.synchDb(tableName); + return success(); + } + + /** + * 批量生成代码 + */ + @ApiOperation("批量生成代码") + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/batchGenCode") + public void batchGenCode(HttpServletResponse response, String tables) throws IOException + { + String[] tableNames = Convert.toStrArray(tables); + byte[] data = genTableService.downloadCode(tableNames); + genCode(response, data); + } + + /** + * 生成zip文件 + */ + private void genCode(HttpServletResponse response, byte[] data) throws IOException + { + response.reset(); + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); + response.setHeader("Content-Disposition", "attachment; filename=\"fastbee.zip\""); + response.addHeader("Content-Length", "" + data.length); + response.setContentType("application/octet-stream; charset=UTF-8"); + IOUtils.write(data, response.getOutputStream()); + } +} \ No newline at end of file diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/domain/GenTable.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/domain/GenTable.java new file mode 100644 index 00000000..8048c173 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/domain/GenTable.java @@ -0,0 +1,399 @@ +package com.fastbee.generator.domain; + +import java.util.List; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.apache.commons.lang3.ArrayUtils; +import com.fastbee.common.constant.GenConstants; +import com.fastbee.common.core.domain.BaseEntity; +import com.fastbee.common.utils.StringUtils; + +/** + * 业务表 gen_table + * + * @author ruoyi + */ +@ApiModel(value = "GenTable", description = "业务表 gen_table") +public class GenTable extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 编号 */ + @ApiModelProperty("编号") + private Long tableId; + + /** 表名称 */ + @ApiModelProperty(value = "表名称", required = true) + @NotBlank(message = "表名称不能为空") + private String tableName; + + /** 表描述 */ + @ApiModelProperty(value = "表描述", required = true) + @NotBlank(message = "表描述不能为空") + private String tableComment; + + /** 关联父表的表名 */ + @ApiModelProperty("关联父表的表名") + private String subTableName; + + /** 本表关联父表的外键名 */ + @ApiModelProperty("本表关联父表的外键名") + private String subTableFkName; + + /** 实体类名称(首字母大写) */ + @ApiModelProperty(value = "实体类名称(首字母大写)", required = true) + @NotBlank(message = "实体类名称不能为空") + private String className; + + /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */ + @ApiModelProperty(value = "使用的模板", notes = "(crud单表操作 tree树表操作 sub主子表操作)") + private String tplCategory; + + /** 生成包路径 */ + @ApiModelProperty(value = "生成包路径", required = true) + @NotBlank(message = "生成包路径不能为空") + private String packageName; + + /** 生成模块名 */ + @ApiModelProperty(value = "生成模块名", required = true) + @NotBlank(message = "生成模块名不能为空") + private String moduleName; + + /** 生成业务名 */ + @ApiModelProperty(value = "生成业务名", required = true) + @NotBlank(message = "生成业务名不能为空") + private String businessName; + + /** 生成功能名 */ + @ApiModelProperty(value = "生成功能名", required = true) + @NotBlank(message = "生成功能名不能为空") + private String functionName; + + /** 生成作者 */ + @ApiModelProperty(value = "生成作者", required = true) + @NotBlank(message = "作者不能为空") + private String functionAuthor; + + /** 生成代码方式(0zip压缩包 1自定义路径) */ + @ApiModelProperty(value = "生成代码方式", notes = "(0zip压缩包 1自定义路径)") + private String genType; + + /** 生成路径(不填默认项目路径) */ + @ApiModelProperty("生成路径") + private String genPath; + + /** 主键信息 */ + @ApiModelProperty("主键信息") + private GenTableColumn pkColumn; + + /** 子表信息 */ + @ApiModelProperty("子表信息") + private GenTable subTable; + + /** 表列信息 */ + @ApiModelProperty("表列信息") + @Valid + private List columns; + + /** 其它生成选项 */ + @ApiModelProperty("其它生成选项") + private String options; + + /** 树编码字段 */ + @ApiModelProperty("树编码字段") + private String treeCode; + + /** 树父编码字段 */ + @ApiModelProperty("树父编码字段") + private String treeParentCode; + + /** 树名称字段 */ + @ApiModelProperty("树名称字段") + private String treeName; + + /** 上级菜单ID字段 */ + @ApiModelProperty("上级菜单ID字段") + private String parentMenuId; + + /** 上级菜单名称字段 */ + @ApiModelProperty("上级菜单名称字段") + private String parentMenuName; + + public Long getTableId() + { + return tableId; + } + + public void setTableId(Long tableId) + { + this.tableId = tableId; + } + + public String getTableName() + { + return tableName; + } + + public void setTableName(String tableName) + { + this.tableName = tableName; + } + + public String getTableComment() + { + return tableComment; + } + + public void setTableComment(String tableComment) + { + this.tableComment = tableComment; + } + + public String getSubTableName() + { + return subTableName; + } + + public void setSubTableName(String subTableName) + { + this.subTableName = subTableName; + } + + public String getSubTableFkName() + { + return subTableFkName; + } + + public void setSubTableFkName(String subTableFkName) + { + this.subTableFkName = subTableFkName; + } + + public String getClassName() + { + return className; + } + + public void setClassName(String className) + { + this.className = className; + } + + public String getTplCategory() + { + return tplCategory; + } + + public void setTplCategory(String tplCategory) + { + this.tplCategory = tplCategory; + } + + public String getPackageName() + { + return packageName; + } + + public void setPackageName(String packageName) + { + this.packageName = packageName; + } + + public String getModuleName() + { + return moduleName; + } + + public void setModuleName(String moduleName) + { + this.moduleName = moduleName; + } + + public String getBusinessName() + { + return businessName; + } + + public void setBusinessName(String businessName) + { + this.businessName = businessName; + } + + public String getFunctionName() + { + return functionName; + } + + public void setFunctionName(String functionName) + { + this.functionName = functionName; + } + + public String getFunctionAuthor() + { + return functionAuthor; + } + + public void setFunctionAuthor(String functionAuthor) + { + this.functionAuthor = functionAuthor; + } + + public String getGenType() + { + return genType; + } + + public void setGenType(String genType) + { + this.genType = genType; + } + + public String getGenPath() + { + return genPath; + } + + public void setGenPath(String genPath) + { + this.genPath = genPath; + } + + public GenTableColumn getPkColumn() + { + return pkColumn; + } + + public void setPkColumn(GenTableColumn pkColumn) + { + this.pkColumn = pkColumn; + } + + public GenTable getSubTable() + { + return subTable; + } + + public void setSubTable(GenTable subTable) + { + this.subTable = subTable; + } + + public List getColumns() + { + return columns; + } + + public void setColumns(List columns) + { + this.columns = columns; + } + + public String getOptions() + { + return options; + } + + public void setOptions(String options) + { + this.options = options; + } + + public String getTreeCode() + { + return treeCode; + } + + public void setTreeCode(String treeCode) + { + this.treeCode = treeCode; + } + + public String getTreeParentCode() + { + return treeParentCode; + } + + public void setTreeParentCode(String treeParentCode) + { + this.treeParentCode = treeParentCode; + } + + public String getTreeName() + { + return treeName; + } + + public void setTreeName(String treeName) + { + this.treeName = treeName; + } + + public String getParentMenuId() + { + return parentMenuId; + } + + public void setParentMenuId(String parentMenuId) + { + this.parentMenuId = parentMenuId; + } + + public String getParentMenuName() + { + return parentMenuName; + } + + public void setParentMenuName(String parentMenuName) + { + this.parentMenuName = parentMenuName; + } + + public boolean isSub() + { + return isSub(this.tplCategory); + } + + public static boolean isSub(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory); + } + + public boolean isTree() + { + return isTree(this.tplCategory); + } + + public static boolean isTree(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory); + } + + public boolean isCrud() + { + return isCrud(this.tplCategory); + } + + public static boolean isCrud(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory); + } + + public boolean isSuperColumn(String javaField) + { + return isSuperColumn(this.tplCategory, javaField); + } + + public static boolean isSuperColumn(String tplCategory, String javaField) + { + if (isTree(tplCategory)) + { + return StringUtils.equalsAnyIgnoreCase(javaField, + ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY)); + } + return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY); + } +} \ No newline at end of file diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/domain/GenTableColumn.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/domain/GenTableColumn.java new file mode 100644 index 00000000..654bd2eb --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/domain/GenTableColumn.java @@ -0,0 +1,394 @@ +package com.fastbee.generator.domain; + +import javax.validation.constraints.NotBlank; +import com.fastbee.common.core.domain.BaseEntity; +import com.fastbee.common.utils.StringUtils; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +/** + * 代码生成业务字段表 gen_table_column + * + * @author ruoyi + */ +@ApiModel(value = "GenTableColumn", description = "代码生成业务字段表 gen_table_column") +public class GenTableColumn extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 编号 */ + @ApiModelProperty("编号") + private Long columnId; + + /** 归属表编号 */ + @ApiModelProperty("归属表编号") + private Long tableId; + + /** 列名称 */ + @ApiModelProperty("列名称") + private String columnName; + + /** 列描述 */ + @ApiModelProperty("列描述") + private String columnComment; + + /** 列类型 */ + @ApiModelProperty("列类型") + private String columnType; + + /** JAVA类型 */ + @ApiModelProperty("JAVA类型") + private String javaType; + + /** JAVA字段名 */ + @ApiModelProperty(value = "JAVA字段名", required = true) + @NotBlank(message = "Java属性不能为空") + private String javaField; + + /** 是否主键(1是) */ + @ApiModelProperty("是否主键(1是)") + private String isPk; + + /** 是否自增(1是) */ + @ApiModelProperty("是否自增(1是)") + private String isIncrement; + + /** 是否必填(1是) */ + @ApiModelProperty("是否必填(1是)") + private String isRequired; + + /** 是否为插入字段(1是) */ + @ApiModelProperty("是否为插入字段(1是)") + private String isInsert; + + /** 是否编辑字段(1是) */ + @ApiModelProperty("是否编辑字段(1是)") + private String isEdit; + + /** 是否列表字段(1是) */ + @ApiModelProperty("是否列表字段(1是)") + private String isList; + + /** 是否查询字段(1是) */ + @ApiModelProperty("是否查询字段(1是)") + private String isQuery; + + /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */ + @ApiModelProperty(value = "查询方式", notes = "(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围)") + private String queryType; + + /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) */ + @ApiModelProperty(value = "显示类型", notes = "(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件)") + private String htmlType; + + /** 字典类型 */ + @ApiModelProperty("字典类型") + private String dictType; + + /** 排序 */ + @ApiModelProperty("排序") + private Integer sort; + + public void setColumnId(Long columnId) + { + this.columnId = columnId; + } + + public Long getColumnId() + { + return columnId; + } + + public void setTableId(Long tableId) + { + this.tableId = tableId; + } + + public Long getTableId() + { + return tableId; + } + + public void setColumnName(String columnName) + { + this.columnName = columnName; + } + + public String getColumnName() + { + return columnName; + } + + public void setColumnComment(String columnComment) + { + this.columnComment = columnComment; + } + + public String getColumnComment() + { + return columnComment; + } + + public void setColumnType(String columnType) + { + this.columnType = columnType; + } + + public String getColumnType() + { + return columnType; + } + + public void setJavaType(String javaType) + { + this.javaType = javaType; + } + + public String getJavaType() + { + return javaType; + } + + public void setJavaField(String javaField) + { + this.javaField = javaField; + } + + public String getJavaField() + { + return javaField; + } + + public String getCapJavaField() + { + return StringUtils.capitalize(javaField); + } + + public void setIsPk(String isPk) + { + this.isPk = isPk; + } + + public String getIsPk() + { + return isPk; + } + + public boolean isPk() + { + return isPk(this.isPk); + } + + public boolean isPk(String isPk) + { + return isPk != null && StringUtils.equals("1", isPk); + } + + public String getIsIncrement() + { + return isIncrement; + } + + public void setIsIncrement(String isIncrement) + { + this.isIncrement = isIncrement; + } + + public boolean isIncrement() + { + return isIncrement(this.isIncrement); + } + + public boolean isIncrement(String isIncrement) + { + return isIncrement != null && StringUtils.equals("1", isIncrement); + } + + public void setIsRequired(String isRequired) + { + this.isRequired = isRequired; + } + + public String getIsRequired() + { + return isRequired; + } + + public boolean isRequired() + { + return isRequired(this.isRequired); + } + + public boolean isRequired(String isRequired) + { + return isRequired != null && StringUtils.equals("1", isRequired); + } + + public void setIsInsert(String isInsert) + { + this.isInsert = isInsert; + } + + public String getIsInsert() + { + return isInsert; + } + + public boolean isInsert() + { + return isInsert(this.isInsert); + } + + public boolean isInsert(String isInsert) + { + return isInsert != null && StringUtils.equals("1", isInsert); + } + + public void setIsEdit(String isEdit) + { + this.isEdit = isEdit; + } + + public String getIsEdit() + { + return isEdit; + } + + public boolean isEdit() + { + return isInsert(this.isEdit); + } + + public boolean isEdit(String isEdit) + { + return isEdit != null && StringUtils.equals("1", isEdit); + } + + public void setIsList(String isList) + { + this.isList = isList; + } + + public String getIsList() + { + return isList; + } + + public boolean isList() + { + return isList(this.isList); + } + + public boolean isList(String isList) + { + return isList != null && StringUtils.equals("1", isList); + } + + public void setIsQuery(String isQuery) + { + this.isQuery = isQuery; + } + + public String getIsQuery() + { + return isQuery; + } + + public boolean isQuery() + { + return isQuery(this.isQuery); + } + + public boolean isQuery(String isQuery) + { + return isQuery != null && StringUtils.equals("1", isQuery); + } + + public void setQueryType(String queryType) + { + this.queryType = queryType; + } + + public String getQueryType() + { + return queryType; + } + + public String getHtmlType() + { + return htmlType; + } + + public void setHtmlType(String htmlType) + { + this.htmlType = htmlType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + public String getDictType() + { + return dictType; + } + + public void setSort(Integer sort) + { + this.sort = sort; + } + + public Integer getSort() + { + return sort; + } + + public boolean isSuperColumn() + { + return isSuperColumn(this.javaField); + } + + public static boolean isSuperColumn(String javaField) + { + return StringUtils.equalsAnyIgnoreCase(javaField, + // BaseEntity + "createBy", "createTime", "updateBy", "updateTime", "remark", + // TreeEntity + "parentName", "parentId", "orderNum", "ancestors"); + } + + public boolean isUsableColumn() + { + return isUsableColumn(javaField); + } + + public static boolean isUsableColumn(String javaField) + { + // isSuperColumn()中的名单用于避免生成多余Domain属性,若某些属性在生成页面时需要用到不能忽略,则放在此处白名单 + return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark"); + } + + public String readConverterExp() + { + String remarks = StringUtils.substringBetween(this.columnComment, "(", ")"); + StringBuffer sb = new StringBuffer(); + if (StringUtils.isNotEmpty(remarks)) + { + for (String value : remarks.split(" ")) + { + if (StringUtils.isNotEmpty(value)) + { + Object startStr = value.subSequence(0, 1); + String endStr = value.substring(1); + sb.append("").append(startStr).append("=").append(endStr).append(","); + } + } + return sb.deleteCharAt(sb.length() - 1).toString(); + } + else + { + return this.columnComment; + } + } +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/mapper/GenTableColumnMapper.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/mapper/GenTableColumnMapper.java new file mode 100644 index 00000000..a4a5cce0 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/mapper/GenTableColumnMapper.java @@ -0,0 +1,60 @@ +package com.fastbee.generator.mapper; + +import java.util.List; +import com.fastbee.generator.domain.GenTableColumn; + +/** + * 业务字段 数据层 + * + * @author ruoyi + */ +public interface GenTableColumnMapper +{ + /** + * 根据表名称查询列信息 + * + * @param tableName 表名称 + * @return 列信息 + */ + public List selectDbTableColumnsByName(String tableName); + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段 + * + * @param genTableColumns 列数据 + * @return 结果 + */ + public int deleteGenTableColumns(List genTableColumns); + + /** + * 批量删除业务字段 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(Long[] ids); +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/mapper/GenTableMapper.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/mapper/GenTableMapper.java new file mode 100644 index 00000000..21ba4c8e --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/mapper/GenTableMapper.java @@ -0,0 +1,83 @@ +package com.fastbee.generator.mapper; + +import java.util.List; +import com.fastbee.generator.domain.GenTable; + +/** + * 业务 数据层 + * + * @author ruoyi + */ +public interface GenTableMapper +{ + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询表ID业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 查询表名称业务信息 + * + * @param tableName 表名称 + * @return 业务信息 + */ + public GenTable selectGenTableByName(String tableName); + + /** + * 新增业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int insertGenTable(GenTable genTable); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int updateGenTable(GenTable genTable); + + /** + * 批量删除业务 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableByIds(Long[] ids); +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/service/GenTableColumnServiceImpl.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/service/GenTableColumnServiceImpl.java new file mode 100644 index 00000000..086e7a76 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/service/GenTableColumnServiceImpl.java @@ -0,0 +1,68 @@ +package com.fastbee.generator.service; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.fastbee.common.core.text.Convert; +import com.fastbee.generator.domain.GenTableColumn; +import com.fastbee.generator.mapper.GenTableColumnMapper; + +/** + * 业务字段 服务层实现 + * + * @author ruoyi + */ +@Service +public class GenTableColumnServiceImpl implements IGenTableColumnService +{ + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + @Override + public List selectGenTableColumnListByTableId(Long tableId) + { + return genTableColumnMapper.selectGenTableColumnListByTableId(tableId); + } + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + @Override + public int insertGenTableColumn(GenTableColumn genTableColumn) + { + return genTableColumnMapper.insertGenTableColumn(genTableColumn); + } + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + @Override + public int updateGenTableColumn(GenTableColumn genTableColumn) + { + return genTableColumnMapper.updateGenTableColumn(genTableColumn); + } + + /** + * 删除业务字段对象 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteGenTableColumnByIds(String ids) + { + return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids)); + } +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/service/GenTableServiceImpl.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/service/GenTableServiceImpl.java new file mode 100644 index 00000000..269067fc --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/service/GenTableServiceImpl.java @@ -0,0 +1,521 @@ +package com.fastbee.generator.service; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.constant.GenConstants; +import com.fastbee.common.core.text.CharsetKit; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.generator.domain.GenTable; +import com.fastbee.generator.domain.GenTableColumn; +import com.fastbee.generator.mapper.GenTableColumnMapper; +import com.fastbee.generator.mapper.GenTableMapper; +import com.fastbee.generator.util.GenUtils; +import com.fastbee.generator.util.VelocityInitializer; +import com.fastbee.generator.util.VelocityUtils; + +/** + * 业务 服务层实现 + * + * @author ruoyi + */ +@Service +public class GenTableServiceImpl implements IGenTableService +{ + private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class); + + @Autowired + private GenTableMapper genTableMapper; + + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + @Override + public GenTable selectGenTableById(Long id) + { + GenTable genTable = genTableMapper.selectGenTableById(id); + setTableFromOptions(genTable); + return genTable; + } + + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + @Override + public List selectGenTableList(GenTable genTable) + { + return genTableMapper.selectGenTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + @Override + public List selectDbTableList(GenTable genTable) + { + return genTableMapper.selectDbTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + @Override + public List selectDbTableListByNames(String[] tableNames) + { + return genTableMapper.selectDbTableListByNames(tableNames); + } + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + @Override + public List selectGenTableAll() + { + return genTableMapper.selectGenTableAll(); + } + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + @Override + @Transactional + public void updateGenTable(GenTable genTable) + { + String options = JSON.toJSONString(genTable.getParams()); + genTable.setOptions(options); + int row = genTableMapper.updateGenTable(genTable); + if (row > 0) + { + for (GenTableColumn cenTableColumn : genTable.getColumns()) + { + genTableColumnMapper.updateGenTableColumn(cenTableColumn); + } + } + } + + /** + * 删除业务对象 + * + * @param tableIds 需要删除的数据ID + * @return 结果 + */ + @Override + @Transactional + public void deleteGenTableByIds(Long[] tableIds) + { + genTableMapper.deleteGenTableByIds(tableIds); + genTableColumnMapper.deleteGenTableColumnByIds(tableIds); + } + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + */ + @Override + @Transactional + public void importGenTable(List tableList) + { + String operName = SecurityUtils.getUsername(); + try + { + for (GenTable table : tableList) + { + String tableName = table.getTableName(); + GenUtils.initTable(table, operName); + int row = genTableMapper.insertGenTable(table); + if (row > 0) + { + // 保存列信息 + List genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + for (GenTableColumn column : genTableColumns) + { + GenUtils.initColumnField(column, table); + genTableColumnMapper.insertGenTableColumn(column); + } + } + } + } + catch (Exception e) + { + throw new ServiceException("导入失败:" + e.getMessage()); + } + } + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + @Override + public Map previewCode(Long tableId) + { + Map dataMap = new LinkedHashMap<>(); + // 查询表信息 + GenTable table = genTableMapper.selectGenTableById(tableId); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory()); + for (String template : templates) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + dataMap.put(template, sw.toString()); + } + return dataMap; + } + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + @Override + public byte[] downloadCode(String tableName) + { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + generatorCode(tableName, zip); + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + */ + @Override + public void generatorCode(String tableName) + { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory()); + for (String template : templates) + { + if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try + { + String path = getGenPath(table, template); + FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8); + } + catch (IOException e) + { + throw new ServiceException("渲染模板失败,表名:" + table.getTableName()); + } + } + } + } + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + @Override + @Transactional + public void synchDb(String tableName) + { + GenTable table = genTableMapper.selectGenTableByName(tableName); + List tableColumns = table.getColumns(); + Map tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity())); + + List dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + if (StringUtils.isEmpty(dbTableColumns)) + { + throw new ServiceException("同步数据失败,原表结构不存在"); + } + List dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList()); + + dbTableColumns.forEach(column -> { + GenUtils.initColumnField(column, table); + if (tableColumnMap.containsKey(column.getColumnName())) + { + GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName()); + column.setColumnId(prevColumn.getColumnId()); + if (column.isList()) + { + // 如果是列表,继续保留查询方式/字典类型选项 + column.setDictType(prevColumn.getDictType()); + column.setQueryType(prevColumn.getQueryType()); + } + if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk() + && (column.isInsert() || column.isEdit()) + && ((column.isUsableColumn()) || (!column.isSuperColumn()))) + { + // 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项 + column.setIsRequired(prevColumn.getIsRequired()); + column.setHtmlType(prevColumn.getHtmlType()); + } + genTableColumnMapper.updateGenTableColumn(column); + } + else + { + genTableColumnMapper.insertGenTableColumn(column); + } + }); + + List delColumns = tableColumns.stream().filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList()); + if (StringUtils.isNotEmpty(delColumns)) + { + genTableColumnMapper.deleteGenTableColumns(delColumns); + } + } + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + @Override + public byte[] downloadCode(String[] tableNames) + { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + for (String tableName : tableNames) + { + generatorCode(tableName, zip); + } + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 查询表信息并生成代码 + */ + private void generatorCode(String tableName, ZipOutputStream zip) + { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory()); + for (String template : templates) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try + { + // 添加到zip + zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table))); + IOUtils.write(sw.toString(), zip, Constants.UTF8); + IOUtils.closeQuietly(sw); + zip.flush(); + zip.closeEntry(); + } + catch (IOException e) + { + log.error("渲染模板失败,表名:" + table.getTableName(), e); + } + } + } + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + @Override + public void validateEdit(GenTable genTable) + { + if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) + { + String options = JSON.toJSONString(genTable.getParams()); + JSONObject paramsObj = JSON.parseObject(options); + if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) + { + throw new ServiceException("树编码字段不能为空"); + } + else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) + { + throw new ServiceException("树父编码字段不能为空"); + } + else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) + { + throw new ServiceException("树名称字段不能为空"); + } + else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) + { + if (StringUtils.isEmpty(genTable.getSubTableName())) + { + throw new ServiceException("关联子表的表名不能为空"); + } + else if (StringUtils.isEmpty(genTable.getSubTableFkName())) + { + throw new ServiceException("子表关联的外键名不能为空"); + } + } + } + } + + /** + * 设置主键列信息 + * + * @param table 业务表信息 + */ + public void setPkColumn(GenTable table) + { + for (GenTableColumn column : table.getColumns()) + { + if (column.isPk()) + { + table.setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getPkColumn())) + { + table.setPkColumn(table.getColumns().get(0)); + } + if (GenConstants.TPL_SUB.equals(table.getTplCategory())) + { + for (GenTableColumn column : table.getSubTable().getColumns()) + { + if (column.isPk()) + { + table.getSubTable().setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getSubTable().getPkColumn())) + { + table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0)); + } + } + } + + /** + * 设置主子表信息 + * + * @param table 业务表信息 + */ + public void setSubTable(GenTable table) + { + String subTableName = table.getSubTableName(); + if (StringUtils.isNotEmpty(subTableName)) + { + table.setSubTable(genTableMapper.selectGenTableByName(subTableName)); + } + } + + /** + * 设置代码生成其他选项值 + * + * @param genTable 设置后的生成对象 + */ + public void setTableFromOptions(GenTable genTable) + { + JSONObject paramsObj = JSON.parseObject(genTable.getOptions()); + if (StringUtils.isNotNull(paramsObj)) + { + String treeCode = paramsObj.getString(GenConstants.TREE_CODE); + String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID); + String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); + + genTable.setTreeCode(treeCode); + genTable.setTreeParentCode(treeParentCode); + genTable.setTreeName(treeName); + genTable.setParentMenuId(parentMenuId); + genTable.setParentMenuName(parentMenuName); + } + } + + /** + * 获取代码生成地址 + * + * @param table 业务表信息 + * @param template 模板文件路径 + * @return 生成地址 + */ + public static String getGenPath(GenTable table, String template) + { + String genPath = table.getGenPath(); + if (StringUtils.equals(genPath, "/")) + { + return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table); + } + return genPath + File.separator + VelocityUtils.getFileName(template, table); + } +} \ No newline at end of file diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/service/IGenTableColumnService.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/service/IGenTableColumnService.java new file mode 100644 index 00000000..e2cd041b --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/service/IGenTableColumnService.java @@ -0,0 +1,44 @@ +package com.fastbee.generator.service; + +import java.util.List; +import com.fastbee.generator.domain.GenTableColumn; + +/** + * 业务字段 服务层 + * + * @author ruoyi + */ +public interface IGenTableColumnService +{ + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(String ids); +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/service/IGenTableService.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/service/IGenTableService.java new file mode 100644 index 00000000..d03dba4d --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/service/IGenTableService.java @@ -0,0 +1,121 @@ +package com.fastbee.generator.service; + +import java.util.List; +import java.util.Map; +import com.fastbee.generator.domain.GenTable; + +/** + * 业务 服务层 + * + * @author ruoyi + */ +public interface IGenTableService +{ + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public void updateGenTable(GenTable genTable); + + /** + * 删除业务信息 + * + * @param tableIds 需要删除的表数据ID + * @return 结果 + */ + public void deleteGenTableByIds(Long[] tableIds); + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + */ + public void importGenTable(List tableList); + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + public Map previewCode(Long tableId); + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + public byte[] downloadCode(String tableName); + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + * @return 数据 + */ + public void generatorCode(String tableName); + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + public void synchDb(String tableName); + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + public byte[] downloadCode(String[] tableNames); + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + public void validateEdit(GenTable genTable); +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/util/GenUtils.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/util/GenUtils.java new file mode 100644 index 00000000..59584d41 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/util/GenUtils.java @@ -0,0 +1,257 @@ +package com.fastbee.generator.util; + +import java.util.Arrays; +import org.apache.commons.lang3.RegExUtils; +import com.fastbee.common.constant.GenConstants; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.generator.config.GenConfig; +import com.fastbee.generator.domain.GenTable; +import com.fastbee.generator.domain.GenTableColumn; + +/** + * 代码生成器 工具类 + * + * @author ruoyi + */ +public class GenUtils +{ + /** + * 初始化表信息 + */ + public static void initTable(GenTable genTable, String operName) + { + genTable.setClassName(convertClassName(genTable.getTableName())); + genTable.setPackageName(GenConfig.getPackageName()); + genTable.setModuleName(getModuleName(GenConfig.getPackageName())); + genTable.setBusinessName(getBusinessName(genTable.getTableName())); + genTable.setFunctionName(replaceText(genTable.getTableComment())); + genTable.setFunctionAuthor(GenConfig.getAuthor()); + genTable.setCreateBy(operName); + } + + /** + * 初始化列属性字段 + */ + public static void initColumnField(GenTableColumn column, GenTable table) + { + String dataType = getDbType(column.getColumnType()); + String columnName = column.getColumnName(); + column.setTableId(table.getTableId()); + column.setCreateBy(table.getCreateBy()); + // 设置java字段名 + column.setJavaField(StringUtils.toCamelCase(columnName)); + // 设置默认类型 + column.setJavaType(GenConstants.TYPE_STRING); + column.setQueryType(GenConstants.QUERY_EQ); + + if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) + { + // 字符串长度超过500设置为文本域 + Integer columnLength = getColumnLength(column.getColumnType()); + String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT; + column.setHtmlType(htmlType); + } + else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) + { + column.setJavaType(GenConstants.TYPE_DATE); + column.setHtmlType(GenConstants.HTML_DATETIME); + } + else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) + { + column.setHtmlType(GenConstants.HTML_INPUT); + + // 如果是浮点型 统一用BigDecimal + String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ","); + if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) + { + column.setJavaType(GenConstants.TYPE_BIGDECIMAL); + } + // 如果是整形 + else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) + { + column.setJavaType(GenConstants.TYPE_INTEGER); + } + // 长整形 + else + { + column.setJavaType(GenConstants.TYPE_LONG); + } + } + + // 插入字段(默认所有字段都需要插入) + column.setIsInsert(GenConstants.REQUIRE); + + // 编辑字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk()) + { + column.setIsEdit(GenConstants.REQUIRE); + } + // 列表字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk()) + { + column.setIsList(GenConstants.REQUIRE); + } + // 查询字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) + { + column.setIsQuery(GenConstants.REQUIRE); + } + + // 查询字段类型 + if (StringUtils.endsWithIgnoreCase(columnName, "name")) + { + column.setQueryType(GenConstants.QUERY_LIKE); + } + // 状态字段设置单选框 + if (StringUtils.endsWithIgnoreCase(columnName, "status")) + { + column.setHtmlType(GenConstants.HTML_RADIO); + } + // 类型&性别字段设置下拉框 + else if (StringUtils.endsWithIgnoreCase(columnName, "type") + || StringUtils.endsWithIgnoreCase(columnName, "sex")) + { + column.setHtmlType(GenConstants.HTML_SELECT); + } + // 图片字段设置图片上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "image")) + { + column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD); + } + // 文件字段设置文件上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "file")) + { + column.setHtmlType(GenConstants.HTML_FILE_UPLOAD); + } + // 内容字段设置富文本控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "content")) + { + column.setHtmlType(GenConstants.HTML_EDITOR); + } + } + + /** + * 校验数组是否包含指定值 + * + * @param arr 数组 + * @param targetValue 值 + * @return 是否包含 + */ + public static boolean arraysContains(String[] arr, String targetValue) + { + return Arrays.asList(arr).contains(targetValue); + } + + /** + * 获取模块名 + * + * @param packageName 包名 + * @return 模块名 + */ + public static String getModuleName(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + int nameLength = packageName.length(); + return StringUtils.substring(packageName, lastIndex + 1, nameLength); + } + + /** + * 获取业务名 + * + * @param tableName 表名 + * @return 业务名 + */ + public static String getBusinessName(String tableName) + { + int lastIndex = tableName.lastIndexOf("_"); + int nameLength = tableName.length(); + return StringUtils.substring(tableName, lastIndex + 1, nameLength); + } + + /** + * 表名转换成Java类名 + * + * @param tableName 表名称 + * @return 类名 + */ + public static String convertClassName(String tableName) + { + boolean autoRemovePre = GenConfig.getAutoRemovePre(); + String tablePrefix = GenConfig.getTablePrefix(); + if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) + { + String[] searchList = StringUtils.split(tablePrefix, ","); + tableName = replaceFirst(tableName, searchList); + } + return StringUtils.convertToCamelCase(tableName); + } + + /** + * 批量替换前缀 + * + * @param replacementm 替换值 + * @param searchList 替换列表 + * @return + */ + public static String replaceFirst(String replacementm, String[] searchList) + { + String text = replacementm; + for (String searchString : searchList) + { + if (replacementm.startsWith(searchString)) + { + text = replacementm.replaceFirst(searchString, ""); + break; + } + } + return text; + } + + /** + * 关键字替换 + * + * @param text 需要被替换的名字 + * @return 替换后的名字 + */ + public static String replaceText(String text) + { + return RegExUtils.replaceAll(text, "(?:表|若依)", ""); + } + + /** + * 获取数据库类型字段 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static String getDbType(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + return StringUtils.substringBefore(columnType, "("); + } + else + { + return columnType; + } + } + + /** + * 获取字段长度 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static Integer getColumnLength(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + String length = StringUtils.substringBetween(columnType, "(", ")"); + return Integer.valueOf(length); + } + else + { + return 0; + } + } +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/util/VelocityInitializer.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/util/VelocityInitializer.java new file mode 100644 index 00000000..832d9734 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/util/VelocityInitializer.java @@ -0,0 +1,34 @@ +package com.fastbee.generator.util; + +import java.util.Properties; +import org.apache.velocity.app.Velocity; +import com.fastbee.common.constant.Constants; + +/** + * VelocityEngine工厂 + * + * @author ruoyi + */ +public class VelocityInitializer +{ + /** + * 初始化vm方法 + */ + public static void initVelocity() + { + Properties p = new Properties(); + try + { + // 加载classpath目录下的vm文件 + p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + // 定义字符集 + p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8); + // 初始化Velocity引擎,指定配置Properties + Velocity.init(p); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/util/VelocityUtils.java b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/util/VelocityUtils.java new file mode 100644 index 00000000..270c438e --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/java/com/fastbee/generator/util/VelocityUtils.java @@ -0,0 +1,402 @@ +package com.fastbee.generator.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.velocity.VelocityContext; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.constant.GenConstants; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.generator.domain.GenTable; +import com.fastbee.generator.domain.GenTableColumn; + +/** + * 模板处理工具类 + * + * @author ruoyi + */ +public class VelocityUtils +{ + /** 项目空间路径 */ + private static final String PROJECT_PATH = "main/java"; + + /** mybatis空间路径 */ + private static final String MYBATIS_PATH = "main/resources/mapper"; + + /** 默认上级菜单,系统工具 */ + private static final String DEFAULT_PARENT_MENU_ID = "3"; + + /** + * 设置模板变量信息 + * + * @return 模板列表 + */ + public static VelocityContext prepareContext(GenTable genTable) + { + String moduleName = genTable.getModuleName(); + String businessName = genTable.getBusinessName(); + String packageName = genTable.getPackageName(); + String tplCategory = genTable.getTplCategory(); + String functionName = genTable.getFunctionName(); + + VelocityContext velocityContext = new VelocityContext(); + velocityContext.put("tplCategory", genTable.getTplCategory()); + velocityContext.put("tableName", genTable.getTableName()); + velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】"); + velocityContext.put("ClassName", genTable.getClassName()); + velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName())); + velocityContext.put("moduleName", genTable.getModuleName()); + velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName())); + velocityContext.put("businessName", genTable.getBusinessName()); + velocityContext.put("basePackage", getPackagePrefix(packageName)); + velocityContext.put("packageName", packageName); + velocityContext.put("author", genTable.getFunctionAuthor()); + velocityContext.put("datetime", DateUtils.getDate()); + velocityContext.put("pkColumn", genTable.getPkColumn()); + velocityContext.put("importList", getImportList(genTable)); + velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); + velocityContext.put("columns", genTable.getColumns()); + velocityContext.put("table", genTable); + velocityContext.put("dicts", getDicts(genTable)); + setMenuVelocityContext(velocityContext, genTable); + if (GenConstants.TPL_TREE.equals(tplCategory)) + { + setTreeVelocityContext(velocityContext, genTable); + } + if (GenConstants.TPL_SUB.equals(tplCategory)) + { + setSubVelocityContext(velocityContext, genTable); + } + return velocityContext; + } + + public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String parentMenuId = getParentMenuId(paramsObj); + context.put("parentMenuId", parentMenuId); + } + + public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeCode = getTreecode(paramsObj); + String treeParentCode = getTreeParentCode(paramsObj); + String treeName = getTreeName(paramsObj); + + context.put("treeCode", treeCode); + context.put("treeParentCode", treeParentCode); + context.put("treeName", treeName); + context.put("expandColumn", getExpandColumn(genTable)); + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) + { + context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + if (paramsObj.containsKey(GenConstants.TREE_NAME)) + { + context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME)); + } + } + + public static void setSubVelocityContext(VelocityContext context, GenTable genTable) + { + GenTable subTable = genTable.getSubTable(); + String subTableName = genTable.getSubTableName(); + String subTableFkName = genTable.getSubTableFkName(); + String subClassName = genTable.getSubTable().getClassName(); + String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName); + + context.put("subTable", subTable); + context.put("subTableName", subTableName); + context.put("subTableFkName", subTableFkName); + context.put("subTableFkClassName", subTableFkClassName); + context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName)); + context.put("subClassName", subClassName); + context.put("subclassName", StringUtils.uncapitalize(subClassName)); + context.put("subImportList", getImportList(genTable.getSubTable())); + } + + /** + * 获取模板信息 + * + * @return 模板列表 + */ + public static List getTemplateList(String tplCategory) + { + List templates = new ArrayList(); + templates.add("vm/java/domain.java.vm"); + templates.add("vm/java/mapper.java.vm"); + templates.add("vm/java/service.java.vm"); + templates.add("vm/java/serviceImpl.java.vm"); + templates.add("vm/java/controller.java.vm"); + templates.add("vm/xml/mapper.xml.vm"); + templates.add("vm/sql/sql.vm"); + templates.add("vm/js/api.js.vm"); + if (GenConstants.TPL_CRUD.equals(tplCategory)) + { + templates.add("vm/vue/index.vue.vm"); + } + else if (GenConstants.TPL_TREE.equals(tplCategory)) + { + templates.add("vm/vue/index-tree.vue.vm"); + } + else if (GenConstants.TPL_SUB.equals(tplCategory)) + { + templates.add("vm/vue/index.vue.vm"); + templates.add("vm/java/sub-domain.java.vm"); + } + return templates; + } + + /** + * 获取文件名 + */ + public static String getFileName(String template, GenTable genTable) + { + // 文件名称 + String fileName = ""; + // 包路径 + String packageName = genTable.getPackageName(); + // 模块名 + String moduleName = genTable.getModuleName(); + // 大写类名 + String className = genTable.getClassName(); + // 业务名称 + String businessName = genTable.getBusinessName(); + + String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/"); + String mybatisPath = MYBATIS_PATH + "/" + moduleName; + String vuePath = "vue"; + + if (template.contains("domain.java.vm")) + { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, className); + } + if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory())) + { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName()); + } + else if (template.contains("mapper.java.vm")) + { + fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className); + } + else if (template.contains("service.java.vm")) + { + fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className); + } + else if (template.contains("serviceImpl.java.vm")) + { + fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className); + } + else if (template.contains("controller.java.vm")) + { + fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className); + } + else if (template.contains("mapper.xml.vm")) + { + fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className); + } + else if (template.contains("sql.vm")) + { + fileName = businessName + "Menu.sql"; + } + else if (template.contains("api.js.vm")) + { + fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName); + } + else if (template.contains("index.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + else if (template.contains("index-tree.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + return fileName; + } + + /** + * 获取包前缀 + * + * @param packageName 包名称 + * @return 包前缀名称 + */ + public static String getPackagePrefix(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + return StringUtils.substring(packageName, 0, lastIndex); + } + + /** + * 根据列类型获取导入包 + * + * @param genTable 业务表对象 + * @return 返回需要导入的包列表 + */ + public static HashSet getImportList(GenTable genTable) + { + List columns = genTable.getColumns(); + GenTable subGenTable = genTable.getSubTable(); + HashSet importList = new HashSet(); + if (StringUtils.isNotNull(subGenTable)) + { + importList.add("java.util.List"); + } + for (GenTableColumn column : columns) + { + if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) + { + importList.add("java.util.Date"); + importList.add("com.fasterxml.jackson.annotation.JsonFormat"); + } + else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) + { + importList.add("java.math.BigDecimal"); + } + } + return importList; + } + + /** + * 根据列类型获取字典组 + * + * @param genTable 业务表对象 + * @return 返回字典组 + */ + public static String getDicts(GenTable genTable) + { + List columns = genTable.getColumns(); + Set dicts = new HashSet(); + addDicts(dicts, columns); + if (StringUtils.isNotNull(genTable.getSubTable())) + { + List subColumns = genTable.getSubTable().getColumns(); + addDicts(dicts, subColumns); + } + return StringUtils.join(dicts, ", "); + } + + /** + * 添加字典列表 + * + * @param dicts 字典列表 + * @param columns 列集合 + */ + public static void addDicts(Set dicts, List columns) + { + for (GenTableColumn column : columns) + { + if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny( + column.getHtmlType(), + new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) + { + dicts.add("'" + column.getDictType() + "'"); + } + } + } + + /** + * 获取权限前缀 + * + * @param moduleName 模块名称 + * @param businessName 业务名称 + * @return 返回权限前缀 + */ + public static String getPermissionPrefix(String moduleName, String businessName) + { + return StringUtils.format("{}:{}", moduleName, businessName); + } + + /** + * 获取上级菜单ID字段 + * + * @param paramsObj 生成其他选项 + * @return 上级菜单ID字段 + */ + public static String getParentMenuId(JSONObject paramsObj) + { + if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID) + && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) + { + return paramsObj.getString(GenConstants.PARENT_MENU_ID); + } + return DEFAULT_PARENT_MENU_ID; + } + + /** + * 获取树编码 + * + * @param paramsObj 生成其他选项 + * @return 树编码 + */ + public static String getTreecode(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_CODE)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树父编码 + * + * @param paramsObj 生成其他选项 + * @return 树父编码 + */ + public static String getTreeParentCode(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树名称 + * + * @param paramsObj 生成其他选项 + * @return 树名称 + */ + public static String getTreeName(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_NAME)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME)); + } + return StringUtils.EMPTY; + } + + /** + * 获取需要在哪一列上面显示展开按钮 + * + * @param genTable 业务表对象 + * @return 展开按钮列序号 + */ + public static int getExpandColumn(GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + int num = 0; + for (GenTableColumn column : genTable.getColumns()) + { + if (column.isList()) + { + num++; + String columnName = column.getColumnName(); + if (columnName.equals(treeName)) + { + break; + } + } + } + return num; + } +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/generator.yml b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/generator.yml new file mode 100644 index 00000000..50e8a15f --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/generator.yml @@ -0,0 +1,10 @@ +# 代码生成 +gen: + # 作者 + author: kerwincui + # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool + packageName: com.fastbee.iot + # 自动去除表前缀,默认是false + autoRemovePre: true + # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) + tablePrefix: iot_ \ No newline at end of file diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml new file mode 100644 index 00000000..725a249d --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, create_by, create_time, update_by, update_time from gen_table_column + + + + + + + + insert into gen_table_column ( + table_id, + column_name, + column_comment, + column_type, + java_type, + java_field, + is_pk, + is_increment, + is_required, + is_insert, + is_edit, + is_list, + is_query, + query_type, + html_type, + dict_type, + sort, + create_by, + create_time + )values( + #{tableId}, + #{columnName}, + #{columnComment}, + #{columnType}, + #{javaType}, + #{javaField}, + #{isPk}, + #{isIncrement}, + #{isRequired}, + #{isInsert}, + #{isEdit}, + #{isList}, + #{isQuery}, + #{queryType}, + #{htmlType}, + #{dictType}, + #{sort}, + #{createBy}, + sysdate() + ) + + + + update gen_table_column + + column_comment = #{columnComment}, + java_type = #{javaType}, + java_field = #{javaField}, + is_insert = #{isInsert}, + is_edit = #{isEdit}, + is_list = #{isList}, + is_query = #{isQuery}, + is_required = #{isRequired}, + query_type = #{queryType}, + html_type = #{htmlType}, + dict_type = #{dictType}, + sort = #{sort}, + update_by = #{updateBy}, + update_time = sysdate() + + where column_id = #{columnId} + + + + delete from gen_table_column where table_id in + + #{tableId} + + + + + delete from gen_table_column where column_id in + + #{item.columnId} + + + + \ No newline at end of file diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/mapper/generator/GenTableMapper.xml new file mode 100644 index 00000000..14d523d5 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/mapper/generator/GenTableMapper.xml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table + + + + + + + + + + + + + + + + + + insert into gen_table ( + table_name, + table_comment, + class_name, + tpl_category, + package_name, + module_name, + business_name, + function_name, + function_author, + gen_type, + gen_path, + remark, + create_by, + create_time + )values( + #{tableName}, + #{tableComment}, + #{className}, + #{tplCategory}, + #{packageName}, + #{moduleName}, + #{businessName}, + #{functionName}, + #{functionAuthor}, + #{genType}, + #{genPath}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update gen_table + + table_name = #{tableName}, + table_comment = #{tableComment}, + sub_table_name = #{subTableName}, + sub_table_fk_name = #{subTableFkName}, + class_name = #{className}, + function_author = #{functionAuthor}, + gen_type = #{genType}, + gen_path = #{genPath}, + tpl_category = #{tplCategory}, + package_name = #{packageName}, + module_name = #{moduleName}, + business_name = #{businessName}, + function_name = #{functionName}, + options = #{options}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where table_id = #{tableId} + + + + delete from gen_table where table_id in + + #{tableId} + + + + \ No newline at end of file diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/controller.java.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/controller.java.vm new file mode 100644 index 00000000..a5a9891f --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/controller.java.vm @@ -0,0 +1,115 @@ +package ${packageName}.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.enums.BusinessType; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.fastbee.common.utils.poi.ExcelUtil; +#if($table.crud || $table.sub) +import com.fastbee.common.core.page.TableDataInfo; +#elseif($table.tree) +#end + +/** + * ${functionName}Controller + * + * @author ${author} + * @date ${datetime} + */ +@RestController +@RequestMapping("/${moduleName}/${businessName}") +public class ${ClassName}Controller extends BaseController +{ + @Autowired + private I${ClassName}Service ${className}Service; + + /** + * 查询${functionName}列表 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')") + @GetMapping("/list") +#if($table.crud || $table.sub) + public TableDataInfo list(${ClassName} ${className}) + { + startPage(); + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return getDataTable(list); + } +#elseif($table.tree) + public AjaxResult list(${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return success(list); + } +#end + + /** + * 导出${functionName}列表 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')") + @Log(title = "${functionName}", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, ${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + util.exportExcel(response, list, "${functionName}数据"); + } + + /** + * 获取${functionName}详细信息 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')") + @GetMapping(value = "/{${pkColumn.javaField}}") + public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) + { + return success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } + + /** + * 新增${functionName} + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')") + @Log(title = "${functionName}", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.insert${ClassName}(${className})); + } + + /** + * 修改${functionName} + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')") + @Log(title = "${functionName}", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.update${ClassName}(${className})); + } + + /** + * 删除${functionName} + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')") + @Log(title = "${functionName}", businessType = BusinessType.DELETE) + @DeleteMapping("/{${pkColumn.javaField}s}") + public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) + { + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); + } +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/domain.java.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/domain.java.vm new file mode 100644 index 00000000..517abcc9 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/domain.java.vm @@ -0,0 +1,105 @@ +package ${packageName}.domain; + +#foreach ($import in $importList) +import ${import}; +#end +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fastbee.common.annotation.Excel; +#if($table.crud || $table.sub) +import com.fastbee.common.core.domain.BaseEntity; +#elseif($table.tree) +import com.fastbee.common.core.domain.TreeEntity; +#end + +/** + * ${functionName}对象 ${tableName} + * + * @author ${author} + * @date ${datetime} + */ +#if($table.crud || $table.sub) +#set($Entity="BaseEntity") +#elseif($table.tree) +#set($Entity="TreeEntity") +#end +public class ${ClassName} extends ${Entity} +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#if($table.sub) + /** $table.subTable.functionName信息 */ + private List<${subClassName}> ${subclassName}List; + +#end +#foreach ($column in $columns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } +#end +#end + +#if($table.sub) + public List<${subClassName}> get${subClassName}List() + { + return ${subclassName}List; + } + + public void set${subClassName}List(List<${subClassName}> ${subclassName}List) + { + this.${subclassName}List = ${subclassName}List; + } + +#end + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $columns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#end +#if($table.sub) + .append("${subclassName}List", get${subClassName}List()) +#end + .toString(); + } +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/mapper.java.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/mapper.java.vm new file mode 100644 index 00000000..7e7d7c26 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/mapper.java.vm @@ -0,0 +1,91 @@ +package ${packageName}.mapper; + +import java.util.List; +import ${packageName}.domain.${ClassName}; +#if($table.sub) +import ${packageName}.domain.${subClassName}; +#end + +/** + * ${functionName}Mapper接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface ${ClassName}Mapper +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 删除${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); +#if($table.sub) + + /** + * 批量删除${subTable.functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 批量新增${subTable.functionName} + * + * @param ${subclassName}List ${subTable.functionName}列表 + * @return 结果 + */ + public int batch${subClassName}(List<${subClassName}> ${subclassName}List); + + + /** + * 通过${functionName}主键删除${subTable.functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}ID + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); +#end +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/service.java.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/service.java.vm new file mode 100644 index 00000000..264882b2 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/service.java.vm @@ -0,0 +1,61 @@ +package ${packageName}.service; + +import java.util.List; +import ${packageName}.domain.${ClassName}; + +/** + * ${functionName}Service接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface I${ClassName}Service +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/serviceImpl.java.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/serviceImpl.java.vm new file mode 100644 index 00000000..1bcdadba --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -0,0 +1,169 @@ +package ${packageName}.service.impl; + +import java.util.List; +#foreach ($column in $columns) +#if($column.javaField == 'createTime' || $column.javaField == 'updateTime') +import com.fastbee.common.utils.DateUtils; +#break +#end +#end +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +#if($table.sub) +import java.util.ArrayList; +import com.fastbee.common.utils.StringUtils; +import org.springframework.transaction.annotation.Transactional; +import ${packageName}.domain.${subClassName}; +#end +import ${packageName}.mapper.${ClassName}Mapper; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; + +/** + * ${functionName}Service业务层处理 + * + * @author ${author} + * @date ${datetime} + */ +@Service +public class ${ClassName}ServiceImpl implements I${ClassName}Service +{ + @Autowired + private ${ClassName}Mapper ${className}Mapper; + + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + @Override + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { + return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName} + */ + @Override + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) + { + return ${className}Mapper.select${ClassName}List(${className}); + } + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int insert${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'createTime') + ${className}.setCreateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + int rows = ${className}Mapper.insert${ClassName}(${className}); + insert${subClassName}(${className}); + return rows; +#else + return ${className}Mapper.insert${ClassName}(${className}); +#end + } + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int update${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'updateTime') + ${className}.setUpdateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()); + insert${subClassName}(${className}); +#end + return ${className}Mapper.update${ClassName}(${className}); + } + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s); + } + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } +#if($table.sub) + + /** + * 新增${subTable.functionName}信息 + * + * @param ${className} ${functionName}对象 + */ + public void insert${subClassName}(${ClassName} ${className}) + { + List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); + ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); + if (StringUtils.isNotNull(${subclassName}List)) + { + List<${subClassName}> list = new ArrayList<${subClassName}>(); + for (${subClassName} ${subclassName} : ${subclassName}List) + { + ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); + list.add(${subclassName}); + } + if (list.size() > 0) + { + ${className}Mapper.batch${subClassName}(list); + } + } + } +#end +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/sub-domain.java.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/sub-domain.java.vm new file mode 100644 index 00000000..5fe04c47 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/java/sub-domain.java.vm @@ -0,0 +1,76 @@ +package ${packageName}.domain; + +#foreach ($import in $subImportList) +import ${import}; +#end +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * ${subTable.functionName}对象 ${subTableName} + * + * @author ${author} + * @date ${datetime} + */ +public class ${subClassName} extends BaseEntity +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } +#end +#end + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $subTable.columns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#end + .toString(); + } +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/js/api.js.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/js/api.js.vm new file mode 100644 index 00000000..9295524a --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/js/api.js.vm @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询${functionName}列表 +export function list${BusinessName}(query) { + return request({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }) +} + +// 查询${functionName}详细 +export function get${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }) +} + +// 新增${functionName} +export function add${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'post', + data: data + }) +} + +// 修改${functionName} +export function update${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'put', + data: data + }) +} + +// 删除${functionName} +export function del${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }) +} diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/sql/sql.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/sql/sql.vm new file mode 100644 index 00000000..05755835 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/sql/sql.vm @@ -0,0 +1,22 @@ +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, '${functionName}菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/index-tree.vue.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/index-tree.vue.vm new file mode 100644 index 00000000..a4c64a09 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/index-tree.vue.vm @@ -0,0 +1,505 @@ + + + diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/index.vue.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/index.vue.vm new file mode 100644 index 00000000..6296014b --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/index.vue.vm @@ -0,0 +1,602 @@ + + + diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm new file mode 100644 index 00000000..7bbd2fc5 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm @@ -0,0 +1,474 @@ + + + diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/v3/index.vue.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/v3/index.vue.vm new file mode 100644 index 00000000..8b25665a --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/v3/index.vue.vm @@ -0,0 +1,590 @@ + + + diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/v3/readme.txt b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/v3/readme.txt new file mode 100644 index 00000000..99239bb5 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/vue/v3/readme.txt @@ -0,0 +1 @@ +ʹõRuoYi-Vue3ǰˣôҪһ´Ŀ¼ģindex.vue.vmindex-tree.vue.vmļϼvueĿ¼ \ No newline at end of file diff --git a/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/xml/mapper.xml.vm b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/xml/mapper.xml.vm new file mode 100644 index 00000000..0ceb3d85 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -0,0 +1,135 @@ + + + + + +#foreach ($column in $columns) + +#end + +#if($table.sub) + + + + + + +#foreach ($column in $subTable.columns) + +#end + +#end + + + select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName} + + + + + + + + insert into ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + $column.columnName, +#end +#end + + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + #{$column.javaField}, +#end +#end + + + + + update ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName) + $column.columnName = #{$column.javaField}, +#end +#end + + where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} in + + #{${pkColumn.javaField}} + + +#if($table.sub) + + + delete from ${subTableName} where ${subTableFkName} in + + #{${subTableFkclassName}} + + + + + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} + + + + insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values + + (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end) + + +#end + \ No newline at end of file diff --git a/springboot/fastbee-plugs/fastbee-quartz/pom.xml b/springboot/fastbee-plugs/fastbee-quartz/pom.xml new file mode 100644 index 00000000..e45e24d9 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/pom.xml @@ -0,0 +1,40 @@ + + + + fastbee-plugs + com.fastbee + 3.8.5 + + 4.0.0 + + fastbee-quartz + + + quartz定时任务 + + + + + + + org.quartz-scheduler + quartz + + + com.mchange + c3p0 + + + + + + + com.fastbee + fastbee-common + + + + + \ No newline at end of file diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/config/ScheduleConfig.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/config/ScheduleConfig.java new file mode 100644 index 00000000..059eaba6 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/config/ScheduleConfig.java @@ -0,0 +1,57 @@ +//package com.fastbee.quartz.config; +// +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.scheduling.quartz.SchedulerFactoryBean; +//import javax.sql.DataSource; +//import java.util.Properties; +// +///** +// * 定时任务配置(单机部署建议删除此类和qrtz数据库表,默认走内存会最高效) +// * +// * @author ruoyi +// */ +//@Configuration +//public class ScheduleConfig +//{ +// @Bean +// public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) +// { +// SchedulerFactoryBean factory = new SchedulerFactoryBean(); +// factory.setDataSource(dataSource); +// +// // quartz参数 +// Properties prop = new Properties(); +// prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler"); +// prop.put("org.quartz.scheduler.instanceId", "AUTO"); +// // 线程池配置 +// prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); +// prop.put("org.quartz.threadPool.threadCount", "20"); +// prop.put("org.quartz.threadPool.threadPriority", "5"); +// // JobStore配置 +// prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore"); +// // 集群配置 +// prop.put("org.quartz.jobStore.isClustered", "true"); +// prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); +// prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1"); +// prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true"); +// +// // sqlserver 启用 +// // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?"); +// prop.put("org.quartz.jobStore.misfireThreshold", "12000"); +// prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); +// factory.setQuartzProperties(prop); +// +// factory.setSchedulerName("RuoyiScheduler"); +// // 延时启动 +// factory.setStartupDelay(1); +// factory.setApplicationContextSchedulerContextKey("applicationContextKey"); +// // 可选,QuartzScheduler +// // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 +// factory.setOverwriteExistingJobs(true); +// // 设置自动启动,默认为true +// factory.setAutoStartup(true); +// +// return factory; +// } +//} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/controller/SysJobController.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/controller/SysJobController.java new file mode 100644 index 00000000..f826c2a8 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/controller/SysJobController.java @@ -0,0 +1,197 @@ +package com.fastbee.quartz.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.exception.job.TaskException; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.quartz.domain.SysJob; +import com.fastbee.quartz.service.ISysJobService; +import com.fastbee.quartz.util.CronUtils; +import com.fastbee.quartz.util.ScheduleUtils; + +/** + * 调度任务信息操作处理 + * + * @author ruoyi + */ +@Api(tags = "调度任务信息操作处理") +@RestController +@RequestMapping("/monitor/job") +public class SysJobController extends BaseController +{ + @Autowired + private ISysJobService jobService; + + /** + * 查询定时任务列表 + */ + @ApiOperation("查询定时任务列表") + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJob sysJob) + { + startPage(); + List list = jobService.selectJobList(sysJob); + return getDataTable(list); + } + + /** + * 导出定时任务列表 + */ + @ApiOperation("导出定时任务列表") + @PreAuthorize("@ss.hasPermi('monitor:job:export')") + @Log(title = "定时任务", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysJob sysJob) + { + List list = jobService.selectJobList(sysJob); + ExcelUtil util = new ExcelUtil(SysJob.class); + util.exportExcel(response, list, "定时任务"); + } + + /** + * 获取定时任务详细信息 + */ + @ApiOperation("获取定时任务详细信息") + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobId}") + public AjaxResult getInfo(@PathVariable("jobId") Long jobId) + { + return success(jobService.selectJobById(jobId)); + } + + /** + * 新增定时任务 + */ + @ApiOperation("新增定时任务") + @PreAuthorize("@ss.hasPermi('monitor:job:add')") + @Log(title = "定时任务", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + job.setCreateBy(getUsername()); + return toAjax(jobService.insertJob(job)); + } + + /** + * 修改定时任务 + */ + @ApiOperation("修改定时任务") + @PreAuthorize("@ss.hasPermi('monitor:job:edit')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + job.setUpdateBy(getUsername()); + return toAjax(jobService.updateJob(job)); + } + + /** + * 定时任务状态修改 + */ + @ApiOperation("定时任务状态修改") + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException + { + SysJob newJob = jobService.selectJobById(job.getJobId()); + newJob.setStatus(job.getStatus()); + return toAjax(jobService.changeStatus(newJob)); + } + + /** + * 定时任务立即执行一次 + */ + @ApiOperation("定时任务立即执行一次") + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/run") + public AjaxResult run(@RequestBody SysJob job) throws SchedulerException + { + boolean result = jobService.run(job); + return result ? success() : error("任务不存在或已过期!"); + } + + /** + * 删除定时任务 + */ + @ApiOperation("删除定时任务") + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobIds}") + public AjaxResult remove(@PathVariable Long[] jobIds) throws SchedulerException, TaskException + { + jobService.deleteJobByIds(jobIds); + return success(); + } +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/controller/SysJobLogController.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/controller/SysJobLogController.java new file mode 100644 index 00000000..996de06f --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/controller/SysJobLogController.java @@ -0,0 +1,101 @@ +package com.fastbee.quartz.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.fastbee.common.annotation.Log; +import com.fastbee.common.core.controller.BaseController; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.page.TableDataInfo; +import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.quartz.domain.SysJobLog; +import com.fastbee.quartz.service.ISysJobLogService; + +/** + * 调度日志操作处理 + * + * @author ruoyi + */ +@Api(tags = "调度日志操作处理") +@RestController +@RequestMapping("/monitor/jobLog") +public class SysJobLogController extends BaseController +{ + @Autowired + private ISysJobLogService jobLogService; + + /** + * 查询定时任务调度日志列表 + */ + @ApiOperation("查询定时任务调度日志列表") + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJobLog sysJobLog) + { + startPage(); + List list = jobLogService.selectJobLogList(sysJobLog); + return getDataTable(list); + } + + /** + * 导出定时任务调度日志列表 + */ + @ApiOperation("导出定时任务调度日志列表") + @PreAuthorize("@ss.hasPermi('monitor:job:export')") + @Log(title = "任务调度日志", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysJobLog sysJobLog) + { + List list = jobLogService.selectJobLogList(sysJobLog); + ExcelUtil util = new ExcelUtil(SysJobLog.class); + util.exportExcel(response, list, "调度日志"); + } + + /** + * 根据调度编号获取详细信息 + */ + @ApiOperation("根据调度编号获取详细信息") + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobLogId}") + public AjaxResult getInfo(@PathVariable Long jobLogId) + { + return success(jobLogService.selectJobLogById(jobLogId)); + } + + + /** + * 删除定时任务调度日志 + */ + @ApiOperation("删除定时任务调度日志") + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务调度日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobLogIds}") + public AjaxResult remove(@PathVariable Long[] jobLogIds) + { + return toAjax(jobLogService.deleteJobLogByIds(jobLogIds)); + } + + /** + * 清空定时任务调度日志 + */ + @ApiOperation("清空定时任务调度日志") + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "调度日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + jobLogService.cleanJobLog(); + return success(); + } +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/domain/SysJob.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/domain/SysJob.java new file mode 100644 index 00000000..a0b3563e --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/domain/SysJob.java @@ -0,0 +1,183 @@ +package com.fastbee.quartz.domain; + +import java.util.Date; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +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 com.fasterxml.jackson.annotation.JsonFormat; +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.annotation.Excel.ColumnType; +import com.fastbee.common.constant.ScheduleConstants; +import com.fastbee.common.core.domain.BaseEntity; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.quartz.util.CronUtils; + +/** + * 定时任务调度表 sys_job + * + * @author ruoyi + */ +@ApiModel(value = "SysJob", description = "定时任务调度表 sys_job") +public class SysJob extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 任务ID */ + @ApiModelProperty("任务ID") + @Excel(name = "任务序号", cellType = ColumnType.NUMERIC) + private Long jobId; + + /** 任务名称 */ + @ApiModelProperty("任务名称") + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名 */ + @ApiModelProperty("任务组名") + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串 */ + @ApiModelProperty("调用目标字符串") + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** cron执行表达式 */ + @ApiModelProperty("cron执行表达式") + @Excel(name = "执行表达式 ") + private String cronExpression; + + /** cron计划策略 */ + @ApiModelProperty("cron计划策略") + @Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行") + private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT; + + /** 是否并发执行(0允许 1禁止) */ + @ApiModelProperty("是否并发执行(0允许 1禁止)") + @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止") + private String concurrent; + + /** 任务状态(0正常 1暂停) */ + @ApiModelProperty("任务状态(0正常 1暂停)") + @Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停") + private String status; + + public Long getJobId() + { + return jobId; + } + + public void setJobId(Long jobId) + { + this.jobId = jobId; + } + + @NotBlank(message = "任务名称不能为空") + @Size(min = 0, max = 64, message = "任务名称不能超过64个字符") + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + @NotBlank(message = "调用目标字符串不能为空") + @Size(min = 0, max = 500, message = "调用目标字符串长度不能超过500个字符") + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + @NotBlank(message = "Cron执行表达式不能为空") + @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符") + public String getCronExpression() + { + return cronExpression; + } + + public void setCronExpression(String cronExpression) + { + this.cronExpression = cronExpression; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public Date getNextValidTime() + { + if (StringUtils.isNotEmpty(cronExpression)) + { + return CronUtils.getNextExecution(cronExpression); + } + return null; + } + + public String getMisfirePolicy() + { + return misfirePolicy; + } + + public void setMisfirePolicy(String misfirePolicy) + { + this.misfirePolicy = misfirePolicy; + } + + public String getConcurrent() + { + return concurrent; + } + + public void setConcurrent(String concurrent) + { + this.concurrent = concurrent; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobId", getJobId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("cronExpression", getCronExpression()) + .append("nextValidTime", getNextValidTime()) + .append("misfirePolicy", getMisfirePolicy()) + .append("concurrent", getConcurrent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/domain/SysJobLog.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/domain/SysJobLog.java new file mode 100644 index 00000000..5839c59b --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/domain/SysJobLog.java @@ -0,0 +1,168 @@ +package com.fastbee.quartz.domain; + +import java.util.Date; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 定时任务调度日志表 sys_job_log + * + * @author ruoyi + */ +@ApiModel(value = "SysJobLog", description = "定时任务调度日志表 sys_job_log") +public class SysJobLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @ApiModelProperty("ID") + @Excel(name = "日志序号") + private Long jobLogId; + + /** 任务名称 */ + @ApiModelProperty("任务名称") + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名 */ + @ApiModelProperty("任务组名") + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串 */ + @ApiModelProperty("调用目标字符串") + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** 日志信息 */ + @ApiModelProperty("日志信息") + @Excel(name = "日志信息") + private String jobMessage; + + /** 执行状态(0正常 1失败) */ + @ApiModelProperty("执行状态(0正常 1失败)") + @Excel(name = "执行状态", readConverterExp = "0=正常,1=失败") + private String status; + + /** 异常信息 */ + @ApiModelProperty("异常信息") + @Excel(name = "异常信息") + private String exceptionInfo; + + /** 开始时间 */ + @ApiModelProperty("开始时间") + private Date startTime; + + /** 停止时间 */ + @ApiModelProperty("停止时间") + private Date stopTime; + + public Long getJobLogId() + { + return jobLogId; + } + + public void setJobLogId(Long jobLogId) + { + this.jobLogId = jobLogId; + } + + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + public String getJobMessage() + { + return jobMessage; + } + + public void setJobMessage(String jobMessage) + { + this.jobMessage = jobMessage; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getExceptionInfo() + { + return exceptionInfo; + } + + public void setExceptionInfo(String exceptionInfo) + { + this.exceptionInfo = exceptionInfo; + } + + public Date getStartTime() + { + return startTime; + } + + public void setStartTime(Date startTime) + { + this.startTime = startTime; + } + + public Date getStopTime() + { + return stopTime; + } + + public void setStopTime(Date stopTime) + { + this.stopTime = stopTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobLogId", getJobLogId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("jobMessage", getJobMessage()) + .append("status", getStatus()) + .append("exceptionInfo", getExceptionInfo()) + .append("startTime", getStartTime()) + .append("stopTime", getStopTime()) + .toString(); + } +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/mapper/SysJobLogMapper.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/mapper/SysJobLogMapper.java new file mode 100644 index 00000000..716ed77b --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/mapper/SysJobLogMapper.java @@ -0,0 +1,64 @@ +package com.fastbee.quartz.mapper; + +import java.util.List; +import com.fastbee.quartz.domain.SysJobLog; + +/** + * 调度任务日志信息 数据层 + * + * @author ruoyi + */ +public interface SysJobLogMapper +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 查询所有调度任务日志 + * + * @return 调度任务日志列表 + */ + public List selectJobLogAll(); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + * @return 结果 + */ + public int insertJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/mapper/SysJobMapper.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/mapper/SysJobMapper.java new file mode 100644 index 00000000..47ce8fee --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/mapper/SysJobMapper.java @@ -0,0 +1,67 @@ +package com.fastbee.quartz.mapper; + +import java.util.List; +import com.fastbee.quartz.domain.SysJob; + +/** + * 调度任务信息 数据层 + * + * @author ruoyi + */ +public interface SysJobMapper +{ + /** + * 查询调度任务日志集合 + * + * @param job 调度信息 + * @return 操作日志集合 + */ + public List selectJobList(SysJob job); + + /** + * 查询所有调度任务 + * + * @return 调度任务列表 + */ + public List selectJobAll(); + + /** + * 通过调度ID查询调度任务信息 + * + * @param jobId 调度ID + * @return 角色对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 通过调度ID删除调度任务信息 + * + * @param jobId 调度ID + * @return 结果 + */ + public int deleteJobById(Long jobId); + + /** + * 批量删除调度任务信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteJobByIds(Long[] ids); + + /** + * 修改调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int updateJob(SysJob job); + + /** + * 新增调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int insertJob(SysJob job); +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/ISysJobLogService.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/ISysJobLogService.java new file mode 100644 index 00000000..f89c2c39 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/ISysJobLogService.java @@ -0,0 +1,56 @@ +package com.fastbee.quartz.service; + +import java.util.List; +import com.fastbee.quartz.domain.SysJobLog; + +/** + * 定时任务调度日志信息信息 服务层 + * + * @author ruoyi + */ +public interface ISysJobLogService +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + public void addJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的日志ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/ISysJobService.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/ISysJobService.java new file mode 100644 index 00000000..f3c21485 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/ISysJobService.java @@ -0,0 +1,102 @@ +package com.fastbee.quartz.service; + +import java.util.List; +import org.quartz.SchedulerException; +import com.fastbee.common.exception.job.TaskException; +import com.fastbee.quartz.domain.SysJob; + +/** + * 定时任务调度信息信息 服务层 + * + * @author ruoyi + */ +public interface ISysJobService +{ + /** + * 获取quartz调度器的计划任务 + * + * @param job 调度信息 + * @return 调度任务集合 + */ + public List selectJobList(SysJob job); + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 暂停任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int pauseJob(SysJob job) throws SchedulerException; + + /** + * 恢复任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int resumeJob(SysJob job) throws SchedulerException; + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + * @return 结果 + */ + public int deleteJob(SysJob job) throws SchedulerException; + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + public void deleteJobByIds(Long[] jobIds) throws SchedulerException; + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + * @return 结果 + */ + public int changeStatus(SysJob job) throws SchedulerException; + + /** + * 立即运行任务 + * + * @param job 调度信息 + * @return 结果 + */ + public boolean run(SysJob job) throws SchedulerException; + + /** + * 新增任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int insertJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 更新任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int updateJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + public boolean checkCronExpressionIsValid(String cronExpression); +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/impl/SysJobLogServiceImpl.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/impl/SysJobLogServiceImpl.java new file mode 100644 index 00000000..7cd5883c --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/impl/SysJobLogServiceImpl.java @@ -0,0 +1,87 @@ +package com.fastbee.quartz.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.fastbee.quartz.domain.SysJobLog; +import com.fastbee.quartz.mapper.SysJobLogMapper; +import com.fastbee.quartz.service.ISysJobLogService; + +/** + * 定时任务调度日志信息 服务层 + * + * @author ruoyi + */ +@Service +public class SysJobLogServiceImpl implements ISysJobLogService +{ + @Autowired + private SysJobLogMapper jobLogMapper; + + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + @Override + public List selectJobLogList(SysJobLog jobLog) + { + return jobLogMapper.selectJobLogList(jobLog); + } + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + @Override + public SysJobLog selectJobLogById(Long jobLogId) + { + return jobLogMapper.selectJobLogById(jobLogId); + } + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + @Override + public void addJobLog(SysJobLog jobLog) + { + jobLogMapper.insertJobLog(jobLog); + } + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteJobLogByIds(Long[] logIds) + { + return jobLogMapper.deleteJobLogByIds(logIds); + } + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + */ + @Override + public int deleteJobLogById(Long jobId) + { + return jobLogMapper.deleteJobLogById(jobId); + } + + /** + * 清空任务日志 + */ + @Override + public void cleanJobLog() + { + jobLogMapper.cleanJobLog(); + } +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/impl/SysJobServiceImpl.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/impl/SysJobServiceImpl.java new file mode 100644 index 00000000..6514931c --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/impl/SysJobServiceImpl.java @@ -0,0 +1,261 @@ +package com.fastbee.quartz.service.impl; + +import java.util.List; +import javax.annotation.PostConstruct; +import org.quartz.JobDataMap; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.fastbee.common.constant.ScheduleConstants; +import com.fastbee.common.exception.job.TaskException; +import com.fastbee.quartz.domain.SysJob; +import com.fastbee.quartz.mapper.SysJobMapper; +import com.fastbee.quartz.service.ISysJobService; +import com.fastbee.quartz.util.CronUtils; +import com.fastbee.quartz.util.ScheduleUtils; + +/** + * 定时任务调度信息 服务层 + * + * @author ruoyi + */ +@Service +public class SysJobServiceImpl implements ISysJobService +{ + @Autowired + private Scheduler scheduler; + + @Autowired + private SysJobMapper jobMapper; + + /** + * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据) + */ + // @PostConstruct + public void init() throws SchedulerException, TaskException + { + scheduler.clear(); + List jobList = jobMapper.selectJobAll(); + for (SysJob job : jobList) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + } + + /** + * 获取quartz调度器的计划任务列表 + * + * @param job 调度信息 + * @return + */ + @Override + public List selectJobList(SysJob job) + { + return jobMapper.selectJobList(job); + } + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + @Override + public SysJob selectJobById(Long jobId) + { + return jobMapper.selectJobById(jobId); + } + + /** + * 暂停任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int pauseJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 恢复任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int resumeJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.NORMAL.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + int rows = jobMapper.deleteJobById(jobId); + if (rows > 0) + { + scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteJobByIds(Long[] jobIds) throws SchedulerException + { + for (Long jobId : jobIds) + { + SysJob job = jobMapper.selectJobById(jobId); + deleteJob(job); + } + } + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int changeStatus(SysJob job) throws SchedulerException + { + int rows = 0; + String status = job.getStatus(); + if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) + { + rows = resumeJob(job); + } + else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) + { + rows = pauseJob(job); + } + return rows; + } + + /** + * 立即运行任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean run(SysJob job) throws SchedulerException + { + boolean result = false; + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + SysJob properties = selectJobById(job.getJobId()); + // 参数 + JobDataMap dataMap = new JobDataMap(); + dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties); + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + if (scheduler.checkExists(jobKey)) + { + result = true; + scheduler.triggerJob(jobKey, dataMap); + } + return result; + } + + /** + * 新增任务 + * + * @param job 调度信息 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int insertJob(SysJob job) throws SchedulerException, TaskException + { + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.insertJob(job); + if (rows > 0) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + return rows; + } + + /** + * 更新任务的时间表达式 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateJob(SysJob job) throws SchedulerException, TaskException + { + SysJob properties = selectJobById(job.getJobId()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + updateSchedulerJob(job, properties.getJobGroup()); + } + return rows; + } + + /** + * 更新任务 + * + * @param job 任务对象 + * @param jobGroup 任务组名 + */ + public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException + { + Long jobId = job.getJobId(); + // 判断是否存在 + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + if (scheduler.checkExists(jobKey)) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(jobKey); + } + ScheduleUtils.createScheduleJob(scheduler, job); + } + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + @Override + public boolean checkCronExpressionIsValid(String cronExpression) + { + return CronUtils.isValid(cronExpression); + } +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/task/RyTask.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/task/RyTask.java new file mode 100644 index 00000000..38e85c61 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/task/RyTask.java @@ -0,0 +1,28 @@ +package com.fastbee.quartz.task; + +import org.springframework.stereotype.Component; +import com.fastbee.common.utils.StringUtils; + +/** + * 定时任务调度测试 + * + * @author ruoyi + */ +@Component("ryTask") +public class RyTask +{ + public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) + { + System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i)); + } + + public void ryParams(String params) + { + System.out.println("执行有参方法:" + params); + } + + public void ryNoParams() + { + System.out.println("执行无参方法"); + } +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/AbstractQuartzJob.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/AbstractQuartzJob.java new file mode 100644 index 00000000..603c767d --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/AbstractQuartzJob.java @@ -0,0 +1,107 @@ +package com.fastbee.quartz.util; + +import java.util.Date; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.constant.ScheduleConstants; +import com.fastbee.common.utils.ExceptionUtil; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.bean.BeanUtils; +import com.fastbee.common.utils.spring.SpringUtils; +import com.fastbee.quartz.domain.SysJob; +import com.fastbee.quartz.domain.SysJobLog; +import com.fastbee.quartz.service.ISysJobLogService; + +/** + * 抽象quartz调用 + * + * @author ruoyi + */ +public abstract class AbstractQuartzJob implements Job +{ + private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); + + /** + * 线程本地变量 + */ + private static ThreadLocal threadLocal = new ThreadLocal<>(); + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException + { + SysJob sysJob = new SysJob(); + BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES)); + try + { + before(context, sysJob); + if (sysJob != null) + { + doExecute(context, sysJob); + } + after(context, sysJob, null); + } + catch (Exception e) + { + log.error("任务执行异常 - :", e); + after(context, sysJob, e); + } + } + + /** + * 执行前 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + */ + protected void before(JobExecutionContext context, SysJob sysJob) + { + threadLocal.set(new Date()); + } + + /** + * 执行后 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + */ + protected void after(JobExecutionContext context, SysJob sysJob, Exception e) + { + Date startTime = threadLocal.get(); + threadLocal.remove(); + + final SysJobLog sysJobLog = new SysJobLog(); + sysJobLog.setJobName(sysJob.getJobName()); + sysJobLog.setJobGroup(sysJob.getJobGroup()); + sysJobLog.setInvokeTarget(sysJob.getInvokeTarget()); + sysJobLog.setStartTime(startTime); + sysJobLog.setStopTime(new Date()); + long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime(); + sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒"); + if (e != null) + { + sysJobLog.setStatus(Constants.FAIL); + String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000); + sysJobLog.setExceptionInfo(errorMsg); + } + else + { + sysJobLog.setStatus(Constants.SUCCESS); + } + + // 写入数据库当中 + SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); + } + + /** + * 执行方法,由子类重载 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + * @throws Exception 执行过程中的异常 + */ + protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception; +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/CronUtils.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/CronUtils.java new file mode 100644 index 00000000..3f2d424a --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/CronUtils.java @@ -0,0 +1,63 @@ +package com.fastbee.quartz.util; + +import java.text.ParseException; +import java.util.Date; +import org.quartz.CronExpression; + +/** + * cron表达式工具类 + * + * @author ruoyi + * + */ +public class CronUtils +{ + /** + * 返回一个布尔值代表一个给定的Cron表达式的有效性 + * + * @param cronExpression Cron表达式 + * @return boolean 表达式是否有效 + */ + public static boolean isValid(String cronExpression) + { + return CronExpression.isValidExpression(cronExpression); + } + + /** + * 返回一个字符串值,表示该消息无效Cron表达式给出有效性 + * + * @param cronExpression Cron表达式 + * @return String 无效时返回表达式错误描述,如果有效返回null + */ + public static String getInvalidMessage(String cronExpression) + { + try + { + new CronExpression(cronExpression); + return null; + } + catch (ParseException pe) + { + return pe.getMessage(); + } + } + + /** + * 返回下一个执行时间根据给定的Cron表达式 + * + * @param cronExpression Cron表达式 + * @return Date 下次Cron表达式执行时间 + */ + public static Date getNextExecution(String cronExpression) + { + try + { + CronExpression cron = new CronExpression(cronExpression); + return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis())); + } + catch (ParseException e) + { + throw new IllegalArgumentException(e.getMessage()); + } + } +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/JobInvokeUtil.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/JobInvokeUtil.java new file mode 100644 index 00000000..e824c455 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/JobInvokeUtil.java @@ -0,0 +1,182 @@ +package com.fastbee.quartz.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.spring.SpringUtils; +import com.fastbee.quartz.domain.SysJob; + +/** + * 任务执行工具 + * + * @author ruoyi + */ +public class JobInvokeUtil +{ + /** + * 执行方法 + * + * @param sysJob 系统任务 + */ + public static void invokeMethod(SysJob sysJob) throws Exception + { + String invokeTarget = sysJob.getInvokeTarget(); + String beanName = getBeanName(invokeTarget); + String methodName = getMethodName(invokeTarget); + List methodParams = getMethodParams(invokeTarget); + + if (!isValidClassName(beanName)) + { + Object bean = SpringUtils.getBean(beanName); + invokeMethod(bean, methodName, methodParams); + } + else + { + Object bean = Class.forName(beanName).newInstance(); + invokeMethod(bean, methodName, methodParams); + } + } + + /** + * 调用任务方法 + * + * @param bean 目标对象 + * @param methodName 方法名称 + * @param methodParams 方法参数 + */ + private static void invokeMethod(Object bean, String methodName, List methodParams) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException + { + if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) + { + Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams)); + method.invoke(bean, getMethodParamsValue(methodParams)); + } + else + { + Method method = bean.getClass().getMethod(methodName); + method.invoke(bean); + } + } + + /** + * 校验是否为为class包名 + * + * @param invokeTarget 名称 + * @return true是 false否 + */ + public static boolean isValidClassName(String invokeTarget) + { + return StringUtils.countMatches(invokeTarget, ".") > 1; + } + + /** + * 获取bean名称 + * + * @param invokeTarget 目标字符串 + * @return bean名称 + */ + public static String getBeanName(String invokeTarget) + { + String beanName = StringUtils.substringBefore(invokeTarget, "("); + return StringUtils.substringBeforeLast(beanName, "."); + } + + /** + * 获取bean方法 + * + * @param invokeTarget 目标字符串 + * @return method方法 + */ + public static String getMethodName(String invokeTarget) + { + String methodName = StringUtils.substringBefore(invokeTarget, "("); + return StringUtils.substringAfterLast(methodName, "."); + } + + /** + * 获取method方法参数相关列表 + * + * @param invokeTarget 目标字符串 + * @return method方法相关参数列表 + */ + public static List getMethodParams(String invokeTarget) + { + String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")"); + if (StringUtils.isEmpty(methodStr)) + { + return null; + } + String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)"); + List classs = new LinkedList<>(); + for (int i = 0; i < methodParams.length; i++) + { + String str = StringUtils.trimToEmpty(methodParams[i]); + // String字符串类型,以'或"开头 + if (StringUtils.startsWithAny(str, "'", "\"")) + { + classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class }); + } + // boolean布尔类型,等于true或者false + else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) + { + classs.add(new Object[] { Boolean.valueOf(str), Boolean.class }); + } + // long长整形,以L结尾 + else if (StringUtils.endsWith(str, "L")) + { + classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class }); + } + // double浮点类型,以D结尾 + else if (StringUtils.endsWith(str, "D")) + { + classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class }); + } + // 其他类型归类为整形 + else + { + classs.add(new Object[] { Integer.valueOf(str), Integer.class }); + } + } + return classs; + } + + /** + * 获取参数类型 + * + * @param methodParams 参数相关列表 + * @return 参数类型列表 + */ + public static Class[] getMethodParamsType(List methodParams) + { + Class[] classs = new Class[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Class) os[1]; + index++; + } + return classs; + } + + /** + * 获取参数值 + * + * @param methodParams 参数相关列表 + * @return 参数值列表 + */ + public static Object[] getMethodParamsValue(List methodParams) + { + Object[] classs = new Object[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Object) os[0]; + index++; + } + return classs; + } +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/QuartzDisallowConcurrentExecution.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/QuartzDisallowConcurrentExecution.java new file mode 100644 index 00000000..db4c1c6f --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/QuartzDisallowConcurrentExecution.java @@ -0,0 +1,21 @@ +package com.fastbee.quartz.util; + +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import com.fastbee.quartz.domain.SysJob; + +/** + * 定时任务处理(禁止并发执行) + * + * @author ruoyi + * + */ +@DisallowConcurrentExecution +public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/QuartzJobExecution.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/QuartzJobExecution.java new file mode 100644 index 00000000..f17ea62a --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/QuartzJobExecution.java @@ -0,0 +1,19 @@ +package com.fastbee.quartz.util; + +import org.quartz.JobExecutionContext; +import com.fastbee.quartz.domain.SysJob; + +/** + * 定时任务处理(允许并发执行) + * + * @author ruoyi + * + */ +public class QuartzJobExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/ScheduleUtils.java b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/ScheduleUtils.java new file mode 100644 index 00000000..d7f8fb08 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/ScheduleUtils.java @@ -0,0 +1,139 @@ +package com.fastbee.quartz.util; + +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.constant.ScheduleConstants; +import com.fastbee.common.exception.job.TaskException; +import com.fastbee.common.exception.job.TaskException.Code; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.spring.SpringUtils; +import com.fastbee.quartz.domain.SysJob; + +/** + * 定时任务工具类 + * + * @author ruoyi + * + */ +public class ScheduleUtils +{ + /** + * 得到quartz任务类 + * + * @param sysJob 执行计划 + * @return 具体执行任务类 + */ + private static Class getQuartzJobClass(SysJob sysJob) + { + boolean isConcurrent = "0".equals(sysJob.getConcurrent()); + return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class; + } + + /** + * 构建任务触发对象 + */ + public static TriggerKey getTriggerKey(Long jobId, String jobGroup) + { + return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 构建任务键对象 + */ + public static JobKey getJobKey(Long jobId, String jobGroup) + { + return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 创建定时任务 + */ + public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException + { + Class jobClass = getQuartzJobClass(job); + // 构建job信息 + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build(); + + // 表达式调度构建器 + CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); + cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder); + + // 按新的cronExpression表达式构建一个新的trigger + CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup)) + .withSchedule(cronScheduleBuilder).build(); + + // 放入参数,运行时的方法可以获取 + jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job); + + // 判断是否存在 + if (scheduler.checkExists(getJobKey(jobId, jobGroup))) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(getJobKey(jobId, jobGroup)); + } + + // 判断任务是否过期 + if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression()))) + { + // 执行调度任务 + scheduler.scheduleJob(jobDetail, trigger); + } + + // 暂停任务 + if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + } + + /** + * 设置定时任务策略 + */ + public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb) + throws TaskException + { + switch (job.getMisfirePolicy()) + { + case ScheduleConstants.MISFIRE_DEFAULT: + return cb; + case ScheduleConstants.MISFIRE_IGNORE_MISFIRES: + return cb.withMisfireHandlingInstructionIgnoreMisfires(); + case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED: + return cb.withMisfireHandlingInstructionFireAndProceed(); + case ScheduleConstants.MISFIRE_DO_NOTHING: + return cb.withMisfireHandlingInstructionDoNothing(); + default: + throw new TaskException("The task misfire policy '" + job.getMisfirePolicy() + + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR); + } + } + + /** + * 检查包名是否为白名单配置 + * + * @param invokeTarget 目标字符串 + * @return 结果 + */ + public static boolean whiteList(String invokeTarget) + { + String packageName = StringUtils.substringBefore(invokeTarget, "("); + int count = StringUtils.countMatches(packageName, "."); + if (count > 1) + { + return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR); + } + Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]); + return StringUtils.containsAnyIgnoreCase(obj.getClass().getPackage().getName(), Constants.JOB_WHITELIST_STR); + } +} diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml b/springboot/fastbee-plugs/fastbee-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml new file mode 100644 index 00000000..2ea754ea --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time + from sys_job_log + + + + + + + + + + delete from sys_job_log where job_log_id = #{jobLogId} + + + + delete from sys_job_log where job_log_id in + + #{jobLogId} + + + + + truncate table sys_job_log + + + + insert into sys_job_log( + job_log_id, + job_name, + job_group, + invoke_target, + job_message, + status, + exception_info, + create_time + )values( + #{jobLogId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{jobMessage}, + #{status}, + #{exceptionInfo}, + sysdate() + ) + + + \ No newline at end of file diff --git a/springboot/fastbee-plugs/fastbee-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml b/springboot/fastbee-plugs/fastbee-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml new file mode 100644 index 00000000..247b3101 --- /dev/null +++ b/springboot/fastbee-plugs/fastbee-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark + from sys_job + + + + + + + + + + delete from sys_job where job_id = #{jobId} + + + + delete from sys_job where job_id in + + #{jobId} + + + + + update sys_job + + job_name = #{jobName}, + job_group = #{jobGroup}, + invoke_target = #{invokeTarget}, + cron_expression = #{cronExpression}, + misfire_policy = #{misfirePolicy}, + concurrent = #{concurrent}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where job_id = #{jobId} + + + + insert into sys_job( + job_id, + job_name, + job_group, + invoke_target, + cron_expression, + misfire_policy, + concurrent, + status, + remark, + create_by, + create_time + )values( + #{jobId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{cronExpression}, + #{misfirePolicy}, + #{concurrent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/springboot/fastbee-plugs/pom.xml b/springboot/fastbee-plugs/pom.xml new file mode 100644 index 00000000..c6e256b5 --- /dev/null +++ b/springboot/fastbee-plugs/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + fastbee + com.fastbee + 3.8.5 + + + pom + fastbee-plugs + 插件工具类整合 + + + fastbee-quartz + fastbee-generator + + + + diff --git a/springboot/fastbee-protocol/fastbee-protocol-collect/pom.xml b/springboot/fastbee-protocol/fastbee-protocol-collect/pom.xml new file mode 100644 index 00000000..2524e2b8 --- /dev/null +++ b/springboot/fastbee-protocol/fastbee-protocol-collect/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + com.fastbee + fastbee-iot-service + + + + fastbee-protocol + com.fastbee + 3.8.5 + + + + fastbee-protocol-collect + + + + diff --git a/springboot/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/common/ProtocolDeCodeService.java b/springboot/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/common/ProtocolDeCodeService.java new file mode 100644 index 00000000..962b615a --- /dev/null +++ b/springboot/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/common/ProtocolDeCodeService.java @@ -0,0 +1,53 @@ +package com.fastbee.common; + +import com.fastbee.common.core.iot.response.DeCodeBo; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.gateway.CRC16Utils; +import io.netty.buffer.ByteBufUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.stereotype.Component; + +/** + * 协议编解码 + * + * @author gsb + * @date 2023/4/8 15:50 + */ +@Component +@Slf4j +public class ProtocolDeCodeService { + + + public String protocolDeCode(DeCodeBo bo) { + if (null == bo) { + throw new ServiceException("输入内容为空"); + } + String payload = bo.getPayload(); + /*1-解析 2-读指令 3-写指令 4-CRC生成 5-CRC校验*/ + switch (bo.getType()) { + case 1: + case 2: + case 3: + case 4: + byte[] crc16Byte = ByteBufUtil.decodeHexDump(payload); + String crc = CRC16Utils.getCRC(crc16Byte); + return payload + crc; + case 5: + byte[] crcByte = ByteBufUtil.decodeHexDump(payload); + byte[] checksCRC = {crcByte[crcByte.length -2],crcByte[crcByte.length-1]}; + byte[] sourceCRC = ArrayUtils.subarray(crcByte, 0, crcByte.length - 2); + String crc1 = CRC16Utils.getCRC(sourceCRC); + String check = ByteBufUtil.hexDump(checksCRC); + if (!crc1.equalsIgnoreCase(check)){ + return "原报文CRC:" + check +"校验失败,CRC值应为:" + crc1 + + "
完整报文:" + ByteBufUtil.hexDump(sourceCRC) +crc1; + }else { + return "校验通过!"; + } + } + return null; + } + + +} diff --git a/springboot/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/json/JsonProtocolService.java b/springboot/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/json/JsonProtocolService.java new file mode 100644 index 00000000..758c2f25 --- /dev/null +++ b/springboot/fastbee-protocol/fastbee-protocol-collect/src/main/java/com/fastbee/json/JsonProtocolService.java @@ -0,0 +1,70 @@ +package com.fastbee.json; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.annotation.SysProtocol; +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.mq.DeviceReport; +import com.fastbee.common.core.mq.message.DeviceData; +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; +import com.fastbee.common.core.thingsModel.ThingsModelValuesInput; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.model.ThingsModels.ValueItem; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +/** + * @author gsb + * @date 2022/10/10 16:55 + */ +@Slf4j +@Component +@SysProtocol(name = "JSONArray解析协议",protocolCode = FastBeeConstant.PROTOCOL.JsonArray,description = "系统内置JSONArray解析协议") +public class JsonProtocolService { + + + public DeviceReport decode(DeviceData deviceData, String clientId) { + try { + DeviceReport reportMessage = new DeviceReport(); + // bytep[] 转String + String data = new String(deviceData.getData(),StandardCharsets.UTF_8); + List values = JSON.parseArray(data, ThingsModelSimpleItem.class); + //上报数据时间 + for (ThingsModelSimpleItem value : values) { + value.setTs(DateUtils.getNowDate()); + } + ThingsModelValuesInput valuesInput = new ThingsModelValuesInput(); + valuesInput.setThingsModelValueRemarkItem(values); + reportMessage.setValuesInput(valuesInput); + reportMessage.setClientId(clientId); + reportMessage.setSerialNumber(clientId); + return reportMessage; + }catch (Exception e){ + throw new ServiceException("数据解析异常"+e.getMessage()); + } + } + + + public byte[] encode(DeviceData message, String clientId) { + try { + JSONObject body = (JSONObject) message.getDownMessage().getBody(); + ValueItem valueItem = new ValueItem(); + for (Map.Entry entry : body.entrySet()) { + valueItem.setId(entry.getKey()); + valueItem.setValue(entry.getValue()+""); + valueItem.setRemark(""); + } + String msg = "[" + JSONObject.toJSONString(valueItem) +"]"; + return msg.getBytes(StandardCharsets.UTF_8); + }catch (Exception e){ + log.error("=>指令编码异常,device={},data={}",message.getSerialNumber(), + message.getDownMessage().getBody()); + return null; + } + } +} diff --git a/springboot/fastbee-protocol/pom.xml b/springboot/fastbee-protocol/pom.xml new file mode 100644 index 00000000..c73f0536 --- /dev/null +++ b/springboot/fastbee-protocol/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + fastbee + com.fastbee + 3.8.5 + + + fastbee-protocol + 设备协议模块 + + pom + + + fastbee-protocol-collect + + + + + + com.fastbee + fastbee-common + + + + + + + diff --git a/springboot/fastbee-server/base-server/pom.xml b/springboot/fastbee-server/base-server/pom.xml new file mode 100644 index 00000000..3cb96e4f --- /dev/null +++ b/springboot/fastbee-server/base-server/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + fastbee-server + com.fastbee + 3.8.5 + + + base-server + diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/codec/Delimiter.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/codec/Delimiter.java new file mode 100644 index 00000000..897bcf8b --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/codec/Delimiter.java @@ -0,0 +1,29 @@ +package com.fastbee.base.codec; + +/** + * 分隔符报文处理器 + * 处理分割符报文,tcp粘包处理 + * @author bill + */ +public class Delimiter { + + public final byte[] value; + public final boolean strip; + + public Delimiter(byte[] value) { + this(value, true); + } + + public Delimiter(byte[] value, boolean strip) { + this.value = value; + this.strip = strip; + } + + public byte[] getValue() { + return value; + } + + public boolean isStrip() { + return strip; + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/codec/LengthField.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/codec/LengthField.java new file mode 100644 index 00000000..3fabdabf --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/codec/LengthField.java @@ -0,0 +1,76 @@ +package com.fastbee.base.codec; + +import static io.netty.util.internal.ObjectUtil.checkPositive; +import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; + +/** + * 固定长度报文处理器 + * 处理固定长度报文,tcp粘包处理 + * @author bill + */ +public class LengthField { + + public final byte[] prefix; + /*最大帧长度*/ + public final int lengthFieldMaxFrameLength; + /*偏移量*/ + public final int lengthFieldOffset; + /*字段长度*/ + public final int lengthFieldLength; + /*结尾偏移量*/ + public final int lengthFieldEndOffset; + /*报文调整 默认0,不调整*/ + public final int lengthAdjustment; + /*,默认0*/ + public final int initialBytesToStrip; + + /**构造固定长度处理器*/ + public LengthField(byte[] prefix, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) { + this(prefix, maxFrameLength, lengthFieldOffset, lengthFieldLength, 0, 0); + } + + public LengthField(byte[] prefix, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) { + checkPositive(maxFrameLength, "maxFrameLength_LengthField"); + checkPositiveOrZero(lengthFieldOffset, "lengthFieldOffset"); + checkPositiveOrZero(initialBytesToStrip, "initialBytesToStrip"); + if (lengthFieldOffset > maxFrameLength - lengthFieldLength) { + throw new IllegalArgumentException("maxFrameLength_LengthField (" + maxFrameLength + ") must be equal to or greater than lengthFieldOffset (" + lengthFieldOffset + ") + lengthFieldLength (" + lengthFieldLength + ")."); + } else { + this.prefix = prefix; + this.lengthFieldMaxFrameLength = maxFrameLength; + this.lengthFieldOffset = lengthFieldOffset; + this.lengthFieldLength = lengthFieldLength; + this.lengthAdjustment = lengthAdjustment; + this.lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength; + this.initialBytesToStrip = initialBytesToStrip; + } + } + + public byte[] getPrefix() { + return prefix; + } + + public int getLengthFieldMaxFrameLength() { + return lengthFieldMaxFrameLength; + } + + public int getLengthFieldOffset() { + return lengthFieldOffset; + } + + public int getLengthFieldLength() { + return lengthFieldLength; + } + + public int getLengthFieldEndOffset() { + return lengthFieldEndOffset; + } + + public int getLengthAdjustment() { + return lengthAdjustment; + } + + public int getInitialBytesToStrip() { + return initialBytesToStrip; + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/codec/MessageDecoder.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/codec/MessageDecoder.java new file mode 100644 index 00000000..ba2c3624 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/codec/MessageDecoder.java @@ -0,0 +1,18 @@ +package com.fastbee.base.codec; + + +import com.fastbee.common.core.mq.DeviceReport; +import io.netty.buffer.ByteBuf; + +/** + * 基础消息解码类 + * + * @author bill + */ +public interface MessageDecoder { + + /** + * TCP3.进站消息解码方法 + */ + DeviceReport decode(ByteBuf buf, String clientId); +} \ No newline at end of file diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/codec/MessageEncoder.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/codec/MessageEncoder.java new file mode 100644 index 00000000..0a2e4a80 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/codec/MessageEncoder.java @@ -0,0 +1,15 @@ +package com.fastbee.base.codec; + +import com.fastbee.common.core.protocol.Message; +import com.fastbee.base.session.Session; +import io.netty.buffer.ByteBuf; + +/** + * 基础消息编码类 + * + * @author bill + */ +public interface MessageEncoder{ + + ByteBuf encode(Message message, String clientId); +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/AbstractHandlerMapping.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/AbstractHandlerMapping.java new file mode 100644 index 00000000..d8895335 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/AbstractHandlerMapping.java @@ -0,0 +1,61 @@ +package com.fastbee.base.core; + +import com.fastbee.base.core.annotation.Async; +import com.fastbee.base.core.annotation.AsyncBatch; +import com.fastbee.base.core.annotation.PakMapping; +import com.fastbee.base.core.hanler.AsyncBatchHandler; +import com.fastbee.base.core.hanler.BaseHandler; +import com.fastbee.base.core.hanler.SyncHandler; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** + * 消息处理映射 + * + * @author bill + */ +public abstract class AbstractHandlerMapping implements HandlerMapping { + + private final Map handlerMap = new HashMap<>(64); + + /** + * 将node中被@Column标记的方法注册到映射表 + */ + protected synchronized void registerHandlers(Object bean) { + Class beanClass = bean.getClass(); + Method[] methods = beanClass.getDeclaredMethods(); + + for (Method method : methods) { + PakMapping annotation = method.getAnnotation(PakMapping.class); + if (annotation != null) { + + String desc = annotation.desc(); + int[] types = annotation.types(); + + AsyncBatch asyncBatch = method.getAnnotation(AsyncBatch.class); + BaseHandler baseHandler; + // 异步处理 + if (asyncBatch != null) { + baseHandler = new AsyncBatchHandler(bean, method, desc, asyncBatch.poolSize(), asyncBatch.maxMessageSize(), asyncBatch.maxWaitTime()); + } else { + baseHandler = new SyncHandler(bean, method, desc, method.isAnnotationPresent(Async.class)); + } + + for (int type : types) { + handlerMap.put(type, baseHandler); + } + + } + } + } + + /** + * 根据消息类型获取handler + */ + @Override + public BaseHandler getHandler(int messageId) { + return handlerMap.get(messageId); + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/DefaultHandlerMapping.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/DefaultHandlerMapping.java new file mode 100644 index 00000000..47966ff9 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/DefaultHandlerMapping.java @@ -0,0 +1,27 @@ +package com.fastbee.base.core; + +import com.fastbee.base.core.annotation.Node; +import com.fastbee.base.util.ClassUtils; + +import java.util.List; + +/** + * 默认消息映射处理类 + * @author bill + */ +public class DefaultHandlerMapping extends AbstractHandlerMapping { + + public DefaultHandlerMapping(String endpointPackage) { + List endpointClasses = ClassUtils.getClassList(endpointPackage, Node.class); + + for (Class endpointClass : endpointClasses) { + try { + Object bean = endpointClass.getDeclaredConstructor((Class[]) null).newInstance((Object[]) null); + super.registerHandlers(bean); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/HandlerInterceptor.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/HandlerInterceptor.java new file mode 100644 index 00000000..464795cd --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/HandlerInterceptor.java @@ -0,0 +1,37 @@ +package com.fastbee.base.core; + + +import com.fastbee.common.core.protocol.Message; +import com.fastbee.base.session.Session; + +/** + * 消息拦截器 + * @author bill + */ +public interface HandlerInterceptor { + + /** + * 未匹配到对应的Handle(消息处理) + */ + T notSupported(T request, Session session); + + /** + * 调用之前 + * 处理消息类型匹配 + */ + boolean beforeHandle(T request, Session session); + + /** + * 需要应答设备,在这里执行 + * 调用之后,返回值为void的 */ + T successful(T request, Session session); + + /** 调用之后,有返回值的 */ + void afterHandle(T request, T response, Session session); + + /** + * 报错应答方法 + * 调用之后抛出异常的 + */ + T exceptional(T request, Session session, Exception e); +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/HandlerMapping.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/HandlerMapping.java new file mode 100644 index 00000000..029b7bf0 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/HandlerMapping.java @@ -0,0 +1,12 @@ +package com.fastbee.base.core; + +import com.fastbee.base.core.hanler.BaseHandler; + +/** + * 消息处理接口 + * @author bill + */ +public interface HandlerMapping { + + BaseHandler getHandler(int messageId); +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/SpringHandlerMapping.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/SpringHandlerMapping.java new file mode 100644 index 00000000..5b2cb554 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/SpringHandlerMapping.java @@ -0,0 +1,23 @@ +package com.fastbee.base.core; + +import com.fastbee.base.core.annotation.Node; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +import java.util.Map; + +/** + * + * @author bill + */ +public class SpringHandlerMapping extends AbstractHandlerMapping implements ApplicationContextAware { + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + Map endpoints = applicationContext.getBeansWithAnnotation(Node.class); + for (Object bean : endpoints.values()) { + super.registerHandlers(bean); + } + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/annotation/Async.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/annotation/Async.java new file mode 100644 index 00000000..90ef0b3e --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/annotation/Async.java @@ -0,0 +1,15 @@ +package com.fastbee.base.core.annotation; + +import java.lang.annotation.*; + +/** + * 异步处理设备数据 + * + * @author bill + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Async { + +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/annotation/AsyncBatch.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/annotation/AsyncBatch.java new file mode 100644 index 00000000..3cbf559b --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/annotation/AsyncBatch.java @@ -0,0 +1,27 @@ +package com.fastbee.base.core.annotation; + +import java.lang.annotation.*; + +/** + * 多线程异步处理设备数据,新建线程组处理 + * + * @author bill + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface AsyncBatch { + + /*批量处理的最大消息数*/ + int maxMessageSize() default 5000; + + /*线程数*/ + int poolSize() default 2; + + /*最大等待时间*/ + int maxWaitTime() default 1000; + + /*最小处理消息数*/ + int minMessageSize() default 100; + +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/annotation/Node.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/annotation/Node.java new file mode 100644 index 00000000..6cee49e2 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/annotation/Node.java @@ -0,0 +1,14 @@ +package com.fastbee.base.core.annotation; + +import java.lang.annotation.*; + +/** + * 消息节点 + * @author bill + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Node { + +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/annotation/PakMapping.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/annotation/PakMapping.java new file mode 100644 index 00000000..0945ac11 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/annotation/PakMapping.java @@ -0,0 +1,17 @@ +package com.fastbee.base.core.annotation; + +import java.lang.annotation.*; + +/** + * 字段映射 + * @author bill + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface PakMapping { + + int[] types(); + + String desc() default ""; +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/hanler/AsyncBatchHandler.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/hanler/AsyncBatchHandler.java new file mode 100644 index 00000000..d13d554f --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/hanler/AsyncBatchHandler.java @@ -0,0 +1,128 @@ +package com.fastbee.base.core.hanler; + +import com.fastbee.common.core.protocol.Message; +import com.fastbee.base.session.Session; +import com.fastbee.base.util.VirtualList; +import com.fastbee.common.exception.ServiceException; +import io.netty.util.concurrent.DefaultThreadFactory; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 异步批量处理报文 + * @author bill + */ +@Slf4j +public class AsyncBatchHandler extends BaseHandler{ + + + /*消息处理队列*/ + private final ConcurrentLinkedQueue queue; + + /*线程池*/ + private final ThreadPoolExecutor executor; + + private final int poolSize; + + private final int maxEventSize; + + private final int maxWait; + + private final int warningLines; + + + public AsyncBatchHandler(Object target, Method targetMethod, String desc, int poolSize, int maxEventSize, int maxWait) { + + super(target, targetMethod, desc); + Class[] parameterTypes = targetMethod.getParameterTypes(); + if (parameterTypes.length >1){ + throw new ServiceException("参数列表过长"); + } + if (!parameterTypes[0].isAssignableFrom(List.class)){ + throw new ServiceException("参数不是List类型"); + } + + this.poolSize = poolSize; + this.maxEventSize = maxEventSize; + this.maxWait = maxWait; + this.warningLines = maxEventSize * poolSize * 50; + + this.queue = new ConcurrentLinkedQueue<>(); + this.executor = new ThreadPoolExecutor(this.poolSize,this.poolSize,1000L, TimeUnit.MILLISECONDS + ,new LinkedBlockingQueue<>(500),new DefaultThreadFactory(targetMethod.getName())); + + for (int i = 0; i < poolSize; i++) { + boolean start = i == 0; + executor.execute(()->{ + try { + startInternal(start); + }catch (Exception e){ + log.error("线程池处理数据出错",e); + } + }); + } + + } + + @Override + public T invoke(T request, Session session) throws Exception { + queue.offer(request); + return null; + } + + public void startInternal(boolean master) { + Message[] array = new Message[maxEventSize]; + long logtime = 0; + long starttime = 0; + + for (; ; ) { + Message temp; + int i = 0; + while ((temp = queue.poll()) != null) { + array[i++] = temp; + if (i >= maxEventSize) { + break; + } + } + + if (i > 0) { + starttime = System.currentTimeMillis(); + try { + targetMethod.invoke(targetObject, new VirtualList<>(array, i)); + } catch (Exception e) { + log.warn(targetMethod.getName(), e); + } + long time = System.currentTimeMillis() - starttime; + if (time > 1000L) { + log.warn("线程池处理数据耗时:{}ms,共{}条记录", time, i); + } + } + + if (i < maxEventSize) { + try { + for (int j = 0; j < i; j++) { + array[j] = null; + } + Thread.sleep(maxWait); + } catch (InterruptedException e) { + log.error("sleep error!"); + } + } else if (master) { + if (logtime < starttime) { + logtime = starttime + 5000L; + + int size = queue.size(); + if (size > warningLines) { + log.warn("线程池队列已满, size:{}", size); + } + } + } + } + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/hanler/BaseHandler.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/hanler/BaseHandler.java new file mode 100644 index 00000000..7b4d93a2 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/hanler/BaseHandler.java @@ -0,0 +1,86 @@ +package com.fastbee.base.core.hanler; + +import com.fastbee.common.core.protocol.Message; +import com.fastbee.base.session.Session; + +import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +/** + * 基础处理类 + * + * @author bill + */ +public abstract class BaseHandler { + + public static final int MESSAGE = 0; + public static final int SESSION = 1; + + public final Object targetObject; + public final Method targetMethod; + public final int[] parameterTypes; + public final boolean returnVoid; + public final boolean async; + public final String desc; + + public BaseHandler(Object target, Method targetMethod, String desc) { + this(target, targetMethod, desc, false); + } + + public BaseHandler(Object targetObject, Method targetMethod, String desc, boolean async) { + this.targetObject = targetObject; + this.targetMethod = targetMethod; + this.returnVoid = targetMethod.getReturnType().isAssignableFrom(Void.TYPE); + this.async = async; + if (desc == null || desc.isEmpty()) + desc = targetMethod.getName(); + this.desc = desc; + + Type[] types = targetMethod.getGenericParameterTypes(); + int[] parameterTypes = new int[types.length]; + try { + for (int i = 0; i < types.length; i++) { + Type type = types[i]; + Class clazz; + if (type instanceof ParameterizedTypeImpl) { + clazz = (Class) ((ParameterizedTypeImpl) type).getActualTypeArguments()[0]; + } else { + clazz = (Class) type; + } + + if (Message.class.isAssignableFrom(clazz)) { + parameterTypes[i] = MESSAGE; + } else if (Session.class.isAssignableFrom(clazz)) { + parameterTypes[i] = SESSION; + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + this.parameterTypes = parameterTypes; + } + + public T invoke(T request, Session session) throws Exception { + Object[] args = new Object[parameterTypes.length]; + + for (int i = 0; i < parameterTypes.length; i++) { + int type = parameterTypes[i]; + switch (type) { + case BaseHandler.MESSAGE: + args[i] = request; + break; + case BaseHandler.SESSION: + args[i] = session; + break; + } + } + return (T) targetMethod.invoke(targetObject, args); + } + + @Override + public String toString() { + return desc; + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/hanler/SyncHandler.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/hanler/SyncHandler.java new file mode 100644 index 00000000..66605376 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/hanler/SyncHandler.java @@ -0,0 +1,23 @@ +package com.fastbee.base.core.hanler; + +import com.fastbee.common.core.protocol.Message; +import com.fastbee.base.session.Session; + + +import java.lang.reflect.Method; + +/** + * 同步处理报文 + * @author bill + */ +public class SyncHandler extends BaseHandler{ + + public SyncHandler(Object target, Method targetMethod, String desc,boolean async) { + super(target, targetMethod, desc, async); + } + + @Override + public T invoke(T request, Session session) throws Exception { + return super.invoke(request, session); + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/model/Response.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/model/Response.java new file mode 100644 index 00000000..8d17e20d --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/core/model/Response.java @@ -0,0 +1,12 @@ +package com.fastbee.base.core.model; + +/** + * 消息流水号响应 + * @author gsb + * @date 2022/11/7 10:19 + */ +public interface Response { + + /**应答消息流水号*/ + int getResponseSerialNo(); +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/model/DeviceMsg.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/model/DeviceMsg.java new file mode 100644 index 00000000..b3bffcc6 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/model/DeviceMsg.java @@ -0,0 +1,19 @@ +package com.fastbee.base.model; + +import lombok.Data; + +/** + * @author gsb + * @date 2023/3/9 10:07 + */ +@Data +public class DeviceMsg { + + protected String clientId; + + protected Long deviceId; + + private int protocolVersion; + + private Long productId; +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/model/SessionKey.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/model/SessionKey.java new file mode 100644 index 00000000..22b6549a --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/model/SessionKey.java @@ -0,0 +1,16 @@ +package com.fastbee.base.model; + +import com.fastbee.base.session.Session; + +/** + * @author gsb + * @date 2023/3/9 10:03 + */ +public enum SessionKey { + + DeviceMsg; + + public static DeviceMsg getDeviceMsg(Session session){ + return (DeviceMsg)session.getAttribute(SessionKey.DeviceMsg); + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/service/ISessionStore.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/service/ISessionStore.java new file mode 100644 index 00000000..4bd83178 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/service/ISessionStore.java @@ -0,0 +1,60 @@ +package com.fastbee.base.service; + + +import com.fastbee.base.session.Session; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * 服务会话存储接口 + * @author gsb + * @date 2022/10/14 14:16 + */ +public interface ISessionStore { + /** + * session 会话存储 + * + * @param clientId: 客户端标识 + * @param session: session会话 + + */ + void storeSession(String clientId, Session session); + + /** + * 根据客户端标识获取相应会话 + * + * @param clientId: 客户端标识 + */ + Session getSession(String clientId); + + /** + * 清除历史会话状态 + * + * @param clientId: 客户端标识 + */ + void cleanSession(String clientId); + + /** + * 根据客户端标识查看是否存在该会话 + * + * @param clientId: + */ + boolean containsKey(String clientId); + + /** + * 获取集合 + * @return MAP + */ + ConcurrentHashMap getSessionMap(); + + /** + * map分页(从1开始) + * + * @param sourceMap 分页数据 + * @param pageSize 页面大小 + * @param currentPage 当前页面 + */ + public Map listPage(Map sourceMap, int pageSize, int currentPage); +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/service/impl/SessionStoreImpl.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/service/impl/SessionStoreImpl.java new file mode 100644 index 00000000..da2819f6 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/service/impl/SessionStoreImpl.java @@ -0,0 +1,104 @@ +package com.fastbee.base.service.impl; + +import com.fastbee.base.service.ISessionStore; +import com.fastbee.base.session.Session; +import org.springframework.stereotype.Service; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 内存存储服务会话 + * + * @author gsb + * @date 2022/10/14 14:18 + */ +@Service +public class SessionStoreImpl implements ISessionStore { + + /*session存储集合*/ + private final ConcurrentHashMap sessionMap = new ConcurrentHashMap<>(); + + /** + * MQTT会话存储 + * + * @param clientId: 客户端标识 + * @param session: MQTT会话 + */ + @Override + public void storeSession(String clientId, Session session) { + sessionMap.put(clientId, session); + } + + /** + * 根据客户端标识获取相应会话 + * + * @param clientId: 客户端标识 + */ + @Override + public Session getSession(String clientId) { + return sessionMap.get(clientId); + } + + /** + * 清除历史会话状态 + * + * @param clientId: 客户端标识 + */ + @Override + public void cleanSession(String clientId) { + sessionMap.remove(clientId); + } + + /** + * 根据客户端标识查看是否存在该会话 + * + * @param clientId: + */ + @Override + public boolean containsKey(String clientId) { + return sessionMap.containsKey(clientId); + } + + /** + * 获取集合 + * @return MAP + */ + @Override + public ConcurrentHashMap getSessionMap(){ + return sessionMap; + } + + + /** + * map分页(从1开始) + * + * @param sourceMap 分页数据 + * @param pageSize 页面大小 + * @param currentPage 当前页面 + */ + @Override + public Map listPage(Map sourceMap, int pageSize, int currentPage) { + Map map = new LinkedHashMap<>(); + if (sourceMap.size() > 0) { + AtomicInteger flag = new AtomicInteger(0); + AtomicInteger size = new AtomicInteger(0); + int currIdx = (currentPage > 1 ? (currentPage - 1) * pageSize : 0); + sourceMap.forEach((ass, list_km) -> { + if (flag.get() >= currIdx) { + if (size.get() < pageSize) { + map.put(ass, list_km); + } else { + return; + } + size.getAndIncrement(); + } + flag.getAndIncrement(); + }); + + } + return map; + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/session/Packet.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/session/Packet.java new file mode 100644 index 00000000..c57b144d --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/session/Packet.java @@ -0,0 +1,74 @@ +package com.fastbee.base.session; + +import com.fastbee.common.core.protocol.Message; +import io.netty.buffer.ByteBuf; +import io.netty.channel.socket.DatagramPacket; + +/** + * 报文消息的包装 + * @author bill + */ +public abstract class Packet { + + /*session*/ + public final Session session; + /*基础消息*/ + public Message message; + /*报文缓存buf*/ + public ByteBuf byteBuf; + + public static Packet of(Session session, Message message) { + if (session.isUdp()) { + return new UDP(session, message, null); + } + return new TCP(session, message, message.getPayload()); + } + + public static Packet of(Session session, ByteBuf message) { + if (session.isUdp()) { + return new UDP(session, null, message); + } + return new TCP(session, null, message); + } + + private Packet(Session session, Message message, ByteBuf byteBuf) { + this.session = session; + this.message = message; + this.byteBuf = byteBuf; + } + + public Packet replace(Message message) { + this.message = message; + return this; + } + + public ByteBuf take() { + ByteBuf temp = this.byteBuf; + this.byteBuf = null; + return temp; + } + + public abstract Object wrap(ByteBuf byteBuf); + + private static class TCP extends Packet { + private TCP(Session session, Message message, ByteBuf byteBuf) { + super(session, message, byteBuf); + } + + @Override + public Object wrap(ByteBuf byteBuf) { + return byteBuf; + } + } + + private static class UDP extends Packet { + private UDP(Session session, Message message, ByteBuf byteBuf) { + super(session, message, byteBuf); + } + + @Override + public Object wrap(ByteBuf byteBuf) { + return new DatagramPacket(byteBuf, session.remoteAddress()); + } + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/session/Session.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/session/Session.java new file mode 100644 index 00000000..4f6c0ac3 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/session/Session.java @@ -0,0 +1,286 @@ +package com.fastbee.base.session; + +import com.fastbee.common.core.protocol.Message; +import com.fastbee.common.enums.ServerType; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.gateway.mq.Topics; +import com.fastbee.base.core.model.Response; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessageType; +import io.netty.handler.codec.mqtt.MqttVersion; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoSink; + +import java.net.InetSocketAddress; +import java.util.*; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; + +/** + * 会话 + * + * @Author guanshubiao + * @Date 2022/9/12 20:22 + */ +@Data +@Slf4j +public class Session { + + private boolean udp; + private Function remover; + protected Channel channel; + /** + * 客户端id + */ + private String clientId; + private String productId; + private SessionManager sessionManager; + private InetSocketAddress remoteAddress; + private String remoteAddressStr; + + private long creationTime; + private long lastAccessTime; + private Map attributes; + + private String sessionId; + //原子计数器,报文没有消息流水号的,充当流水号 + private AtomicInteger serialNo = new AtomicInteger(0); + private int keepAlive = 60; + + + /*mqtt版本号*/ + private MqttVersion version; + /*是否清楚会话*/ + private Boolean cleanSession = false; + /*mqtt账号*/ + private String username; + /*是否链接*/ + private Boolean connected = false; + /*mqtt消息类型*/ + private MqttMessageType mqttMessageType; + private int keepAliveMax = 120; + /*主题*/ + private String topicName; + /*Channel处理类上下文*/ + private ChannelHandlerContext handlerContext; + + private List topics; + private Integer topicCount; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date connected_at; + private String ip; + /*服务协议类型 MQTT,TCP UDP COAP*/ + private ServerType serverType; + + public Session(){ + + } + + public Session(SessionManager sessionManager, + Channel channel, + InetSocketAddress remoteAddress, + Function remover, + boolean udp, + ServerType serverType) { + this.channel = channel; + this.creationTime = DateUtils.getTimestamp(); + this.lastAccessTime = creationTime; + this.sessionManager = sessionManager; + this.remoteAddress = remoteAddress; + this.remoteAddressStr = remoteAddress.toString(); + this.remover = remover; + this.udp = udp; + this.serverType = serverType; + this.ip = remoteAddress.getHostName(); + this.connected = true; + this.connected_at = DateUtils.getNowDate(); + if (sessionManager != null && sessionManager.getSessionKeys() != null) { + this.attributes = new EnumMap(sessionManager.getSessionKeys()); + } else { + this.attributes = new TreeMap<>(); + } + } + + /** + * 判断设备是否已经注册上线 + */ + public boolean isRegistered() { + return clientId != null; + } + + /*设备端注册*/ + public void register(Message message) { + register(message.getClientId(), message); + } + + public void register(String clientId, Message message) { + //已经注册,不再发送注册包数据 + if (isRegistered()){ + return; + } + if (clientId == null) { + throw new ServiceException("客户端注册clientId不能为空"); + } + this.clientId = clientId.toUpperCase(); + if (sessionManager != null) { + sessionManager.add(this); + } + } + + public Object getAttribute(Object name) { + return attributes.get(name); + } + + public void setAttribute(Object name, Object value) { + attributes.put(name, value); + } + + /** + * 获取最后上线时间 + */ + public long access() { + return lastAccessTime = DateUtils.getTimestamp(); + } + + /** + * 获取远程端口 + */ + public InetSocketAddress remoteAddress() { + return remoteAddress; + } + + /** + * 获取流水号 + */ + public int nextSerialNO() { + int current; + int next; + do { + current = serialNo.get(); + next = current > 0xffff ? 0 : current; + } while (!serialNo.compareAndSet(current, next + 1)); + return next; + } + + /** + * 处理离线方法 + */ + public void invalidate() { + if (isRegistered() && sessionManager != null) { + sessionManager.remove(this); + } + remover.apply(this); + } + + public boolean isUdp() { + return udp; + } + + private final Map topicSubscribers = new HashMap<>(); + + private static final Mono rejected = Mono.error(new RejectedExecutionException("设备端暂无响应")); + + /** + * 异步发送通知类消息 + * 同步发送 mono.block() + * 订阅回调 mono.doOnSuccess(处理成功).doOnError(处理异常).subscribe(开始订阅) + */ + public Mono notify(Message message) { + return mono(channel.writeAndFlush(Packet.of(this, message))); + } + + public Mono notify(ByteBuf message) { + return mono(channel.writeAndFlush(Packet.of(this, message))); + } + + public static Mono mono(ChannelFuture channelFuture) { + return Mono.create(sink -> channelFuture.addListener(future -> { + if (future.isSuccess()) { + sink.success(); + } else { + sink.error(future.cause()); + } + })); + } + + /** + * 异步发送消息,接收响应 + * 同步接收 mono.block() + * 订阅回调 mono.doOnSuccess(处理成功).doOnError(处理异常).subscribe(开始订阅) + */ + public Mono request(Message message, Class responseClass) { + String key = requestKey(message, responseClass); + Mono subscribe = this.subscribe(key); + if (subscribe == null) { + return rejected; + } + ChannelFuture channelFuture = channel.writeAndFlush(Packet.of(this, message)); + return Mono.create(sink -> channelFuture.addListener(future -> { + if (future.isSuccess()) { + sink.success(future); + } else { + sink.error(future.cause()); + } + })).then(subscribe).doFinally(signal -> unsubscribe(key)); + } + + /** + * 消息响应 + */ + public boolean response(Message message){ + MonoSink monoSink = topicSubscribers.get(responseKey(message)); + if (monoSink != null){ + monoSink.success(message); + return true; + } + return false; + } + + /** + * 订阅 + */ + private Mono subscribe(String key) { + synchronized (topicSubscribers) { + if (!topicSubscribers.containsKey(key)) { + return Mono.create(sink -> topicSubscribers.put(key, sink)); + } + } + return null; + } + + /** + * 取消订阅 + */ + private void unsubscribe(String key) { + topicSubscribers.remove(key); + } + + /*生成流水号*/ + private static String requestKey(Message request, Class responseClass) { + String className = responseClass.getName(); + if (Response.class.isAssignableFrom(responseClass)) { + String serNo = request.getSerNo(); + return new StringBuffer(34).append(className).append('.').append(serNo).toString(); + } + return className; + } + + /*返回流水号*/ + private static String responseKey(Object response) { + String className = response.getClass().getName(); + if (response instanceof Response) { + int serialNo = ((Response) response).getResponseSerialNo(); + return new StringBuffer(34).append(className).append('.').append(serialNo).toString(); + } + return className; + } + +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/session/SessionListener.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/session/SessionListener.java new file mode 100644 index 00000000..b910e003 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/session/SessionListener.java @@ -0,0 +1,26 @@ +package com.fastbee.base.session; + +/** + * session监听 + * @author gsb + * @date 2022/11/7 8:57 + */ + +public interface SessionListener { + + /** 客户端建立连接 */ + default void sessionCreated(Session session) { + + } + + /** 客户端完成注册或鉴权 */ + default void sessionRegistered(Session session) { + } + + /** + * 客户端注销或离线 + */ + default void sessionDestroyed(Session session) { + + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/session/SessionManager.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/session/SessionManager.java new file mode 100644 index 00000000..54bcf9b3 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/session/SessionManager.java @@ -0,0 +1,125 @@ +package com.fastbee.base.session; + +import com.fastbee.common.enums.ServerType; +import com.fastbee.common.utils.spring.SpringUtils; +import com.fastbee.base.service.ISessionStore; +import io.netty.channel.Channel; +import lombok.extern.slf4j.Slf4j; + +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.Locale; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * session管理 + * @author gsb + * @date 2022/11/7 8:55 + */ +@Slf4j +public class SessionManager { + + private final Class sessionKeys; + + private final SessionListener sessionListener; + + /*Session会话存储*/ + private static ISessionStore sessionStore = SpringUtils.getBean(ISessionStore.class); + + public SessionManager(){ + this(null,null); + } + + public SessionManager(SessionListener sessionListener){ + this(null,sessionListener); + } + + public SessionManager(Class sessionKeys, SessionListener sessionListener){ + this.sessionKeys = sessionKeys; + this.sessionListener = sessionListener; + } + + /** + * 获取Session + */ + public Session getSession(String clientId) { + return sessionStore.getSession(clientId); + } + + /** + * 获取所有session + */ + public Collection all(){ + return sessionStore.getSessionMap().values(); + } + + /** + * 新建session TCP + * @return + */ + public Session newInstance(Channel channel){ + InetSocketAddress sender = (InetSocketAddress) channel.remoteAddress(); + Session session = new Session(this, channel, sender, s -> { + channel.close(); + return true; + }, false, ServerType.TCP); + if (sessionListener != null) { + try { + sessionListener.sessionCreated(session); + } catch (Exception e) { + log.error("sessionCreated", e); + } + } + return session; + } + + /** + * 新建session UDP + */ + public Session newInstance(Channel channel, InetSocketAddress sender, Function remover) { + Session session = new Session(this, channel, sender, remover, true,ServerType.UDP); + if (sessionListener != null) { + try { + sessionListener.sessionCreated(session); + } catch (Exception e) { + log.error("sessionCreated", e); + } + } + return session; + } + + /** + * 设备端离线 + */ + protected void remove(Session session){ + sessionStore.cleanSession(session.getClientId()); + if (null != sessionListener){ + try { + //设备状态业务处理 + sessionListener.sessionDestroyed(session); + }catch (Exception e){ + log.error("设备端离线异常",e); + } + } + } + + /** + * 设备端上线 + */ + protected void add(Session session){ + sessionStore.storeSession(session.getClientId().toUpperCase(),session); + if (null != sessionListener){ + try { + sessionListener.sessionRegistered(session); + }catch (Exception e){ + log.error("设备端注册",e); + } + } + } + + public Class getSessionKeys(){ + return sessionKeys; + } + +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/AttributeUtils.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/AttributeUtils.java new file mode 100644 index 00000000..8e2658f7 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/AttributeUtils.java @@ -0,0 +1,42 @@ +package com.fastbee.base.util; + + +import com.fastbee.base.session.Session; +import io.netty.channel.Channel; +import io.netty.util.AttributeKey; + + +/** + * channel和pipeline桥梁Attribute信息构建 + * @author gsb + * @date 2022/10/7 18:40 + */ +public class AttributeUtils { + + /*存储客户端连接信息*/ + private static final AttributeKey SESSION_KEY = AttributeKey.valueOf("session"); + /*存储客户端id*/ + private static final AttributeKey CLIENT_ID_KEY = AttributeKey.valueOf("clientId"); + + + /*添加session*/ + public static void setSession(Channel channel, Session session){ + channel.attr(SESSION_KEY).set(session); + } + + /*获取session*/ + public static Session getSession(Channel channel){ + return (Session) channel.attr(SESSION_KEY).get(); + } + + /*添加clientId*/ + public static void setClientId(Channel channel, String clientId){ + channel.attr(CLIENT_ID_KEY).set(clientId); + } + + /*获取clientId*/ + public static String getClientId(Channel channel){ + return (String) channel.attr(CLIENT_ID_KEY).get(); + } + +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/ClassUtils.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/ClassUtils.java new file mode 100644 index 00000000..8812ccda --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/ClassUtils.java @@ -0,0 +1,106 @@ +package com.fastbee.base.util; + +import java.io.File; +import java.lang.annotation.Annotation; +import java.net.JarURLConnection; +import java.net.URL; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * 类操作工具 + * @author bill + */ +public class ClassUtils { + public static List getClassList(String packageName, Class annotationClass) { + List classList = getClassList(packageName); + classList.removeIf(next -> !next.isAnnotationPresent(annotationClass)); + return classList; + } + + public static List getClassList(String packageName) { + List classList = new LinkedList<>(); + String path = packageName.replace(".", "/"); + try { + Enumeration urls = ClassUtils.getClassLoader().getResources(path); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + + if (url != null) { + String protocol = url.getProtocol(); + + if (protocol.equals("file")) { + addClass(classList, url.toURI().getPath(), packageName); + + } else if (protocol.equals("jar")) { + JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection(); + JarFile jarFile = jarURLConnection.getJarFile(); + + Enumeration jarEntries = jarFile.entries(); + while (jarEntries.hasMoreElements()) { + + JarEntry jarEntry = jarEntries.nextElement(); + String entryName = jarEntry.getName(); + + if (entryName.startsWith(path) && entryName.endsWith(".class")) { + String className = entryName.substring(0, entryName.lastIndexOf(".")).replaceAll("/", "."); + addClass(classList, className); + } + } + } + } + } + } catch (Exception e) { + throw new RuntimeException("Initial class error!"); + } + return classList; + } + + private static void addClass(List classList, String packagePath, String packageName) { + try { + File[] files = new File(packagePath).listFiles(file -> (file.isDirectory() || file.getName().endsWith(".class"))); + if (files != null) + for (File file : files) { + String fileName = file.getName(); + if (file.isFile()) { + String className = fileName.substring(0, fileName.lastIndexOf(".")); + if (packageName != null) { + className = packageName + "." + className; + } + addClass(classList, className); + } else { + String subPackagePath = fileName; + if (packageName != null) { + subPackagePath = packagePath + "/" + subPackagePath; + } + String subPackageName = fileName; + if (packageName != null) { + subPackageName = packageName + "." + subPackageName; + } + addClass(classList, subPackagePath, subPackageName); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void addClass(List classList, String className) { + classList.add(loadClass(className, false)); + } + + public static Class loadClass(String className, boolean isInitialized) { + try { + return Class.forName(className, isInitialized, getClassLoader()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public static ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/ConcurrentStorage.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/ConcurrentStorage.java new file mode 100644 index 00000000..43b614f5 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/ConcurrentStorage.java @@ -0,0 +1,51 @@ +package com.fastbee.base.util; + +import lombok.Data; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * concurrentMap存储类 + * @author bill + */ +@Data +public abstract class ConcurrentStorage implements Storage{ + + private volatile ConcurrentHashMap map; + + public ConcurrentStorage(){ + this(new ConcurrentHashMap()); + } + + public ConcurrentStorage(ConcurrentHashMap map){ + this.map = map; + } + + + @Override + public V push(K key, V value) { + return map.put(key,value); + } + + @Override + public V pop(K key) { + return map.get(key); + } + + @Override + public V remove(K key) { + return map.remove(key); + } + + @Override + public boolean isContains(Object key) { + return map.containsKey(key); + } + + @Override + public Object getStorage() { + return this.map; + } + + +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/DeviceUtils.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/DeviceUtils.java new file mode 100644 index 00000000..6dfe560f --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/DeviceUtils.java @@ -0,0 +1,30 @@ +package com.fastbee.base.util; + +import com.fastbee.common.core.mq.DeviceStatusBo; +import com.fastbee.common.enums.DeviceStatus; +import com.fastbee.common.utils.DateUtils; +import io.netty.channel.Channel; + +import java.net.InetSocketAddress; + +/** + * 设备信息工具类 + * @author bill + */ +public class DeviceUtils { + + + + + /*构造返回MQ的设备状态model*/ + public static DeviceStatusBo buildStatusMsg(Channel channel, String clientId, DeviceStatus status, String ip){ + InetSocketAddress address = (InetSocketAddress) channel.remoteAddress(); + return DeviceStatusBo.builder() + .serialNumber(clientId) + .status(status) + .ip(ip) + .hostName(address.getHostName()) + .port(address.getPort()) + .timestamp(DateUtils.getNowDate()).build(); + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/Stopwatch.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/Stopwatch.java new file mode 100644 index 00000000..bd1f50f3 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/Stopwatch.java @@ -0,0 +1,48 @@ +package com.fastbee.base.util; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author bill + */ +public class Stopwatch { + private final AtomicInteger count = new AtomicInteger(); + private final Thread thread; + + public Stopwatch start() { + this.thread.start(); + return this; + } + + public int increment() { + return count.incrementAndGet(); + } + + public Stopwatch() { + thread = new Thread(() -> { + long start; + while (true) { + if (count.get() > 0) { + start = System.currentTimeMillis(); + break; + } + try { + Thread.sleep(1L); + } catch (Exception e) { + } + } + while (true) { + try { + Thread.sleep(2000L); + } catch (Exception e) { + } + int num = count.get(); + long time = (System.currentTimeMillis() - start) / 1000; + System.out.println(time + "\t" + num + "\t" + num / time); + } + }); + thread.setName(Thread.currentThread().getName() + "-c"); + thread.setPriority(Thread.MIN_PRIORITY); + thread.setDaemon(true); + } +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/Storage.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/Storage.java new file mode 100644 index 00000000..5c803d51 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/Storage.java @@ -0,0 +1,18 @@ +package com.fastbee.base.util; + +/** + * 存储管理 + * @author bill + */ +public interface Storage { + + V push(K key, V value); + + V pop(K key); + + V remove(K key); + + boolean isContains(K key); + + Object getStorage(); +} diff --git a/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/VirtualList.java b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/VirtualList.java new file mode 100644 index 00000000..6423cce4 --- /dev/null +++ b/springboot/fastbee-server/base-server/src/main/java/com/fastbee/base/util/VirtualList.java @@ -0,0 +1,101 @@ +package com.fastbee.base.util; + +import java.io.Serializable; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.UnaryOperator; + +/** + * @author bill + */ +public class VirtualList extends AbstractList implements RandomAccess, Serializable { + + private final E[] elementData; + private final int size; + + public VirtualList(E[] array, int length) { + this.elementData = array; + this.size = length; + } + + @Override + public int size() { + return size; + } + + @Override + public Object[] toArray() { + return elementData.clone(); + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + if (a.length < size) + return Arrays.copyOf(this.elementData, size, + (Class) a.getClass()); + System.arraycopy(this.elementData, 0, a, 0, size); + if (a.length > size) + a[size] = null; + return a; + } + + @Override + public E get(int index) { + return elementData[index]; + } + + @Override + public E set(int index, E element) { + E oldValue = elementData[index]; + elementData[index] = element; + return oldValue; + } + + @Override + public int indexOf(Object o) { + E[] a = this.elementData; + if (o == null) { + for (int i = 0; i < size; i++) + if (a[i] == null) + return i; + } else { + for (int i = 0; i < size; i++) + if (o.equals(a[i])) + return i; + } + return -1; + } + + @Override + public boolean contains(Object o) { + return indexOf(o) != -1; + } + + @Override + public Spliterator spliterator() { + return Spliterators.spliterator(elementData, 0, size, Spliterator.ORDERED); + } + + @Override + public void forEach(Consumer action) { + Objects.requireNonNull(action); + for (int i = 0; i < size; i++) { + action.accept(elementData[i]); + } + } + + @Override + public void replaceAll(UnaryOperator operator) { + Objects.requireNonNull(operator); + E[] a = this.elementData; + for (int i = 0; i < size; i++) { + a[i] = operator.apply(a[i]); + } + } + + @Override + public void sort(Comparator c) { + Arrays.sort(elementData, 0, size, c); + } +} \ No newline at end of file diff --git a/springboot/fastbee-server/boot-strap/pom.xml b/springboot/fastbee-server/boot-strap/pom.xml new file mode 100644 index 00000000..d0ec5509 --- /dev/null +++ b/springboot/fastbee-server/boot-strap/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + fastbee-server + com.fastbee + 3.8.5 + + boot-strap + 网关服务启动模块 + + + + + com.fastbee + mqtt-broker + + + + com.fastbee + fastbee-protocol-collect + + + com.fastbee + base-server + 3.8.5 + compile + + + + + + + diff --git a/springboot/fastbee-server/boot-strap/src/main/java/com/fastbee/bootstrap/mqtt/MQTTBootStrap.java b/springboot/fastbee-server/boot-strap/src/main/java/com/fastbee/bootstrap/mqtt/MQTTBootStrap.java new file mode 100644 index 00000000..37d20449 --- /dev/null +++ b/springboot/fastbee-server/boot-strap/src/main/java/com/fastbee/bootstrap/mqtt/MQTTBootStrap.java @@ -0,0 +1,70 @@ +package com.fastbee.bootstrap.mqtt; + +import com.fastbee.mqtt.server.MqttServer; +import com.fastbee.mqtt.server.WebSocketServer; +import com.fastbee.server.Server; +import com.fastbee.server.config.NettyConfig; +import com.fastbee.common.enums.ServerType; +import lombok.Data; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +/** + * MQTT-BROKER启动 + * @author gsb + * @date 2022/9/17 17:25 + */ +@Order(10) +@Configuration +@ConfigurationProperties(value = "server.broker") +@Data +public class MQTTBootStrap { + + @Autowired + private MqttServer mqttServer; + @Autowired + private WebSocketServer webSocketServer; + /*服务器集群节点*/ + private String brokerNode; + /*端口*/ + private int port; + /*心跳时间*/ + private int keepAlive; + /*webSocket端口*/ + private int websocketPort; + /*webSocket路由*/ + private String websocketPath; + + + /** + * 启动mqttBroker + * @return server + */ + @ConditionalOnProperty(value = "server.broker.enabled", havingValue = "true") + @Bean(initMethod = "start", destroyMethod = "stop") + public Server mqttBroker() { + return NettyConfig.custom() + .setIdleStateTime(0,0,keepAlive) + .setName(ServerType.MQTT.getDes()) + .setType(ServerType.MQTT) + .setPort(port) + .setServer(mqttServer) + .build(); + } + + @ConditionalOnProperty(value = "server.broker.enabled", havingValue = "true") + @Bean(initMethod = "start",destroyMethod = "stop") + public Server webSocket(){ + return NettyConfig.custom() + .setIdleStateTime(0,0,keepAlive) + .setName(ServerType.WEBSOCKET.getDes()) + .setType(ServerType.WEBSOCKET) + .setPort(websocketPort) + .setServer(webSocketServer) + .build(); + } +} diff --git a/springboot/fastbee-server/iot-server-core/pom.xml b/springboot/fastbee-server/iot-server-core/pom.xml new file mode 100644 index 00000000..a008bbe2 --- /dev/null +++ b/springboot/fastbee-server/iot-server-core/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + fastbee-server + com.fastbee + 3.8.5 + + + iot-server-core + + + + com.fastbee + base-server + compile + + + com.fastbee + fastbee-mq + + + + diff --git a/springboot/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/Server.java b/springboot/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/Server.java new file mode 100644 index 00000000..4256484c --- /dev/null +++ b/springboot/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/Server.java @@ -0,0 +1,77 @@ +package com.fastbee.server; + + +import com.fastbee.server.config.NettyConfig; +import io.netty.bootstrap.AbstractBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.EventLoopGroup; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.ExecutorService; + +/** + * 基础服务器启动类 + * + * @Author guanshubiao + * @Date 2022/9/12 20:22 + */ +@Slf4j +@NoArgsConstructor +public abstract class Server { + + protected EventLoopGroup bossGroup; + protected EventLoopGroup workerGroup; + protected ExecutorService businessService; + protected boolean isRunning; + public NettyConfig config; + + + + protected Server(NettyConfig config){ + this.config = config; + } + + /*初始化方法*/ + protected abstract AbstractBootstrap initialize(); + + + public synchronized boolean start() { + if (isRunning) { + log.warn("=>服务:{},在端口:{},已经运行", config.name, config.port); + return isRunning; + } + AbstractBootstrap bootstrap = initialize(); + ChannelFuture future = bootstrap.bind(config.port).awaitUninterruptibly(); + future.channel().closeFuture().addListener(event -> { + if (isRunning) { + stop(); + } + }); + if (isRunning = future.isSuccess()) { + log.info("=>服务:{},在端口:{},启动成功!", config.name, config.port); + return isRunning; + } + + if (future.cause() != null) { + log.error("服务启动失败", future.cause()); + } + return isRunning; + } + + + public synchronized void stop() { + + isRunning = false; + bossGroup.shutdownGracefully(); + if (workerGroup != null) { + workerGroup.shutdownGracefully(); + } + if (businessService != null) { + businessService.shutdown(); + } + log.warn("=>服务:{},在端口:{},已经停止!", config.name, config.port); + } + + +} diff --git a/springboot/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/config/NettyConfig.java b/springboot/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/config/NettyConfig.java new file mode 100644 index 00000000..142e940c --- /dev/null +++ b/springboot/fastbee-server/iot-server-core/src/main/java/com/fastbee/server/config/NettyConfig.java @@ -0,0 +1,254 @@ +package com.fastbee.server.config; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.enums.ServerType; +import com.fastbee.server.Server; +import com.fastbee.base.codec.Delimiter; +import com.fastbee.base.codec.LengthField; +import com.fastbee.base.codec.MessageDecoder; +import com.fastbee.base.codec.MessageEncoder; +import com.fastbee.base.core.HandlerInterceptor; +import com.fastbee.base.core.HandlerMapping; +import com.fastbee.base.session.SessionManager; +import io.netty.util.NettyRuntime; +import io.netty.util.internal.ObjectUtil; + +/** + * 基础配置类 + * @Author guanshubiao + * @Date 2022/9/12 20:22 + */ +public class NettyConfig { + + public final int workerCore; + /*boss线程核数*/ + public final int businessCore; + /*读空闲时间*/ + public final int readerIdleTime; + /*写空闲时间*/ + public final int writerIdleTime; + /*读写空闲时间*/ + public final int allIdleTime; + /*端口*/ + public final Integer port; + /*TCP/UDP数据最大长度限定*/ + public final Integer maxFrameLength; + /*基础编码*/ + public final MessageDecoder decoder; + /*基础解码*/ + public final MessageEncoder encoder; + public final Delimiter[] delimiters; + public final LengthField lengthField; + public final HandlerMapping handlerMapping; + public final HandlerInterceptor handlerInterceptor; + public final SessionManager sessionManager; + /*基础服务端*/ + public Server server; + public String name; + /*服务名*/ + public final ServerType type; + + + + + public NettyConfig(int workerGroup, + int businessGroup, + int readerIdleTime, + int writerIdleTime, + int allIdleTime, + Integer port, + Integer maxFrameLength, + LengthField lengthField, + Delimiter[] delimiters, + MessageDecoder decoder, + MessageEncoder encoder, + HandlerMapping handlerMapping, + HandlerInterceptor handlerInterceptor, + SessionManager sessionManager, + ServerType type, + String name, + Server server) { + + /*校验值是否正确*/ + ObjectUtil.checkNotNull(port, FastBeeConstant.SERVER.PORT); + ObjectUtil.checkPositive(port, FastBeeConstant.SERVER.PORT); + + if (ServerType.UDP == type || ServerType.TCP == type){ + ObjectUtil.checkNotNull(decoder, "decoder"); + ObjectUtil.checkNotNull(encoder, "encoder"); + ObjectUtil.checkNotNull(handlerMapping, "handlerMapping"); + ObjectUtil.checkNotNull(handlerInterceptor, "handlerInterceptor"); + } + if (type == ServerType.TCP){ + ObjectUtil.checkNotNull(maxFrameLength, FastBeeConstant.SERVER.MAXFRAMELENGTH); + ObjectUtil.checkPositive(maxFrameLength, FastBeeConstant.SERVER.MAXFRAMELENGTH); + // ObjectUtil.checkNotNull(delimiters,FastBeeConstant.SERVER.DELIMITERS); + + } + /*获取核数*/ + int processors = NettyRuntime.availableProcessors(); + this.workerCore = workerGroup > 0 ? workerGroup : processors + 2; + this.businessCore = businessGroup > 0 ? businessGroup : Math.max(1, processors >> 1); + this.readerIdleTime = readerIdleTime; + this.writerIdleTime = writerIdleTime; + this.allIdleTime = allIdleTime; + this.port = port; + this.maxFrameLength = maxFrameLength; + this.lengthField = lengthField; + this.delimiters = delimiters; + this.decoder = decoder; + this.encoder = encoder; + this.handlerMapping = handlerMapping; + this.handlerInterceptor = handlerInterceptor; + this.sessionManager = sessionManager != null ? sessionManager : new SessionManager(); + this.type = type; + + switch (type){ + case MQTT: + case WEBSOCKET: + this.name = name != null ? name : ServerType.MQTT.name(); + this.server = server; + this.server.config = this; + break; + default: + } + } + + + public Server build() { + return server; + } + + public static NettyConfig.Builder custom() { + return new Builder(); + } + + public static class Builder { + + private int workerCore; + private int businessCore ; + private int readerIdleTime = 240; + private int writerIdleTime = 0; + private int allIdleTime = 0; + private Integer port; + private Integer maxFrameLength; + private LengthField lengthField; + private Delimiter[] delimiters; + private MessageDecoder decoder; + private MessageEncoder encoder; + private HandlerMapping handlerMapping; + private HandlerInterceptor handlerInterceptor; + private SessionManager sessionManager; + private ServerType type; + private String name; + private Server server; + + public Builder() { + } + + public Builder setThreadGroup(int workerCore, int businessCore) { + this.workerCore = workerCore; + this.businessCore = businessCore; + return this; + } + + public Builder setIdleStateTime(int readerIdleTime, int writerIdleTime, int allIdleTime) { + this.readerIdleTime = readerIdleTime; + this.writerIdleTime = writerIdleTime; + this.allIdleTime = allIdleTime; + return this; + } + + public Builder setPort(Integer port) { + this.port = port; + return this; + } + + public Builder setServer(Server server){ + this.server = server; + return this; + } + + public Builder setMaxFrameLength(Integer maxFrameLength) { + this.maxFrameLength = maxFrameLength; + return this; + } + + public Builder setLengthField(LengthField lengthField) { + this.lengthField = lengthField; + return this; + } + + public Builder setDelimiters(byte[][] delimiters) { + Delimiter[] t = new Delimiter[delimiters.length]; + for (int i = 0; i < delimiters.length; i++) { + t[i] = new Delimiter(delimiters[i]); + } + this.delimiters = t; + return this; + } + + public Builder setDelimiters(Delimiter... delimiters) { + this.delimiters = delimiters; + return this; + } + + public Builder setDecoder(MessageDecoder decoder) { + this.decoder = decoder; + return this; + } + + public Builder setEncoder(MessageEncoder encoder) { + this.encoder = encoder; + return this; + } + + public Builder setHandlerMapping(HandlerMapping handlerMapping) { + this.handlerMapping = handlerMapping; + return this; + } + + public Builder setHandlerInterceptor(HandlerInterceptor handlerInterceptor) { + this.handlerInterceptor = handlerInterceptor; + return this; + } + + public Builder setSessionManager(SessionManager sessionManager) { + this.sessionManager = sessionManager; + return this; + } + + public Builder setType(ServerType type){ + this.type = type; + return this; + } + + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Server build() { + return new NettyConfig( + this.workerCore, + this.businessCore, + this.readerIdleTime, + this.writerIdleTime, + this.allIdleTime, + this.port, + this.maxFrameLength, + this.lengthField, + this.delimiters, + this.decoder, + this.encoder, + this.handlerMapping, + this.handlerInterceptor, + this.sessionManager, + this.type, + this.name, + this.server + ).build(); + } + } +} diff --git a/springboot/fastbee-server/mqtt-broker/pom.xml b/springboot/fastbee-server/mqtt-broker/pom.xml new file mode 100644 index 00000000..0e7adad7 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + fastbee-server + com.fastbee + 3.8.5 + + + mqtt-broker + 基于netty搭建的mqttBroker + + + + com.fastbee + iot-server-core + + + + com.fastbee + fastbee-mq + + + + + + diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/annotation/Process.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/annotation/Process.java new file mode 100644 index 00000000..89210b09 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/annotation/Process.java @@ -0,0 +1,21 @@ +package com.fastbee.mqtt.annotation; + +import io.netty.handler.codec.mqtt.MqttMessageType; +import org.springframework.stereotype.Component; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +/*自动注入bean*/ +@Component +public @interface Process { + + + /*消息类型*/ + MqttMessageType type() default MqttMessageType.PUBLISH; +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/auth/AuthService.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/auth/AuthService.java new file mode 100644 index 00000000..322ff380 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/auth/AuthService.java @@ -0,0 +1,101 @@ +package com.fastbee.mqtt.auth; + +import com.fastbee.common.constant.Constants; +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.iot.model.MqttAuthenticationModel; +import com.fastbee.iot.service.IToolService; +import com.fastbee.mq.mqttClient.MqttClientConfig; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 客户端认证 + * + * @author gsb + * @date 2022/9/15 13:40 + */ +@Slf4j +@Component +public class AuthService { + + @Resource + private IToolService toolService; + @Resource + private RedisCache redisCache; + @Resource + private MqttClientConfig mqttConfig; + // 令牌秘钥 + @Value("${token.secret}") + private String secret; + @Value("${server.broker.must-pass}") + private boolean mustPass; + + /** + * MQTT客户端认证 + * + * @param clientId 客户端id + * @param username 用户名 + * @param password 密码 + * @return 结果 + */ + public boolean auth(String clientId, String username, String password) { + //不需要账号密码校验,直接返回true + if (!mustPass) return true; + if (StringUtils.isEmpty(clientId) || StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { + log.error("=>客户端参数缺少,clientId:{},username:{},password:{}", clientId, username, password); + return false; + } + try { + if (clientId.startsWith("server")) { + // 服务端认证:配置的账号密码认证 + if (mqttConfig.getUsername().equals(username) && mqttConfig.getPassword().equals(password)) { + log.info("-----------服务端mqtt认证成功,clientId:" + clientId + "---------------"); + return true; + } else { + ResponseEntity response = toolService.returnUnauthorized(new MqttAuthenticationModel(clientId, username, password), "mqtt账号和密码与认证服务器配置不匹配"); + log.warn("=>服务端认证失败[{}]", response.getBody()); + throw new ServiceException("服务端认证失败:"+response.getBody()); + } + } else if (clientId.startsWith("web") || clientId.startsWith("phone")) { + // web端和移动端认证:token认证 + String token = password; + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) { + token = token.replace(Constants.TOKEN_PREFIX, ""); + } + try { + Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + log.info("-----------移动端/Web端mqtt认证成功,clientId:" + clientId + "---------------"); + return true; + } catch (Exception ex) { + ResponseEntity response = toolService.returnUnauthorized(new MqttAuthenticationModel(clientId, username, password), ex.getMessage()); + log.warn("=>移动端/Web端mqtt认证失败[{}]",response.getBody()); + throw new ServiceException("移动端/Web端mqtt认证失败:"+response.getBody()); + } + } else { + // 设备端认证 + ResponseEntity response = toolService.clientAuth(clientId, username, password); + if (response.getStatusCodeValue() == HttpStatus.OK.value()) { + return true; + } else { + log.warn("=>设备端认证失败"); + throw new ServiceException("设备端认证失败:"+response.getBody()); + } + } + } catch (Exception e) { + log.error("=>客户端认证失败", e); + return false; + } + } + + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/codec/WebSocketMqttCodec.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/codec/WebSocketMqttCodec.java new file mode 100644 index 00000000..c90ec70c --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/codec/WebSocketMqttCodec.java @@ -0,0 +1,30 @@ +package com.fastbee.mqtt.codec; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageCodec; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * ws协议与MQTT协议编码转换 + * @author gsb + * @date 2022/9/15 14:35 + */ +@ChannelHandler.Sharable +@Component +public class WebSocketMqttCodec extends MessageToMessageCodec { + + @Override + protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { + list.add(new BinaryWebSocketFrame(byteBuf.retain())); + } + + @Override + protected void decode(ChannelHandlerContext channelHandlerContext, BinaryWebSocketFrame binaryWebSocketFrame, List list) throws Exception { + list.add(binaryWebSocketFrame.retain().content()); + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttConnect.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttConnect.java new file mode 100644 index 00000000..4d61bdab --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttConnect.java @@ -0,0 +1,137 @@ +package com.fastbee.mqtt.handler; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.enums.ServerType; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.mqtt.annotation.Process; +import com.fastbee.mqtt.auth.AuthService; +import com.fastbee.mqtt.handler.adapter.MqttHandler; +import com.fastbee.mqtt.manager.ResponseManager; +import com.fastbee.mqtt.manager.SessionManger; +import com.fastbee.mqtt.manager.WillMessageManager; +import com.fastbee.mqtt.model.WillMessage; +import com.fastbee.base.session.Session; +import com.fastbee.base.util.AttributeUtils; +import com.fastbee.mqtt.utils.MqttMessageUtils; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.*; +import io.netty.util.CharsetUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.Resource; +import java.net.InetSocketAddress; + + +@Slf4j +@Process(type = MqttMessageType.CONNECT) +public class MqttConnect implements MqttHandler { + + @Autowired + private AuthService authService; + @Resource + private RedisCache redisCache; + + @Override + public void handler(ChannelHandlerContext ctx, MqttMessage message) { + MqttConnectMessage connectMessage = (MqttConnectMessage) message; + /*获取客户端Id*/ + String clientId = connectMessage.payload().clientIdentifier(); + if (clientId.contains("&")){ + clientId = clientId.split("&")[1]; + } + log.debug("=>客户端:{} 连接:{}", clientId, message); + /*获取session*/ + Session session = new Session(); + /*mqtt版本*/ + MqttVersion version = MqttVersion.fromProtocolNameAndLevel(connectMessage.variableHeader().name(), (byte) connectMessage.variableHeader().version()); + boolean cleanSession = connectMessage.variableHeader().isCleanSession(); + session.setHandlerContext(ctx); + session.setVersion(version); + session.setClientId(clientId); + session.setCleanSession(cleanSession); + session.setUsername(connectMessage.payload().userName()); + session.setConnected_at(DateUtils.getNowDate()); + InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress(); + session.setIp(socketAddress.getAddress().getHostAddress()); + session.setServerType(ServerType.MQTT); + /*设置客户端ping时间*/ + MqttConnectVariableHeader header = connectMessage.variableHeader(); + if (header.keepAliveTimeSeconds() > 0 && session.getKeepAliveMax() >= header.keepAliveTimeSeconds()) { + session.setKeepAlive(header.keepAliveTimeSeconds()); + } + /*mqtt客户端登录校验*/ + if (!check(session, connectMessage)) { + log.error("=>客户端:{},连接异常", clientId); + session.getHandlerContext().close(); + return; + } + + /*保存ClientId 和 session 到Attribute*/ + AttributeUtils.setClientId(ctx.channel(), clientId); + AttributeUtils.setSession(ctx.channel(), session); + SessionManger.removeClient(clientId); + session.setConnected(true); + + SessionManger.buildSession(clientId, session); + handleWill(connectMessage); + ResponseManager.responseMessage(session, + MqttMessageFactory.newMessage( + new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0x02), + new MqttConnAckVariableHeader(MqttConnectReturnCode.CONNECTION_ACCEPTED, false), + null), + true + ); + } + + /** + * 遗嘱消息处理 + * + * @param message 连接消息 + */ + private void handleWill(MqttConnectMessage message) { + /*如果没有设置处理遗嘱消息,返回*/ + if (!message.variableHeader().isWillFlag()) { + return; + } + /*生成客户端model*/ + MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage( + new MqttFixedHeader(MqttMessageType.PUBLISH, false, + MqttQoS.valueOf(message.variableHeader().willQos()), + message.variableHeader().isWillRetain(), 0), + new MqttPublishVariableHeader(message.payload().willTopic(), 0), + Unpooled.buffer().writeBytes(message.payload().willMessageInBytes())); + + WillMessage msg = new WillMessage(message.payload().clientIdentifier(), + message.variableHeader().isCleanSession(), message.payload().willTopic(), publishMessage); + WillMessageManager.push(msg); + } + + /** + * 客户端连接校验 + * + * @param session 客户端 + * @param message 连接消息 + * @return 结果 + */ + private boolean check(Session session, MqttConnectMessage message) { + /*获取客户端连接地址*/ + InetSocketAddress address = (InetSocketAddress) session.getHandlerContext().channel().remoteAddress(); + String host = address.getAddress().getHostAddress(); + /*webSocket客户端 系统内部客户端不校验*/ + String clientId = message.payload().clientIdentifier(); + /*根据用户名,密码校验*/ + String username = message.payload().userName(); + String password = message.payload().passwordInBytes() == null ? null : new String(message.payload().passwordInBytes(), CharsetUtil.UTF_8); + /*验证失败,应答客户端*/ + if (!authService.auth(clientId, username, password)) { + MqttConnAckMessage connAckMessage = MqttMessageUtils.buildConntAckMessage(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, false); + ResponseManager.responseMessage(session, connAckMessage, true); + return false; + } + return true; + } + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttDisConnect.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttDisConnect.java new file mode 100644 index 00000000..12ef4722 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttDisConnect.java @@ -0,0 +1,41 @@ +package com.fastbee.mqtt.handler; + +import com.fastbee.mqtt.annotation.Process; +import com.fastbee.mqtt.handler.adapter.MqttHandler; +import com.fastbee.mqtt.manager.ClientManager; +import com.fastbee.mqtt.manager.SessionManger; + +import com.fastbee.base.session.Session; +import com.fastbee.base.util.AttributeUtils; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageType; +import lombok.extern.slf4j.Slf4j; + + +@Process(type = MqttMessageType.DISCONNECT) +@Slf4j +public class MqttDisConnect implements MqttHandler { + + + @Override + public void handler(ChannelHandlerContext ctx, MqttMessage message) { + /*获取clientId*/ + String clientId = AttributeUtils.getClientId(ctx.channel()); + /*获取session*/ + Session session = AttributeUtils.getSession(ctx.channel()); + log.debug("=>客户端正常断开,clientId:[{}]", clientId); + try { + if (!session.getConnected()) { + session.getHandlerContext().close(); + return; + } + /*处理断开客户端连接*/ + SessionManger.pingTimeout(session.getClientId()); + /*移除相关topic*/ + ClientManager.remove(session.getClientId()); + } catch (Exception e) { + log.error("=>客户端断开连接异常:{}", session); + } + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPingreq.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPingreq.java new file mode 100644 index 00000000..ccc89a3c --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPingreq.java @@ -0,0 +1,37 @@ +package com.fastbee.mqtt.handler; + +import com.fastbee.mqtt.annotation.Process; +import com.fastbee.mqtt.handler.adapter.MqttHandler; +import com.fastbee.mqtt.manager.ClientManager; +import com.fastbee.mqtt.manager.ResponseManager; +import com.fastbee.base.util.AttributeUtils; +import com.fastbee.mqtt.utils.MqttMessageUtils; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.*; +import lombok.extern.slf4j.Slf4j; + +/** + * 客户端Ping消息应答 + * + * @author bill + */ +@Slf4j +@Process(type = MqttMessageType.PINGREQ) +public class MqttPingreq implements MqttHandler { + + @Override + public void handler(ChannelHandlerContext ctx, MqttMessage message) { + /*获取客户端id*/ + String clientId = AttributeUtils.getClientId(ctx.channel()); + try { + // log.debug("=>客户端:{},心跳信息", clientId); + /*更新客户端ping时间*/ + ClientManager.updatePing(clientId); + /*响应设备的ping消息*/ + MqttMessage pingResp = MqttMessageUtils.buildPingResp(); + ResponseManager.sendMessage(pingResp, clientId, true); + } catch (Exception e) { + log.error("=>客户端:{},ping异常:{}", clientId, e); + } + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPubAck.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPubAck.java new file mode 100644 index 00000000..73db00e5 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPubAck.java @@ -0,0 +1,39 @@ +package com.fastbee.mqtt.handler; + +import com.fastbee.mqtt.annotation.Process; +import com.fastbee.mqtt.handler.adapter.MqttHandler; +import com.fastbee.mqtt.manager.ClientManager; +import com.fastbee.mqtt.service.IMessageStore; +import com.fastbee.base.session.Session; +import com.fastbee.base.util.AttributeUtils; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageType; +import io.netty.handler.codec.mqtt.MqttPubAckMessage; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * 消息等级=Qos1,收到发布消息确认 + * + * @author bill + */ +@Process(type = MqttMessageType.PUBACK) +@Slf4j +public class MqttPubAck implements MqttHandler { + + @Autowired + private IMessageStore messageStore; + + @Override + public void handler(ChannelHandlerContext ctx, MqttMessage message) { + MqttPubAckMessage ackMessage = (MqttPubAckMessage) message; + // PacketId + int packetId = ackMessage.variableHeader().messageId(); + Session session = AttributeUtils.getSession(ctx.channel()); + // Qos1 的存储消息释放 + messageStore.removePubMsg(packetId); + /*更新平台ping*/ + ClientManager.updatePing(session.getClientId()); + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPubRec.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPubRec.java new file mode 100644 index 00000000..46a29707 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPubRec.java @@ -0,0 +1,47 @@ +package com.fastbee.mqtt.handler; + +import com.fastbee.mqtt.annotation.Process; +import com.fastbee.mqtt.handler.adapter.MqttHandler; +import com.fastbee.mqtt.manager.ClientManager; +import com.fastbee.mqtt.manager.ResponseManager; +import com.fastbee.mqtt.service.IMessageStore; +import com.fastbee.base.session.Session; +import com.fastbee.base.util.AttributeUtils; +import com.fastbee.mqtt.utils.MqttMessageUtils; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader; +import io.netty.handler.codec.mqtt.MqttMessageType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * 消息等级=Qos2 发布消息收到 交付第一步 + * + * @author bill + */ +@Process(type = MqttMessageType.PUBREC) +@Slf4j +public class MqttPubRec implements MqttHandler { + + @Autowired + private IMessageStore messageStore; + + @Override + public void handler(ChannelHandlerContext ctx, MqttMessage message) { + MqttMessageIdVariableHeader variableHeader = MqttMessageUtils.getIdVariableHeader(message); + //clientId + String clientId = AttributeUtils.getClientId(ctx.channel()); + Session session = AttributeUtils.getSession(ctx.channel()); + //获取packetId + int messageId = variableHeader.messageId(); + /*释放消息*/ + messageStore.removePubMsg(messageId); + messageStore.saveRelOutMsg(messageId); + // 回复REL 进入第二阶段 + MqttMessage mqttMessage = MqttMessageUtils.buildPubRelMessage(message); + ResponseManager.responseMessage(session, mqttMessage, true); + /*更新平台ping*/ + ClientManager.updatePing(session.getClientId()); + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPubRel.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPubRel.java new file mode 100644 index 00000000..16c493cf --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPubRel.java @@ -0,0 +1,41 @@ +package com.fastbee.mqtt.handler; + +import com.fastbee.mqtt.annotation.Process; +import com.fastbee.mqtt.handler.adapter.MqttHandler; +import com.fastbee.mqtt.manager.ClientManager; +import com.fastbee.mqtt.manager.ResponseManager; +import com.fastbee.mqtt.service.IMessageStore; +import com.fastbee.base.session.Session; +import com.fastbee.base.util.AttributeUtils; +import com.fastbee.mqtt.utils.MqttMessageUtils; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader; +import io.netty.handler.codec.mqtt.MqttMessageType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * 消息等级=Qos2 发布消息释放 PUBREL + * @author bill + */ +@Slf4j +@Process(type = MqttMessageType.PUBREL) +public class MqttPubRel implements MqttHandler { + + @Autowired + private IMessageStore messageStore; + + @Override + public void handler(ChannelHandlerContext ctx, MqttMessage message){ + MqttMessageIdVariableHeader variableHeader = MqttMessageUtils.getIdVariableHeader(message); + Session session = AttributeUtils.getSession(ctx.channel()); + //获取packetId + int messageId = variableHeader.messageId(); + messageStore.removeRelInMsg(messageId); + MqttMessage mqttMessage = MqttMessageUtils.buildPubCompMessage(message); + ResponseManager.responseMessage(session,mqttMessage,true); + /*更新本地ping时间*/ + ClientManager.updatePing(session.getClientId()); + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPubcomp.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPubcomp.java new file mode 100644 index 00000000..543c1149 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPubcomp.java @@ -0,0 +1,38 @@ +package com.fastbee.mqtt.handler; + +import com.fastbee.mqtt.annotation.Process; +import com.fastbee.mqtt.handler.adapter.MqttHandler; +import com.fastbee.mqtt.manager.ClientManager; +import com.fastbee.mqtt.service.IMessageStore; +import com.fastbee.base.session.Session; +import com.fastbee.base.util.AttributeUtils; +import com.fastbee.mqtt.utils.MqttMessageUtils; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader; +import io.netty.handler.codec.mqtt.MqttMessageType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * 消息等级=Qos2 发布消息完成 + * @author bill + */ +@Process(type = MqttMessageType.PUBCOMP) +@Slf4j +public class MqttPubcomp implements MqttHandler { + + @Autowired + private IMessageStore messageStore; + + @Override + public void handler(ChannelHandlerContext ctx, MqttMessage message){ + MqttMessageIdVariableHeader variableHeader = MqttMessageUtils.getIdVariableHeader(message); + Session session = AttributeUtils.getSession(ctx.channel()); + //获取packetId + int messageId = variableHeader.messageId(); + messageStore.removeRelOutMsg(messageId); + /*更新平台ping*/ + ClientManager.updatePing(session.getClientId()); + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPublish.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPublish.java new file mode 100644 index 00000000..ca89598b --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttPublish.java @@ -0,0 +1,204 @@ +package com.fastbee.mqtt.handler; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.mq.DeviceReportBo; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.enums.ServerType; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import com.fastbee.mq.redischannel.producer.MessageProducer; +import com.fastbee.mq.service.IDeviceReportMessageService; +import com.fastbee.mqtt.annotation.Process; +import com.fastbee.mqtt.handler.adapter.MqttHandler; +import com.fastbee.mqtt.manager.ClientManager; +import com.fastbee.mqtt.manager.ResponseManager; +import com.fastbee.mqtt.manager.RetainMsgManager; +import com.fastbee.mqtt.model.ClientMessage; +import com.fastbee.mqtt.service.IMessageStore; +import com.fastbee.base.session.Session; +import com.fastbee.base.util.AttributeUtils; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.*; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.Resource; + +/** + * 客户端消息推送处理类 + * + * @author bill + */ +@Slf4j +@Process(type = MqttMessageType.PUBLISH) +public class MqttPublish implements MqttHandler { + + @Autowired + private IMessageStore messageStore; + @Resource + private TopicsUtils topicsUtils; + @Resource + private RedisCache redisCache; + @Resource + private IDeviceReportMessageService deviceReportMessageService; + + @Override + public void handler(ChannelHandlerContext ctx, MqttMessage message) { + MqttPublishMessage publishMessage = (MqttPublishMessage) message; + /*获取客户端id*/ + String clientId = AttributeUtils.getClientId(ctx.channel()); + String topicName = publishMessage.variableHeader().topicName(); + log.debug("=>***客户端[{}],主题[{}],推送消息[{}]", clientId, topicName, + ByteBufUtil.hexDump(publishMessage.content())); + // 以get结尾是模拟客户端数据,只转发消息 + if (topicName.endsWith(FastBeeConstant.MQTT.PROPERTY_GET_SIMULATE)) { + sendTestToMQ(publishMessage); + } else { + /*获取客户端session*/ + Session session = AttributeUtils.getSession(ctx.channel()); + /*推送保留信息*/ + pubRetain(publishMessage); + /*响应客户端消息到达Broker*/ + callBack(session, publishMessage, clientId); + /*推送到订阅的客户端*/ + sendMessageToClients(publishMessage); + /*推送到MQ处理*/ + sendToMQ(publishMessage); + /*累计接收消息数*/ + redisCache.incr2(FastBeeConstant.REDIS.MESSAGE_RECEIVE_TOTAL, -1L); + redisCache.incr2(FastBeeConstant.REDIS.MESSAGE_RECEIVE_TODAY, 60 * 60 * 24); + } + } + + /** + * 消息推送 + * + * @param message 推送消息 + */ + @SneakyThrows + public void sendToMQ(MqttPublishMessage message) { + /*获取topic*/ + String topicName = message.variableHeader().topicName(); + /*只处理上报数据*/ + if (!topicName.endsWith(FastBeeConstant.MQTT.UP_TOPIC_SUFFIX)) { + return; + } + DeviceReportBo reportBo = DeviceReportBo.builder() + .serialNumber(topicsUtils.parseSerialNumber(topicName)) + .topicName(topicName) + .packetId((long) message.variableHeader().packetId()) + .platformDate(DateUtils.getNowDate()) + .data(ByteBufUtil.getBytes(message.content())) + .serverType(ServerType.MQTT) + .build(); + if (topicName.endsWith(FastBeeConstant.TOPIC.MSG_REPLY) || + topicName.endsWith(FastBeeConstant.TOPIC.SUB_UPGRADE_REPLY) || + topicName.endsWith(FastBeeConstant.TOPIC.UPGRADE_REPLY)) { + /*设备应答服务器回调数据*/ + reportBo.setReportType(2); + } else { + /*设备上报数据*/ + reportBo.setReportType(1); + } + if (topicName.contains("property")) { + deviceReportMessageService.parseReportMsg(reportBo); + } + + } + + /** + * 发送模拟数据进行处理 + * @param message + */ + public void sendTestToMQ(MqttPublishMessage message) { + /*获取topic*/ + String topicName = message.variableHeader().topicName(); + DeviceReportBo reportBo = DeviceReportBo.builder() + .serialNumber(topicsUtils.parseSerialNumber(topicName)) + .topicName(topicName) + .packetId((long) message.variableHeader().packetId()) + .platformDate(DateUtils.getNowDate()) + .data(ByteBufUtil.getBytes(message.content())) + .build(); + MessageProducer.sendOtherMsg(reportBo); + } + + + /** + * 推送消息到订阅客户端 + * + * @param message 消息 + */ + public void sendMessageToClients(MqttPublishMessage message) { + ClientManager.pubTopic(message); + } + + + /** + * 应答客户端,消息到达Broker + * + * @param session 客户端 + * @param message 消息 + */ + private void callBack(Session session, MqttPublishMessage message, String clientId) { + /*获取消息等级*/ + MqttQoS mqttQoS = message.fixedHeader().qosLevel(); + int packetId = message.variableHeader().packetId(); + MqttFixedHeader header; + switch (mqttQoS.value()) { + /*0,1消息等级,直接回复*/ + case 0: + case 1: + header = new MqttFixedHeader(MqttMessageType.PUBACK, false, mqttQoS, false, 0); + break; + case 2: + // 处理Qos2的消息确认 + if (!messageStore.outRelContains(packetId)) { + messageStore.saveRelInMsg(packetId); + } + header = new MqttFixedHeader(MqttMessageType.PUBREC, false, MqttQoS.AT_MOST_ONCE, false, 0); + break; + default: + header = null; + } + /*处理消息等级*/ + handleMqttQos(packetId, mqttQoS, true, clientId); + /*响应客户端*/ + MqttMessageIdVariableHeader variableHeader = null; + if (packetId > 0) { + variableHeader = MqttMessageIdVariableHeader.from(packetId); + } + MqttPubAckMessage ackMessage = new MqttPubAckMessage(header, variableHeader); + if (mqttQoS.value() >= 1) { + ResponseManager.responseMessage(session, ackMessage, true); + } + /*更新客户端ping时间*/ + ClientManager.updatePing(session.getClientId()); + + } + + /** + * Qos不同消息处理 + */ + private void handleMqttQos(int packetId, MqttQoS qoS, boolean clearSession, String clientId) { + if (qoS == MqttQoS.AT_LEAST_ONCE || qoS == MqttQoS.EXACTLY_ONCE) { + ClientMessage clientMessage = ClientMessage.of(clientId, qoS, null, false); + messageStore.savePubMsg(packetId, clientMessage); + } + } + + + /** + * 推送保留信息 + */ + @SneakyThrows + private void pubRetain(MqttPublishMessage message) { + redisCache.incr2(FastBeeConstant.REDIS.MESSAGE_RETAIN_TOTAL, -1L); + /*根据message.fixedHeader().isRetain() 判断是否有保留信息*/ + RetainMsgManager.pushMessage(message); + } + + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttSubscribe.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttSubscribe.java new file mode 100644 index 00000000..eaac09e8 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttSubscribe.java @@ -0,0 +1,107 @@ +package com.fastbee.mqtt.handler; + +import com.alibaba.fastjson2.JSON; +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.mqtt.annotation.Process; +import com.fastbee.mqtt.model.ClientMessage; +import com.fastbee.base.session.Session; +import com.fastbee.base.util.AttributeUtils; +import com.fastbee.mqtt.utils.MqttMessageUtils; +import com.fastbee.mqtt.model.RetainMessage; +import com.fastbee.mqtt.handler.adapter.MqttHandler; +import com.fastbee.mqtt.manager.ClientManager; +import com.fastbee.mqtt.manager.ResponseManager; +import com.fastbee.mqtt.manager.RetainMsgManager; +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.*; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + + +@Slf4j +@Process(type = MqttMessageType.SUBSCRIBE) +public class MqttSubscribe implements MqttHandler { + + @Resource + private RedisCache redisCache; + + @Override + public void handler(ChannelHandlerContext ctx, MqttMessage message) { + subscribe(ctx, (MqttSubscribeMessage) message); + } + + + public void subscribe(ChannelHandlerContext ctx, MqttSubscribeMessage message) { + /*获取session*/ + Session session = AttributeUtils.getSession(ctx.channel()); + /*获取客户端订阅的topic列表*/ + List topList = message.payload().topicSubscriptions(); + /*获取topicName列表*/ + List topicNameList = topList.stream().map(MqttTopicSubscription::topicName).collect(Collectors.toList()); + log.debug("=>客户端:{},订阅主题:{}", session.getClientId(), JSON.toJSONString(topicNameList)); + if (!TopicsUtils.validTopicFilter(topicNameList)) { + log.error("=>订阅主题不合法:{}", JSON.toJSONString(topicNameList)); + return; + } + /*存储到本地topic缓存*/ + topicNameList.forEach(topicName -> { + ClientManager.push(topicName, session); + /*累计订阅数*/ + redisCache.incr2(FastBeeConstant.REDIS.MESSAGE_SUBSCRIBE_TOTAL,-1L); + }); + /*更新客户端ping*/ + ClientManager.updatePing(session.getClientId()); + /*应答客户端订阅成功*/ + MqttSubAckMessage subAckMessage = MqttMessageUtils.buildSubAckMessage(message); + ResponseManager.responseMessage(session, subAckMessage, true); + /*客户端订阅了遗留消息主题后,推送遗留消息给客户端*/ + topList.forEach(topic -> { + retain(topic.topicName(), session, topic.qualityOfService()); + }); + } + + + /** + * 推送遗留消息 + * + * @param topicName 主题 + * @param session 客户端 + * @param mqttQoS 消息质量 + */ + @SneakyThrows + private void retain(String topicName, Session session, MqttQoS mqttQoS) { + RetainMessage message = RetainMsgManager.getRetain(topicName); + if (null == message) { + return; + } + MqttQoS qos = message.getQos() > mqttQoS.value() ? mqttQoS : MqttQoS.valueOf(message.getQos()); + switch (qos.value()) { + case 0: + buildMessage(qos, topicName, 0, message.getMessage(), session); + break; + case 1: + case 2: + /*使用实时时间戳充当 packId*/ + buildMessage(qos, topicName, (int) System.currentTimeMillis(), message.getMessage(), session); + break; + } + } + + /*组装推送数据*/ + private void buildMessage(MqttQoS qos, String topicName, int packetId, byte[] message, Session session) { + /*生成客户端model*/ + ClientMessage clientMessage = ClientMessage.of(qos, topicName, false, message); + /*组建推送消息*/ + MqttPublishMessage publishMessage = MqttMessageUtils.buildPublishMessage(clientMessage, packetId); + /*推送消息*/ + ResponseManager.publishClients(publishMessage, session); + } + + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttUnsubscribe.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttUnsubscribe.java new file mode 100644 index 00000000..640236bb --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/MqttUnsubscribe.java @@ -0,0 +1,38 @@ +package com.fastbee.mqtt.handler; + +import com.fastbee.mqtt.annotation.Process; +import com.fastbee.mqtt.handler.adapter.MqttHandler; +import com.fastbee.mqtt.manager.ClientManager; +import com.fastbee.mqtt.manager.ResponseManager; +import com.fastbee.base.session.Session; +import com.fastbee.base.util.AttributeUtils; +import com.fastbee.mqtt.utils.MqttMessageUtils; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageType; +import io.netty.handler.codec.mqtt.MqttUnsubAckMessage; +import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + + +@Slf4j +@Process(type = MqttMessageType.UNSUBSCRIBE) +public class MqttUnsubscribe implements MqttHandler { + + @Override + public void handler(ChannelHandlerContext ctx, MqttMessage message) { + MqttUnsubscribeMessage unsubscribeMessage = (MqttUnsubscribeMessage) message; + List topics = unsubscribeMessage.payload().topics(); + log.debug("=>收到取消订阅请求,topics[{}]", topics); + Session session = AttributeUtils.getSession(ctx.channel()); + topics.forEach(topic -> { + ClientManager.unsubscribe(topic, session); + }); + MqttUnsubAckMessage unsubAckMessage = MqttMessageUtils.buildUnsubAckMessage(unsubscribeMessage); + ResponseManager.responseMessage(session, unsubAckMessage, true); + /*更新客户端平台时间*/ + ClientManager.updatePing(session.getClientId()); + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/adapter/MqttHandler.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/adapter/MqttHandler.java new file mode 100644 index 00000000..aad6dbe2 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/adapter/MqttHandler.java @@ -0,0 +1,12 @@ +package com.fastbee.mqtt.handler.adapter; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessage; + +/** + * @author bill + */ +public interface MqttHandler { + + public void handler(ChannelHandlerContext ctx, MqttMessage message); +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/adapter/MqttMessageAdapter.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/adapter/MqttMessageAdapter.java new file mode 100644 index 00000000..220e7cb4 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/adapter/MqttMessageAdapter.java @@ -0,0 +1,89 @@ +package com.fastbee.mqtt.handler.adapter; + +import com.fastbee.common.exception.iot.MqttAuthorizationException; +import com.fastbee.common.exception.iot.MqttClientUserNameOrPassException; +import com.fastbee.mqtt.manager.SessionManger; +import com.fastbee.base.util.AttributeUtils; +import com.fastbee.mqtt.utils.MqttMessageUtils; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.mqtt.*; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Optional; + +/** + * @author gsb + * @date 2022/9/15 10:34 + */ +@Slf4j +@Component +@ChannelHandler.Sharable +public class MqttMessageAdapter extends SimpleChannelInboundHandler { + + + // @Autowired + private MqttMessageDelegate messageDelegate; + + public MqttMessageAdapter(MqttMessageDelegate delegate) { + this.messageDelegate = delegate; + } + + /** + * 客户端上报消息处理 + * + * @param context 上下文 + * @param message 消息 + */ + @Override + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + protected void channelRead0(ChannelHandlerContext context, MqttMessage message) { + try { + /*校验消息*/ + if (message.decoderResult().isFailure()) { + exceptionCaught(context, message.decoderResult().cause()); + return; + } + messageDelegate.process(context, message); + }catch (Exception e){ + log.error("=>数据进栈异常",e); + } + } + + /** + * 客户端心跳处理 + */ + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress(); + String host = socketAddress.getAddress().getHostAddress(); + int port = socketAddress.getPort(); + String clientId = AttributeUtils.getClientId(ctx.channel()); + if (evt instanceof IdleStateEvent) { + IdleStateEvent idleStateEvent = (IdleStateEvent) evt; + IdleState state = idleStateEvent.state(); + if (state == IdleState.ALL_IDLE || state == IdleState.READER_IDLE || state == IdleState.WRITER_IDLE) { + log.error("客户端id[{}] 客户端[{}]port:[{}]心跳超时!",clientId, host, port); + /*关闭通道*/ + ctx.close(); + } + } else { + super.userEventTriggered(ctx, evt); + } + } + + /** + * 处理消息异常情况 + */ + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + log.error("=>mqtt连接异常",cause); + } + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/adapter/MqttMessageDelegate.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/adapter/MqttMessageDelegate.java new file mode 100644 index 00000000..558df7b4 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/handler/adapter/MqttMessageDelegate.java @@ -0,0 +1,58 @@ +package com.fastbee.mqtt.handler.adapter; + +import com.fastbee.common.exception.ServiceException; +import com.fastbee.mqtt.annotation.Process; +import com.fastbee.base.util.AttributeUtils; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * 消息代理类,根据注解{@link com.fastbee.mqtt.annotation.Process} 分发处理类 + * @author gsb + * @date 2022/10/8 11:50 + */ +@Component +@Slf4j +public class MqttMessageDelegate { + /**mqtt报文类型为key,报文处理类为值*/ + private final Map processor = new HashMap<>(); + + public MqttMessageDelegate(List handlers){ + if (CollectionUtils.isEmpty(handlers)){ + throw new ServiceException("报文处理类为空"); + } + /*将处理类缓存到map*/ + handlers.forEach(handler ->{ + Process annotation = handler.getClass().getAnnotation(Process.class); + Optional.ofNullable(annotation) + .map(Process::type) + .ifPresent(messageType ->processor.put(messageType,handler)); + }); + } + + /** + * 匹配报文处理类 + */ + public void process(ChannelHandlerContext ctx, MqttMessage message){ + /*获取固定头的报文类型*/ + MqttMessageType messageType = message.fixedHeader().messageType(); + + /*处理客户端连接时,先判断Attribute是否存储Session*/ + if (MqttMessageType.CONNECT != messageType && + AttributeUtils.getSession(ctx.channel()) == null){ + log.error("=>客户端未连接"); + throw new ServiceException("客户端未连接"); + } + Optional.of(processor.get(messageType)) + .ifPresent(mqttHandler -> mqttHandler.handler(ctx,message)); + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/ClientManager.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/ClientManager.java new file mode 100644 index 00000000..4d77780d --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/ClientManager.java @@ -0,0 +1,174 @@ +package com.fastbee.mqtt.manager; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.base.session.Session; +import io.netty.handler.codec.mqtt.MqttPublishMessage; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.CollectionUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 客户端管理 + * + * @author gsb + * @date 2022/9/15 16:02 + */ +@Slf4j +public class ClientManager { + + /*topic本地缓存*/ + public static Map> topicMap = new ConcurrentHashMap<>(); + /*客户端最后一次ping时间,设备不正常断开判断*/ + private static Map pingMap = new ConcurrentHashMap<>(); + /*客户端与topic关联本地缓存*/ + public static Map> clientTopicMap = new ConcurrentHashMap<>(); + + /** + * 将client的上下文相关信息添加到映射关系表中 + * @param topic 主题 + * @param session + */ + public static void push(String topic, Session session) { + try { + /*处理topic对应的topic*/ + Map clientMap = topicMap.get(topic); + if (StringUtils.isEmpty(clientMap)) { + clientMap = new ConcurrentHashMap<>(); + } + clientMap.put(session.getClientId(), session); + topicMap.put(topic, clientMap); + + /*处理client对应的所有topic*/ + Map topicsMap = null; + if (clientTopicMap.containsKey(session.getClientId())) { + topicsMap = clientTopicMap.get(session.getClientId()); + if (!topicsMap.containsKey(topic)) { + topicsMap.put(topic, true); + } + } else { + topicsMap = new HashMap<>(); + topicsMap.put(topic, true); + clientTopicMap.put(session.getClientId(), topicsMap); + } + } catch (Exception e) { + log.error("=>clientId映射topic出现异常:{},e=", e.getMessage(), e); + } + } + + /** + * 清理对应client下的所有数据 + * + * @param clientId 客户端id + */ + public static void remove(String clientId) { + try { + /*移除client对应的topic*/ + Map topics = clientTopicMap.get(clientId); + if (null != topics) { + /*从topic中移除client*/ + for (String key : topics.keySet()) { + Map clientMap = topicMap.get(key); + if (CollectionUtils.isEmpty(clientMap)) { + continue; + } + clientMap.remove(clientId); + } + clientTopicMap.remove(clientId); + } + pingMap.remove(clientId); + } catch (Exception e) { + log.warn("=>移除client[{}]异常", e.getMessage()); + } + } + + /** + * 客户端取消订阅 + * 删除指定topic下的指定client + * + * @param topic 主题 + * @param session 客户端 + */ + public static void unsubscribe(String topic, Session session) { + try { + Map clientMap = topicMap.get(topic); + if (StringUtils.isEmpty(clientMap)) { + return; + } + Session s = clientMap.get(session.getClientId()); + if (null == s) { + return; + } + clientMap.remove(session.getClientId()); + } catch (Exception e) { + log.error("=>客户端取消订阅异常:{}", e.getMessage()); + } + } + + /** + * 将消息发送到指定topic下的所有client上去 + * + * @param msg 推送消息 + */ + public static void pubTopic(MqttPublishMessage msg) { + String topic = msg.variableHeader().topicName(); + List topicList = TopicsUtils.searchTopic(topic); + for (String itemTopic : topicList) { + Map clientMap = topicMap.get(itemTopic); + if (StringUtils.isEmpty(clientMap)) { + continue; + } + for (Session session : clientMap.values()) { + String clientId = session.getClientId(); + if (!validClient(clientId)) { + ///*ws的客户端不正常断开连接后,直接移除所有信息*/ + //if (session.getClientId().startsWith(FastBeeConstant.SERVER.WS_PREFIX)) { + // log.debug("=>移除ws客户端,clientId={}", session); + // remove(clientId); + //} + log.warn("=>{}不在线", clientId); + continue; + } + ResponseManager.publishClients(msg, session); + } + } + } + + /** + * 更新客户端在线时间,给客户端发送消息时用这个看客户端最近是否在线 + * 用来判断设备不正常掉线没有应答服务器的情况 + * + * @param clientId 客户端id + */ + public static void updatePing(String clientId) { + pingMap.put(clientId, DateUtils.getTimestamp()); + } + + /** + * 平台判定设备状态 Ping客户端是否在线 + * + * @param clientId 客户端id + * @return 结果 + */ + public static Boolean validClient(String clientId) { + long currTime = DateUtils.getTimestamp(); + /*获取客户端连接时,时间*/ + Long timestamp = pingMap.get(clientId); + if (null == timestamp) { + return false; + } + //当设备缓存的心跳时间大于 平台判断时间 1.5f 表示设备不正常断开了服务器 + if (currTime - timestamp > FastBeeConstant.SERVER.DEVICE_PING_EXPIRED) { + //pingMap.remove(clientId); + //SessionManger.removeClient(clientId); + return false; + } + return true; + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/MqttRemoteManager.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/MqttRemoteManager.java new file mode 100644 index 00000000..adc0aa57 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/MqttRemoteManager.java @@ -0,0 +1,77 @@ +package com.fastbee.mqtt.manager; + +import com.alibaba.fastjson2.JSON; +import com.fastbee.common.enums.DeviceStatus; +import com.fastbee.common.enums.TopicType; +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.service.IDeviceService; +import com.fastbee.mq.mqttClient.PubMqttClient; +import com.fastbee.mqtt.model.PushMessageBo; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.mqtt.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; + + +@Component +public class MqttRemoteManager { + + @Resource + private TopicsUtils topicsUtils; + @Resource + private IDeviceService deviceService; + /** + * true: 使用netty搭建的mqttBroker false: 使用emq + */ + @Value("${server.broker.enabled}") + private Boolean enabled; + + @Resource + private PubMqttClient pubMqttClient; + + /** + * 推送设备状态 + * @param serialNumber 设备 + * @param status 状态 + */ + public void pushDeviceStatus(Long productId, String serialNumber, DeviceStatus status){ + //兼容emqx推送TCP客户端上线 + Device device = deviceService.selectDeviceNoModel(serialNumber); + String message = "{\"status\":" + status.getType() + ",\"isShadow\":" + device.getIsShadow() + ",\"rssi\":" + device.getRssi() + "}"; + String topic = topicsUtils.buildTopic(device.getProductId(), serialNumber, TopicType.STATUS_POST); + if (enabled){ + MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage( + new MqttFixedHeader(MqttMessageType.PUBLISH, false, MqttQoS.AT_MOST_ONCE, false, 0), + new MqttPublishVariableHeader(topic, 0), + Unpooled.buffer().writeBytes(message.getBytes(StandardCharsets.UTF_8)) + ); + ClientManager.pubTopic(publishMessage); + }else { + //emqx直接用客户端推送 + pubMqttClient.publish(1,false,topic,message); + } + + } + + /** + * 公共推送消息方法 + * @param bo 消息体 + */ + public void pushCommon(PushMessageBo bo){ + //netty版本发送 + if (enabled){ + MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage( + new MqttFixedHeader(MqttMessageType.PUBLISH, false, MqttQoS.AT_MOST_ONCE, false, 0), + new MqttPublishVariableHeader(bo.getTopic(), 0), + Unpooled.buffer().writeBytes(bo.getMessage().getBytes(StandardCharsets.UTF_8)) + ); + ClientManager.pubTopic(publishMessage); + }else { + pubMqttClient.publish(0,false,bo.getTopic(), bo.getMessage()); + } + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/ResponseManager.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/ResponseManager.java new file mode 100644 index 00000000..0b5ebfe6 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/ResponseManager.java @@ -0,0 +1,76 @@ +package com.fastbee.mqtt.manager; + +import com.fastbee.base.session.Session; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.handler.codec.mqtt.*; +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +public class ResponseManager { + + + /** + * 发送信息:用于服务端收到消息客户端数据后,向客户端发送响应信息 + * + * @param session 上下文 + * @param msg mqtt消息 + * @param flush 是否刷新 + */ + public static void responseMessage(Session session, MqttMessage msg, boolean flush) { + ChannelFuture future = flush ? session.getHandlerContext().writeAndFlush(msg) : session.getHandlerContext().write(msg); + future.addListener(f -> { + if (!f.isSuccess()) { + log.error("=>响应设备[{}],发送消息:{},失败原因:{}", session.getClientId(), msg, f.cause()); + }else { + //log.debug("=>相应设备:[{}],发送消息:[{}]",session.getClientId(),msg); + } + }); + } + + /** + * 发送信息:用于服务端向客户端通过clientID下发消息(单客户端) + * + * @param msg mqtt消息 + * @param clientId 客户端id + * @param flush 是否刷新 + */ + public static void sendMessage(MqttMessage msg, String clientId, boolean flush) { + Session session = SessionManger.getSession(clientId); + if (session == null || null == session.getHandlerContext()) { + return; + } + responseMessage(session, msg, flush); + } + + /** + * 推送消息给订阅客户端(所有订阅客户端) + * + * @param msg 推送消息 + * @param session 客户端 + */ + public static void publishClients(MqttPublishMessage msg, Session session) { + try { + final Channel channel = session.getHandlerContext().channel(); + MqttQoS qos = msg.fixedHeader().qosLevel(); + ByteBuf sendBuf = msg.content().retainedDuplicate(); + sendBuf.resetReaderIndex(); + /*配置推送消息类型*/ + MqttFixedHeader Header = new MqttFixedHeader(MqttMessageType.PUBLISH, + false, qos, msg.fixedHeader().isRetain(), 0); + /*设置topic packetId*/ + MqttPublishVariableHeader publishVariableHeader = new MqttPublishVariableHeader( + msg.variableHeader().topicName(), msg.variableHeader().packetId()); + /*推送消息*/ + MqttPublishMessage publishMessage = new MqttPublishMessage(Header, + publishVariableHeader, sendBuf); + channel.writeAndFlush(publishMessage); + + } catch (Exception e) { + log.error("=>发送消息异常 {}", msg, e); + } + } + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/RetainMsgManager.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/RetainMsgManager.java new file mode 100644 index 00000000..0450883f --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/RetainMsgManager.java @@ -0,0 +1,53 @@ +package com.fastbee.mqtt.manager; + +import com.fastbee.mqtt.model.RetainMessage; +import io.netty.handler.codec.mqtt.MqttPublishMessage; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +@Slf4j +@Component +public class RetainMsgManager { + + /*保存topic的retain消息*/ + private static Map retainMap = new ConcurrentHashMap<>(); + + /** + * 推送保留信息到订阅客户端 + * + * @param message 推送消息 + */ + public static void pushMessage(MqttPublishMessage message) { + if (null == message || !message.fixedHeader().isRetain()) { + return; + } + byte[] bytes = new byte[message.payload().readableBytes()]; + if (bytes.length > 0) { + RetainMessage retainMsg = RetainMessage.builder() + .topic(message.variableHeader().topicName()) + .qos(message.fixedHeader().qosLevel().value()).message(bytes).build(); + retainMap.put(message.variableHeader().topicName(), retainMsg); + } else { + retainMap.remove(message.variableHeader().topicName()); + } + } + + public static Integer getSize() { + return retainMap.size(); + } + + /** + * 获取消息 + * + * @param topic 主题 + * @return 消息 + */ + public static RetainMessage getRetain(String topic) { + return retainMap.get(topic); + } + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/SessionManger.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/SessionManger.java new file mode 100644 index 00000000..21591b64 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/SessionManger.java @@ -0,0 +1,162 @@ +package com.fastbee.mqtt.manager; + +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.common.core.mq.DeviceStatusBo; +import com.fastbee.common.enums.DeviceStatus; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.spring.SpringUtils; +import com.fastbee.iot.service.cache.IDeviceCache; +import com.fastbee.mq.redischannel.producer.MessageProducer; +import com.fastbee.mq.service.IMessagePublishService; +import com.fastbee.base.service.ISessionStore; +import com.fastbee.base.session.Session; +import com.fastbee.base.util.AttributeUtils; +import com.fastbee.mqtt.utils.MqttMessageUtils; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessageType; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.Set; + +/** + * 会话管理类 + * + * @Author guanshubiao + * @Date 2022/9/12 20:22 + */ +@Slf4j +public class SessionManger { + + + private static ISessionStore sessionStore = SpringUtils.getBean(ISessionStore.class); + private static MqttRemoteManager remoteManager = SpringUtils.getBean(MqttRemoteManager.class); + private static IDeviceCache deviceCache = SpringUtils.getBean(IDeviceCache.class); + + /** + * mqtt新客户连接 + * + * @param clientId 客户端id + * @param session 客户端 + */ + public static void buildSession(String clientId, Session session) { + log.debug("=>新客户端连接,clientId={}", clientId); + if (StringUtils.isEmpty(clientId) || handleContext(session)) { + log.error("=>客户端id为空或者session未注册!"); + return; + } + + sessionStore.storeSession(clientId, session); + //contextMap.put(session.getHandlerContext(), session); + /*更新客户端在平台的最新响应时间*/ + ClientManager.updatePing(clientId); + /*发送MQ,设备上线*/ + DeviceStatusBo statusBo = MqttMessageUtils.buildStatusMsg(session.getHandlerContext(), session.getClientId(), DeviceStatus.ONLINE, session.getIp()); + if (!statusBo.getSerialNumber().startsWith(FastBeeConstant.SERVER.WM_PREFIX) && + !statusBo.getSerialNumber().startsWith(FastBeeConstant.SERVER.WS_PREFIX) && + !statusBo.getSerialNumber().startsWith(FastBeeConstant.SERVER.FAST_PHONE)) { + deviceCache.updateDeviceStatusCache(statusBo); + remoteManager.pushDeviceStatus(-1L,statusBo.getSerialNumber(), statusBo.getStatus()); + } + } + + /** + * 根据客户端id移除客户端 + * + * @param clientId 客户端id + */ + public static void removeClient(String clientId) { + log.debug("=>移除客户端,clientId={}", clientId); + try { + if (StringUtils.isEmpty(clientId) || !sessionStore.containsKey(clientId) || clientId.endsWith(FastBeeConstant.SERVER.WS_PREFIX) || + clientId.endsWith(FastBeeConstant.SERVER.FAST_PHONE)) { + return; + } + Session session = sessionStore.getSession(clientId); + if (handleContext(session)) { + log.error("移除客户端失败,客户端未注册!"); + return; + } + //关闭通道 + session.getHandlerContext().close(); + //移除client + sessionStore.cleanSession(clientId); + session.setMqttMessageType(MqttMessageType.DISCONNECT); + //发送至MQ,设备下线 + DeviceStatusBo statusBo = MqttMessageUtils.buildStatusMsg(session.getHandlerContext(), session.getClientId(), DeviceStatus.OFFLINE, session.getIp()); + if (!statusBo.getSerialNumber().startsWith(FastBeeConstant.SERVER.WM_PREFIX) && + !statusBo.getSerialNumber().startsWith(FastBeeConstant.SERVER.WS_PREFIX)) { + deviceCache.updateDeviceStatusCache(statusBo); + remoteManager.pushDeviceStatus(-1L,statusBo.getSerialNumber(), statusBo.getStatus()); + } + } catch (Exception e) { + throw new ServiceException("移除客户端失败,message=" + e.getMessage()); + } + } + + /** + * 根据客户通道移除客户端 + * + * @param ctx 上下文通道 + */ + public static void removeContextByContext(ChannelHandlerContext ctx) { + try { + /*获取*/ + Session session = AttributeUtils.getSession(ctx.channel()); + if (handleContext(session)) { + log.error("=>客户端通道不存在!移除失败"); + return; + } + sessionStore.cleanSession(session.getClientId()); + session.setMqttMessageType(MqttMessageType.DISCONNECT); + //发送至MQ,设备下线 + DeviceStatusBo statusBo = MqttMessageUtils.buildStatusMsg(session.getHandlerContext(), session.getClientId(), DeviceStatus.OFFLINE, session.getIp()); + if (!statusBo.getSerialNumber().startsWith(FastBeeConstant.SERVER.WM_PREFIX) && + !statusBo.getSerialNumber().startsWith(FastBeeConstant.SERVER.WS_PREFIX)) { + deviceCache.updateDeviceStatusCache(statusBo); + remoteManager.pushDeviceStatus(-1L,statusBo.getSerialNumber(), statusBo.getStatus()); + } + } catch (Exception e) { + log.error("=>移除客户端失败={}", e.getMessage()); + } + } + + /** + * ping判定时间超时 + * + * @param clientId 客户id + */ + public static void pingTimeout(String clientId) { + try { + removeClient(clientId); + } catch (Exception e) { + throw new ServiceException("移除超时客户端失败"); + } + } + + /** + * 根据clientId获取客户通道 + * + * @param clientId 客户端id + * @return session + */ + public static Session getSession(String clientId) { + return sessionStore.getSession(clientId); + } + + /** + * 校验Session已经注册通道 + * + * @param session 客户端 + * @return 结果 + */ + private static boolean handleContext(Session session) { + if (null == session || null == session.getHandlerContext()) { + return true; + } + return false; + } + + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/WillMessageManager.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/WillMessageManager.java new file mode 100644 index 00000000..a0c1ab90 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/manager/WillMessageManager.java @@ -0,0 +1,30 @@ +package com.fastbee.mqtt.manager; + +import com.fastbee.mqtt.model.WillMessage; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +@Slf4j +public class WillMessageManager { + + private static Map map = new ConcurrentHashMap<>(); + + public static void push(WillMessage message){ + map.put(message.getClientId(),message); + } + + public static void pop(String clientId){ + try { + WillMessage message = map.get(clientId); + if (null == message){ + return; + } + ClientManager.pubTopic(message.getMessage()); + }catch (Exception e){ + log.error("=>发送客户端[{}],遗嘱消息异常",e.getMessage(),e); + } + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/ClientMessage.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/ClientMessage.java new file mode 100644 index 00000000..299944f6 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/ClientMessage.java @@ -0,0 +1,45 @@ +package com.fastbee.mqtt.model; + +import io.netty.handler.codec.mqtt.MqttQoS; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author gsb + * @date 2022/10/7 19:04 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ClientMessage { + + /*共享主题客户端id,不为空则指定客户端发送*/ + private String sharedClientId; + /*客户端id*/ + private String clientId; + /*消息质量*/ + private MqttQoS qos; + /*topic*/ + private String topicName; + /*是否保留消息*/ + private boolean retain; + /*数据*/ + private byte[] payload; + + private int messageId; + /*是否是遗嘱消息*/ + private boolean willFlag; + /*是否是dup消息*/ + private boolean dup; + + + public static ClientMessage of(MqttQoS qos,String topicName,boolean retain, byte[] payload){ + return new ClientMessage(null,null,qos,topicName,retain,payload,0,false,false); + } + + public static ClientMessage of(String clientId,MqttQoS qos,String topicName,boolean retain){ + return new ClientMessage(null,clientId,qos,topicName,retain,null,0,false,false); + } + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/PushMessageBo.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/PushMessageBo.java new file mode 100644 index 00000000..5c1cf75c --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/PushMessageBo.java @@ -0,0 +1,23 @@ +package com.fastbee.mqtt.model; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author bill + */ +@Data +public class PushMessageBo implements Serializable { + + /*主题*/ + private String topic; + /*数据*/ + private String message; + /*消息质量*/ + private int qos; + + private Integer value; + private Integer address; + private Integer slaveId; +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/RetainMessage.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/RetainMessage.java new file mode 100644 index 00000000..cb5db04d --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/RetainMessage.java @@ -0,0 +1,23 @@ +package com.fastbee.mqtt.model; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; + +/** + * 保留消息bo + * @author gsb + * @date 2022/9/16 14:05 + */ +@Data +@Builder +public class RetainMessage implements Serializable { + + /*主题*/ + private String topic; + /*数据*/ + private byte[] message; + /*消息质量*/ + private int qos; +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/Subscribe.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/Subscribe.java new file mode 100644 index 00000000..f525ef94 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/Subscribe.java @@ -0,0 +1,23 @@ +package com.fastbee.mqtt.model; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * 订阅topic信息 + * @author gsb + * @date 2022/10/14 8:30 + */ +@Data +@AllArgsConstructor +public class Subscribe { + /*topic*/ + private String topicName; + /*消息质量*/ + private int qos; + /*客户端id*/ + private String clientId; + /*清楚回话*/ + private boolean cleanSession; + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/WillMessage.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/WillMessage.java new file mode 100644 index 00000000..f1fff3af --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/model/WillMessage.java @@ -0,0 +1,26 @@ +package com.fastbee.mqtt.model; + +import io.netty.handler.codec.mqtt.MqttPublishMessage; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author gsb + * @date 2022/9/15 15:36 + */ +@Data +@AllArgsConstructor +public class WillMessage implements Serializable { + private static final long serialVersionUID = -1L; + + /*客户端Id*/ + private String clientId; + /*清楚客户端*/ + private boolean cleanSession; + /*topic*/ + private String topic; + /*客户端推送消息*/ + private MqttPublishMessage message; +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/server/MqttServer.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/server/MqttServer.java new file mode 100644 index 00000000..2f494b65 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/server/MqttServer.java @@ -0,0 +1,66 @@ +package com.fastbee.mqtt.server; + +import com.fastbee.server.Server; +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.mqtt.handler.adapter.MqttMessageAdapter; +import com.fastbee.server.config.NettyConfig; +import io.netty.bootstrap.AbstractBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.mqtt.MqttDecoder; +import io.netty.handler.codec.mqtt.MqttEncoder; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.handler.timeout.IdleStateHandler; +import io.netty.util.concurrent.DefaultThreadFactory; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +@Component +@Slf4j +public class MqttServer extends Server { + + @Autowired + private MqttMessageAdapter messageAdapter; + + @Override + protected AbstractBootstrap initialize() { + bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory(config.name, Thread.MAX_PRIORITY)); + workerGroup = new NioEventLoopGroup(config.workerCore, new DefaultThreadFactory(config.name, Thread.MAX_PRIORITY)); + + if (config.businessCore > 0) { + businessService = new ThreadPoolExecutor(config.businessCore, config.businessCore, 1L, + TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new DefaultThreadFactory(config.name, true, Thread.NORM_PRIORITY)); + } + return new ServerBootstrap() + .group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .handler(new LoggingHandler(LogLevel.DEBUG)) + .option(ChannelOption.SO_BACKLOG, 511) + .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE) + .childHandler(new ChannelInitializer() { + + @Override + protected void initChannel(NioSocketChannel channel) { + //客户端心跳检测机制 + channel.pipeline() + .addFirst(FastBeeConstant.SERVER.IDLE + , new IdleStateHandler(config.readerIdleTime, config.writerIdleTime, config.allIdleTime, TimeUnit.SECONDS)) + .addLast(FastBeeConstant.SERVER.DECODER, new MqttDecoder(1024 * 1024 * 2)) + .addLast(FastBeeConstant.SERVER.ENCODER, MqttEncoder.INSTANCE) + .addLast(messageAdapter); + } + }); + + + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/server/WebSocketServer.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/server/WebSocketServer.java new file mode 100644 index 00000000..c7a3b6f0 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/server/WebSocketServer.java @@ -0,0 +1,73 @@ +package com.fastbee.mqtt.server; + +import com.fastbee.server.Server; +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.mqtt.codec.WebSocketMqttCodec; +import com.fastbee.mqtt.handler.adapter.MqttMessageAdapter; +import io.netty.bootstrap.AbstractBootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.HttpContentCompressor; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import io.netty.handler.codec.mqtt.MqttDecoder; +import io.netty.handler.codec.mqtt.MqttEncoder; +import io.netty.handler.timeout.IdleStateHandler; +import io.netty.util.concurrent.DefaultThreadFactory; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * @author gsb + * @date 2022/9/15 14:23 + */ +@Component +@Slf4j +public class WebSocketServer extends Server { + + @Autowired + private WebSocketMqttCodec webSocketMqttCodec; + @Autowired + private MqttMessageAdapter mqttMessageAdapter; + + + @Override + protected AbstractBootstrap initialize() { + bossGroup = new NioEventLoopGroup(); + workerGroup = new NioEventLoopGroup(); + return new ServerBootstrap() + .group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new ChannelInitializer() { + + @Override + public void initChannel(SocketChannel ch) { + ch.pipeline() + .addFirst(FastBeeConstant.WS.HEART_BEAT + , new IdleStateHandler(0, 0, 70)) + /*http请求响应*/ + .addLast(FastBeeConstant.WS.HTTP_SERVER_CODEC, new HttpServerCodec()) + /*聚合header与body组成完整的Http请求,最大数据量为1Mb*/ + .addLast(FastBeeConstant.WS.AGGREGATOR, new HttpObjectAggregator(1024 * 1024)) + /*压缩出站数据*/ + .addLast(FastBeeConstant.WS.COMPRESSOR, new HttpContentCompressor()) + /*WebSocket协议配置mqtt*/ + .addLast(FastBeeConstant.WS.PROTOCOL, new WebSocketServerProtocolHandler("/mqtt", + "mqtt,mqttv3.1,mqttv3.1.1,mqttv5.0", true, 65536)) + .addLast(FastBeeConstant.WS.MQTT_WEBSOCKET, webSocketMqttCodec) + .addLast(FastBeeConstant.WS.DECODER, new MqttDecoder()) + .addLast(FastBeeConstant.WS.ENCODER, MqttEncoder.INSTANCE) + .addLast(FastBeeConstant.WS.BROKER_HANDLER, mqttMessageAdapter); + } + }); + } +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/IMessageStore.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/IMessageStore.java new file mode 100644 index 00000000..610c6989 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/IMessageStore.java @@ -0,0 +1,90 @@ +package com.fastbee.mqtt.service; + +import com.fastbee.mqtt.model.ClientMessage; + +import java.util.List; +import java.util.Map; + +/** + * @author gsb + * @date 2022/10/14 14:35 + */ +public interface IMessageStore { + + /** + * 存储控制包 + * + * @param topic: 控制包所属主题 + * @param clientMessage: 需要存储的消息 + */ + void storeMessage(String topic, ClientMessage clientMessage); + + /** + * 清除topic下的所有消息 + * + * @param topic: 主题 + */ + void cleanTopic(String topic); + + /** + * 根据clientId清除消息 + * + * @param clientId: 客户端唯一标识 + */ + void removeMessage(String clientId); + + /** + * 匹配主题过滤器,寻找对应消息 + * + * @param topicFilter: 主题过滤器 + */ + List searchMessages(String topicFilter); + + /** + * 保存 clientMessage + * + * @param messageId 消息id + */ + public void savePubMsg(Integer messageId, ClientMessage clientMessage); + + /** + * 移除 + * + * @param messageId 消息id + */ + public void removePubMsg(int messageId); + + /** + * 保存 REL IN + * + * @param messageId 消息id + */ + public void saveRelInMsg(int messageId); + + /** + * 保存 REL OUT + * + * @param messageId 消息id + */ + public void saveRelOutMsg(int messageId); + + /** + * 移除 + * + * @param messageId 消息id + */ + public void removeRelInMsg(int messageId); + + /** + * 移除 + * + * @param messageId 消息id + */ + public void removeRelOutMsg(int messageId); + + /** + * 判断Rel out是否包含消息id + */ + public boolean outRelContains(int messageId); + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/ISubscriptionService.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/ISubscriptionService.java new file mode 100644 index 00000000..a2f90438 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/ISubscriptionService.java @@ -0,0 +1,40 @@ +package com.fastbee.mqtt.service; + +import com.fastbee.mqtt.model.Subscribe; + +import java.util.List; + +/** + * 订阅缓存 + * @author gsb + * @date 2022/10/14 8:24 + */ +public interface ISubscriptionService { + + /** + * 保存客户订阅的主题 + * + * @param subscribeList 客户订阅 + */ + void subscribe(List subscribeList, String clientId); + + /** + * 解除订阅 + * + * @param clientId 客户id + * @param topicName 主题 + */ + void unsubscribe(String clientId, String topicName); + + /** + * 获取订阅了 topic 的客户id + * + * @param topic 主题 + * @return 订阅了主题的客户id列表 + */ + List searchSubscribeClientList(String topic); + + + + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/DataHandlerImpl.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/DataHandlerImpl.java new file mode 100644 index 00000000..6c287ec8 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/DataHandlerImpl.java @@ -0,0 +1,135 @@ +package com.fastbee.mqtt.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.fastbee.common.enums.TopicType; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.domain.EventLog; +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; +import com.fastbee.common.core.thingsModel.ThingsModelValuesInput; +import com.fastbee.iot.service.IDeviceService; +import com.fastbee.iot.service.IEventLogService; +import com.fastbee.mq.model.ReportDataBo; +import com.fastbee.mq.mqttClient.PubMqttClient; +import com.fastbee.mq.service.IDataHandler; +import com.fastbee.mq.service.IMqttMessagePublish; +import com.fastbee.mqtt.manager.MqttRemoteManager; +import com.fastbee.mqtt.model.PushMessageBo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; + +/** + * 上报数据处理方法集合 + * @author bill + */ +@Service +@Slf4j +public class DataHandlerImpl implements IDataHandler { + + + @Resource + private IDeviceService deviceService; + @Resource + private IEventLogService eventLogService; + @Resource + private IMqttMessagePublish messagePublish; + + @Resource + private MqttRemoteManager remoteManager; + @Resource + private TopicsUtils topicsUtils; + + /** + * 上报属性或功能处理 + * + * @param bo 上报数据模型 + */ + @Override + public void reportData(ReportDataBo bo) { + try { + List thingsModelSimpleItems = bo.getDataList(); + if (CollectionUtils.isEmpty(bo.getDataList()) || bo.getDataList().size() == 0) { + thingsModelSimpleItems = JSON.parseArray(bo.getMessage(), ThingsModelSimpleItem.class); + } + ThingsModelValuesInput input = new ThingsModelValuesInput(); + input.setProductId(bo.getProductId()); + input.setDeviceNumber(bo.getSerialNumber().toUpperCase()); + input.setThingsModelValueRemarkItem(thingsModelSimpleItems); + input.setSlaveId(bo.getSlaveId()); + List result = deviceService.reportDeviceThingsModelValue(input, bo.getType(), bo.isShadow()); + + + } catch (Exception e) { + log.error("接收属性数据,解析数据时异常 message={},e={}", e.getMessage(),e); + } + } + + + /** + * 上报事件 + * + * @param bo 上报数据模型 + */ + @Override + public void reportEvent(ReportDataBo bo) { + try { + List thingsModelSimpleItems = JSON.parseArray(bo.getMessage(), ThingsModelSimpleItem.class); + Device device = deviceService.selectDeviceBySerialNumber(bo.getSerialNumber()); + List results = new ArrayList<>(); + for (int i = 0; i < thingsModelSimpleItems.size(); i++) { + // 添加到设备日志 + EventLog event = new EventLog(); + event.setDeviceId(device.getDeviceId()); + event.setDeviceName(device.getDeviceName()); + event.setLogValue(thingsModelSimpleItems.get(i).getValue()); + event.setRemark(thingsModelSimpleItems.get(i).getRemark()); + event.setSerialNumber(device.getSerialNumber()); + event.setIdentity(thingsModelSimpleItems.get(i).getId()); + event.setLogType(3); + event.setIsMonitor(0); + event.setUserId(device.getUserId()); + event.setUserName(device.getUserName()); + event.setTenantId(device.getTenantId()); + event.setTenantName(device.getTenantName()); + event.setCreateTime(DateUtils.getNowDate()); + // 1=影子模式,2=在线模式,3=其他 + event.setMode(2); + results.add(event); + //eventLogService.insertEventLog(event); + } + eventLogService.insertBatch(results); + } catch (Exception e) { + log.error("接收事件,解析数据时异常 message={}", e.getMessage()); + } + } + + /** + * 上报设备信息 + */ + public void reportDevice(ReportDataBo bo) { + try { + // 设备实体 + Device deviceEntity = deviceService.selectDeviceBySerialNumber(bo.getSerialNumber()); + // 上报设备信息 + Device device = JSON.parseObject(bo.getMessage(), Device.class); + device.setProductId(bo.getProductId()); + device.setSerialNumber(bo.getSerialNumber()); + deviceService.reportDevice(device, deviceEntity); + // 发布设备状态 + messagePublish.publishStatus(bo.getProductId(), bo.getSerialNumber(), 3, deviceEntity.getIsShadow(), device.getRssi()); + } catch (Exception e) { + log.error("接收设备信息,解析数据时异常 message={}", e.getMessage()); + throw new ServiceException(e.getMessage(), 1); + } + } + + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/DeviceReportMessageServiceImpl.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/DeviceReportMessageServiceImpl.java new file mode 100644 index 00000000..1d7fe0bc --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/DeviceReportMessageServiceImpl.java @@ -0,0 +1,141 @@ +package com.fastbee.mqtt.service.impl; + +import com.fastbee.common.core.mq.DeviceReport; +import com.fastbee.common.core.mq.DeviceReportBo; +import com.fastbee.common.core.mq.message.DeviceData; +import com.fastbee.common.enums.ServerType; +import com.fastbee.common.enums.ThingsModelType; +import com.fastbee.common.enums.TopicType; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.service.IDeviceService; +import com.fastbee.json.JsonProtocolService; +import com.fastbee.mq.model.ReportDataBo; +import com.fastbee.mq.service.IDataHandler; +import com.fastbee.mq.service.IDeviceReportMessageService; +import io.netty.buffer.Unpooled; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Optional; + +/** + * 处理类 处理设备主动上报和设备回调信息 + * + * @author bill + */ +@Service +@Slf4j +public class DeviceReportMessageServiceImpl implements IDeviceReportMessageService { + + @Autowired + private IDeviceService deviceService; + @Autowired + private JsonProtocolService jsonProtocolService; + @Resource + private TopicsUtils topicsUtils; + @Resource + private IDataHandler dataHandler; + + + /** + * 处理设备主动上报数据 + */ + @Override + public void parseReportMsg(DeviceReportBo bo) { + if (bo.getServerType() == ServerType.MQTT) { + //构建消息 + Device report = buildReport(bo); + /*获取协议处理器*/ + DeviceData data = DeviceData.builder() + .serialNumber(bo.getSerialNumber()) + .topicName(bo.getTopicName()) + .productId(report.getProductId()) + .data(bo.getData()) + .prop(bo.getProp()) + .buf(Unpooled.wrappedBuffer(bo.getData())) + .build(); + /*根据协议解析后的数据*/ + DeviceReport reportMessage = jsonProtocolService.decode(data, null); + + reportMessage.setSerialNumber(bo.getSerialNumber()); + reportMessage.setProductId(bo.getProductId()); + reportMessage.setPlatformDate(bo.getPlatformDate()); + reportMessage.setServerType(bo.getServerType()); + reportMessage.setUserId(report.getUserId()); + reportMessage.setUserName(report.getUserName()); + reportMessage.setDeviceName(report.getDeviceName()); + processNoSub(reportMessage, bo.getTopicName()); + } + } + + /** + * 构建消息 + * + * @param bo + */ + @Override + public Device buildReport(DeviceReportBo bo) { + String serialNumber = topicsUtils.parseSerialNumber(bo.getTopicName()); + Device device = deviceService.selectDeviceBySerialNumber(serialNumber); + Optional.ofNullable(device).orElseThrow(() -> new ServiceException("设备不存在")); + //设置物模型 + String thingsModel = topicsUtils.getThingsModel(bo.getTopicName()); + ThingsModelType thingsModelType = ThingsModelType.getType(thingsModel); + bo.setType(thingsModelType); + //产品id + bo.setProductId(device.getProductId()); + //设备编号 + bo.setSerialNumber(serialNumber); + return device; + } + + + /** + * 处理网关设备 + * + * @param message + * @param topicName + */ + private void processNoSub(DeviceReport message, String topicName) { + //处理设备上报数据 + handlerReportMessage(message, topicName); + } + + + /** + * 处理设备主动上报属性 + * + * @param topicName + * @param message + */ + public void handlerReportMessage(DeviceReport message, String topicName) { + + if (message.getServerType().equals(ServerType.MQTT)){ + //处理topic以prop结尾上报的数据 (属性) + if (message.getServerType().equals(ServerType.MQTT)) { + if (!topicName.endsWith(TopicType.PROPERTY_POST.getTopicSuffix()) + && !topicName.endsWith(TopicType.PROPERTY_POST_SIMULATE.getTopicSuffix())) { + return; + } + } + } + + ReportDataBo report = new ReportDataBo(); + report.setSerialNumber(message.getSerialNumber()); + report.setProductId(message.getProductId()); + report.setDataList(message.getValuesInput().getThingsModelValueRemarkItem()); + report.setType(1); + report.setSlaveId(message.getSlaveId()); + report.setUserId(message.getUserId()); + report.setUserName(message.getUserName()); + report.setDeviceName(message.getDeviceName()); + //属性上报执行规则引擎 + report.setRuleEngine(true); + dataHandler.reportData(report); + } + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/MessageStoreImpl.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/MessageStoreImpl.java new file mode 100644 index 00000000..0de2109f --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/MessageStoreImpl.java @@ -0,0 +1,159 @@ +package com.fastbee.mqtt.service.impl; + +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import com.fastbee.mqtt.model.ClientMessage; +import com.fastbee.mqtt.service.IMessageStore; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Retain will Qos12消息存储接口 -TODO 后续Redis处理 + * + * @author gsb + * @date 2022/10/14 14:35 + */ +@Service +public class MessageStoreImpl implements IMessageStore { + + + /** + * 存储消息,保留消息,遗留消息 + */ + private final Map willOrRetainMap = new ConcurrentHashMap<>(); + + /** + * Qos2 Pub消息 + */ + private final Map publishMap = new ConcurrentHashMap<>(); + /** + * Qos2 REL IN消息 + */ + private final Set outRelSet = new HashSet<>(); + + /** + * Qos2 REL out + */ + private final Set inRelSet = new HashSet<>(); + + /** + * 存储控制包 + * + * @param topic: 控制包所属主题 + * @param clientMessage: 需要存储的消息 + */ + @Override + public void storeMessage(String topic, ClientMessage clientMessage) { + willOrRetainMap.put(topic, clientMessage); + } + + /** + * 清除topic下的所有消息 + * + * @param topic: 主题 + */ + @Override + public void cleanTopic(String topic) { + willOrRetainMap.remove(topic); + } + + /** + * 根据clientId清除消息 + * + * @param clientId: 客户端唯一标识 + */ + @Override + public void removeMessage(String clientId) { + for (Map.Entry entry : willOrRetainMap.entrySet()) { + if (entry.getValue().getClientId().equals(clientId)) { + willOrRetainMap.remove(entry.getKey()); + } + } + } + + /** + * 匹配主题过滤器,匹配消息 + * + * @param topicFilter: 主题过滤器 + */ + @Override + public List searchMessages(String topicFilter) { + List messageList = new ArrayList<>(); + for (String topic : willOrRetainMap.keySet()) { + if (TopicsUtils.matchTopic(topic, topicFilter)) { + messageList.add(willOrRetainMap.get(topic)); + } + } + return messageList; + } + + /** + * 保存 clientMessage + * + * @param messageId 消息id + */ + @Override + public void savePubMsg(Integer messageId, ClientMessage clientMessage){ + publishMap.put(messageId,clientMessage); + } + + /** + * 移除 + * + * @param messageId 消息id + */ + @Override + public void removePubMsg(int messageId){ + publishMap.remove(messageId); + } + + /** + * 保存 REL IN + * + * @param messageId 消息id + */ + @Override + public void saveRelInMsg(int messageId){ + inRelSet.add(messageId); + } + + /** + * 保存 REL OUT + * + * @param messageId 消息id + */ + @Override + public void saveRelOutMsg(int messageId){ + outRelSet.add(messageId); + } + + /** + * 移除 + * + * @param messageId 消息id + */ + @Override + public void removeRelInMsg(int messageId){ + inRelSet.remove(messageId); + } + + /** + * 移除 + * + * @param messageId 消息id + */ + @Override + public void removeRelOutMsg(int messageId){ + outRelSet.remove(messageId); + } + + /** + * 判断Rel out是否包含消息id + */ + @Override + public boolean outRelContains(int messageId){ + return outRelSet.contains(messageId); + } + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/MqttMessagePublishImpl.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/MqttMessagePublishImpl.java new file mode 100644 index 00000000..5a1f313f --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/MqttMessagePublishImpl.java @@ -0,0 +1,354 @@ +package com.fastbee.mqtt.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.fastbee.common.core.mq.DeviceReportBo; +import com.fastbee.common.core.mq.MQSendMessageBo; +import com.fastbee.common.core.mq.message.DeviceData; +import com.fastbee.common.core.mq.message.DeviceDownMessage; +import com.fastbee.common.core.mq.message.InstructionsMessage; +import com.fastbee.common.core.mq.message.MqttBo; +import com.fastbee.common.core.mq.ota.OtaUpgradeBo; +import com.fastbee.common.core.protocol.modbus.ModbusCode; +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; +import com.fastbee.common.enums.ServerType; +import com.fastbee.common.enums.TopicType; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.gateway.CRC16Utils; +import com.fastbee.common.utils.gateway.mq.TopicsUtils; +import com.fastbee.common.utils.ip.IpUtils; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.domain.FunctionLog; +import com.fastbee.iot.domain.Product; +import com.fastbee.iot.model.NtpModel; +import com.fastbee.iot.model.ThingsModels.PropertyDto; +import com.fastbee.iot.service.IDeviceService; +import com.fastbee.iot.service.IProductService; +import com.fastbee.iot.service.IThingsModelService; +import com.fastbee.iot.service.cache.IFirmwareCache; +import com.fastbee.iot.util.SnowflakeIdWorker; +import com.fastbee.json.JsonProtocolService; +import com.fastbee.mq.model.ReportDataBo; +import com.fastbee.mq.mqttClient.PubMqttClient; +import com.fastbee.mq.service.IDataHandler; +import com.fastbee.mq.service.IMqttMessagePublish; +import com.fastbee.mqtt.manager.MqttRemoteManager; +import com.fastbee.mqtt.model.PushMessageBo; +import lombok.extern.slf4j.Slf4j; +import org.java_websocket.protocols.IProtocol; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * 消息推送方法集合 + * + * @author bill + */ +@Slf4j +@Service +public class MqttMessagePublishImpl implements IMqttMessagePublish { + + @Resource + private IProductService productService; + @Resource + private PubMqttClient mqttClient; + @Resource + private IFirmwareCache firmwareCache; + @Resource + private TopicsUtils topicsUtils; + @Resource + private IDeviceService deviceService; + @Resource + private MqttRemoteManager remoteManager; + + @Resource + private IDataHandler dataHandler; + @Resource + private IThingsModelService thingsModelService; + @Resource + private JsonProtocolService jsonProtocolService; + private SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(3); + + + @Override + public InstructionsMessage buildMessage(DeviceDownMessage downMessage, TopicType type) { + /*返回的组将数据*/ + InstructionsMessage message = new InstructionsMessage(); + /*根据设备编号查询产品信息*/ + if (StringUtils.isEmpty(downMessage.getProtocolCode())) { + Product product = productService.getProductBySerialNumber(downMessage.getSerialNumber()); + Optional.ofNullable(product).orElseThrow(() -> new ServiceException("产品为空")); + downMessage.setProtocolCode(product.getProtocolCode()); + } + String serialNumber = downMessage.getSerialNumber() == null ? "" : downMessage.getSerialNumber(); + + /*组建Topic*/ + String topicName = ""; + if (downMessage.getServerType().equals(ServerType.MQTT)) { + topicName = topicsUtils.buildTopic(downMessage.getProductId(), serialNumber, type); + } + + DeviceData encodeData = DeviceData.builder() + .downMessage(downMessage) + .serialNumber(serialNumber) + .body(downMessage.getBody()) + .code(downMessage.getCode()) + .topicName(topicName).build(); + //根据协议编码后数据 + byte[] data = jsonProtocolService.encode(encodeData, null); + message.setMessage(data); + message.setSerialNumber(serialNumber); + message.setTopicName(topicName); + + return message; + } + + /** + * 服务(指令)下发 + */ + @Override + public void funcSend(MQSendMessageBo bo) { + //如果协议编号为空,则获取 + if (StringUtils.isEmpty(bo.getProtocolCode())) { + Product product = productService.selectProductByProductId(bo.getProductId()); + //bo.setType(ThingsModelType.SERVICE); + bo.setProtocolCode(product.getProtocolCode()); + bo.setTransport(product.getTransport()); + } + + //处理设备影子模式 + if (null != bo.getIsShadow() && bo.getIsShadow()){ + List dataList = new ArrayList<>(); + bo.getValue().forEach((key,value) ->{ + ThingsModelSimpleItem item = new ThingsModelSimpleItem(); + item.setId(key); + item.setValue(value+""); + dataList.add(item); + }); + ReportDataBo dataBo = new ReportDataBo(); + dataBo.setDataList(dataList); + dataBo.setProductId(bo.getProductId()); + dataBo.setSerialNumber(bo.getSerialNumber()); + dataBo.setRuleEngine(false); + dataBo.setShadow(true); + dataBo.setSlaveId(bo.getSlaveId()); + dataBo.setType(bo.getType().getCode()); + dataHandler.reportData(dataBo); + return; + } + + /* 下发服务数据存储对象*/ + FunctionLog log = new FunctionLog(); + log.setCreateTime(DateUtils.getNowDate()); + log.setFunValue(bo.getValue().get(bo.getIdentifier()).toString()); + log.setMessageId(bo.getMessageId()); + log.setSerialNumber(bo.getSerialNumber()); + log.setIdentify(bo.getIdentifier()); + log.setShowValue(bo.getShowValue()); + log.setFunType(1); + log.setModelName(bo.getModelName()); + //兼容子设备 + if (null != bo.getSlaveId()) { + PropertyDto thingModels = thingsModelService.getSingleThingModels(bo.getProductId(), bo.getIdentifier() + "#" + bo.getSlaveId()); + log.setSerialNumber(bo.getSerialNumber() + "_" + bo.getSlaveId()); + bo.setCode(ModbusCode.Write06); + if (!Objects.isNull(thingModels.getCode())){ + bo.setCode(ModbusCode.getInstance(Integer.parseInt(thingModels.getCode()))); + } + } + + ServerType serverType = ServerType.explain(bo.getTransport()); + Optional.ofNullable(serverType).orElseThrow(() -> new ServiceException("产品的传输协议编码为空!")); + /*下发服务数据处理对象*/ + DeviceDownMessage downMessage = DeviceDownMessage.builder() + .messageId(bo.getMessageId()) + .body(bo.getValue()) + .serialNumber(bo.getSerialNumber()) + .productId(bo.getProductId()) + .timestamp(DateUtils.getTimestamp()) + .identifier(bo.getIdentifier()) + .slaveId(bo.getSlaveId()) + .code(bo.getCode() == ModbusCode.Read01 ? ModbusCode.Write05 : ModbusCode.Write06) + .serverType(serverType) + .build(); + switch (serverType) { + case MQTT: + //组建下发服务指令 + InstructionsMessage instruction = buildMessage(downMessage, TopicType.FUNCTION_GET); + mqttClient.publish(instruction.getTopicName(), instruction.getMessage(), log); + MqttMessagePublishImpl.log.debug("=>服务下发,topic=[{}],指令=[{}]", instruction.getTopicName(),new String(instruction.getMessage())); + break; + + } + } + + /** + * OTA升级下发 + * + * @param bo + */ + @Override + public void upGradeOTA(OtaUpgradeBo bo) { + + } + + @Override + public void sendFunctionMessage(DeviceReportBo bo) { + log.warn("=>功能指令下发,sendFunctionMessage bo=[{}]", bo); + Device device = deviceService.selectDeviceBySerialNumber(bo.getSerialNumber()); + Optional.ofNullable(device).orElseThrow(()->new ServiceException("服务下发的设备:["+bo.getSerialNumber()+"]不存在")); + + Product product = productService.selectProductByProductId(topicsUtils.parseProductId(bo.getTopicName())); + ServerType serverType = ServerType.explain(product.getTransport()); + Optional.ofNullable(serverType).orElseThrow(() -> new ServiceException("产品的传输协议编码为空!")); + + switch (serverType) { + case GB28181: + break; + } + } + + /** + * 1.发布设备状态 + */ + @Override + public void publishStatus(Long productId, String deviceNum, int deviceStatus, int isShadow, int rssi) { + String message = "{\"status\":" + deviceStatus + ",\"isShadow\":" + isShadow + ",\"rssi\":" + rssi + "}"; + String topic = topicsUtils.buildTopic(productId, deviceNum, TopicType.STATUS_POST); + mqttClient.publish(1, false, topic, message); + } + + + /** + * 2.发布设备信息 + */ + @Override + public void publishInfo(Long productId, String deviceNum) { + String topic = topicsUtils.buildTopic(productId, deviceNum, TopicType.INFO_GET); + mqttClient.publish(1, false, topic, ""); + } + + /** + * 3.发布时钟同步信息 + * + * @param bo 数据模型 + */ + public void publishNtp(ReportDataBo bo) { + NtpModel ntpModel = JSON.parseObject(bo.getMessage(), NtpModel.class); + ntpModel.setServerRecvTime(System.currentTimeMillis()); + ntpModel.setServerSendTime(System.currentTimeMillis()); + String topic = topicsUtils.buildTopic(bo.getProductId(), bo.getSerialNumber(), TopicType.NTP_GET); + mqttClient.publish(1, false, topic, JSON.toJSONString(ntpModel)); + } + + /** + * 4.发布属性 + * delay 延时,秒为单位 + */ + @Override + public void publishProperty(Long productId, String deviceNum, List thingsList, int delay) { + String pre = ""; + if (delay > 0) { + pre = "$delayed/" + String.valueOf(delay) + "/"; + } + String topic = topicsUtils.buildTopic(productId, deviceNum, TopicType.FUNCTION_GET); + if (thingsList == null) { + mqttClient.publish(1, true, topic, ""); + } else { + mqttClient.publish(1, true, topic, JSON.toJSONString(thingsList)); + } + } + + /** + * 5.发布功能 + * delay 延时,秒为单位 + */ + @Override + public void publishFunction(Long productId, String deviceNum, List thingsList, int delay) { + String pre = ""; + if (delay > 0) { + pre = "$delayed/" + String.valueOf(delay) + "/"; + } + String topic = topicsUtils.buildTopic(productId, deviceNum, TopicType.FUNCTION_GET); + if (thingsList == null) { + mqttClient.publish(1, true, topic, ""); + } else { + mqttClient.publish(1, true, topic, JSON.toJSONString(thingsList)); + } + + } + + /** + * 设备数据同步 + * + * @param deviceNumber 设备编号 + * @return 设备 + */ + public Device deviceSynchronization(String deviceNumber) { + Device device = deviceService.selectDeviceBySerialNumber(deviceNumber); + // 1-未激活,2-禁用,3-在线,4-离线 + if (device.getStatus() == 3) { + device.setStatus(4); + deviceService.updateDeviceStatus(device); + // 发布设备信息 + publishInfo(device.getProductId(), device.getSerialNumber()); + } + return device; + } + + + /** + * 发送模拟设备到WS + */ + public void sendSimulationWs(MqttBo send ,MqttBo receive,String topic){ + PushMessageBo messageBo = new PushMessageBo(); + messageBo.setTopic(topic); + JSONArray array = new JSONArray(); + send.setDirection("send"); + send.setTs(DateUtils.getNowDate()); + receive.setTs(DateUtils.getNowDate()); + receive.setDirection("receive"); + array.add(send); + array.add(receive); + messageBo.setMessage(array.toJSONString()); + remoteManager.pushCommon(messageBo); + } + + public 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; + } + + + /** + * 搭建消息 + * + * @param bo + * @return + */ + private DeviceDownMessage buildMessage(OtaUpgradeBo bo) { + String messageId = String.valueOf(snowflakeIdWorker.nextId()); + bo.setMessageId(messageId); + bo.setOtaUrl("http://" + IpUtils.getHostIp()+bo.getOtaUrl()); + return DeviceDownMessage.builder() + .productId(bo.getProductId()) + .serialNumber(bo.getSerialNumber()) + .body(JSON.toJSON(bo)) + .timestamp(DateUtils.getTimestamp()) + .messageId(messageId) + .build(); + + } + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/SubscriptionServiceImpl.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/SubscriptionServiceImpl.java new file mode 100644 index 00000000..6dbb8341 --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/service/impl/SubscriptionServiceImpl.java @@ -0,0 +1,56 @@ +package com.fastbee.mqtt.service.impl; + +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.mqtt.model.Subscribe; +import com.fastbee.mqtt.service.ISubscriptionService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * @author gsb + * @date 2022/10/14 8:37 + */ +@Slf4j +@Component +public class SubscriptionServiceImpl implements ISubscriptionService { + + @Autowired + private RedisCache redisCache; + + /** + * 保存客户订阅的主题 + * + * @param subscribeList 主题列表 + */ + @Override + public void subscribe(List subscribeList, String clientId) { + redisCache.setCacheList(clientId, subscribeList); + } + + /** + * 解除订阅 + * + * @param clientId 客户id + * @param topicName 主题 + */ + @Override + public void unsubscribe(String clientId, String topicName) { + redisCache.delHashValue(topicName, clientId); + } + + /** + * 获取订阅了 topic 的客户id + * + * @param topic 主题 + * @return 订阅了主题的客户id列表 + */ + @Override + public List searchSubscribeClientList(String topic) { + return null; + } + + +} diff --git a/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/utils/MqttMessageUtils.java b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/utils/MqttMessageUtils.java new file mode 100644 index 00000000..89224a9b --- /dev/null +++ b/springboot/fastbee-server/mqtt-broker/src/main/java/com/fastbee/mqtt/utils/MqttMessageUtils.java @@ -0,0 +1,144 @@ +package com.fastbee.mqtt.utils; + +import com.fastbee.common.core.mq.DeviceStatusBo; +import com.fastbee.common.enums.DeviceStatus; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.mqtt.model.ClientMessage; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.*; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 服务器应答信息构建 + * @author gsb + * @date 2022/10/7 14:17 + */ +public class MqttMessageUtils { + + + /** + * 服务器确认连接应答消息 CONNACK + */ + public static MqttConnAckMessage buildConntAckMessage(MqttConnectReturnCode code, boolean sessionPresent) { + MqttFixedHeader fixedHeader = buildFixedHeader(MqttMessageType.CONNACK); + MqttConnAckVariableHeader variableHeader = new MqttConnAckVariableHeader(code, sessionPresent); + return new MqttConnAckMessage(fixedHeader, variableHeader); + } + + + /** + * 设备ping(心跳信息)应答 PINGRESP + */ + public static MqttMessage buildPingResp() { + MqttFixedHeader fixedHeader = buildFixedHeader(MqttMessageType.PINGRESP); + return new MqttMessage(fixedHeader); + } + + /** + * 取消订阅消息应答 UNSUBACK + */ + public static MqttUnsubAckMessage buildUnsubAckMessage(MqttMessage message) { + /*构建固定报文*/ + MqttFixedHeader fixedHeader = buildFixedHeader(MqttMessageType.UNSUBACK); + return new MqttUnsubAckMessage(fixedHeader, getIdVariableHeader(message)); + } + + /** + * 订阅确认应答 SUBACK + */ + public static MqttSubAckMessage buildSubAckMessage(MqttMessage message) { + /*构建固定报文*/ + MqttFixedHeader fixedHeader = buildFixedHeader(MqttMessageType.SUBACK); + /*构建可变报文*/ + MqttSubscribeMessage mqttSubscribeMessage = (MqttSubscribeMessage) message; + + /*获取订阅topic的Qos*/ + Set topics = mqttSubscribeMessage.payload().topicSubscriptions().stream().map(MqttTopicSubscription::topicName).collect(Collectors.toSet()); + List grantedQos = new ArrayList<>(topics.size()); + for (int i = 0; i < topics.size(); i++) { + grantedQos.add(mqttSubscribeMessage.payload().topicSubscriptions().get(i).qualityOfService().value()); + } + /*负载*/ + MqttSubAckPayload payload = new MqttSubAckPayload(grantedQos); + return new MqttSubAckMessage(fixedHeader, getIdVariableHeader(message), payload); + } + + /** + * 构建推送应答消息 PUBLISH + */ + public static MqttPublishMessage buildPublishMessage(ClientMessage msg, int packageId) { + /*报文固定头*/ + MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, msg.isDup(), msg.getQos(), false, 0); + /*报文可变头*/ + MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(msg.getTopicName(), packageId); + /*负载*/ + ByteBuf payload = msg.getPayload() == null ? Unpooled.EMPTY_BUFFER : Unpooled.wrappedBuffer(msg.getPayload()); + /*完整报文,固定头+可变头+payload*/ + return new MqttPublishMessage(fixedHeader, variableHeader, payload); + } + + /** + * Qos1 收到发布消息确认 无负载 PUBACK + */ + public static MqttPubAckMessage buildAckMessage(MqttMessage message) { + MqttFixedHeader fixedHeader = buildFixedHeader(MqttMessageType.PUBACK); + return new MqttPubAckMessage(fixedHeader, getIdVariableHeader(message)); + } + + /** + * Qos2 发到消息收到 无负载 PUBREC + */ + public static MqttMessage buildPubRecMessage(MqttMessage message){ + MqttFixedHeader fixedHeader = buildFixedHeader(MqttMessageType.PUBREC); + return new MqttMessage(fixedHeader, getIdVariableHeader(message)); + } + + /** + * Qos2 发布消息释放 PUBREL + */ + public static MqttMessage buildPubRelMessage(MqttMessage message){ + MqttFixedHeader fixedHeader = buildFixedHeader(MqttMessageType.PUBREL); + return new MqttMessage(fixedHeader, getIdVariableHeader(message)); + + } + + /** + * Qos2 发布消息完成 PUBCOMP + */ + public static MqttMessage buildPubCompMessage(MqttMessage message){ + MqttFixedHeader fixedHeader = buildFixedHeader(MqttMessageType.PUBCOMP); + return new MqttMessage(fixedHeader, getIdVariableHeader(message)); + } + + /** + * 固定头定制 + */ + public static MqttFixedHeader buildFixedHeader(MqttMessageType messageType) { + return new MqttFixedHeader(messageType, false, MqttQoS.AT_MOST_ONCE, false, 0); + } + + /** + * 构造MqttMessageIdVariableHeader + */ + public static MqttMessageIdVariableHeader getIdVariableHeader(MqttMessage mqttMessage) { + MqttMessageIdVariableHeader idVariableHeader = (MqttMessageIdVariableHeader) mqttMessage.variableHeader(); + return MqttMessageIdVariableHeader.from(idVariableHeader.messageId()); + } + + /*构造返回MQ的设备状态model*/ + public static DeviceStatusBo buildStatusMsg(ChannelHandlerContext ctx, String clientId,DeviceStatus status,String ip){ + return DeviceStatusBo.builder() + .serialNumber(clientId) + .status(status) + .ip(ip) + .hostName(ip) + .timestamp(DateUtils.getNowDate()).build(); + } +} diff --git a/springboot/fastbee-server/pom.xml b/springboot/fastbee-server/pom.xml new file mode 100644 index 00000000..3dd9f4da --- /dev/null +++ b/springboot/fastbee-server/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + fastbee + com.fastbee + 3.8.5 + + + pom + fastbee-server + 服务集成模块 + + + + base-server + + boot-strap + + mqtt-broker + + iot-server-core + + + + + + io.netty + netty-all + 4.1.56.Final + compile + + + + com.fastbee + fastbee-common + 3.8.5 + + + + org.projectlombok + lombok + + + + com.fastbee + fastbee-iot-service + + + + + + diff --git a/springboot/fastbee-service/fastbee-iot-service/pom.xml b/springboot/fastbee-service/fastbee-iot-service/pom.xml new file mode 100644 index 00000000..454be92c --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/pom.xml @@ -0,0 +1,108 @@ + + + + fastbee-service + com.fastbee + 3.8.5 + + 4.0.0 + + fastbee-iot-service + + + 设备业务模块 + + + + + + + com.fastbee + fastbee-common + + + io.swagger + swagger-annotations + 1.6.2 + compile + + + org.apache.velocity + velocity-engine-core + + + com.fastbee + fastbee-framework + + + com.fastbee + fastbee-quartz + + + org.apache.commons + commons-text + 1.6 + + + + org.springframework.security.oauth + spring-security-oauth2 + 2.5.1.RELEASE + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.5 + + + + + org.springframework + spring-context + 5.2.3.RELEASE + + + + + me.zhyd.oauth + JustAuth + ${justAuth.version} + + + + + com.dtflys.forest + forest-spring-boot-starter + ${forest.version} + + + + + + com.taosdata.jdbc + taos-jdbcdriver + 2.0.38 + + + + + cn.hutool + hutool-all + + + + + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Category.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Category.java new file mode 100644 index 00000000..fa2c31c2 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Category.java @@ -0,0 +1,134 @@ +package com.fastbee.iot.domain; + +import com.fastbee.common.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 com.fastbee.common.annotation.Excel; + +/** + * 产品分类对象 iot_category + * + * @author kerwincui + * @date 2021-12-16 + */ +@ApiModel(value = "Category", description = "产品分类对象 iot_category") +public class Category extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 产品分类ID */ + @ApiModelProperty("产品分类ID") + private Long categoryId; + + /** 产品分类名称 */ + @ApiModelProperty("产品分类名称") + @Excel(name = "产品分类名称") + private String categoryName; + + /** 租户ID */ + @ApiModelProperty("租户ID") + @Excel(name = "租户ID") + private Long tenantId; + + /** 租户名称 */ + @ApiModelProperty("租户名称") + @Excel(name = "租户名称") + private String tenantName; + + /** 是否系统通用(0-否,1-是) */ + @ApiModelProperty("是否系统通用(0-否,1-是)") + @Excel(name = "是否系统通用", readConverterExp = "0=-否,1-是") + private Integer isSys; + + /** 显示顺序 */ + @ApiModelProperty("显示顺序") + @Excel(name = "显示顺序") + private Integer orderNum; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志(0代表存在 2代表删除)") + private String delFlag; + + public void setCategoryId(Long categoryId) + { + this.categoryId = categoryId; + } + + public Long getCategoryId() + { + return categoryId; + } + public void setCategoryName(String categoryName) + { + this.categoryName = categoryName; + } + + public String getCategoryName() + { + return categoryName; + } + public void setTenantId(Long tenantId) + { + this.tenantId = tenantId; + } + + public Long getTenantId() + { + return tenantId; + } + public void setTenantName(String tenantName) + { + this.tenantName = tenantName; + } + + public String getTenantName() + { + return tenantName; + } + public void setIsSys(Integer isSys) + { + this.isSys = isSys; + } + public Integer getIsSys() + { + return isSys; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + public Integer getOrderNum() + { + return orderNum; + } + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("categoryId", getCategoryId()) + .append("categoryName", getCategoryName()) + .append("tenantId", getTenantId()) + .append("tenantName", getTenantName()) + .append("isSys", getIsSys()) + .append("orderNum", getOrderNum()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Device.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Device.java new file mode 100644 index 00000000..edf534f7 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Device.java @@ -0,0 +1,473 @@ +package com.fastbee.iot.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.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 java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + * 设备对象 iot_device + * + * @author kerwincui + * @date 2021-12-16 + */ +@ApiModel(value = "Device", description = "设备对象 iot_device") +public class Device extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 设备ID */ + @ApiModelProperty("设备ID") + private Long deviceId; + + /** 设备名称 */ + @ApiModelProperty("设备名称") + @Excel(name = "设备名称") + private String deviceName; + + /** 产品ID */ + @ApiModelProperty("产品ID") + @Excel(name = "产品ID") + private Long productId; + + /** 产品名称 */ + @ApiModelProperty("产品名称") + @Excel(name = "产品名称") + private String productName; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** 用户昵称 */ + @ApiModelProperty("用户昵称") + @Excel(name = "用户昵称") + private String userName; + + /** 租户ID */ + @ApiModelProperty("租户ID") + @Excel(name = "租户ID") + private Long tenantId; + + /** 租户名称 */ + @ApiModelProperty("租户名称") + @Excel(name = "租户名称") + private String tenantName; + + /** 设备编号 */ + @ApiModelProperty("设备编号") + @Excel(name = "设备编号") + private String serialNumber; + + /** 固件版本 */ + @ApiModelProperty("固件版本") + @Excel(name = "固件版本") + private BigDecimal firmwareVersion; + + /** 设备类型(1-直连设备、2-网关设备、3-监控设备) */ + @ApiModelProperty("设备类型(1-直连设备、2-网关设备、3-监控设备)") + private Integer deviceType; + + /** 设备状态(1-未激活,2-禁用,3-在线,4-离线) */ + @ApiModelProperty("设备状态(1-未激活,2-禁用,3-在线,4-离线)") + @Excel(name = "设备状态") + private Integer status; + + /** wifi信号强度(信号极好4格[-55— 0],信号好3格[-70— -55],信号一般2格[-85— -70],信号差1格[-100— -85]) */ + @ApiModelProperty("wifi信号强度(信号极好4格[-55— 0],信号好3格[-70— -55],信号一般2格[-85— -70],信号差1格[-100— -85])") + @Excel(name = "wifi信号强度") + private Integer rssi; + + /** 设备影子 */ + @ApiModelProperty("是否启用设备影子(0=禁用,1=启用)") + private Integer isShadow; + + /** 设备所在地址 */ + @ApiModelProperty("设备所在地址") + @Excel(name = "设备所在地址") + private String networkAddress; + + /** 设备入网IP */ + @ApiModelProperty("设备入网IP") + @Excel(name = "设备入网IP") + private String networkIp; + + /** 设备经度 */ + @ApiModelProperty("设备经度") + @Excel(name = "设备经度") + private BigDecimal longitude; + + /** 设备纬度 */ + @ApiModelProperty("设备纬度") + @Excel(name = "设备纬度") + private BigDecimal latitude; + + /** 激活时间 */ + @ApiModelProperty("激活时间") + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "激活时间", width = 30, dateFormat = "yyyy-MM-dd") + private Date activeTime; + + /** 子设备网关编号 */ + @ApiModelProperty("子设备网关编号") + @Excel(name = "网关设备编号(子设备使用)") + private String gwDevCode; + + /** 物模型值 */ + @ApiModelProperty("物模型值") + @Excel(name = "物模型") + private String thingsModelValue; + + /** 图片地址 */ + @ApiModelProperty("图片地址") + private String imgUrl; + + /** 是否自定义位置 **/ + @ApiModelProperty("定位方式(1=ip自动定位,2=设备定位,3=自定义)") + private Integer locationWay; + + /** 设备摘要 **/ + @ApiModelProperty("设备摘要") + private String summary; + + /** 分组ID,用于分组查询 **/ + @ApiModelProperty("分组ID,用于分组查询") + private Long groupId; + + /** 是否设备所有者,用于查询 **/ + @ApiModelProperty("是否设备所有者,用于查询") + private Integer isOwner; + /**子设备数量*/ + @ApiModelProperty("子设备数量") + private Integer subDeviceCount; + /**是否是模拟设备*/ + @ApiModelProperty("是否是模拟设备") + private Integer isSimulate; + /**子设备地址*/ + @ApiModelProperty("子设备地址") + private Integer slaveId; + /**设备传输协议*/ + @ApiModelProperty("设备传输协议") + private String transport; + + public String getTransport() { + return transport; + } + + public void setTransport(String transport) { + this.transport = transport; + } + + public Integer getSlaveId() { + return slaveId; + } + + public void setSlaveId(Integer slaveId) { + this.slaveId = slaveId; + } + + public Integer getIsSimulate() { + return isSimulate; + } + + public void setIsSimulate(Integer isSimulate) { + this.isSimulate = isSimulate; + } + + private List subDeviceList; + + public List getSubDeviceList() { + return subDeviceList; + } + + public void setSubDeviceList(List subDeviceList) { + this.subDeviceList = subDeviceList; + } + + public Integer getSubDeviceCount() { + return subDeviceCount; + } + + public void setSubDeviceCount(Integer subDeviceCount) { + this.subDeviceCount = subDeviceCount; + } + + public String getGwDevCode() { + return gwDevCode; + } + + public void setGwDevCode(String gwDevCode) { + this.gwDevCode = gwDevCode; + } + + public Integer getLocationWay() { + return locationWay; + } + + public void setLocationWay(Integer locationWay) { + this.locationWay = locationWay; + } + + public Integer getIsOwner() { + return isOwner; + } + + public void setIsOwner(Integer isOwner) { + this.isOwner = isOwner; + } + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志") + private String delFlag; + + public Long getGroupId() { + return groupId; + } + + public void setGroupId(Long groupId) { + this.groupId = groupId; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public String getImgUrl() { + return imgUrl; + } + + public void setImgUrl(String imgUrl) { + this.imgUrl = imgUrl; + } + + public Integer getIsShadow() { + return isShadow; + } + + public void setIsShadow(Integer isShadow) { + this.isShadow = isShadow; + } + + public static long getSerialVersionUID() { + return serialVersionUID; + } + + public String getDelFlag() { + return delFlag; + } + + public void setDelFlag(String delFlag) { + this.delFlag = delFlag; + } + + public void setDeviceId(Long deviceId) + { + this.deviceId = deviceId; + } + + public Long getDeviceId() + { + return deviceId; + } + public void setDeviceName(String deviceName) + { + this.deviceName = deviceName; + } + + public String getDeviceName() + { + return deviceName; + } + public void setProductId(Long productId) + { + this.productId = productId; + } + + public Long getProductId() + { + return productId; + } + public void setProductName(String productName) + { + this.productName = productName; + } + + public String getProductName() + { + return productName; + } + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getUserId() + { + return userId; + } + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getUserName() + { + return userName; + } + public void setTenantId(Long tenantId) + { + this.tenantId = tenantId; + } + + public Long getTenantId() + { + return tenantId; + } + public void setTenantName(String tenantName) + { + this.tenantName = tenantName; + } + + public String getTenantName() + { + return tenantName; + } + public void setSerialNumber(String serialNumber) + { + this.serialNumber = serialNumber; + } + + public String getSerialNumber() + { + return serialNumber; + } + public void setFirmwareVersion(BigDecimal firmwareVersion) + { + this.firmwareVersion = firmwareVersion; + } + + public BigDecimal getFirmwareVersion() + { + return firmwareVersion; + } + public void setStatus(Integer status) + { + this.status = status; + } + public void setDeviceType(Integer deviceType) + { + this.deviceType = deviceType; + } + + public Integer getDeviceType() + { + return deviceType; + } + public Integer getStatus() + { + return status; + } + public void setRssi(Integer rssi) + { + this.rssi = rssi; + } + + public Integer getRssi() + { + return rssi; + } + public void setThingsModelValue(String thingsModelValue) + { + this.thingsModelValue = thingsModelValue; + } + + public String getThingsModelValue() + { + return thingsModelValue; + } + public void setNetworkAddress(String networkAddress) + { + this.networkAddress = networkAddress; + } + + public String getNetworkAddress() + { + return networkAddress; + } + public void setNetworkIp(String networkIp) + { + this.networkIp = networkIp; + } + + public String getNetworkIp() + { + return networkIp; + } + public void setLongitude(BigDecimal longitude) + { + this.longitude = longitude; + } + + public BigDecimal getLongitude() + { + return longitude; + } + public void setLatitude(BigDecimal latitude) + { + this.latitude = latitude; + } + + public BigDecimal getLatitude() + { + return latitude; + } + public void setActiveTime(Date activeTime) + { + this.activeTime = activeTime; + } + + public Date getActiveTime() + { + return activeTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("deviceId", getDeviceId()) + .append("deviceName", getDeviceName()) + .append("productId", getProductId()) + .append("productName", getProductName()) + .append("userId", getUserId()) + .append("userName", getUserName()) + .append("tenantId", getTenantId()) + .append("tenantName", getTenantName()) + .append("serialNumber", getSerialNumber()) + .append("firmwareVersion", getFirmwareVersion()) + .append("status", getStatus()) + .append("rssi", getRssi()) + .append("networkAddress", getNetworkAddress()) + .append("networkIp", getNetworkIp()) + .append("longitude", getLongitude()) + .append("latitude", getLatitude()) + .append("activeTime", getActiveTime()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/DeviceGroup.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/DeviceGroup.java new file mode 100644 index 00000000..bf0cbb68 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/DeviceGroup.java @@ -0,0 +1,46 @@ +package com.fastbee.iot.domain; + +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.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; + +/** + * 设备分组对象 iot_device_group + * + * @author kerwincui + * @date 2021-12-16 + */ +@ApiModel(value = "DeviceGroup", description = "设备分组对象 iot_device_group") +public class DeviceGroup +{ + private static final long serialVersionUID = 1L; + + /** 分组ID */ + @ApiModelProperty("分组ID") + private Long groupId; + + /** 设备ID */ + @ApiModelProperty("设备ID") + private Long deviceId; + + public Long getGroupId() { + return groupId; + } + + public void setGroupId(Long groupId) { + this.groupId = groupId; + } + + public Long getDeviceId() { + return deviceId; + } + + public void setDeviceId(Long deviceId) { + this.deviceId = deviceId; + } + + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/DeviceJob.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/DeviceJob.java new file mode 100644 index 00000000..3f346947 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/DeviceJob.java @@ -0,0 +1,308 @@ +package com.fastbee.iot.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.annotation.Excel.ColumnType; +import com.fastbee.common.constant.ScheduleConstants; +import com.fastbee.common.core.domain.BaseEntity; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.quartz.util.CronUtils; +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.Size; +import java.util.Date; + +/** + * 设备定时任务 + * + * @author kerwincui + */ +@ApiModel(value = "DeviceJob", description = "设备定时任务 iot_device_job") +public class DeviceJob extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 任务ID */ + @ApiModelProperty("任务ID") + @Excel(name = "任务序号", cellType = ColumnType.NUMERIC) + private Long jobId; + + /** 任务名称 */ + @ApiModelProperty("任务名称") + @Excel(name = "任务名称") + private String jobName; + + /** 设备编号 */ + @ApiModelProperty("设备编号") + @Excel(name = "设备编号") + private String serialNumber; + + /** 设备id */ + @ApiModelProperty("设备id") + private Long deviceId; + + /** 设备名称 */ + @ApiModelProperty("设备名称") + private String deviceName; + + /** 执行动作 */ + @ApiModelProperty("执行动作") + private String actions; + + /** 告警触发器 */ + @ApiModelProperty("告警触发器") + private String alertTrigger; + + /** 任务组名 */ + @ApiModelProperty("任务组名") + @Excel(name = "任务组名") + private String jobGroup; + + /** cron执行表达式 */ + @ApiModelProperty("cron执行表达式") + @Excel(name = "执行表达式 ") + private String cronExpression; + + /** cron计划策略 */ + @ApiModelProperty(value = "cron执行表达式", notes = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行") + @Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行") + private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT; + + /** 是否并发执行(0允许 1禁止) */ + @ApiModelProperty(value = "是否并发执行", notes = "0=允许,1=禁止") + @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止") + private String concurrent; + + /** 任务状态(0正常 1暂停) */ + @ApiModelProperty("任务状态(0正常 1暂停)") + @Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停") + private String status; + + /** 是否详细corn表达式 */ + @ApiModelProperty("是否详细corn表达式") + private Integer isAdvance; + + /** 定时类型(1=设备定时,2=设备告警,3=场景联动) */ + @ApiModelProperty(value = "定时类型", notes = "(1=设备定时,2=设备告警,3=场景联动)") + @Excel(name = "定时类型", readConverterExp = "1==设备定时,2=设备告警,3=场景联动") + private Integer jobType; + + /** 产品ID */ + @ApiModelProperty("产品ID") + @Excel(name = "产品ID") + private Long productId; + + /** 产品名称 */ + @ApiModelProperty("产品名称") + @Excel(name = "产品名称") + private String productName; + + /** 场景联动ID */ + @ApiModelProperty("场景联动ID") + @Excel(name = "场景联动ID") + private Long sceneId; + + /** 告警ID */ + @ApiModelProperty("告警ID") + @Excel(name = "告警ID") + private Long alertId; + + public String getAlertTrigger() { + return alertTrigger; + } + + public void setAlertTrigger(String alertTrigger) { + this.alertTrigger = alertTrigger; + } + + public static long getSerialVersionUID() { + return serialVersionUID; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public Integer getIsAdvance() { + return isAdvance; + } + + public void setIsAdvance(Integer isAdvance) { + this.isAdvance = isAdvance; + } + + public Integer getJobType() { + return jobType; + } + + public void setJobType(Integer jobType) { + this.jobType = jobType; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public Long getSceneId() { + return sceneId; + } + + public void setSceneId(Long sceneId) { + this.sceneId = sceneId; + } + + public Long getAlertId() { + return alertId; + } + + public void setAlertId(Long alertId) { + this.alertId = alertId; + } + + public Long getJobId() + { + return jobId; + } + + public void setJobId(Long jobId) + { + this.jobId = jobId; + } + + @NotBlank(message = "任务名称不能为空") + @Size(min = 0, max = 64, message = "任务名称不能超过64个字符") + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + public Long getDeviceId() { + return deviceId; + } + + public void setDeviceId(Long deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getActions() { + return actions; + } + + public void setActions(String actions) { + this.actions = actions; + } + + @NotBlank(message = "Cron执行表达式不能为空") + @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符") + public String getCronExpression() + { + return cronExpression; + } + + public void setCronExpression(String cronExpression) + { + this.cronExpression = cronExpression; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public Date getNextValidTime() + { + if (StringUtils.isNotEmpty(cronExpression)) + { + return CronUtils.getNextExecution(cronExpression); + } + return null; + } + + public String getMisfirePolicy() + { + return misfirePolicy; + } + + public void setMisfirePolicy(String misfirePolicy) + { + this.misfirePolicy = misfirePolicy; + } + + public String getConcurrent() + { + return concurrent; + } + + public void setConcurrent(String concurrent) + { + this.concurrent = concurrent; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobId", getJobId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("cronExpression", getCronExpression()) + .append("nextValidTime", getNextValidTime()) + .append("misfirePolicy", getMisfirePolicy()) + .append("concurrent", getConcurrent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/DeviceLog.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/DeviceLog.java new file mode 100644 index 00000000..c7e273d1 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/DeviceLog.java @@ -0,0 +1,474 @@ +package com.fastbee.iot.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fastbee.iot.model.ThingsModelItem.*; +import com.fastbee.iot.model.ThingsModels.ThingsModelValueItemDto; +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 设备日志对象 iot_device_log + * + * @author kerwincui + * @date 2022-01-13 + */ +@ApiModel(value = "DeviceLog", description = "设备日志对象 iot_device_log") +public class DeviceLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + @Excel(name = "时间戳") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date ts; + /** 设备日志ID */ + @ApiModelProperty("设备日志ID") + private Long logId; + + /** 类型(1=属性上报,2=事件上报,3=调用功能,4=设备升级,5=设备上线,6=设备离线) */ + @ApiModelProperty(value = "类型", notes = "1=属性上报,2=事件上报,3=调用功能,4=设备升级,5=设备上线,6=设备离线") + @Excel(name = "类型", readConverterExp = "1=属性上报,2=事件上报,3=调用功能,4=设备升级,5=设备上线,6=设备离线") + private Integer logType; + + /** 日志值 */ + @ApiModelProperty("日志值") + @Excel(name = "日志值") + private String logValue; + + /** 物模型名称 */ + @ApiModelProperty("物模型名称") + @Excel(name = "物模型名称") + private String modelName; + + /** 设备ID */ + @ApiModelProperty("设备ID") + @Excel(name = "设备ID") + private Long deviceId; + + /** 设备名称 */ + @ApiModelProperty("设备名称") + @Excel(name = "设备名称") + private String deviceName; + + /** 设备编号 */ + @ApiModelProperty("设备编号") + @Excel(name = "设备编号") + private String serialNumber; + + /** 标识符 */ + @ApiModelProperty("标识符") + @Excel(name = "标识符") + private String identity; + + /** 是否监测数据(1=是,0=否) */ + @ApiModelProperty("是否监测数据(1=是,0=否)") + @Excel(name = "是否监测数据", readConverterExp = "1=是,0=否") + private Integer isMonitor; + + /** 模式 */ + @ApiModelProperty(value = "模式", notes = "1=影子模式,2=在线模式,3=其他") + @Excel(name = "模式", readConverterExp = "1=影子模式,2=在线模式,3=其他") + private Integer mode; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** 用户昵称 */ + @ApiModelProperty("用户昵称") + @Excel(name = "用户昵称") + private String userName; + + /** 租户ID */ + @ApiModelProperty("租户ID") + @Excel(name = "租户ID") + private Long tenantId; + + /** 租户名称 */ + @ApiModelProperty("租户名称") + @Excel(name = "租户名称") + private String tenantName; + + /** 查询用的开始时间 */ + @ApiModelProperty("查询用的开始时间") + private String beginTime; + + /** 查询用的结束时间 */ + @ApiModelProperty("查询用的结束时间") + private String endTime; + + /** 查询的总数 */ + @ApiModelProperty("查询的总数") + private int total; + + /*消息ID,或消息流水号*/ + @ApiModelProperty("消息ID,或消息流水号") + private String serNo; + + private String specs; + + private DataType dataType; + + private Integer slaveId; + /** + * 计算公式 + */ + private String formula; + + private Integer isParams; + + /*是否历史存储*/ + private Integer isHistory; + + public Integer getIsHistory() { + return isHistory; + } + + public void setIsHistory(Integer isHistory) { + this.isHistory = isHistory; + } + + public Integer getIsParams() { + return isParams; + } + + public void setIsParams(Integer isParams) { + this.isParams = isParams; + } + + public String getFormula() { + return formula; + } + + public void setFormula(String formula) { + this.formula = formula; + } + + + public Integer getSlaveId() { + return slaveId; + } + + public void setSlaveId(Integer slaveId) { + this.slaveId = slaveId; + } + + public DataType getDataType() { + return dataType; + } + + public void setDataType(DataType dataType) { + this.dataType = dataType; + } + + public String getModelName() { + return modelName; + } + + public void setModelName(String modelName) { + this.modelName = modelName; + } + + public String getSpecs() { + return specs; + } + + public void setSpecs(String specs) { + this.specs = specs; + } + + public String getSerNo() { + return serNo; + } + + public void setSerNo(String serNo) { + this.serNo = serNo; + } + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } + + public String getBeginTime() { + return beginTime; + } + + public void setBeginTime(String beginTime) { + this.beginTime = beginTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public Integer getMode() { + return mode; + } + + public void setMode(Integer mode) { + this.mode = mode; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public String getTenantName() { + return tenantName; + } + + public void setTenantName(String tenantName) { + this.tenantName = tenantName; + } + + public Date getTs() { + return ts; + } + + public void setTs(Date ts) { + this.ts = ts; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public void setLogId(Long logId) + { + this.logId = logId; + } + + public Long getLogId() + { + return logId; + } + + public void setLogType(Integer logType) + { + this.logType = logType; + } + + public Integer getLogType() + { + return logType; + } + public void setLogValue(String logValue) + { + this.logValue = logValue; + } + + public String getLogValue() + { + return logValue; + } + public void setDeviceId(Long deviceId) + { + this.deviceId = deviceId; + } + + public Long getDeviceId() + { + return deviceId; + } + public void setDeviceName(String deviceName) + { + this.deviceName = deviceName; + } + + public String getDeviceName() + { + return deviceName; + } + public void setIdentity(String identity) + { + this.identity = identity; + } + + public String getIdentity() + { + return identity; + } + public void setIsMonitor(Integer isMonitor) + { + this.isMonitor = isMonitor; + } + + public Integer getIsMonitor() + { + return isMonitor; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("logId", getLogId()) + .append("logType", getLogType()) + .append("logValue", getLogValue()) + .append("deviceId", getDeviceId()) + .append("deviceName", getDeviceName()) + .append("identity", getIdentity()) + .append("createBy", getCreateBy()) + .append("isMonitor", getIsMonitor()) + .append("createTime", getCreateTime()) + .append("remark", getRemark()) + .toString(); + } + + @ApiModel + public static class DataType{ + private String type; + private String falseText; + private String trueText; + private Integer maxLength; + private String arrayType; + private String unit; + private BigDecimal min; + private BigDecimal max; + private BigDecimal step; + private List enumList; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getFalseText() { + return falseText; + } + + public void setFalseText(String falseText) { + this.falseText = falseText; + } + + public String getTrueText() { + return trueText; + } + + public void setTrueText(String trueText) { + this.trueText = trueText; + } + + public Integer getMaxLength() { + return maxLength; + } + + public void setMaxLength(Integer maxLength) { + this.maxLength = maxLength; + } + + public String getArrayType() { + return arrayType; + } + + public void setArrayType(String arrayType) { + this.arrayType = arrayType; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public BigDecimal getMin() { + return min; + } + + public void setMin(BigDecimal min) { + this.min = min; + } + + public BigDecimal getMax() { + return max; + } + + public void setMax(BigDecimal max) { + this.max = max; + } + + public BigDecimal getStep() { + return step; + } + + public void setStep(BigDecimal step) { + this.step = step; + } + + public List getEnumList() { + return enumList; + } + + public void setEnumList(List enumList) { + this.enumList = enumList; + } + } + + @ApiModel + public static class EnumItem + { + private String text; + private String value; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/DeviceUser.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/DeviceUser.java new file mode 100644 index 00000000..ae85b4fd --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/DeviceUser.java @@ -0,0 +1,167 @@ +package com.fastbee.iot.domain; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 设备用户对象 iot_device_user + * + * @author kerwincui + * @date 2021-12-16 + */ +@ApiModel(value = "DeviceUser", description = "设备用户对象 iot_device_user") +public class DeviceUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 固件ID */ + @ApiModelProperty("设备ID") + private Long deviceId; + + /** 用户ID */ + @ApiModelProperty("用户ID") + private Long userId; + + /** 设备名称 */ + @ApiModelProperty("设备名称") + @Excel(name = "设备名称") + private String deviceName; + + /** 用户昵称 */ + @ApiModelProperty("用户昵称") + @Excel(name = "用户昵称") + private String userName; + + /** 是否为设备所有者 */ + @ApiModelProperty(value = "是否为设备所有者", notes = "0=否,1=是") + @Excel(name = "是否为设备所有者") + private Integer isOwner; + + /** 租户ID */ + @ApiModelProperty("租户ID") + private Long tenantId; + + /** 租户名称 */ + @ApiModelProperty("租户名称") + private String tenantName; + + /** 手机号码 */ + @ApiModelProperty("手机号码") + private String phonenumber; + + /** 分享用户设备权限 */ + @ApiModelProperty("物模型权限") + private String perms; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志(0代表存在 2代表删除)") + private String delFlag; + + public String getPerms() { + return perms; + } + + public void setPerms(String perms) { + this.perms = perms; + } + + public String getPhonenumber() { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) { + this.phonenumber = phonenumber; + } + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public String getTenantName() { + return tenantName; + } + + public void setTenantName(String tenantName) { + this.tenantName = tenantName; + } + + public void setDeviceId(Long deviceId) + { + this.deviceId = deviceId; + } + + public Long getDeviceId() + { + return deviceId; + } + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getUserId() + { + return userId; + } + public void setDeviceName(String deviceName) + { + this.deviceName = deviceName; + } + + public String getDeviceName() + { + return deviceName; + } + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getUserName() + { + return userName; + } + public void setIsOwner(Integer isOwner) + { + this.isOwner = isOwner; + } + + public Integer getIsOwner() + { + return isOwner; + } + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("deviceId", getDeviceId()) + .append("userId", getUserId()) + .append("deviceName", getDeviceName()) + .append("userName", getUserName()) + .append("isOwner", getIsOwner()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/EventLog.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/EventLog.java new file mode 100644 index 00000000..abf92613 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/EventLog.java @@ -0,0 +1,237 @@ +package com.fastbee.iot.domain; + +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.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; + +/** + * @author gsb + * @date 2023/3/28 16:25 + */ +@ApiModel(value = "EventLog", description = "事件日志对象 iot_event_log") +public class EventLog extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** 设备日志ID */ + @ApiModelProperty("设备日志ID") + private Long logId; + + /** 标识符 */ + @ApiModelProperty("标识符") + @Excel(name = "标识符") + private String identity; + + /** 物模型名称 */ + @ApiModelProperty("物模型名称") + @Excel(name = "物模型名称") + private String modelName; + + /** 类型(3=事件上报,5=设备上线,6=设备离线) */ + @ApiModelProperty(value = "类型", notes = "3==事件上报,5=设备上线,6=设备离线") + @Excel(name = "类型", readConverterExp = "3==事件上报,5=设备上线,6=设备离线") + private Integer logType; + + /** 日志值 */ + @ApiModelProperty("日志值") + @Excel(name = "日志值") + private String logValue; + + /** 设备ID */ + @ApiModelProperty("设备ID") + @Excel(name = "设备ID") + private Long deviceId; + + /** 设备名称 */ + @ApiModelProperty("设备名称") + @Excel(name = "设备名称") + private String deviceName; + + /** 设备编号 */ + @ApiModelProperty("设备编号") + @Excel(name = "设备编号") + private String serialNumber; + + /** 是否监测数据(1=是,0=否) */ + @ApiModelProperty(value = "是否监测数据", notes = "(1=是,0=否)") + @Excel(name = "是否监测数据", readConverterExp = "1==是,0=否") + private Integer isMonitor; + + /** 模式(1=影子模式,2=在线模式,3=其他) */ + @ApiModelProperty(value = "模式", notes = "(1=影子模式,2=在线模式,3=其他)") + @Excel(name = "模式(1=影子模式,2=在线模式,3=其他)") + private Integer mode; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** 用户昵称 */ + @ApiModelProperty("用户昵称") + @Excel(name = "用户昵称") + private String userName; + + /** 租户ID */ + @ApiModelProperty("租户ID") + @Excel(name = "租户ID") + private Long tenantId; + + /** 租户名称 */ + @ApiModelProperty("租户名称") + @Excel(name = "租户名称") + private String tenantName; + + public void setLogId(Long logId) + { + this.logId = logId; + } + + public Long getLogId() + { + return logId; + } + public void setIdentity(String identity) + { + this.identity = identity; + } + + public String getIdentity() + { + return identity; + } + public void setModelName(String modelName) + { + this.modelName = modelName; + } + + public String getModelName() + { + return modelName; + } + public void setLogType(Integer logType) + { + this.logType = logType; + } + + public Integer getLogType() + { + return logType; + } + public void setLogValue(String logValue) + { + this.logValue = logValue; + } + + public String getLogValue() + { + return logValue; + } + public void setDeviceId(Long deviceId) + { + this.deviceId = deviceId; + } + + public Long getDeviceId() + { + return deviceId; + } + public void setDeviceName(String deviceName) + { + this.deviceName = deviceName; + } + + public String getDeviceName() + { + return deviceName; + } + public void setSerialNumber(String serialNumber) + { + this.serialNumber = serialNumber; + } + + public String getSerialNumber() + { + return serialNumber; + } + public void setIsMonitor(Integer isMonitor) + { + this.isMonitor = isMonitor; + } + + public Integer getIsMonitor() + { + return isMonitor; + } + public void setMode(Integer mode) + { + this.mode = mode; + } + + public Integer getMode() + { + return mode; + } + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getUserId() + { + return userId; + } + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getUserName() + { + return userName; + } + public void setTenantId(Long tenantId) + { + this.tenantId = tenantId; + } + + public Long getTenantId() + { + return tenantId; + } + public void setTenantName(String tenantName) + { + this.tenantName = tenantName; + } + + public String getTenantName() + { + return tenantName; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("logId", getLogId()) + .append("identity", getIdentity()) + .append("modelName", getModelName()) + .append("logType", getLogType()) + .append("logValue", getLogValue()) + .append("deviceId", getDeviceId()) + .append("deviceName", getDeviceName()) + .append("serialNumber", getSerialNumber()) + .append("isMonitor", getIsMonitor()) + .append("mode", getMode()) + .append("userId", getUserId()) + .append("userName", getUserName()) + .append("tenantId", getTenantId()) + .append("tenantName", getTenantName()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/FunctionLog.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/FunctionLog.java new file mode 100644 index 00000000..84bea9c7 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/FunctionLog.java @@ -0,0 +1,121 @@ +package com.fastbee.iot.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 设备服务下发日志对象 iot_function_log + * + * @author kerwincui + * @date 2022-10-22 + */ +@ApiModel(value = "FunctionLog", description = "设备服务下发日志对象 iot_function_log") +@Data +public class FunctionLog +{ + private static final long serialVersionUID = 1L; + + /** 设备日志ID */ + @ApiModelProperty("设备日志ID") + private Long id; + + /** 标识符 */ + @ApiModelProperty("标识符") + @Excel(name = "标识符") + private String identify; + + /** 仅用于查询时筛选条件 */ + private String prefixIdentify; + + @ApiModelProperty("物模型名称") + private String modelName; + + + /** 类型(1=服务下发,2=属性获取,3.OTA升级) */ + @ApiModelProperty(value = "类型", notes = "(1=服务下发,2=属性获取,3.OTA升级)") + @Excel(name = "类型", readConverterExp = "1==服务下发,2=属性获取,3.OTA升级") + private Integer funType; + + /** 日志值 */ + @ApiModelProperty("日志值") + @Excel(name = "日志值") + private String funValue; + /** + * 显示值 + */ + @ApiModelProperty("显示值") + private String showValue; + + /** 消息id */ + @ApiModelProperty("消息id") + @Excel(name = "消息id") + private String messageId; + + /** 设备名称 */ + @ApiModelProperty("设备名称") + @Excel(name = "设备名称") + private String deviceName; + + /** 设备编号 */ + @ApiModelProperty("设备编号") + @Excel(name = "设备编号") + private String serialNumber; + + /** 模式(1=影子模式,2=在线模式,3=其他) */ + @ApiModelProperty(value = "模式", notes = "(1=影子模式,2=在线模式,3=其他)") + @Excel(name = "模式(1=影子模式,2=在线模式,3=其他)") + private Integer mode; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** 下发结果描述 */ + @ApiModelProperty("下发结果描述") + @Excel(name = "下发结果描述") + private String resultMsg; + + /** 下发结果代码 */ + @ApiModelProperty("下发结果代码") + @Excel(name = "下发结果代码") + private Integer resultCode; + + /** + * 设备回复时间 + */ + @ApiModelProperty("设备回复时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date replyTime; + + /** 创建者 */ + @ApiModelProperty("创建者") + private String createBy; + + /** 创建时间 */ + @ApiModelProperty("创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date beginTime; + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date endTime; + private List list; + /** 备注 */ + private String remark; + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Group.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Group.java new file mode 100644 index 00000000..04e0f631 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Group.java @@ -0,0 +1,120 @@ +package com.fastbee.iot.domain; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 设备分组对象 iot_group + * + * @author kerwincui + * @date 2021-12-16 + */ +@ApiModel(value = "Group", description = "设备分组对象 iot_group") +public class Group extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 分组ID */ + @ApiModelProperty("分组ID") + private Long groupId; + + /** 分组名称 */ + @ApiModelProperty("分组名称") + @Excel(name = "分组名称") + private String groupName; + + /** 分组排序 */ + @ApiModelProperty("分组排序") + @Excel(name = "分组排序") + private Long groupOrder; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** 用户昵称 */ + @ApiModelProperty("用户昵称") + @Excel(name = "用户昵称") + private String userName; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志") + private String delFlag; + + public void setGroupId(Long groupId) + { + this.groupId = groupId; + } + + public Long getGroupId() + { + return groupId; + } + public void setGroupName(String groupName) + { + this.groupName = groupName; + } + + public String getGroupName() + { + return groupName; + } + public void setGroupOrder(Long groupOrder) + { + this.groupOrder = groupOrder; + } + + public Long getGroupOrder() + { + return groupOrder; + } + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getUserId() + { + return userId; + } + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getUserName() + { + return userName; + } + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("groupId", getGroupId()) + .append("groupName", getGroupName()) + .append("groupOrder", getGroupOrder()) + .append("userId", getUserId()) + .append("userName", getUserName()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/News.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/News.java new file mode 100644 index 00000000..bbcadada --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/News.java @@ -0,0 +1,195 @@ +package com.fastbee.iot.domain; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 新闻资讯对象 news + * + * @author kerwincui + * @date 2022-04-09 + */ +@ApiModel(value = "News", description = "新闻资讯对象 news") +public class News extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 新闻ID */ + @ApiModelProperty("新闻ID") + private Long newsId; + + /** 标题 */ + @ApiModelProperty("标题") + @Excel(name = "标题") + private String title; + + /** 内容 */ + @ApiModelProperty("内容") + @Excel(name = "内容") + private String content; + + /** 图片 */ + @ApiModelProperty("图片") + @Excel(name = "图片") + private String imgUrl; + + /** 置顶 */ + @ApiModelProperty("是否置顶") + @Excel(name = "置顶") + private Integer isTop; + + /** 广告 */ + @ApiModelProperty("广告") + @Excel(name = "广告") + private Integer isBanner; + + /** 分类ID */ + @ApiModelProperty("分类ID") + @Excel(name = "分类ID") + private Long categoryId; + + /** 分类名称 */ + @ApiModelProperty("分类名称") + @Excel(name = "分类名称") + private String categoryName; + + /** 发布 */ + @ApiModelProperty("新闻状态(0-未发布,1-已发布)") + @Excel(name = "发布") + private Integer status; + + /** 作者 */ + @ApiModelProperty("作者") + @Excel(name = "作者") + private String author; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志") + private String delFlag; + + public void setNewsId(Long newsId) + { + this.newsId = newsId; + } + + public Long getNewsId() + { + return newsId; + } + public void setTitle(String title) + { + this.title = title; + } + + public String getTitle() + { + return title; + } + public void setContent(String content) + { + this.content = content; + } + + public String getContent() + { + return content; + } + public void setImgUrl(String imgUrl) + { + this.imgUrl = imgUrl; + } + + public String getImgUrl() + { + return imgUrl; + } + public void setIsTop(Integer isTop) + { + this.isTop = isTop; + } + + public Integer getIsTop() + { + return isTop; + } + public void setIsBanner(Integer isBanner) + { + this.isBanner = isBanner; + } + + public Integer getIsBanner() + { + return isBanner; + } + public void setCategoryId(Long categoryId) + { + this.categoryId = categoryId; + } + + public Long getCategoryId() + { + return categoryId; + } + public void setCategoryName(String categoryName) + { + this.categoryName = categoryName; + } + + public String getCategoryName() + { + return categoryName; + } + public void setStatus(Integer status) + { + this.status = status; + } + + public Integer getStatus() + { + return status; + } + public void setAuthor(String author) + { + this.author = author; + } + + public String getAuthor() + { + return author; + } + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("newsId", getNewsId()) + .append("title", getTitle()) + .append("content", getContent()) + .append("imgUrl", getImgUrl()) + .append("isTop", getIsTop()) + .append("isBanner", getIsBanner()) + .append("categoryId", getCategoryId()) + .append("categoryName", getCategoryName()) + .append("status", getStatus()) + .append("author", getAuthor()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/NewsCategory.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/NewsCategory.java new file mode 100644 index 00000000..dfdd8edd --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/NewsCategory.java @@ -0,0 +1,91 @@ +package com.fastbee.iot.domain; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 新闻分类对象 news_category + * + * @author kerwincui + * @date 2022-04-09 + */ +@ApiModel(value = "NewsCategory", description = "新闻分类对象 news_category") +public class NewsCategory extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 分类ID */ + @ApiModelProperty("分类ID") + @Excel(name = "分类ID") + private Long categoryId; + + /** 分类名字 */ + @ApiModelProperty("分类名字") + @Excel(name = "分类名字") + private String categoryName; + + /** 显示顺序 */ + @ApiModelProperty("显示顺序") + @Excel(name = "显示顺序") + private Integer orderNum; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志") + private String delFlag; + + public void setCategoryId(Long categoryId) + { + this.categoryId = categoryId; + } + + public Long getCategoryId() + { + return categoryId; + } + public void setCategoryName(String categoryName) + { + this.categoryName = categoryName; + } + + public String getCategoryName() + { + return categoryName; + } + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public Integer getOrderNum() + { + return orderNum; + } + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("categoryId", getCategoryId()) + .append("categoryName", getCategoryName()) + .append("orderNum", getOrderNum()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/OauthClientDetails.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/OauthClientDetails.java new file mode 100644 index 00000000..814d5631 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/OauthClientDetails.java @@ -0,0 +1,206 @@ +package com.fastbee.iot.domain; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 云云对接对象 oauth_client_details + * + * @author kerwincui + * @date 2022-02-07 + */ +@ApiModel(value = "OauthClientDetails", description = "云云对接对象 oauth_client_details") +public class OauthClientDetails extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 客户端ID */ + @ApiModelProperty("客户端ID") + @Excel(name = "客户端ID") + private String clientId; + + /** 资源 */ + @ApiModelProperty("资源") + @Excel(name = "资源") + private String resourceIds; + + /** 客户端秘钥 */ + @ApiModelProperty("客户端秘钥") + private String clientSecret; + + /** 权限范围 */ + @ApiModelProperty("权限范围") + @Excel(name = "权限范围") + private String scope; + + /** 授权模式 */ + @ApiModelProperty("授权模式") + @Excel(name = "授权模式") + private String authorizedGrantTypes; + + /** 回调地址 */ + @ApiModelProperty("回调地址") + @Excel(name = "回调地址") + private String webServerRedirectUri; + + /** 权限 */ + @ApiModelProperty("权限") + @Excel(name = "权限") + private String authorities; + + /** access token有效时间 */ + @ApiModelProperty("access token有效时间") + @Excel(name = "access token有效时间") + private Long accessTokenValidity; + + /** refresh token有效时间 */ + @ApiModelProperty("refresh token有效时间") + @Excel(name = "refresh token有效时间") + private Long refreshTokenValidity; + + /** 预留的字段 */ + @ApiModelProperty("预留的字段") + @Excel(name = "预留的字段") + private String additionalInformation; + + /** 自动授权 */ + @ApiModelProperty("自动授权") + @Excel(name = "自动授权") + private String autoapprove; + + /** 平台 */ + @ApiModelProperty("平台") + @Excel(name = "平台") + private Integer type; + + public void setClientId(String clientId) + { + this.clientId = clientId; + } + + public String getClientId() + { + return clientId; + } + public void setResourceIds(String resourceIds) + { + this.resourceIds = resourceIds; + } + + public String getResourceIds() + { + return resourceIds; + } + public void setClientSecret(String clientSecret) + { + this.clientSecret = clientSecret; + } + + public String getClientSecret() + { + return clientSecret; + } + public void setScope(String scope) + { + this.scope = scope; + } + + public String getScope() + { + return scope; + } + public void setAuthorizedGrantTypes(String authorizedGrantTypes) + { + this.authorizedGrantTypes = authorizedGrantTypes; + } + + public String getAuthorizedGrantTypes() + { + return authorizedGrantTypes; + } + public void setWebServerRedirectUri(String webServerRedirectUri) + { + this.webServerRedirectUri = webServerRedirectUri; + } + + public String getWebServerRedirectUri() + { + return webServerRedirectUri; + } + public void setAuthorities(String authorities) + { + this.authorities = authorities; + } + + public String getAuthorities() + { + return authorities; + } + public void setAccessTokenValidity(Long accessTokenValidity) + { + this.accessTokenValidity = accessTokenValidity; + } + + public Long getAccessTokenValidity() + { + return accessTokenValidity; + } + public void setRefreshTokenValidity(Long refreshTokenValidity) + { + this.refreshTokenValidity = refreshTokenValidity; + } + + public Long getRefreshTokenValidity() + { + return refreshTokenValidity; + } + public void setAdditionalInformation(String additionalInformation) + { + this.additionalInformation = additionalInformation; + } + + public String getAdditionalInformation() + { + return additionalInformation; + } + public void setAutoapprove(String autoapprove) + { + this.autoapprove = autoapprove; + } + + public String getAutoapprove() + { + return autoapprove; + } + public void setType(Integer type) + { + this.type = type; + } + + public Integer getType() + { + return type; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("clientId", getClientId()) + .append("resourceIds", getResourceIds()) + .append("clientSecret", getClientSecret()) + .append("scope", getScope()) + .append("authorizedGrantTypes", getAuthorizedGrantTypes()) + .append("webServerRedirectUri", getWebServerRedirectUri()) + .append("authorities", getAuthorities()) + .append("accessTokenValidity", getAccessTokenValidity()) + .append("refreshTokenValidity", getRefreshTokenValidity()) + .append("additionalInformation", getAdditionalInformation()) + .append("autoapprove", getAutoapprove()) + .append("type", getType()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Product.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Product.java new file mode 100644 index 00000000..7e53ea4c --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Product.java @@ -0,0 +1,318 @@ +package com.fastbee.iot.domain; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 产品对象 iot_product + * + * @author kerwincui + * @date 2021-12-16 + */ +@ApiModel(value = "Product", description = "产品对象 iot_product") +public class Product extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 产品ID */ + @ApiModelProperty("产品ID") + private Long productId; + + /** 产品名称 */ + @ApiModelProperty("产品名称") + @Excel(name = "产品名称") + private String productName; + + /** 产品分类ID */ + @ApiModelProperty("产品分类ID") + @Excel(name = "产品分类ID") + private Long categoryId; + + /** 产品分类名称 */ + @ApiModelProperty("产品分类名称") + @Excel(name = "产品分类名称") + private String categoryName; + + /** 租户ID */ + @ApiModelProperty("租户ID") + @Excel(name = "租户ID") + private Long tenantId; + + /** 租户名称 */ + @ApiModelProperty("租户名称") + @Excel(name = "租户名称") + private String tenantName; + + /** 是否系统通用(0-否,1-是) */ + @ApiModelProperty(value = "是否系统通用", notes = "(0-否,1-是)") + @Excel(name = "是否系统通用", readConverterExp = "0=-否,1-是") + private Integer isSys; + + /** 是否启用授权码(0-否,1-是) */ + @ApiModelProperty(value = "是否启用授权码", notes = "(0-否,1-是)") + @Excel(name = "是否启用授权码", readConverterExp = "0=-否,1-是") + private Integer isAuthorize; + + /** mqtt账号 */ + @ApiModelProperty("mqtt账号") + private String mqttAccount; + + /** mqtt密码 */ + @ApiModelProperty("mqtt密码") + private String mqttPassword; + + /** 产品秘钥 */ + @ApiModelProperty("产品秘钥") + private String mqttSecret; + + /*产品协议编号*/ + @ApiModelProperty("产品协议编号") + private String protocolCode; + + /*产品支持的传输协议,多个的选一个即可*/ + @ApiModelProperty("产品支持的传输协议,多个的选一个即可") + private String transport; + + public String getTransport() { + return transport; + } + + public void setTransport(String transport) { + this.transport = transport; + } + + public String getProtocolCode() { + return protocolCode; + } + + public void setProtocolCode(String protocolCode) { + this.protocolCode = protocolCode; + } + + public String getMqttSecret() { + return mqttSecret; + } + + public void setMqttSecret(String mqttSecret) { + this.mqttSecret = mqttSecret; + } + + /** 状态(1-未发布,2-已发布,不能修改) */ + @ApiModelProperty(value = "状态", notes = "(1-未发布,2-已发布,不能修改)") + @Excel(name = "状态", readConverterExp = "1==未发布,2=已发布,不能修改") + private Integer status; + + /** 设备类型(1-直连设备、2-网关子设备、3-网关设备) */ + @ApiModelProperty(value = "设备类型", notes = "(1-直连设备、2-网关子设备、3-网关设备)") + @Excel(name = "设备类型", readConverterExp = "1=直连设备、2=网关设备、3=监控设备") + private Integer deviceType; + + /** 联网方式(1=-wifi、2-蜂窝(2G/3G/4G/5G)、3-以太网、4-其他) */ + @ApiModelProperty(value = "联网方式", notes = "(1=-wifi、2-蜂窝(2G/3G/4G/5G)、3-以太网、4-其他)") + @Excel(name = "联网方式", readConverterExp = "1=-wifi、2=蜂窝(2G/3G/4G/5G)、3=以太网、4=其他") + private Integer networkMethod; + + /** 认证方式(1-账号密码、2-证书、3-Http) */ + @ApiModelProperty(value = "认证方式", notes = "(1-账号密码、2-证书、3-Http)") + @Excel(name = "认证方式", readConverterExp = "1=账号密码、2=证书、3=Http") + private Integer vertificateMethod; + + /** 图片地址 */ + @ApiModelProperty("图片地址") + private String imgUrl; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty(value = "删除标志", notes = "(0代表存在 2代表删除)") + private String delFlag; + + /** 物模型Json **/ + @ApiModelProperty("物模型Json") + private String thingsModelsJson; + + /**采集点模板id*/ + @ApiModelProperty("采集点模板id") + private Long templateId; + + public Long getTemplateId() { + return templateId; + } + + public void setTemplateId(Long templateId) { + this.templateId = templateId; + } + + public String getThingsModelsJson() { + return thingsModelsJson; + } + + public void setThingsModelsJson(String thingsModelsJson) { + this.thingsModelsJson = thingsModelsJson; + } + + public String getImgUrl() { + return imgUrl; + } + + public void setImgUrl(String imgUrl) { + this.imgUrl = imgUrl; + } + + public void setProductId(Long productId) + { + this.productId = productId; + } + + public Long getProductId() + { + return productId; + } + public void setProductName(String productName) + { + this.productName = productName; + } + + public String getProductName() + { + return productName; + } + public void setCategoryId(Long categoryId) + { + this.categoryId = categoryId; + } + + public Long getCategoryId() + { + return categoryId; + } + public void setCategoryName(String categoryName) + { + this.categoryName = categoryName; + } + + public String getCategoryName() + { + return categoryName; + } + public void setTenantId(Long tenantId) + { + this.tenantId = tenantId; + } + + public Long getTenantId() + { + return tenantId; + } + + public void setTenantName(String tenantName) + { + this.tenantName = tenantName; + } + public String getTenantName() + { + return tenantName; + } + + public void setIsSys(Integer isSys) + { + this.isSys = isSys; + } + public Integer getIsSys() + { + return isSys; + } + + public void setIsAuthorize(Integer isAuthorize) {this.isAuthorize = isAuthorize;} + public Integer getIsAuthorize() {return isAuthorize;} + + public void setMqttAccount(String mqttAccount) + { + this.mqttAccount = mqttAccount; + } + public String getMqttAccount() + { + return mqttAccount; + } + + public void setMqttPassword(String mqttPassword) + { + this.mqttPassword = mqttPassword; + } + public String getMqttPassword() + { + return mqttPassword; + } + + public void setStatus(Integer status) + { + this.status = status; + } + + public Integer getStatus() + { + return status; + } + public void setDeviceType(Integer deviceType) + { + this.deviceType = deviceType; + } + + public Integer getDeviceType() + { + return deviceType; + } + public void setNetworkMethod(Integer networkMethod) + { + this.networkMethod = networkMethod; + } + + public Integer getNetworkMethod() + { + return networkMethod; + } + public void setVertificateMethod(Integer vertificateMethod) + { + this.vertificateMethod = vertificateMethod; + } + + public Integer getVertificateMethod() + { + return vertificateMethod; + } + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("productId", getProductId()) + .append("productName", getProductName()) + .append("categoryId", getCategoryId()) + .append("categoryName", getCategoryName()) + .append("tenantId", getTenantId()) + .append("tenantName", getTenantName()) + .append("isSys", getIsSys()) + .append("isAuthorize", getIsAuthorize()) + .append("status", getStatus()) + .append("deviceType", getDeviceType()) + .append("networkMethod", getNetworkMethod()) + .append("vertificateMethod", getVertificateMethod()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ProductAuthorize.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ProductAuthorize.java new file mode 100644 index 00000000..6a45e901 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ProductAuthorize.java @@ -0,0 +1,171 @@ +package com.fastbee.iot.domain; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 产品授权码对象 iot_product_authorize + * + * @author kami + * @date 2022-04-11 + */ +@ApiModel(value = "ProductAuthorize", description = "产品授权码对象 iot_product_authorize") +public class ProductAuthorize extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 授权码ID */ + @ApiModelProperty("授权码ID") + private Long authorizeId; + + /** 授权码 */ + @ApiModelProperty("授权码") + @Excel(name = "授权码") + private String authorizeCode; + + /** 产品ID */ + @ApiModelProperty("产品ID") + @Excel(name = "产品ID") + private Long productId; + + /** 设备ID */ + @ApiModelProperty("设备ID") + @Excel(name = "设备ID") + private Long deviceId; + + /** 设备编号 */ + @ApiModelProperty("设备编号") + @Excel(name = "设备编号") + private String serialNumber; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** 用户名称 */ + @ApiModelProperty("用户名称") + @Excel(name = "用户名称") + private String userName; + + /** 状态(1-未发布,2-已发布,不能修改) */ + @ApiModelProperty(value = "状态", notes = "(1-未发布,2-已发布,不能修改)") + @Excel(name = "状态", readConverterExp = "1=未使用,2=已使用") + private Integer status; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志") + private String delFlag; + + public ProductAuthorize() { + } + + public ProductAuthorize(String authorizeCode, Long productId) { + this.authorizeCode = authorizeCode; + this.productId = productId; + } + + public void setStatus(Integer status) + { + this.status = status; + } + public Integer getStatus() + { + return status; + } + public void setAuthorizeId(Long authorizeId) + { + this.authorizeId = authorizeId; + } + + public Long getAuthorizeId() + { + return authorizeId; + } + public void setAuthorizeCode(String authorizeCode) + { + this.authorizeCode = authorizeCode; + } + + public String getAuthorizeCode() + { + return authorizeCode; + } + public void setProductId(Long productId) + { + this.productId = productId; + } + + public Long getProductId() + { + return productId; + } + public void setDeviceId(Long deviceId) + { + this.deviceId = deviceId; + } + + public Long getDeviceId() + { + return deviceId; + } + public void setSerialNumber(String serialNumber) + { + this.serialNumber = serialNumber; + } + + public String getSerialNumber() + { + return serialNumber; + } + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getUserId() + { + return userId; + } + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getUserName() + { + return userName; + } + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("authorizeId", getAuthorizeId()) + .append("authorizeCode", getAuthorizeCode()) + .append("productId", getProductId()) + .append("deviceId", getDeviceId()) + .append("serialNumber", getSerialNumber()) + .append("userId", getUserId()) + .append("userName", getUserName()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Protocol.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Protocol.java new file mode 100644 index 00000000..39eecd13 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/Protocol.java @@ -0,0 +1,47 @@ +package com.fastbee.iot.domain; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +/** + * 协议管理model + * @author gsb + * @date 2022/10/19 15:35 + */ +@ApiModel(value = "Protocol", description = "协议管理对象 iot_protocol") +@Data +public class Protocol { + + @ApiModelProperty("主键ID") + private Long id; + /*协议编码*/ + @ApiModelProperty("协议编码") + private String protocolCode; + /*协议名称*/ + @ApiModelProperty("协议名称") + private String protocolName; + /*协议jar包,js包 c程序上传地址*/ + @ApiModelProperty("协议jar包,js包 c程序上传地址") + private String protocolFileUrl; + /*协议类型 0:未知 1:jar 2:js 3:C*/ + @ApiModelProperty("协议类型 0:未知 1:jar 2:js 3:C") + private Integer protocolType; + /*协议文件摘要(文件的md5)*/ + @ApiModelProperty("协议文件摘要(文件的md5)") + private String jarSign; + /*创建时间*/ + @ApiModelProperty("创建时间") + private Date createTime; + /*更新时间*/ + @ApiModelProperty("更新时间") + private Date updateTime; + /*0:草稿 1:启用 2:停用*/ + @ApiModelProperty("0:草稿 1:启用 2:停用") + private Integer protocolStatus; + /*'0:正常 1:删除*/ + @ApiModelProperty("'0:正常 1:删除") + private Integer delFlag; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/SocialPlatform.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/SocialPlatform.java new file mode 100644 index 00000000..d3f40b5f --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/SocialPlatform.java @@ -0,0 +1,189 @@ +package com.fastbee.iot.domain; + +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.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; + +/** + * 第三方登录平台控制对象 iot_social_platform + * + * @author kerwincui + * @date 2022-04-11 + */ +@ApiModel(value = "SocialPlatform", description = "第三方登录平台控制对象 iot_social_platform") +public class SocialPlatform extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** + * 第三方登录平台主键 + */ + @ApiModelProperty("第三方登录平台主键") + private Long socialPlatformId; + + /** + * 第三方登录平台 + */ + @ApiModelProperty("第三方登录平台") + @Excel(name = "第三方登录平台") + private String platform; + + /** + * 0、内部上线 1、上线 2、下线 + */ + @ApiModelProperty("0、内部上线 1、上线 2、下线") + @Excel(name = "0、内部上线 1、上线 2、下线") + private String status; + + /** + * 第三方平台申请Id + */ + @ApiModelProperty("第三方平台申请Id") + @Excel(name = "第三方平台申请Id") + private String clientId; + + /** + * 第三方平台密钥 + */ + @ApiModelProperty("第三方平台密钥") + @Excel(name = "第三方平台密钥") + private String secretKey; + + /** + * 用户认证后跳转地址 + */ + @ApiModelProperty("用户认证后跳转地址") + @Excel(name = "用户认证后跳转地址") + private String redirectUri; + + /** + * 删除标记位(0代表存在,2代表删除) + */ + @ApiModelProperty("删除标记位(0代表存在,2代表删除)") + private String delFlag; + + /** + * 绑定注册登录uri + */ + @ApiModelProperty("绑定注册登录uri") + @Excel(name = "绑定注册登录uri") + private String bindUri; + + /** + * 跳转登录uri + */ + @ApiModelProperty("跳转登录uri") + @Excel(name = "跳转登录uri") + private String redirectLoginUri; + + /** + * 错误提示uri + */ + @ApiModelProperty("错误提示uri") + @Excel(name = "错误提示uri") + private String errorMsgUri; + + public Long getSocialPlatformId() { + return socialPlatformId; + } + + public void setSocialPlatformId(Long socialPlatformId) { + this.socialPlatformId = socialPlatformId; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public String getRedirectUri() { + return redirectUri; + } + + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } + + public String getDelFlag() { + return delFlag; + } + + public void setDelFlag(String delFlag) { + this.delFlag = delFlag; + } + + public String getBindUri() { + return bindUri; + } + + public void setBindUri(String bindUri) { + this.bindUri = bindUri; + } + + public String getRedirectLoginUri() { + return redirectLoginUri; + } + + public void setRedirectLoginUri(String redirectLoginUri) { + this.redirectLoginUri = redirectLoginUri; + } + + public String getErrorMsgUri() { + return errorMsgUri; + } + + public void setErrorMsgUri(String errorMsgUri) { + this.errorMsgUri = errorMsgUri; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("socialPlatformId", getSocialPlatformId()) + .append("platform", getPlatform()) + .append("status", getStatus()) + .append("clientId", getClientId()) + .append("secretKey", getSecretKey()) + .append("redirectUri", getRedirectUri()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateTime", getUpdateTime()) + .append("updateBy", getUpdateBy()) + .append("remark", getRemark()) + .append("bindUri", getBindUri()) + .append("redirectLoginUri", getRedirectLoginUri()) + .append("errorMsgUri", getErrorMsgUri()) + .toString(); + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/SocialUser.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/SocialUser.java new file mode 100644 index 00000000..bcc51187 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/SocialUser.java @@ -0,0 +1,188 @@ +package com.fastbee.iot.domain; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 用户第三方用户信息对象 iot_social_user + * + * @author json + * @date 2022-04-18 + */ +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "SocialUser", description = "用户第三方用户信息对象 iot_social_user") +@Accessors(chain = true) +@Data +public class SocialUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 第三方系统用户表主键 */ + @ApiModelProperty("第三方系统用户表主键") + private Long socialUserId; + + /** 第三方系统的唯一ID */ + @ApiModelProperty("第三方系统的唯一ID") + @Excel(name = "第三方系统的唯一ID") + private String uuid; + + /** 第三方用户来源 */ + @ApiModelProperty("第三方用户来源") + @Excel(name = "第三方用户来源") + private String source; + + /** 用户的授权令牌 */ + @ApiModelProperty("用户的授权令牌") + @Excel(name = "用户的授权令牌") + private String accessToken; + + /** 第三方用户的授权令牌的有效期(部分平台可能没有) */ + @ApiModelProperty(value = "第三方用户的授权令牌的有效期", notes = "(部分平台可能没有)") + @Excel(name = "第三方用户的授权令牌的有效期", readConverterExp = "部=分平台可能没有") + private Long expireIn; + + /** 刷新令牌(部分平台可能没有) */ + @ApiModelProperty(value = "刷新令牌", notes = "(部分平台可能没有)") + @Excel(name = "刷新令牌(部分平台可能没有)") + private String refreshToken; + + /** 第三方用户的 open id(部分平台可能没有) */ + @ApiModelProperty(value = "第三方用户的 open id", notes = "(部分平台可能没有)") + @Excel(name = "第三方用户的 open id", readConverterExp = "部=分平台可能没有") + private String openId; + + /** 第三方用户的 ID(部分平台可能没有) */ + @ApiModelProperty(value = "第三方用户的 ID", notes = "(部分平台可能没有)") + @Excel(name = "第三方用户的 ID(部分平台可能没有)") + private String uid; + + /** 个别平台的授权信息(部分平台可能没有) */ + @ApiModelProperty(value = "个别平台的授权信息", notes = "(部分平台可能没有)") + @Excel(name = "个别平台的授权信息", readConverterExp = "部=分平台可能没有") + private String accessCode; + + /** 第三方用户的 union id(部分平台可能没有) */ + @ApiModelProperty(value = "第三方用户的 union id", notes = "(部分平台可能没有)") + @Excel(name = "第三方用户的 union id(部分平台可能没有)") + private String unionId; + + /** 第三方用户授予的权限(部分平台可能没有) */ + @ApiModelProperty(value = "第三方用户授予的权限", notes = "(部分平台可能没有)") + @Excel(name = "第三方用户授予的权限(部分平台可能没有)") + private String scope; + + /** 个别平台的授权信息(部分平台可能没有) */ + @ApiModelProperty(value = "个别平台的授权信息", notes = "(部分平台可能没有)") + @Excel(name = "个别平台的授权信息", readConverterExp = "部=分平台可能没有") + private String tokenType; + + /** id token(部分平台可能没有) */ + @ApiModelProperty(value = "id token", notes = "(部分平台可能没有)") + @Excel(name = "id token", readConverterExp = "部=分平台可能没有") + private String idToken; + + /** 小米平台用户的附带属性(部分平台可能没有) */ + @ApiModelProperty(value = "小米平台用户的附带属性", notes = "(部分平台可能没有)") + @Excel(name = "小米平台用户的附带属性", readConverterExp = "部=分平台可能没有") + private String macAlgorithm; + + /** 小米平台用户的附带属性(部分平台可能没有) */ + @ApiModelProperty(value = "小米平台用户的附带属性", notes = "(部分平台可能没有) ") + @Excel(name = "小米平台用户的附带属性(部分平台可能没有)") + private String macKey; + + /** 用户的授权code(部分平台可能没有) */ + @ApiModelProperty(value = "用户的授权code", notes = "(部分平台可能没有)") + @Excel(name = "用户的授权code", readConverterExp = "部=分平台可能没有") + private String code; + + /** Twitter平台用户的附带属性(部分平台可能没有) */ + @ApiModelProperty(value = "Twitter平台用户的附带属性", notes = "(部分平台可能没有) ") + @Excel(name = "Twitter平台用户的附带属性(部分平台可能没有)") + private String oauthToken; + + /** Twitter平台用户的附带属性(部分平台可能没有) */ + @ApiModelProperty(value = "Twitter平台用户的附带属性", notes = "(部分平台可能没有)") + @Excel(name = "Twitter平台用户的附带属性(部分平台可能没有)") + private String oauthTokenSecret; + + /** 删除标记位(0代表存在,2代表删除) */ + @ApiModelProperty("删除标记位") + private String delFlag; + + /** 绑定状态(0:未绑定,1:绑定) */ + @ApiModelProperty(value = "绑定状态", notes = "(0:未绑定,1:绑定)") + @Excel(name = "绑定状态(0:未绑定,1:绑定)") + private String status; + + /** 用户ID */ + @ApiModelProperty("用户ID") + @Excel(name = "用户ID") + private Long sysUserId; + + /** 用户名 */ + @ApiModelProperty("用户名") + @Excel(name = "用户名") + private String username; + + /** 用户昵称 */ + @ApiModelProperty("用户昵称") + @Excel(name = "用户昵称") + private String nickname; + + /** 用户头像 */ + @ApiModelProperty("用户头像") + @Excel(name = "用户头像") + private String avatar; + + /** 用户性别 */ + @ApiModelProperty("用户性别") + @Excel(name = "用户性别") + private Integer gender; + /** 三方登录具体来源 */ + @ApiModelProperty("三方登录具体来源") + @Excel(name = "三方登录具体来源") + private String sourceClient; + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("socialUserId", getSocialUserId()) + .append("uuid", getUuid()) + .append("source", getSource()) + .append("accessToken", getAccessToken()) + .append("expireIn", getExpireIn()) + .append("refreshToken", getRefreshToken()) + .append("openId", getOpenId()) + .append("uid", getUid()) + .append("accessCode", getAccessCode()) + .append("unionId", getUnionId()) + .append("scope", getScope()) + .append("tokenType", getTokenType()) + .append("idToken", getIdToken()) + .append("macAlgorithm", getMacAlgorithm()) + .append("macKey", getMacKey()) + .append("code", getCode()) + .append("oauthToken", getOauthToken()) + .append("oauthTokenSecret", getOauthTokenSecret()) + .append("createTime", getCreateTime()) + .append("createBy", getCreateBy()) + .append("updateTime", getUpdateTime()) + .append("updateBy", getUpdateBy()) + .append("delFlag", getDelFlag()) + .append("status", getStatus()) + .append("sysUserId", getSysUserId()) + .append("username", getUsername()) + .append("nickname", getNickname()) + .append("avatar", getAvatar()) + .append("gender", getGender()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ThingsModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ThingsModel.java new file mode 100644 index 00000000..c78737d8 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ThingsModel.java @@ -0,0 +1,452 @@ +package com.fastbee.iot.domain; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 物模型对象 iot_things_model + * + * @author kerwincui + * @date 2023-01-14 + */ +@ApiModel(value = "ThingsModel", description = "物模型对象 iot_things_model") +public class ThingsModel extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 物模型ID */ + @ApiModelProperty("物模型ID") + private Long modelId; + + /** 物模型名称 */ + @ApiModelProperty("物模型名称") + @Excel(name = "物模型名称" ,prompt = "必填") + private String modelName; + + /** 产品ID */ + @ApiModelProperty("产品ID") + private Long productId; + + /** 产品名称 */ + @ApiModelProperty("产品名称") + private String productName; + + /** 租户ID */ + @ApiModelProperty("租户ID") + private Long tenantId; + + /** 租户名称 */ + @ApiModelProperty("租户名称") + private String tenantName; + + /** 标识符,产品下唯一 */ + @ApiModelProperty("标识符,产品下唯一") + @Excel(name = "标识符",prompt = "modbus不填默认为寄存器地址") + private String identifier; + + /** 模型类别(1-属性,2-功能,3-事件) */ + @ApiModelProperty(value = "模型类别", notes = "(1-属性,2-功能,3-事件)") + @Excel(name = "模型类别", readConverterExp = "1=属性,2=功能,3=事件,4=属性和功能", + prompt ="1=属性,2-功能,3-事件") + private Integer type; + + /** 从机id */ + @ApiModelProperty("从机id") + private Integer tempSlaveId; + + /** 寄存器地址 */ + @ApiModelProperty("寄存器地址") + @Excel(name = "寄存器地址" , prompt = "可填写10进制或16进制,16带H,例如:0020H") + private String regStr; + + /** 计算公式 */ + @ApiModelProperty("计算公式") + @Excel(name = "计算公式",prompt = "选填,例如:%s*10,%s是占位符") + private String formula; + + /** 数据定义 */ + //@Excel(name = "数据定义") + @ApiModelProperty("数据定义") + private String specs; + + + /** 控制公式 */ + @ApiModelProperty("控制公式") + @Excel(name = "控制公式",prompt = "选填,例如:%s*10,%s是占位符") + private String reverseFormula; + + /** 寄存器地址值 */ + @ApiModelProperty("寄存器地址值") + private Integer regAddr; + + + /** 是否图表显示(0-否,1-是) */ + @ApiModelProperty(value = "是否图表显示", notes = "(0-否,1-是)") + @Excel(name = "是否图表显示", readConverterExp = "0=否,1=是") + private Integer isChart; + + /** 是否历史存储(0-否,1-是) */ + @ApiModelProperty(value = "是否历史存储", notes = "(0-否,1-是)") + @Excel(name = "是否历史存储", readConverterExp = "0=否,1=是") + private Integer isHistory; + + /** 是否实时监测(0-否,1-是) */ + @ApiModelProperty(value = "是否实时监测", notes = "(0-否,1-是) ") + @Excel(name = "是否实时监测", readConverterExp = "0=否,1=是") + private Integer isMonitor; + + /** 是否分享设备权限(0-否,1-是) */ + @ApiModelProperty(value = "是否分享设备权限", notes = "(0-否,1-是) ") + @Excel(name = "是否分享设备权限", readConverterExp = "0=否,1=是") + private Integer isSharePerm; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志") + private String delFlag; + + @Excel(name = "单位") + private String unit; + + /** 数值类型 1.数值 2.选项 */ + @ApiModelProperty(value = "数值类型", notes = "1.数值 2.选项") + @Excel(name = "解析类型" ,prompt = "解析类型 INT,INT16,UINT16") + private Integer valueType; + + /** 数据类型(integer、decimal、string、bool、array、enum) */ + @ApiModelProperty(value = "数据类型", notes = "(integer、decimal、string、bool、array、enum)") + @Excel(name = "数据类型", prompt = "integer、decimal、string、bool、array、enum") + private String datatype; + + @Excel(name = "有效值范围") + private String limitValue; + + /** 位定义选项 */ + @ApiModelProperty("位定义选项") + @Excel(name = "位定义选项") + private String bitOption; + + /** 是否只读数据(0-否,1-是) */ + @ApiModelProperty(value = "是否只读数据", notes = "(0-否,1-是)") + @Excel(name = "是否只读", readConverterExp = "0=否,1=是",prompt = "0=否,1=是") + private Integer isReadonly; + + @ApiModelProperty("是否是计算参数,默认否") + @Excel(name = "是否是计算参数,默认否",readConverterExp = "0=否,1=是") + private Integer isParams; + + @ApiModelProperty("读取寄存器个数") + @Excel(name ="读取寄存器个数") + private Integer quantity; + + @ApiModelProperty("解析类型") + @Excel(name = "解析类型") + private String parseType; + + + public String getParseType() { + return parseType; + } + + public void setParseType(String parseType) { + this.parseType = parseType; + } + + /** + * 功能码 + */ + private String code; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + private Integer modelOrder; + + public Integer getIsParams() { + return isParams; + } + + public void setIsParams(Integer isParams) { + this.isParams = isParams; + } + + public String getLimitValue() { + return limitValue; + } + + public void setLimitValue(String limitValue) { + this.limitValue = limitValue; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public String getRegStr() { + return regStr; + } + + public void setRegStr(String regStr) { + this.regStr = regStr; + } + + public void setModelId(Long modelId) + { + this.modelId = modelId; + } + + public Long getModelId() + { + return modelId; + } + public void setModelName(String modelName) + { + this.modelName = modelName; + } + + public String getModelName() + { + return modelName; + } + public void setProductId(Long productId) + { + this.productId = productId; + } + + public Long getProductId() + { + return productId; + } + public void setProductName(String productName) + { + this.productName = productName; + } + + public String getProductName() + { + return productName; + } + public void setTenantId(Long tenantId) + { + this.tenantId = tenantId; + } + + public Long getTenantId() + { + return tenantId; + } + public void setTenantName(String tenantName) + { + this.tenantName = tenantName; + } + + public String getTenantName() + { + return tenantName; + } + public void setIdentifier(String identifier) + { + this.identifier = identifier; + } + + public String getIdentifier() + { + return identifier; + } + public void setType(Integer type) + { + this.type = type; + } + + public Integer getType() + { + return type; + } + public void setDatatype(String datatype) + { + this.datatype = datatype; + } + + public String getDatatype() + { + return datatype; + } + + public Integer getTempSlaveId() { + return tempSlaveId; + } + + public void setTempSlaveId(Integer tempSlaveId) { + this.tempSlaveId = tempSlaveId; + } + + public void setFormula(String formula) + { + this.formula = formula; + } + + public String getFormula() + { + return formula; + } + public void setSpecs(String specs) + { + this.specs = specs; + } + + public String getSpecs() + { + return specs; + } + public void setIsChart(Integer isChart) + { + this.isChart = isChart; + } + + public Integer getIsChart() + { + return isChart; + } + + public void setIsHistory(Integer isHistory) + { + this.isHistory = isHistory; + } + + public Integer getIsHistory() + { + return isHistory; + } + public void setReverseFormula(String reverseFormula) + { + this.reverseFormula = reverseFormula; + } + + public String getReverseFormula() + { + return reverseFormula; + } + + public Integer getRegAddr() { + return regAddr; + } + + public void setRegAddr(Integer regAddr) { + this.regAddr = regAddr; + } + + public void setIsMonitor(Integer isMonitor) + { + this.isMonitor = isMonitor; + } + + public Integer getIsMonitor() + { + return isMonitor; + } + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + public void setBitOption(String bitOption) + { + this.bitOption = bitOption; + } + + public String getBitOption() + { + return bitOption; + } + public void setValueType(Integer valueType) + { + this.valueType = valueType; + } + + public Integer getValueType() + { + return valueType; + } + public void setIsReadonly(Integer isReadonly) + { + this.isReadonly = isReadonly; + } + + public Integer getIsReadonly() + { + return isReadonly; + } + public void setModelOrder(Integer modelOrder) + { + this.modelOrder = modelOrder; + } + + public Integer getModelOrder() + { + return modelOrder; + } + + public Integer getIsSharePerm() { + return isSharePerm; + } + + public void setIsSharePerm(Integer isSharePerm) { + this.isSharePerm = isSharePerm; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("modelId", getModelId()) + .append("modelName", getModelName()) + .append("productId", getProductId()) + .append("productName", getProductName()) + .append("tenantId", getTenantId()) + .append("tenantName", getTenantName()) + .append("identifier", getIdentifier()) + .append("type", getType()) + .append("datatype", getDatatype()) + .append("tempSlaveId", getTempSlaveId()) + .append("formula", getFormula()) + .append("specs", getSpecs()) + .append("isChart", getIsChart()) + .append("reverseFormula", getReverseFormula()) + .append("regAddr", getRegAddr()) + .append("isMonitor", getIsMonitor()) + .append("delFlag", getDelFlag()) + .append("bitOption", getBitOption()) + .append("valueType", getValueType()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("isReadonly", getIsReadonly()) + .append("modelOrder", getModelOrder()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ThingsModelTemplate.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ThingsModelTemplate.java new file mode 100644 index 00000000..e74d542f --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/ThingsModelTemplate.java @@ -0,0 +1,447 @@ +package com.fastbee.iot.domain; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 通用物模型对象 iot_things_model_template + * + * @author kerwincui + * @date 2023-01-15 + */ +@ApiModel(value = "ThingsModelTemplate", description = "通用物模型对象 iot_things_model_template") +public class ThingsModelTemplate extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 物模型ID */ + @ApiModelProperty("物模型ID") + private Long templateId; + + /** 物模型名称 */ + @ApiModelProperty("物模型名称") + @Excel(name = "物模型名称" ,prompt = "必填") + private String templateName; + + /** 租户ID */ + @ApiModelProperty("租户ID") + private Long tenantId; + + /** 租户名称 */ + @ApiModelProperty("租户名称") + private String tenantName; + + /** 标识符,产品下唯一 */ + @ApiModelProperty("标识符,产品下唯一") + @Excel(name = "标识符",prompt = "modbus不填,默认为寄存器地址") + private String identifier; + + /** 模型类别(1-属性,2-功能,3-事件) */ + @ApiModelProperty(value = "模型类别", notes = "(1-属性,2-功能,3-事件)") + @Excel(name = "模型类别", readConverterExp = "1=属性,2=功能,3=事件",prompt ="1=属性,2-功能,3-事件") + private Integer type; + + /** 从机id */ + @ApiModelProperty("从机id") + private String tempSlaveId; + + /** 寄存器地址 */ + @ApiModelProperty("寄存器地址") + @Excel(name = "寄存器地址" , prompt = "可填写10进制或16进制,16带H,例如:0020H") + private String regStr; + + /** 计算公式 */ + @ApiModelProperty("计算公式") + @Excel(name = "计算公式",prompt = "选填,例如:%s*10,%s是占位符") + private String formula; + + /** 数据定义 */ + //@Excel(name = "数据定义") + @ApiModelProperty("数据定义") + private String specs; + + /** 是否系统通用(0-否,1-是) */ + @ApiModelProperty("是否系统通用(0-否,1-是)") + private Integer isSys; + + /** 控制公式 */ + @ApiModelProperty("控制公式") + @Excel(name = "控制公式",prompt = "选填,例如:%s*10,%s是占位符") + private String reverseFormula; + + /** 寄存器地址值 */ + @ApiModelProperty("寄存器地址值") + private Integer regAddr; + + + + /** 是否图表显示(0-否,1-是) */ + @ApiModelProperty("是否图表显示(0-否,1-是)") + @Excel(name = "是否图表显示", readConverterExp = "0=否,1=是") + private Integer isChart; + + /** 是否历史存储(0-否,1-是) */ + @ApiModelProperty("是否历史存储(0-否,1-是)") + @Excel(name = "是否历史存储", readConverterExp = "0=否,1=是") + private Integer isHistory; + + /** 是否实时监测(0-否,1-是) */ + @ApiModelProperty("是否实时监测(0-否,1-是)") + @Excel(name = "是否实时监测", readConverterExp = "0=否,1=是") + private Integer isMonitor; + + /** 是否分享设备权限(0-否,1-是) */ + @ApiModelProperty(value = "是否分享设备权限", notes = "(0-否,1-是) ") + @Excel(name = "是否分享设备权限", readConverterExp = "0=否,1=是") + private Integer isSharePerm; + + /** 删除标志(0代表存在 2代表删除) */ + @ApiModelProperty("删除标志(0代表存在 2代表删除)") + private String delFlag; + + @Excel(name = "单位") + private String unit; + + /** 数值类型 1.数值 2.选项 */ + @ApiModelProperty("数值类型 1.数值 2.选项") + @Excel(name = "解析类型" ,prompt = "解析类型 INT,INT16,UINT16") + private String valueType; + + /** 数据类型(integer、decimal、string、bool、array、enum) */ + @ApiModelProperty(value = "数据类型", notes = "(integer、decimal、string、bool、array、enum)") + @Excel(name = "数据类型", prompt = "integer、decimal、string、bool、array、enum") + private String datatype; + + @ApiModelProperty("有效值范围") + @Excel(name = "有效值范围") + private String limitValue; + + /** 位定义选项 */ + @ApiModelProperty("位定义选项") + @Excel(name = "位定义选项") + private String bitOption; + + /** 是否只读数据(0-否,1-是) */ + @ApiModelProperty("是否只读数据(0-否,1-是)") + @Excel(name = "是否只读", readConverterExp = "0=否,1=是",prompt = "0=否,1=是") + private Integer isReadonly; + + @ApiModelProperty("是否是计算参数,默认否") + @Excel(name = "是否是计算参数,默认否",readConverterExp = "0=否,1=是") + private Integer isParams; + + @ApiModelProperty("读取寄存器个数") + @Excel(name ="读取寄存器个数") + private Integer quantity; + + @ApiModelProperty("解析类型") + @Excel(name = "解析类型") + private String parseType; + + + public String getParseType() { + return parseType; + } + + public void setParseType(String parseType) { + this.parseType = parseType; + } + + private String oldTempSlaveId; + + /** + * 功能码 + */ + private String code; + + public String getOldTempSlaveId() { + return oldTempSlaveId; + } + + public void setOldTempSlaveId(String oldTempSlaveId) { + this.oldTempSlaveId = oldTempSlaveId; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + private Integer modelOrder; + + public Integer getIsParams() { + return isParams; + } + + public void setIsParams(Integer isParams) { + this.isParams = isParams; + } + + public String getRegStr() { + return regStr; + } + + public void setRegStr(String regStr) { + this.regStr = regStr; + } + + public String getLimitValue() { + return limitValue; + } + + public void setLimitValue(String limitValue) { + this.limitValue = limitValue; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public void setTemplateId(Long templateId) + { + this.templateId = templateId; + } + + public Long getTemplateId() + { + return templateId; + } + public void setTemplateName(String templateName) + { + this.templateName = templateName; + } + + public String getTemplateName() + { + return templateName; + } + public void setTenantId(Long tenantId) + { + this.tenantId = tenantId; + } + + public Long getTenantId() + { + return tenantId; + } + public void setTenantName(String tenantName) + { + this.tenantName = tenantName; + } + + public String getTenantName() + { + return tenantName; + } + public void setIdentifier(String identifier) + { + this.identifier = identifier; + } + + public String getIdentifier() + { + return identifier; + } + public void setType(Integer type) + { + this.type = type; + } + + public Integer getType() + { + return type; + } + public void setDatatype(String datatype) + { + this.datatype = datatype; + } + + public String getDatatype() + { + return datatype; + } + public void setSpecs(String specs) + { + this.specs = specs; + } + + public String getSpecs() + { + return specs; + } + public void setIsSys(Integer isSys) + { + this.isSys = isSys; + } + + public Integer getIsSys() + { + return isSys; + } + public void setIsReadonly(Integer isReadonly) + { + this.isReadonly = isReadonly; + } + + public Integer getIsReadonly() + { + return isReadonly; + } + public void setIsChart(Integer isChart) + { + this.isChart = isChart; + } + + public Integer getIsChart() + { + return isChart; + } + public void setIsHistory(Integer isHistory) + { + this.isHistory = isHistory; + } + + public Integer getIsHistory() + { + return isHistory; + } + + public String getTempSlaveId() { + return tempSlaveId; + } + + public void setTempSlaveId(String tempSlaveId) { + this.tempSlaveId = tempSlaveId; + } + + public void setIsMonitor(Integer isMonitor) + { + this.isMonitor = isMonitor; + } + + public Integer getIsMonitor() + { + return isMonitor; + } + public void setFormula(String formula) + { + this.formula = formula; + } + + public String getFormula() + { + return formula; + } + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + public void setReverseFormula(String reverseFormula) + { + this.reverseFormula = reverseFormula; + } + + public String getReverseFormula() + { + return reverseFormula; + } + public void setRegAddr(Integer regAddr) + { + this.regAddr = regAddr; + } + + public Integer getRegAddr() + { + return regAddr; + } + public void setBitOption(String bitOption) + { + this.bitOption = bitOption; + } + + public String getBitOption() + { + return bitOption; + } + + public String getValueType() { + return valueType; + } + + public void setValueType(String valueType) { + this.valueType = valueType; + } + + public void setModelOrder(Integer modelOrder) + { + this.modelOrder = modelOrder; + } + + public Integer getModelOrder() + { + return modelOrder; + } + + public Integer getIsSharePerm() { + return isSharePerm; + } + + public void setIsSharePerm(Integer isSharePerm) { + this.isSharePerm = isSharePerm; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("templateId", getTemplateId()) + .append("templateName", getTemplateName()) + .append("tenantId", getTenantId()) + .append("tenantName", getTenantName()) + .append("identifier", getIdentifier()) + .append("type", getType()) + .append("datatype", getDatatype()) + .append("specs", getSpecs()) + .append("isSys", getIsSys()) + .append("isReadonly", getIsReadonly()) + .append("isChart", getIsChart()) + .append("tempSlaveId", getTempSlaveId()) + .append("isMonitor", getIsMonitor()) + .append("formula", getFormula()) + .append("delFlag", getDelFlag()) + .append("reverseFormula", getReverseFormula()) + .append("createBy", getCreateBy()) + .append("regAddr", getRegAddr()) + .append("createTime", getCreateTime()) + .append("bitOption", getBitOption()) + .append("updateBy", getUpdateBy()) + .append("valueType", getValueType()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("modelOrder", getModelOrder()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/UserSocialProfile.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/UserSocialProfile.java new file mode 100644 index 00000000..b4161ed6 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/UserSocialProfile.java @@ -0,0 +1,116 @@ +package com.fastbee.iot.domain; + +import com.fastbee.common.annotation.Excel; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +public class UserSocialProfile { + /** + * 第三方系统用户表主键 + */ + private Long socialUserId; + + /** + * 第三方用户来源 + */ + @Excel(name = "第三方用户来源") + private String source; + + /** + * 用户名 + */ + @Excel(name = "用户名") + private String username; + + /** + * 用户昵称 + */ + @Excel(name = "用户昵称") + private String nickname; + + /** + * 用户头像 + */ + @Excel(name = "用户头像") + private String avatar; + + /** + * 绑定状态(0:未绑定,1:绑定) + */ + @Excel(name = "绑定状态(0:未绑定,1:绑定)") + private String status; + + /** + * 第三方具体来源 + */ + private String sourceClient; + + public String getSourceClient() { + return sourceClient; + } + + public void setSourceClient(String sourceClient) { + this.sourceClient = sourceClient; + } + + public Long getSocialUserId() { + return socialUserId; + } + + public void setSocialUserId(Long socialUserId) { + this.socialUserId = socialUserId; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getNickname() { + return nickname; + } + + public void setNickname(String nickname) { + this.nickname = nickname; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("socialUserId", getSocialUserId()) + .append("source", getSource()) + .append("status", getStatus()) + .append("username", getUsername()) + .append("nickname", getNickname()) + .append("avatar", getAvatar()) + .toString(); + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/VarTemp.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/VarTemp.java new file mode 100644 index 00000000..29d924f3 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/VarTemp.java @@ -0,0 +1,149 @@ +package com.fastbee.iot.domain; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 设备采集变量模板对象 iot_var_temp + * + * @author kerwincui + * @date 2022-11-30 + */ +@ApiModel(value = "VarTemp", description = "设备采集变量模板对象 iot_var_temp") +public class VarTemp extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键id */ + @ApiModelProperty("主键id") + private Long templateId; + + /** 模板名称 */ + @ApiModelProperty("模板名称") + @Excel(name = "模板名称") + private String templateName; + + /** $column.columnComment */ + @ApiModelProperty("类型") + private Integer type; + + /** 采集方式 1.云端轮询 2.云端边缘计算 */ + @ApiModelProperty(value = "采集方式", notes = "1.云端轮询 2.云端边缘计算") + @Excel(name = "采集方式 1.云端轮询 2.云端边缘计算") + private Integer pollingMethod; + + /** 从机总数 */ + @ApiModelProperty("从机总数") + @Excel(name = "从机总数") + private Long slaveTotal; + + /** 总采集点数 */ + @ApiModelProperty("总采集点数") + @Excel(name = "总采集点数") + private Long pointTotal; + + /** 是否分享 */ + @ApiModelProperty("是否分享") + @Excel(name = "是否分享") + private Integer share; + + /** 模板所属用户 */ + @ApiModelProperty("模板所属用户") + @Excel(name = "模板所属用户") + private Long userId; + + public void setTemplateId(Long templateId) + { + this.templateId = templateId; + } + + public Long getTemplateId() + { + return templateId; + } + public void setTemplateName(String templateName) + { + this.templateName = templateName; + } + + public String getTemplateName() + { + return templateName; + } + public void setType(Integer type) + { + this.type = type; + } + + public Integer getType() + { + return type; + } + public void setPollingMethod(Integer pollingMethod) + { + this.pollingMethod = pollingMethod; + } + + public Integer getPollingMethod() + { + return pollingMethod; + } + public void setSlaveTotal(Long slaveTotal) + { + this.slaveTotal = slaveTotal; + } + + public Long getSlaveTotal() + { + return slaveTotal; + } + public void setPointTotal(Long pointTotal) + { + this.pointTotal = pointTotal; + } + + public Long getPointTotal() + { + return pointTotal; + } + public void setShare(Integer share) + { + this.share = share; + } + + public Integer getShare() + { + return share; + } + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getUserId() + { + return userId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("templateId", getTemplateId()) + .append("templateName", getTemplateName()) + .append("type", getType()) + .append("pollingMethod", getPollingMethod()) + .append("slaveTotal", getSlaveTotal()) + .append("pointTotal", getPointTotal()) + .append("share", getShare()) + .append("createTime", getCreateTime()) + .append("createBy", getCreateBy()) + .append("updateTime", getUpdateTime()) + .append("updateBy", getUpdateBy()) + .append("userId", getUserId()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/VarTempSalve.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/VarTempSalve.java new file mode 100644 index 00000000..0d39ed27 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/domain/VarTempSalve.java @@ -0,0 +1,94 @@ +package com.fastbee.iot.domain; + +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 com.fastbee.common.annotation.Excel; +import com.fastbee.common.core.domain.BaseEntity; + +/** + * 变量模板设备从机对象 iot_var_temp_salve + * + * @author kerwincui + * @date 2022-12-12 + */ +@ApiModel(value = "VarTempSalve", description = "变量模板设备从机对象 iot_var_temp_salve") +@EqualsAndHashCode(callSuper = true) +@Data +public class VarTempSalve extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键id */ + @ApiModelProperty("主键id") + private Long id; + + /** 关联的模板id */ + @ApiModelProperty("关联的模板id") + @Excel(name = "关联的模板id") + private Long deviceTempId; + + /** 从机编号 */ + @ApiModelProperty("从机编号") + @Excel(name = "从机编号") + private Integer slaveAddr; + + /** + * 功能编码 + */ + @ApiModelProperty("功能编码") + private Integer code; + + /** $column.columnComment */ + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") + private Integer slaveIndex; + + /** 从机ip地址 */ + @ApiModelProperty("从机ip地址") + @Excel(name = "从机ip地址") + private String slaveIp; + + /** 从机名称 */ + @ApiModelProperty("从机名称") + @Excel(name = "从机名称") + private String slaveName; + + /** 从机端口 */ + @ApiModelProperty("从机端口") + @Excel(name = "从机端口") + private Integer slavePort; + + /** 寄存器起始地址(10进制) */ + @ApiModelProperty("寄存器起始地址(10进制)") + @Excel(name = "寄存器起始地址(10进制)") + private Long addrStart; + + /** 寄存器结束地址(10进制) */ + @ApiModelProperty("寄存器结束地址(10进制)") + @Excel(name = "寄存器结束地址(10进制)") + private Long addrEnd; + + /** 寄存器批量读取个数*/ + @ApiModelProperty("寄存器批量读取个数") + private Integer packetLength; + + /** 批量获取轮询时间(默认5分钟) */ + @ApiModelProperty("批量获取轮询时间(默认5分钟)") + @Excel(name = "批量获取轮询时间(默认300s)") + private Long timer; + + /** 状态 0-启动 1-失效 */ + @ApiModelProperty("状态 0-启动 1-失效") + @Excel(name = "状态 0-启动 1-失效") + private Integer status; + + + /** + * 轮询方式 + */ + private Integer pollingMethod; + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/enums/DeviceType.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/enums/DeviceType.java new file mode 100644 index 00000000..c75c4912 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/enums/DeviceType.java @@ -0,0 +1,28 @@ +package com.fastbee.iot.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum DeviceType { + /** + * 未知 + */ + UNKNOWN(0, "未知"), + /** + * 直连设备 + */ + DIRECT_DEVICE(1, "直连设备"), + /** + * 网关设备 + */ + GATEWAY(2, "网关设备"), + /** + * 监控设备 + */ + CAMERA(3, "监控设备"); + + private int code; + private String desc; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/CategoryMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/CategoryMapper.java new file mode 100644 index 00000000..29b86bde --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/CategoryMapper.java @@ -0,0 +1,81 @@ +package com.fastbee.iot.mapper; + +import com.fastbee.iot.domain.Category; +import com.fastbee.iot.model.IdAndName; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 产品分类Mapper接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Repository +public interface CategoryMapper +{ + /** + * 查询产品分类 + * + * @param categoryId 产品分类主键 + * @return 产品分类 + */ + public Category selectCategoryByCategoryId(Long categoryId); + + /** + * 查询产品分类列表 + * + * @param category 产品分类 + * @return 产品分类集合 + */ + public List selectCategoryList(Category category); + + /** + * 查询产品简短分类列表 + * + * @return 产品分类集合 + */ + public List selectCategoryShortList(Category category); + + /** + * 新增产品分类 + * + * @param category 产品分类 + * @return 结果 + */ + public int insertCategory(Category category); + + /** + * 修改产品分类 + * + * @param category 产品分类 + * @return 结果 + */ + public int updateCategory(Category category); + + /** + * 删除产品分类 + * + * @param categoryId 产品分类主键 + * @return 结果 + */ + public int deleteCategoryByCategoryId(Long categoryId); + + /** + * 批量删除产品分类 + * + * @param categoryIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteCategoryByCategoryIds(Long[] categoryIds); + + /** + * 分类下的产品数量 + * + * @param categoryIds 需要删除的数据主键集合 + * @return 结果 + */ + public int productCountInCategorys(Long[] categoryIds); + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/DeviceJobMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/DeviceJobMapper.java new file mode 100644 index 00000000..4f7de507 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/DeviceJobMapper.java @@ -0,0 +1,118 @@ +package com.fastbee.iot.mapper; + +import com.fastbee.iot.domain.DeviceJob; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 调度任务信息 数据层 + * + * @author kerwincui + */ +@Repository +public interface DeviceJobMapper +{ + /** + * 查询调度任务日志集合 + * + * @param job 调度信息 + * @return 操作日志集合 + */ + public List selectJobList(DeviceJob job); + + /** + * 根据设备Ids查询调度任务日志集合 + * + * @param deviceIds 设备ID数组 + * @return 操作日志集合 + */ + public List selectShortJobListByDeviceIds(Long[] deviceIds); + + /** + * 根据告警Ids查询调度任务日志集合 + * + * @param alertIds 告警ID数组 + * @return 操作日志集合 + */ + public List selectShortJobListByAlertIds(Long[] alertIds); + + /** + * 根据场景Ids查询调度任务日志集合 + * + * @param sceneIds 场景ID数组 + * @return 操作日志集合 + */ + public List selectShortJobListBySceneIds(Long[] sceneIds); + + /** + * 查询所有调度任务 + * + * @return 调度任务列表 + */ + public List selectJobAll(); + + /** + * 通过调度ID查询调度任务信息 + * + * @param jobId 调度ID + * @return 角色对象信息 + */ + public DeviceJob selectJobById(Long jobId); + + /** + * 通过调度ID删除调度任务信息 + * + * @param jobId 调度ID + * @return 结果 + */ + public int deleteJobById(Long jobId); + + /** + * 批量删除调度任务信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteJobByIds(Long[] ids); + + /** + * 根据设备Ids批量删除调度任务信息 + * + * @param deviceIds 需要删除的数据ID + * @return 结果 + */ + public int deleteJobByDeviceIds(Long[] deviceIds); + + /** + * 根据告警Ids批量删除调度任务信息 + * + * @param alertIds 需要删除的告警IDs + * @return 结果 + */ + public int deleteJobByAlertIds(Long[] alertIds); + + /** + * 根据场景联动Ids批量删除调度任务信息 + * + * @param sceneIds 需要删除的数据ID + * @return 结果 + */ + public int deleteJobBySceneIds(Long[] sceneIds); + + /** + * 修改调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int updateJob(DeviceJob job); + + /** + * 新增调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int insertJob(DeviceJob job); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/DeviceLogMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/DeviceLogMapper.java new file mode 100644 index 00000000..9f8144a8 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/DeviceLogMapper.java @@ -0,0 +1,86 @@ +package com.fastbee.iot.mapper; + +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.domain.DeviceLog; +import com.fastbee.iot.model.DeviceStatistic; +import com.fastbee.iot.model.HistoryModel; +import com.fastbee.iot.model.MonitorModel; +import com.fastbee.iot.tdengine.service.model.TdLogDto; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; + +import java.util.Date; +import java.util.List; + +/** + * 设备日志Mapper接口 + * + * @author kerwincui + * @date 2022-01-13 + */ +@Repository +public interface DeviceLogMapper +{ + /** + * 查询设备日志 + * + * @param logId 设备日志主键 + * @return 设备日志 + */ + public DeviceLog selectDeviceLogByLogId(Long logId); + + /** + * 查询日志分类总数 + * + * @return 设备日志 + */ + public DeviceStatistic selectCategoryLogCount(Device device); + + /** + * 查询设备监测数据 + * + * @param deviceLog 设备日志 + * @return 设备日志集合 + */ + public List selectMonitorList(DeviceLog deviceLog); + + + /** + * 批量保存图片 + */ + public int saveBatch(@Param("list") List list); + + /** + * 修改设备日志 + * + * @param deviceLog 设备日志 + * @return 结果 + */ + public int updateDeviceLog(DeviceLog deviceLog); + + /** + * 删除设备日志 + * + * @param logId 设备日志主键 + * @return 结果 + */ + public int deleteDeviceLogByLogId(Long logId); + + /** + * 批量删除设备日志 + * + * @param logIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteDeviceLogByLogIds(Long[] logIds); + + /** + * 根据设备Ids批量删除设备日志 + * + * @param deviceNumber 需要删除的数据设备Id + * @return 结果 + */ + public int deleteDeviceLogByDeviceNumber(String deviceNumber); + + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/DeviceMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/DeviceMapper.java new file mode 100644 index 00000000..29983d94 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/DeviceMapper.java @@ -0,0 +1,298 @@ +package com.fastbee.iot.mapper; + +import com.fastbee.common.core.thingsModel.ThingsModelValuesInput; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.model.AuthenticateInputModel; +import com.fastbee.iot.model.DeviceAllShortOutput; +import com.fastbee.iot.model.DeviceMqttVO; +import com.fastbee.iot.model.DeviceRelateAlertLogVO; +import com.fastbee.iot.model.DeviceShortOutput; +import com.fastbee.iot.model.DeviceStatistic; +import com.fastbee.iot.model.ProductAuthenticateModel; +import com.fastbee.iot.model.ThingsModels.ThingsModelValuesOutput; +import com.fastbee.iot.model.UserIdDeviceIdModel; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * 设备Mapper接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Repository +public interface DeviceMapper +{ + /** + * 查询设备 + * + * @param deviceId 设备主键 + * @return 设备 + */ + public Device selectDeviceByDeviceId(Long deviceId); + + /** + * 查询设备和产品总数 + * + * @return 设备 + */ + public DeviceStatistic selectDeviceProductAlertCount(Device device); + + /** + * 根据设备编号查询设备 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + public Device selectDeviceBySerialNumber(String serialNumber); + + + /** + * 根据设备编号查询设备数量 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + public int selectDeviceCountBySerialNumber(String serialNumber); + + /** + * 根据设备编号查询简介设备 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + public Device selectShortDeviceBySerialNumber(String serialNumber); + + /** + * 根据设备编号查询设备认证信息 + * + * @param model 设备编号和产品ID + * @return 设备 + */ + public ProductAuthenticateModel selectProductAuthenticate(AuthenticateInputModel model); + + /** + * 查询设备和运行状态 + * + * @param deviceId 设备主键 + * @return 设备 + */ + public DeviceShortOutput selectDeviceRunningStatusByDeviceId(Long deviceId); + + /** + * 查询设备的物模型值 + * + * @param serialNumber 设备编号 + * @return 设备 + */ + public ThingsModelValuesOutput selectDeviceThingsModelValueBySerialNumber(String serialNumber); + + /** + * 修改设备的物模型值 + * + * @param input 设备ID和物模型值 + * @return 结果 + */ + public int updateDeviceThingsModelValue(ThingsModelValuesInput input); + + + /** + * 查询设备列表 + * + * @param device 设备 + * @return 设备集合 + */ + public List selectDeviceList(Device device); + + /** + * 查询未分配授权码设备列表 + * + * @param device 设备 + * @return 设备集合 + */ + public List selectUnAuthDeviceList(Device device); + + /** + * 查询分组可添加设备分页列表 + * + * @param device 设备 + * @return 设备集合 + */ + public List selectDeviceListByGroup(Device device); + + /** + * 查询设备简短列表 + * + * @param device 设备 + * @return 设备集合 + */ + public List selectDeviceShortList(Device device); + + /** + * 查询所有设备简短列表 + * + * @return 设备集合 + */ + public List selectAllDeviceShortList(Device device); + + /** + * 根据产品ID查询产品下所有设备的编号 + * + * @return 设备集合 + */ + public List selectSerialNumberByProductId(Long productId); + + /** + * 获取产品下面的设备数量 + * + * @param productId 产品 + * @return 结果 + */ + public int selectDeviceCountByProductId(Long productId); + + /** + * 新增设备 + * + * @param device 设备 + * @return 结果 + */ + public int insertDevice(Device device); + + /** + * 修改设备 + * + * @param device 设备 + * @return 结果 + */ + public int updateDevice(Device device); + + /** + * 更新设备状态 + * + * @param device 设备 + * @return 结果 + */ + public int updateDeviceStatus(Device device); + + /** + * 更新固件版本 + * @param device + * @return + */ + public int updateDeviceFirmwareVersion(Device device); + + /** + * 通过设备编号修改设备 + * + * @param device 设备 + * @return 结果 + */ + public int updateDeviceBySerialNumber(Device device); + + /** + * 删除设备 + * + * @param deviceId 设备主键 + * @return 结果 + */ + public int deleteDeviceByDeviceId(Long deviceId); + + /** + * 批量删除设备 + * + * @param deviceIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteDeviceByDeviceIds(Long[] deviceIds); + + /** + * 根据网关编号删除子设备 + * @param gwCode + */ + public void deleteSubDevice(String gwCode); + + /** + * 查询设备序列号的数量 + * @param deviceNum + * @return + */ + public int getDeviceNumCount(String deviceNum); + + /** + * 根据设备IDS删除设备分组 + * @param userDeviceGroupIdModel + * @return + */ + public int deleteDeviceGroupByDeviceId(UserIdDeviceIdModel userDeviceGroupIdModel); + + /** + * 重置设备状态 + * @return 结果 + */ + public int resetDeviceStatus(String deviceNum); + + /** + * 根据设备编号查询协议编码 + * @param serialNumber 设备编号 + * @return + */ + public Map selectProtocolBySerialNumber(String serialNumber); + + /** + * 查询产品下所有设备,返回设备编号 + * @param productId 产品id + * @return + */ + public List selectDevicesByProductId(@Param("productId") Long productId,@Param("hasSub") Integer hasSub); + + /** + * 查询子设备总数 + * @param gwDevCode 网关编号 + * @return 数量 + */ + public Integer getSubDeviceCount(String gwDevCode); + + + /** + * 批量更新设备上线 + * @param devices 设备ids + */ + public void batchChangeOnline(List devices); + + /** + * 批量更新设备下线 + * @param devices 设备ids + */ + public void batchChangeOffline(List devices); + + /** + * 查询在线的modbus网关设备 + * @return + */ + public List selectOnlineModbusDevices(); + + /** + * 获取设备MQTT连接参数 + * @param deviceId 设备id + * @return + */ + DeviceMqttVO selectMqttConnectData(Long deviceId); + + /** + * 查询告警日志相关联信息 + * @param deviceNumbers 设备编号集合 + * @return + */ + List selectDeviceBySerialNumbers(@Param("deviceNumbers") Set deviceNumbers); + + /** + * 查询告警日志相关联信息 + * @param deviceNumber 设备编号 + * @return + */ + DeviceRelateAlertLogVO selectRelateAlertLogBySerialNumber(String deviceNumber); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/DeviceUserMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/DeviceUserMapper.java new file mode 100644 index 00000000..50a8edf1 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/DeviceUserMapper.java @@ -0,0 +1,96 @@ +package com.fastbee.iot.mapper; + +import java.util.List; + +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.iot.domain.DeviceUser; +import com.fastbee.iot.model.UserIdDeviceIdModel; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; + +/** + * 设备用户Mapper接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Repository +public interface DeviceUserMapper +{ + /** + * 查询设备用户 + * + * @param deviceId 设备用户主键 + * @return 设备用户 + */ + public List selectDeviceUserByDeviceId(Long deviceId); + + /** + * 查询设备用户列表 + * + * @param deviceUser 设备用户 + * @return 设备用户集合 + */ + public List selectDeviceUserList(DeviceUser deviceUser); + + /** + * 查询设备分享用户 + * + * @param deviceUser 设备用户 + * @return 设备用户集合 + */ + public SysUser selectShareUser(DeviceUser deviceUser); + + /** + * 新增设备用户 + * + * @param deviceUser 设备用户 + * @return 结果 + */ + public int insertDeviceUser(DeviceUser deviceUser); + + /** + * 修改设备用户 + * + * @param deviceUser 设备用户 + * @return 结果 + */ + public int updateDeviceUser(DeviceUser deviceUser); + + /** + * 删除设备用户 + * + * @param UserIdDeviceIdModel 用户ID和设备ID + * @return 结果 + */ + public int deleteDeviceUserByDeviceId(UserIdDeviceIdModel UserIdDeviceIdModel); + + /** + * 批量删除设备用户 + * + * @param deviceIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteDeviceUserByDeviceIds(Long[] deviceIds); + + /** + * 批量添加设备用户 + * @param deviceUsers 设备用户 + * @return 结果 + */ + public int insertDeviceUserList(List deviceUsers); + + /** + * 根据deviceId 和 userId 查询 + * @param deviceId 设备id + * @param userId 用户id + * @return 结果 + */ + public DeviceUser selectDeviceUserByDeviceIdAndUserId(@Param("deviceId") Long deviceId, @Param("userId") Long userId); + + /** + * 根据deviceId 和 userId 删除设备用户,不包含设备所有者 + */ + public int deleteDeviceUser(DeviceUser deviceUser); + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/EventLogMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/EventLogMapper.java new file mode 100644 index 00000000..7c2099b8 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/EventLogMapper.java @@ -0,0 +1,77 @@ +package com.fastbee.iot.mapper; + +import java.util.List; +import com.fastbee.iot.domain.EventLog; +import org.springframework.stereotype.Repository; + +/** + * 事件日志Mapper接口 + * + * @author kerwincui + * @date 2023-03-28 + */ +@Repository +public interface EventLogMapper +{ + /** + * 查询事件日志 + * + * @param logId 事件日志主键 + * @return 事件日志 + */ + public EventLog selectEventLogByLogId(Long logId); + + /** + * 查询事件日志列表 + * + * @param eventLog 事件日志 + * @return 事件日志集合 + */ + public List selectEventLogList(EventLog eventLog); + + /** + * 新增事件日志 + * + * @param eventLog 事件日志 + * @return 结果 + */ + public int insertEventLog(EventLog eventLog); + + /** + * 批量存储事件日志 + * @param list + */ + public void insertBatch(List list); + + /** + * 修改事件日志 + * + * @param eventLog 事件日志 + * @return 结果 + */ + public int updateEventLog(EventLog eventLog); + + /** + * 删除事件日志 + * + * @param logId 事件日志主键 + * @return 结果 + */ + public int deleteEventLogByLogId(Long logId); + + /** + * 根据设备编号删除事件日志 + * + * @param serialNumber 设备编号 + * @return 结果 + */ + public int deleteEventLogBySerialNumber(String serialNumber); + + /** + * 批量删除事件日志 + * + * @param logIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteEventLogByLogIds(Long[] logIds); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/FunctionLogMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/FunctionLogMapper.java new file mode 100644 index 00000000..56059e51 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/FunctionLogMapper.java @@ -0,0 +1,97 @@ +package com.fastbee.iot.mapper; + +import java.util.List; +import com.fastbee.iot.domain.FunctionLog; +import org.springframework.stereotype.Repository; + +/** + * 设备服务下发日志Mapper接口 + * + * @author kerwincui + * @date 2022-10-22 + */ +@Repository +public interface FunctionLogMapper +{ + /** + * 查询设备服务下发日志 + * + * @param id 设备服务下发日志主键 + * @return 设备服务下发日志 + */ + public FunctionLog selectFunctionLogById(Long id); + + /** + * 查询设备服务下发日志列表 + * + * @param functionLog 设备服务下发日志 + * @return 设备服务下发日志集合 + */ + public List selectFunctionLogList(FunctionLog functionLog); + + /** + * 新增设备服务下发日志 + * + * @param functionLog 设备服务下发日志 + * @return 结果 + */ + public int insertFunctionLog(FunctionLog functionLog); + + /** + * 批量插入数据 + * @param list + */ + public void insertBatch(List list); + + /** + * 修改设备服务下发日志 + * + * @param functionLog 设备服务下发日志 + * @return 结果 + */ + public int updateFunctionLog(FunctionLog functionLog); + + /** + * 删除设备服务下发日志 + * + * @param id 设备服务下发日志主键 + * @return 结果 + */ + public int deleteFunctionLogById(Long id); + + /** + * 根据设备编号删除设备服务下发日志 + * + * @param serialNumber 设备编号 + * @return 结果 + */ + public int deleteFunctionLogBySerialNumber(String serialNumber); + + /** + * 根据标识符前缀和设备编号批量删除日志 + * + * @param + * @return 结果 + */ + public int deleteFunctionLogByPreIdentify(FunctionLog functionLog); + + /** + * 批量删除设备服务下发日志 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteFunctionLogByIds(Long[] ids); + + /** + * 批量更新日志状态值 + * @param log 参数 + */ + public void updateFuncLogBatch(FunctionLog log); + + /** + * 根据消息id更新指令下发状态 + * @param log + */ + public void updateByMessageId(FunctionLog log); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/GroupMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/GroupMapper.java new file mode 100644 index 00000000..642ec037 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/GroupMapper.java @@ -0,0 +1,87 @@ +package com.fastbee.iot.mapper; + +import java.util.List; +import com.fastbee.iot.domain.Group; +import com.fastbee.iot.model.DeviceGroupInput; +import com.fastbee.iot.model.IdOutput; +import org.springframework.stereotype.Repository; + +/** + * 设备分组Mapper接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Repository +public interface GroupMapper +{ + /** + * 查询设备分组 + * + * @param groupId 设备分组主键 + * @return 设备分组 + */ + public Group selectGroupByGroupId(Long groupId); + + /** + * 通过分组ID查询关联的设备ID数组 + * @param groupId + * @return + */ + public List selectDeviceIdsByGroupId(Long groupId); + + /** + * 查询设备分组列表 + * + * @param group 设备分组 + * @return 设备分组集合 + */ + public List selectGroupList(Group group); + + /** + * 新增设备分组 + * + * @param group 设备分组 + * @return 结果 + */ + public int insertGroup(Group group); + + /** + * 分组下批量增加设备分组 + * @param input + * @return + */ + public int insertDeviceGroups(DeviceGroupInput input); + + /** + * 修改设备分组 + * + * @param group 设备分组 + * @return 结果 + */ + public int updateGroup(Group group); + + /** + * 删除设备分组 + * + * @param groupId 设备分组主键 + * @return 结果 + */ + public int deleteGroupByGroupId(Long groupId); + + /** + * 批量删除分组 + * + * @param groupIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteGroupByGroupIds(Long[] groupIds); + + /** + * 批量删除设备分组 + * @param groupIds + * @return + */ + public int deleteDeviceGroupByGroupIds(Long[] groupIds); + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/NewsCategoryMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/NewsCategoryMapper.java new file mode 100644 index 00000000..c6ba69ba --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/NewsCategoryMapper.java @@ -0,0 +1,79 @@ +package com.fastbee.iot.mapper; + +import java.util.List; +import com.fastbee.iot.domain.NewsCategory; +import com.fastbee.iot.model.IdAndName; +import org.springframework.stereotype.Repository; + +/** + * 新闻分类Mapper接口 + * + * @author kerwincui + * @date 2022-04-09 + */ +@Repository +public interface NewsCategoryMapper +{ + /** + * 查询新闻分类 + * + * @param categoryId 新闻分类主键 + * @return 新闻分类 + */ + public NewsCategory selectNewsCategoryByCategoryId(Long categoryId); + + /** + * 查询新闻分类列表 + * + * @param newsCategory 新闻分类 + * @return 新闻分类集合 + */ + public List selectNewsCategoryList(NewsCategory newsCategory); + + /** + * 查询新闻分类简短列表 + * + * @return 新闻分类集合 + */ + public List selectNewsCategoryShortList(); + + /** + * 新增新闻分类 + * + * @param newsCategory 新闻分类 + * @return 结果 + */ + public int insertNewsCategory(NewsCategory newsCategory); + + /** + * 修改新闻分类 + * + * @param newsCategory 新闻分类 + * @return 结果 + */ + public int updateNewsCategory(NewsCategory newsCategory); + + /** + * 删除新闻分类 + * + * @param categoryId 新闻分类主键 + * @return 结果 + */ + public int deleteNewsCategoryByCategoryId(Long categoryId); + + /** + * 批量删除新闻分类 + * + * @param categoryIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteNewsCategoryByCategoryIds(Long[] categoryIds); + + /** + * 分类下的新闻数量 + * + * @param categoryIds 需要删除的数据主键集合 + * @return 结果 + */ + public int newsCountInCategorys(Long[] categoryIds); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/NewsMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/NewsMapper.java new file mode 100644 index 00000000..d37500a5 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/NewsMapper.java @@ -0,0 +1,70 @@ +package com.fastbee.iot.mapper; + +import java.util.List; +import com.fastbee.iot.domain.News; +import org.springframework.stereotype.Repository; + +/** + * 新闻资讯Mapper接口 + * + * @author kerwincui + * @date 2022-04-09 + */ +@Repository +public interface NewsMapper +{ + /** + * 查询新闻资讯 + * + * @param newsId 新闻资讯主键 + * @return 新闻资讯 + */ + public News selectNewsByNewsId(Long newsId); + + /** + * 查询新闻资讯列表 + * + * @param news 新闻资讯 + * @return 新闻资讯集合 + */ + public List selectNewsList(News news); + + /** + * 查询置顶新闻资讯列表 + * + * @return 新闻资讯集合 + */ + public List selectTopNewsList(); + + /** + * 新增新闻资讯 + * + * @param news 新闻资讯 + * @return 结果 + */ + public int insertNews(News news); + + /** + * 修改新闻资讯 + * + * @param news 新闻资讯 + * @return 结果 + */ + public int updateNews(News news); + + /** + * 删除新闻资讯 + * + * @param newsId 新闻资讯主键 + * @return 结果 + */ + public int deleteNewsByNewsId(Long newsId); + + /** + * 批量删除新闻资讯 + * + * @param newsIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteNewsByNewsIds(Long[] newsIds); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/OauthClientDetailsMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/OauthClientDetailsMapper.java new file mode 100644 index 00000000..92681db2 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/OauthClientDetailsMapper.java @@ -0,0 +1,63 @@ +package com.fastbee.iot.mapper; + +import java.util.List; +import com.fastbee.iot.domain.OauthClientDetails; +import org.springframework.stereotype.Repository; + +/** + * 云云对接Mapper接口 + * + * @author kerwincui + * @date 2022-02-07 + */ +@Repository +public interface OauthClientDetailsMapper +{ + /** + * 查询云云对接 + * + * @param clientId 云云对接主键 + * @return 云云对接 + */ + public OauthClientDetails selectOauthClientDetailsByClientId(String clientId); + + /** + * 查询云云对接列表 + * + * @param oauthClientDetails 云云对接 + * @return 云云对接集合 + */ + public List selectOauthClientDetailsList(OauthClientDetails oauthClientDetails); + + /** + * 新增云云对接 + * + * @param oauthClientDetails 云云对接 + * @return 结果 + */ + public int insertOauthClientDetails(OauthClientDetails oauthClientDetails); + + /** + * 修改云云对接 + * + * @param oauthClientDetails 云云对接 + * @return 结果 + */ + public int updateOauthClientDetails(OauthClientDetails oauthClientDetails); + + /** + * 删除云云对接 + * + * @param clientId 云云对接主键 + * @return 结果 + */ + public int deleteOauthClientDetailsByClientId(String clientId); + + /** + * 批量删除云云对接 + * + * @param clientIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteOauthClientDetailsByClientIds(String[] clientIds); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProductAuthorizeMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProductAuthorizeMapper.java new file mode 100644 index 00000000..7ffc9289 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProductAuthorizeMapper.java @@ -0,0 +1,92 @@ +package com.fastbee.iot.mapper; + +import java.util.List; +import com.fastbee.iot.domain.ProductAuthorize; +import org.springframework.stereotype.Repository; + +/** + * 产品授权码Mapper接口 + * + * @author kami + * @date 2022-04-11 + */ +@Repository +public interface ProductAuthorizeMapper +{ + /** + * 查询产品授权码 + * + * @param authorizeId 产品授权码主键 + * @return 产品授权码 + */ + public ProductAuthorize selectProductAuthorizeByAuthorizeId(Long authorizeId); + + /** + * 查询产品授权码列表 + * + * @param productAuthorize 产品授权码 + * @return 产品授权码集合 + */ + public List selectProductAuthorizeList(ProductAuthorize productAuthorize); + + /** + * 新增产品授权码 + * + * @param productAuthorize 产品授权码 + * @return 结果 + */ + public int insertProductAuthorize(ProductAuthorize productAuthorize); + + /** + * 修改产品授权码 + * + * @param productAuthorize 产品授权码 + * @return 结果 + */ + public int updateProductAuthorize(ProductAuthorize productAuthorize); + + /** + * 删除产品授权码 + * + * @param authorizeId 产品授权码主键 + * @return 结果 + */ + public int deleteProductAuthorizeByAuthorizeId(Long authorizeId); + + /** + * 批量删除产品授权码 + * + * @param authorizeIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteProductAuthorizeByAuthorizeIds(Long[] authorizeIds); + + /** + * 通过产品ID批量删除产品授权码 + * + * @param productIds 产品ID数组 + * @return 结果 + */ + public int deleteProductAuthorizeByProductIds(Long[] productIds); + + /** + * 批量新增产品授权码 + * @param list + * @return + */ + public int insertBatchAuthorize(List list); + + /** + * 根据授权码查询一条未绑定的授权码 + * @param authorize + * @return + */ + ProductAuthorize selectFirstAuthorizeByAuthorizeCode(ProductAuthorize authorize); + + /** + * 根据产品id查询产品授权码 + * @param productId 产品id + * @return + */ + List selectProductAuthorizeListByProductId(Long productId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProductMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProductMapper.java new file mode 100644 index 00000000..d22e2e32 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProductMapper.java @@ -0,0 +1,149 @@ +package com.fastbee.iot.mapper; + +import com.fastbee.iot.domain.Product; +import com.fastbee.iot.model.ChangeProductStatusModel; +import com.fastbee.iot.model.IdAndName; +import com.fastbee.iot.model.ProductCode; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 产品Mapper接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Repository +public interface ProductMapper +{ + /** + * 查询产品 + * + * @param productId 产品主键 + * @return 产品 + */ + public Product selectProductByProductId(Long productId); + + /** + * 查询产品列表 + * + * @param product 产品 + * @return 产品集合 + */ + public List selectProductList(Product product); + + /** + * 查询产品简短列表 + * + * @param product 产品 + * @return 产品集合 + */ + public List selectProductShortList(Product product); + + /** + * 新增产品 + * + * @param product 产品 + * @return 结果 + */ + public int insertProduct(Product product); + + /** + * 修改产品 + * + * @param product 产品 + * @return 结果 + */ + public int updateProduct(Product product); + + /** + * 更新产品状态,1-未发布,2-已发布 + * + * @param model + * @return 结果 + */ + public int changeProductStatus(ChangeProductStatusModel model); + + /** + * 修改物模型JSON + * + * @param product 产品 + * @return 结果 + */ + public int updateThingsModelJson(Product product); + + /** + * 删除产品 + * + * @param productId 产品主键 + * @return 结果 + */ + public int deleteProductByProductId(Long productId); + + /** + * 批量删除产品 + * + * @param productIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteProductByProductIds(Long[] productIds); + + /** + * 批量删除产品物模型 + * + * @param productIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteProductThingsModelByProductIds(Long[] productIds); + + + /** + * 产品下的设备数量 + * @param productIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deviceCountInProducts(Long[] productIds); + + /** + * 产品下的物模型数量 + * @param productId 需要删除的数据主键集合 + * @return 结果 + */ + public int thingsCountInProduct(Long productId); + + /** + * 产品下的物模型标识符重复数 + * @param productId 需要删除的数据主键集合 + * @return 结果 + */ + public int thingsRepeatCountInProduct(Long productId); + + /** + * 根据设备编号查询产品信息 + * @param serialNumber 设备编号 + * @return 结果 + */ + public Product getProductBySerialNumber(String serialNumber); + + /** + * 根据设备编号查询协议编号 + * @param serialNumber 设备编号 + * @return 协议编号 + */ + public ProductCode getProtocolBySerialNumber(String serialNumber); + + /** + * 根据产品id获取协议编号 + * @param productId 产品id + * @return 协议编号 + */ + public String getProtocolByProductId(Long productId); + + /** + * 根据模板id查询所有使用的产品 + * @param templeId 模板id + * @return + */ + public List selectByTempleId(Long templeId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProtocolMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProtocolMapper.java new file mode 100644 index 00000000..c3b8a004 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ProtocolMapper.java @@ -0,0 +1,80 @@ +package com.fastbee.iot.mapper; + +import com.fastbee.iot.domain.Protocol; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 协议管理Mapper + * @author gsb + * @date 2022/10/19 15:46 + */ +@Repository +public interface ProtocolMapper { + + + /** + * 查询协议 + * + * @param id 协议主键 + * @return 协议 + */ + public Protocol selectProtocolById(Long id); + + /** + * 查询协议列表 + * + * @param protocol 协议 + * @return 协议集合 + */ + public List selectProtocolList(Protocol protocol); + + /** + * 新增协议 + * + * @param protocol 协议 + * @return 结果 + */ + public int insertProtocol(Protocol protocol); + + /** + * 修改协议 + * + * @param protocol 协议 + * @return 结果 + */ + public int updateProtocol(Protocol protocol); + + /** + * 删除协议 + * + * @param id 协议主键 + * @return 结果 + */ + public int deleteProtocolById(Long id); + + /** + * 批量删除协议 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteProtocolByIds(Long[] ids); + + /** + * 获取所有唯一协议 + * @param protocol + * @return + */ + public List selectByUnion(Protocol protocol); + + /** + * 获取所有可用协议 + * @param status + * @param delFlag + * @return + */ + public List selectAll(@Param("status") Integer status, @Param("delFlag") Integer delFlag); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/SocialPlatformMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/SocialPlatformMapper.java new file mode 100644 index 00000000..7fc30e0a --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/SocialPlatformMapper.java @@ -0,0 +1,71 @@ +package com.fastbee.iot.mapper; + +import java.util.List; +import com.fastbee.iot.domain.SocialPlatform; +import org.springframework.stereotype.Repository; + +/** + * 第三方登录平台控制Mapper接口 + * + * @author kerwincui + * @date 2022-04-11 + */ +@Repository +public interface SocialPlatformMapper +{ + /** + * 查询第三方登录平台控制 + * + * @param socialPlatformId 第三方登录平台控制主键 + * @return 第三方登录平台控制 + */ + public SocialPlatform selectSocialPlatformBySocialPlatformId(Long socialPlatformId); + + /** + * 查询第三方登录平台控制 + * + * @param platform 第三方登录平台名称 + * @return 第三方登录平台控制 + */ + public SocialPlatform selectSocialPlatformByPlatform(String platform); + + /** + * 查询第三方登录平台控制列表 + * + * @param socialPlatform 第三方登录平台控制 + * @return 第三方登录平台控制集合 + */ + public List selectSocialPlatformList(SocialPlatform socialPlatform); + + /** + * 新增第三方登录平台控制 + * + * @param socialPlatform 第三方登录平台控制 + * @return 结果 + */ + public int insertSocialPlatform(SocialPlatform socialPlatform); + + /** + * 修改第三方登录平台控制 + * + * @param socialPlatform 第三方登录平台控制 + * @return 结果 + */ + public int updateSocialPlatform(SocialPlatform socialPlatform); + + /** + * 删除第三方登录平台控制 + * + * @param socialPlatformId 第三方登录平台控制主键 + * @return 结果 + */ + public int deleteSocialPlatformBySocialPlatformId(Long socialPlatformId); + + /** + * 批量删除第三方登录平台控制 + * + * @param socialPlatformIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSocialPlatformBySocialPlatformIds(Long[] socialPlatformIds); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/SocialUserMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/SocialUserMapper.java new file mode 100644 index 00000000..4c0ca2ed --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/SocialUserMapper.java @@ -0,0 +1,103 @@ +package com.fastbee.iot.mapper; + +import com.fastbee.iot.domain.SocialUser; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 用户第三方用户信息Mapper接口 + * + * @author json + * @date 2022-04-18 + */ +@Repository +public interface SocialUserMapper +{ + /** + * 查询用户第三方用户信息 + * + * @param socialUserId 用户第三方用户信息主键 + * @return 用户第三方用户信息 + */ + public SocialUser selectSocialUserBySocialUserId(Long socialUserId); + + /** + * 查询用户第三方用户信息列表 + * + * @param socialUser 用户第三方用户信息 + * @return 用户第三方用户信息集合 + */ + public List selectSocialUserList(SocialUser socialUser); + + /** + * 新增用户第三方用户信息 + * + * @param socialUser 用户第三方用户信息 + * @return 结果 + */ + public int insertSocialUser(SocialUser socialUser); + + /** + * 修改用户第三方用户信息 + * + * @param socialUser 用户第三方用户信息 + * @return 结果 + */ + public int updateSocialUser(SocialUser socialUser); + + /** + * 删除用户第三方用户信息 + * + * @param socialUserId 用户第三方用户信息主键 + * @return 结果 + */ + public int deleteSocialUserBySocialUserId(Long socialUserId); + + /** + * 批量删除用户第三方用户信息 + * + * @param socialUserIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSocialUserBySocialUserIds(Long[] socialUserIds); + + /** + * 根据openId和unionId获取用户第三方信息 + * @param openId + * @param unionId + * @return + */ + SocialUser selectOneByOpenIdAndUnionId(@Param("openId") String openId, @Param("unionId") String unionId); + + /** + * 通过unionId查询 + * @param unionId + * @return + */ + Long selectSysUserIdByUnionId(String unionId); + + /** + * 通过系统用户id查询已绑定信息 + * @param sysUserId 系统用户id + * @return + */ + List selectBySysUserId(Long sysUserId); + + /** + * 取消三方登录相关信息 + * @param sysUserId 系统用户id + * @param sourceClientList 来源具体平台 + * @return + */ + int deleteBySysUserIdAndSourceClient(@Param("sysUserId") Long sysUserId, @Param("sourceClientList") List sourceClientList); + + /** + * 取消三方登录相关信息 + * @param sysUserIds 系统用户id集合 + * @param sourceClientList 来源具体平台 + * @return + */ + int deleteBySysUserIdsAndSourceClient(@Param("sysUserIds") Long[] sysUserIds, @Param("sourceClientList") List sourceClientList); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ThingsModelMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ThingsModelMapper.java new file mode 100644 index 00000000..6ae07d36 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ThingsModelMapper.java @@ -0,0 +1,125 @@ +package com.fastbee.iot.mapper; + +import com.fastbee.common.core.iot.response.IdentityAndName; +import com.fastbee.iot.domain.ThingsModel; +import com.fastbee.iot.model.ThingsModelPerm; +import com.fastbee.iot.model.ThingsModels.ThingsItems; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 物模型Mapper接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Repository +public interface ThingsModelMapper +{ + /** + * 查询物模型 + * + * @param modelId 物模型主键 + * @return 物模型 + */ + public ThingsModel selectThingsModelByModelId(Long modelId); + + /** + * 查询单个物模型 + * @param model 物模型 + * @return 单个物模型 + */ + public ThingsModel selectSingleThingsModel(ThingsModel model); + + /** + * 查询物模型列表 + * + * @param thingsModel 物模型 + * @return 物模型集合 + */ + public List selectThingsModelList(ThingsModel thingsModel); + + /** + * 查询物模型对应分享设备权限列表 + * + * @param productId 产品ID + * @return 物模型集合 + */ + public List selectThingsModelPermList(Long productId); + + /** + * 查询物模型列表-轮询 + * + * @param thingsModel 物模型 + * @return 物模型集合 + */ + public List selectThingsModelListCache(ThingsModel thingsModel); + + /** + * 根据产品ID数组获取物模型列表 + * @param modelIds + * @return + */ + public List selectThingsModelListByProductIds(Long[] modelIds); + + /** + * 新增物模型 + * + * @param thingsModel 物模型 + * @return 结果 + */ + public int insertThingsModel(ThingsModel thingsModel); + + /** + * 批量新增物模型 + * @param thingsModels + * @return + */ + public int insertBatchThingsModel(List thingsModels); + + /** + * 修改物模型 + * + * @param thingsModel 物模型 + * @return 结果 + */ + public int updateThingsModel(ThingsModel thingsModel); + + /** + * 删除物模型 + * + * @param modelId 物模型主键 + * @return 结果 + */ + public int deleteThingsModelByModelId(Long modelId); + + /** + * 批量删除物模型 + * + * @param modelIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteThingsModelByModelIds(Long[] modelIds); + + /** + * 根据产品id删除对应的物模型id + * @param productId + * @return + */ + public int deleteThingsModelByProductId(Long productId); + + /** + * 查询物模型是否历史存储 + * @param items + * @return + */ + public List selectThingsModelIsMonitor(ThingsItems items); + + /** + * 根据模板id查询从机采集点列表 + * + * @return 变量模板从机采集点集合 + */ + public List selectAllByTemplateId(Long templateId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ThingsModelTemplateMapper.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ThingsModelTemplateMapper.java new file mode 100644 index 00000000..58cb78ed --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/mapper/ThingsModelTemplateMapper.java @@ -0,0 +1,97 @@ +package com.fastbee.iot.mapper; + +import com.fastbee.iot.domain.ThingsModel; +import com.fastbee.iot.domain.ThingsModelTemplate; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 通用物模型Mapper接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Repository +public interface ThingsModelTemplateMapper +{ + /** + * 查询通用物模型 + * + * @param templateId 通用物模型主键 + * @return 通用物模型 + */ + public ThingsModelTemplate selectThingsModelTemplateByTemplateId(Long templateId); + + /** + * 根据id数组查询通用物模型集合 + * @param templateIds + * @return + */ + public List selectThingsModelTemplateByTemplateIds (Long[] templateIds); + + /** + * 查询通用物模型列表 + * + * @param thingsModelTemplate 通用物模型 + * @return 通用物模型集合 + */ + public List selectThingsModelTemplateList(ThingsModelTemplate thingsModelTemplate); + + /** + * 新增通用物模型 + * + * @param thingsModelTemplate 通用物模型 + * @return 结果 + */ + public int insertThingsModelTemplate(ThingsModelTemplate thingsModelTemplate); + + /** + * 修改通用物模型 + * + * @param thingsModelTemplate 通用物模型 + * @return 结果 + */ + public int updateThingsModelTemplate(ThingsModelTemplate thingsModelTemplate); + + + /** + * 根据从机关联id更新模板信息 + * @param thingsModelTemplate + * @return + */ + public int updateTemplateByTempSlaveId(ThingsModelTemplate thingsModelTemplate); + + /** + * 删除通用物模型 + * + * @param templateId 通用物模型主键 + * @return 结果 + */ + public int deleteThingsModelTemplateByTemplateId(Long templateId); + + /** + * 根据采集点模板ID删除通用物模型 + * + * @param templateId 通用物模型主键 + * @return 结果 + */ + public int deleteThingsModelTemplateByVarTemplateId(Long templateId); + + /** + * 批量删除通用物模型 + * + * @param templateIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteThingsModelTemplateByTemplateIds(Long[] templateIds); + + + /** + * 根据模板id查询从机采集点列表 + * + * @return 变量模板从机采集点集合 + */ + public List selectAllByTemplateId(Long templateId); + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/Action.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/Action.java new file mode 100644 index 00000000..5eac76de --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/Action.java @@ -0,0 +1,110 @@ +package com.fastbee.iot.model; + +/** + * 动作 + * @author kerwincui + * @date 2021-12-16 + */ +public class Action +{ + /** 标识符 */ + private String id; + + /** 名称 */ + private String name; + + /** 值 */ + private String value; + + /** 类型:1=属性,2=功能,3=事件,5=设备上线,6=设备下线 */ + private int type; + + /** 产品ID */ + private Long productId; + + /** 产品名称 */ + private String productName; + + /** 设备ID */ + private Long deviceId; + + /** 设备名称 */ + private String deviceName; + + /** 设备编号 */ + private String serialNumber; + + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + 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; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public Long getDeviceId() { + return deviceId; + } + + public void setDeviceId(Long deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/AuthenticateInputModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/AuthenticateInputModel.java new file mode 100644 index 00000000..d28cbcdf --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/AuthenticateInputModel.java @@ -0,0 +1,36 @@ +package com.fastbee.iot.model; + +/** + * 动作 + * @author kerwincui + * @date 2021-12-16 + */ +public class AuthenticateInputModel +{ + /** 设备编号 */ + private String serialNumber; + + /** 产品ID */ + private Long productId; + + public AuthenticateInputModel(String serialNumber,Long productId){ + this.serialNumber=serialNumber; + this.productId=productId; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/CategoryNews.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/CategoryNews.java new file mode 100644 index 00000000..708e413b --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/CategoryNews.java @@ -0,0 +1,49 @@ +package com.fastbee.iot.model; + +import com.fastbee.iot.domain.News; + +import java.util.ArrayList; +import java.util.List; + +/** + * 产品分类的Id和名称输出 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class CategoryNews +{ + private Long categoryId; + + private String categoryName; + + private List newsList; + + public CategoryNews(){ + this.newsList=new ArrayList<>(); + } + + public Long getCategoryId() { + return categoryId; + } + + public void setCategoryId(Long categoryId) { + this.categoryId = categoryId; + } + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(String categoryName) { + this.categoryName = categoryName; + } + + public List getNewsList() { + return newsList; + } + + public void setNewsList(List newsList) { + this.newsList = newsList; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ChangeProductStatusModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ChangeProductStatusModel.java new file mode 100644 index 00000000..0b59be9e --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ChangeProductStatusModel.java @@ -0,0 +1,41 @@ +package com.fastbee.iot.model; + +/** + * id和name + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ChangeProductStatusModel +{ + private Long productId; + + private Integer status; + + private Integer deviceType; + + public Integer getDeviceType() { + return deviceType; + } + + public void setDeviceType(Integer deviceType) { + this.deviceType = deviceType; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DataResult.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DataResult.java new file mode 100644 index 00000000..983519fe --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DataResult.java @@ -0,0 +1,19 @@ +package com.fastbee.iot.model; + +import lombok.Data; + +import java.util.Date; + +/** + * @author gsb + * @date 2023/4/6 11:57 + */ +@Data +public class DataResult { + + private String id; + /**值*/ + private String value; + /**时间*/ + private Date ts; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceAllShortOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceAllShortOutput.java new file mode 100644 index 00000000..f396a6b5 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceAllShortOutput.java @@ -0,0 +1,220 @@ +package com.fastbee.iot.model; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 设备对象 iot_device + * + * @author kerwincui + * @date 2021-12-16 + */ +public class DeviceAllShortOutput +{ + private static final long serialVersionUID = 1L; + + /** 产品分类ID */ + private Long deviceId; + + /** 产品分类名称 */ + private String deviceName; + + /** 产品名称 */ + private String productName; + + /** 设备类型(1-直连设备、2-网关子设备、3-网关设备) */ + private Integer deviceType; + + /** 用户昵称 */ + private String userName; + + /** 设备编号 */ + private String serialNumber; + + /** 固件版本 */ + private BigDecimal firmwareVersion; + + /** 设备状态(1-未激活,2-禁用,3-在线,4-离线) */ + private Integer status; + + /** 设备影子 */ + private Integer isShadow; + + /** wifi信号强度(信号极好4格[-55— 0],信号好3格[-70— -55],信号一般2格[-85— -70],信号差1格[-100— -85]) */ + private Integer rssi; + + /** 激活时间 */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date activeTime; + + /** 是否自定义位置 **/ + private Integer locationWay; + + /** 设备地址 */ + private String networkAddress; + + /** 经度 */ + private BigDecimal longitude; + + /** 纬度 */ + private BigDecimal latitude; + + /** 是否设备所有者,用于查询 **/ + private Integer isOwner; + + private Integer subDeviceCount; + + public Integer getSubDeviceCount() { + return subDeviceCount; + } + + public void setSubDeviceCount(Integer subDeviceCount) { + this.subDeviceCount = subDeviceCount; + } + + public Integer getLocationWay() { + return locationWay; + } + + public void setLocationWay(Integer locationWay) { + this.locationWay = locationWay; + } + + public Integer getIsOwner() { + return isOwner; + } + + public void setIsOwner(Integer isOwner) { + this.isOwner = isOwner; + } + + public String getNetworkAddress() { + return networkAddress; + } + + public void setNetworkAddress(String networkAddress) { + this.networkAddress = networkAddress; + } + + public BigDecimal getLongitude() { + return longitude; + } + + public void setLongitude(BigDecimal longitude) { + this.longitude = longitude; + } + + public BigDecimal getLatitude() { + return latitude; + } + + public void setLatitude(BigDecimal latitude) { + this.latitude = latitude; + } + + public Integer getIsShadow() { + return isShadow; + } + + public void setIsShadow(Integer isShadow) { + this.isShadow = isShadow; + } + + public void setRssi(Integer rssi) + { + this.rssi = rssi; + } + + public Integer getRssi() + { + return rssi; + } + + public void setDeviceId(Long deviceId) + { + this.deviceId = deviceId; + } + + public Long getDeviceId() + { + return deviceId; + } + public void setDeviceName(String deviceName) + { + this.deviceName = deviceName; + } + + public String getDeviceName() + { + return deviceName; + } + + public void setProductName(String productName) + { + this.productName = productName; + } + + public String getProductName() + { + return productName; + } + public void setDeviceType(Integer deviceType) + { + this.deviceType = deviceType; + } + + public Integer getDeviceType() + { + return deviceType; + } + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getUserName() + { + return userName; + } + + public void setStatus(Integer status) + { + this.status = status; + } + public Integer getStatus() + { + return status; + } + + public void setSerialNumber(String serialNumber) + { + this.serialNumber = serialNumber; + } + + public String getSerialNumber() + { + return serialNumber; + } + public void setFirmwareVersion(BigDecimal firmwareVersion) + { + this.firmwareVersion = firmwareVersion; + } + + public BigDecimal getFirmwareVersion() + { + return firmwareVersion; + } + + public void setActiveTime(Date activeTime) + { + this.activeTime = activeTime; + } + + public Date getActiveTime() + { + return activeTime; + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceGroupInput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceGroupInput.java new file mode 100644 index 00000000..da6a1b12 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceGroupInput.java @@ -0,0 +1,36 @@ +package com.fastbee.iot.model; + +/** + * 设备分组对象 iot_device_group + * + * @author kerwincui + * @date 2021-12-16 + */ +public class DeviceGroupInput +{ + private static final long serialVersionUID = 1L; + + /** 分组ID */ + private Long groupId; + + /** 设备ID */ + private Long[] deviceIds; + + public Long getGroupId() { + return groupId; + } + + public void setGroupId(Long groupId) { + this.groupId = groupId; + } + + public Long[] getDeviceIds() { + return deviceIds; + } + + public void setDeviceIds(Long[] deviceIds) { + this.deviceIds = deviceIds; + } + + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceMqttConnectVO.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceMqttConnectVO.java new file mode 100644 index 00000000..484c7c21 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceMqttConnectVO.java @@ -0,0 +1,35 @@ +package com.fastbee.iot.model; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 设备MQTT连接参数实体类 + * @author fastb + * @date 2023-08-02 17:01 + */ +@Accessors(chain = true) +@Data +public class DeviceMqttConnectVO { + + /** + * 客户端id + */ + private String clientId; + + /** + * 连接用户名 + */ + private String username; + + /** + * 连接密码 + */ + private String passwd; + + /** + * 连接端口号 + */ + private Long port; + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceMqttVO.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceMqttVO.java new file mode 100644 index 00000000..7ce4ef50 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceMqttVO.java @@ -0,0 +1,126 @@ +package com.fastbee.iot.model; + +/** + * MQTT连接相关参数实体类 + * @author fastb + * @date 2023-08-02 17:09 + */ +public class DeviceMqttVO { + + /** + * 设备主键id + */ + private Long deviceId; + + /** + * 设备编号 + */ + private String serialNumber; + + /** + * 产品id + */ + private Long productId; + + /** + * 用户id + */ + private Long userId; + + /** + * mqtt连接账号 + */ + private String mqttAccount; + + /** + * mqtt连接密码 + */ + private String mqttPassword; + + /** + * 产品密匙 + */ + private String mqttSecret; + + /** + * 是否启用授权码(0-否,1-是) + */ + private Integer isAuthorize; + + /** + * 认证方式 + */ + private Integer vertificateMethod; + + public Integer getVertificateMethod() { + return vertificateMethod; + } + + public void setVertificateMethod(Integer vertificateMethod) { + this.vertificateMethod = vertificateMethod; + } + + public Long getDeviceId() { + return deviceId; + } + + public void setDeviceId(Long deviceId) { + this.deviceId = deviceId; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getMqttAccount() { + return mqttAccount; + } + + public void setMqttAccount(String mqttAccount) { + this.mqttAccount = mqttAccount; + } + + public String getMqttPassword() { + return mqttPassword; + } + + public void setMqttPassword(String mqttPassword) { + this.mqttPassword = mqttPassword; + } + + public String getMqttSecret() { + return mqttSecret; + } + + public void setMqttSecret(String mqttSecret) { + this.mqttSecret = mqttSecret; + } + + public Integer getIsAuthorize() { + return isAuthorize; + } + + public void setIsAuthorize(Integer isAuthorize) { + this.isAuthorize = isAuthorize; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceNumberAndProductId.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceNumberAndProductId.java new file mode 100644 index 00000000..88e8e236 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceNumberAndProductId.java @@ -0,0 +1,38 @@ +package com.fastbee.iot.model; + +/** + * + * @author kerwincui + * @date 2021-12-16 + */ +public class DeviceNumberAndProductId +{ + /** 产品ID,用于自动添加设备 */ + private Long productId; + + /** 设备编号集合 */ + private String deviceNumber; + + public DeviceNumberAndProductId(){} + + public DeviceNumberAndProductId(Long productId, String deviceNumber){ + this.productId=productId; + this.deviceNumber=deviceNumber; + } + + 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; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceRelateAlertLogVO.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceRelateAlertLogVO.java new file mode 100644 index 00000000..23a8213a --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceRelateAlertLogVO.java @@ -0,0 +1,31 @@ +package com.fastbee.iot.model; + +import lombok.Data; + +/** + * @author fastb + * @date 2023-09-13 11:18 + */ +@Data +public class DeviceRelateAlertLogVO { + + /** + * 设备ID + */ + private Long deviceId; + + /** + * 设备编号 + */ + private String serialNumber; + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 用户ID + */ + private Long userId; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceRelateUserInput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceRelateUserInput.java new file mode 100644 index 00000000..618cea7b --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceRelateUserInput.java @@ -0,0 +1,41 @@ +package com.fastbee.iot.model; + +import java.util.List; + +/** + * + * @author kerwincui + * @date 2021-12-16 + */ +public class DeviceRelateUserInput +{ + + /** 用户Id */ + private Long userId; + + /** 设备编号和产品ID集合 */ + private List deviceNumberAndProductIds; + + public DeviceRelateUserInput(){} + + public DeviceRelateUserInput(Long userId,List deviceNumberAndProductIds){ + this.userId=userId; + this.deviceNumberAndProductIds=deviceNumberAndProductIds; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public List getDeviceNumberAndProductIds() { + return deviceNumberAndProductIds; + } + + public void setDeviceNumberAndProductIds(List deviceNumberAndProductIds) { + this.deviceNumberAndProductIds = deviceNumberAndProductIds; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceShortOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceShortOutput.java new file mode 100644 index 00000000..e0006773 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceShortOutput.java @@ -0,0 +1,421 @@ +package com.fastbee.iot.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fastbee.common.annotation.Excel; +import com.fastbee.iot.model.ThingsModelItem.*; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 设备对象 iot_device + * + * @author kerwincui + * @date 2021-12-16 + */ +public class DeviceShortOutput +{ + public DeviceShortOutput(){ + this.stringList=new ArrayList<>(); + this.integerList=new ArrayList<>(); + this.decimalList=new ArrayList<>(); + this.enumList=new ArrayList<>(); + this.arrayList=new ArrayList<>(); + this.readOnlyList =new ArrayList<>(); + this.boolList=new ArrayList<>(); + } + + private static final long serialVersionUID = 1L; + + /** 产品分类ID */ + private Long deviceId; + + /** 产品分类名称 */ + @Excel(name = "设备名称") + private String deviceName; + + /** 产品ID */ + @Excel(name = "产品ID") + private Long productId; + + /** 产品名称 */ + @Excel(name = "产品名称") + private String productName; + + /** 设备类型(1-直连设备、2-网关子设备、3-网关设备) */ + private Integer deviceType; + + /** 用户ID */ + @Excel(name = "用户ID") + private Long userId; + + /** 用户昵称 */ + @Excel(name = "用户昵称") + private String userName; + + /** 租户ID */ + @Excel(name = "租户ID") + private Long tenantId; + + /** 租户名称 */ + @Excel(name = "租户名称") + private String tenantName; + + /** 设备编号 */ + @Excel(name = "设备编号") + private String serialNumber; + + /** 固件版本 */ + @Excel(name = "固件版本") + private BigDecimal firmwareVersion; + + /** 设备状态(1-未激活,2-禁用,3-在线,4-离线) */ + @Excel(name = "设备状态") + private Integer status; + + /** 设备影子 */ + private Integer isShadow; + + private Integer isSimulate; + + /** wifi信号强度(信号极好4格[-55— 0],信号好3格[-70— -55],信号一般2格[-85— -70],信号差1格[-100— -85]) */ + @Excel(name = "wifi信号强度") + private Integer rssi; + + @Excel(name = "物模型") + private String thingsModelValue; + + /** 激活时间 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "激活时间", width = 30, dateFormat = "yyyy-MM-dd") + private Date activeTime; + + /** 激活时间 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd") + private Date createTime; + + @Excel(name = "网关设备编号(子设备使用)") + private String gwDevCode; + + /** 是否自定义位置 **/ + private Integer locationWay; + + /** 图片地址 */ + private String imgUrl; + + /** 是否设备所有者,用于查询 **/ + private Integer isOwner; + + + /**子设备数量*/ + private Integer subDeviceCount; + + /**子设备地址*/ + private Integer slaveId; + /*传输协议*/ + private String transport; + /*设备通讯协议*/ + private String protocolCode; + + public String getTransport() { + return transport; + } + + public void setTransport(String transport) { + this.transport = transport; + } + + public String getProtocolCode() { + return protocolCode; + } + + public void setProtocolCode(String protocolCode) { + this.protocolCode = protocolCode; + } + + public Integer getSlaveId() { + return slaveId; + } + + public void setSlaveId(Integer slaveId) { + this.slaveId = slaveId; + } + + public Integer getSubDeviceCount() { + return subDeviceCount; + } + + public void setSubDeviceCount(Integer subDeviceCount) { + this.subDeviceCount = subDeviceCount; + } + + private List thingsModels; + + public List getThingsModels() { + return thingsModels; + } + + public void setThingsModels(List thingsModels) { + this.thingsModels = thingsModels; + } + + private List stringList; + private List integerList; + private List decimalList; + private List enumList; + private List arrayList; + private List boolList; + private List readOnlyList; + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public String getGwDevCode() { + return gwDevCode; + } + + public void setGwDevCode(String gwDevCode) { + this.gwDevCode = gwDevCode; + } + + public Integer getLocationWay() { + return locationWay; + } + + public void setLocationWay(Integer locationWay) { + this.locationWay = locationWay; + } + + public Integer getIsOwner() { + return isOwner; + } + + public void setIsOwner(Integer isOwner) { + this.isOwner = isOwner; + } + + public String getImgUrl() { + return imgUrl; + } + + public void setImgUrl(String imgUrl) { + this.imgUrl = imgUrl; + } + + public Integer getIsSimulate() { + return isSimulate; + } + + public void setIsSimulate(Integer isSimulate) { + this.isSimulate = isSimulate; + } + + public Integer getIsShadow() { + return isShadow; + } + + public void setIsShadow(Integer isShadow) { + this.isShadow = isShadow; + } + + public List getBoolList() { + return boolList; + } + + public void setBoolList(List boolList) { + this.boolList = boolList; + } + + public List getReadOnlyList() { + return readOnlyList; + } + + public void setReadOnlyList(List readOnlyList) { + this.readOnlyList = readOnlyList; + } + + public List getStringList() { + return stringList; + } + + public void setStringList(List stringList) { + this.stringList = stringList; + } + + public List getIntegerList() { + return integerList; + } + + public void setIntegerList(List integerList) { + this.integerList = integerList; + } + + public List getDecimalList() { + return decimalList; + } + + public void setDecimalList(List decimalList) { + this.decimalList = decimalList; + } + + public List getEnumList() { + return enumList; + } + + public void setEnumList(List enumList) { + this.enumList = enumList; + } + + public List getArrayList() { + return arrayList; + } + + public void setArrayList(List arrayList) { + this.arrayList = arrayList; + } + public void setRssi(Integer rssi) + { + this.rssi = rssi; + } + + public Integer getRssi() + { + return rssi; + } + public void setThingsModelValue(String thingsModelValue) + { + this.thingsModelValue = thingsModelValue; + } + + public String getThingsModelValue() + { + return thingsModelValue; + } + public void setDeviceId(Long deviceId) + { + this.deviceId = deviceId; + } + + public Long getDeviceId() + { + return deviceId; + } + public void setDeviceName(String deviceName) + { + this.deviceName = deviceName; + } + + public String getDeviceName() + { + return deviceName; + } + public void setProductId(Long productId) + { + this.productId = productId; + } + + public Long getProductId() + { + return productId; + } + public void setProductName(String productName) + { + this.productName = productName; + } + + public String getProductName() + { + return productName; + } + public void setDeviceType(Integer deviceType) + { + this.deviceType = deviceType; + } + + public Integer getDeviceType() + { + return deviceType; + } + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getUserId() + { + return userId; + } + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getUserName() + { + return userName; + } + public void setTenantId(Long tenantId) + { + this.tenantId = tenantId; + } + + public void setStatus(Integer status) + { + this.status = status; + } + public Integer getStatus() + { + return status; + } + + public Long getTenantId() + { + return tenantId; + } + public void setTenantName(String tenantName) + { + this.tenantName = tenantName; + } + + public String getTenantName() + { + return tenantName; + } + public void setSerialNumber(String serialNumber) + { + this.serialNumber = serialNumber; + } + + public String getSerialNumber() + { + return serialNumber; + } + public void setFirmwareVersion(BigDecimal firmwareVersion) + { + this.firmwareVersion = firmwareVersion; + } + + public BigDecimal getFirmwareVersion() + { + return firmwareVersion; + } + + public void setActiveTime(Date activeTime) + { + this.activeTime = activeTime; + } + + public Date getActiveTime() + { + return activeTime; + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceStatistic.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceStatistic.java new file mode 100644 index 00000000..d00e89fa --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/DeviceStatistic.java @@ -0,0 +1,101 @@ +package com.fastbee.iot.model; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * id和name + * + * @author kerwincui + * @date 2021-12-16 + */ +public class DeviceStatistic +{ + /** 设备数量 **/ + private int deviceCount; + + /** 设备数量 **/ + private int deviceOnlineCount; + + /** 产品数量 **/ + private int productCount; + + /** 告警 **/ + private long alertCount; + + /** 属性上报 **/ + private long propertyCount; + + /** 功能上报 **/ + private long functionCount; + + /** 事件上报 **/ + private long eventCount; + + /** 监测数据上报 **/ + private long monitorCount; + + public int getDeviceOnlineCount() { + return deviceOnlineCount; + } + + public void setDeviceOnlineCount(int deviceOnlineCount) { + this.deviceOnlineCount = deviceOnlineCount; + } + + public long getMonitorCount() { + return monitorCount; + } + + public void setMonitorCount(long monitorCount) { + this.monitorCount = monitorCount; + } + + public int getDeviceCount() { + return deviceCount; + } + + public void setDeviceCount(int deviceCount) { + this.deviceCount = deviceCount; + } + + public int getProductCount() { + return productCount; + } + + public void setProductCount(int productCount) { + this.productCount = productCount; + } + + public long getAlertCount() { + return alertCount; + } + + public void setAlertCount(long alertCount) { + this.alertCount = alertCount; + } + + public long getPropertyCount() { + return propertyCount; + } + + public void setPropertyCount(long propertyCount) { + this.propertyCount = propertyCount; + } + + public long getFunctionCount() { + return functionCount; + } + + public void setFunctionCount(long functionCount) { + this.functionCount = functionCount; + } + + public long getEventCount() { + return eventCount; + } + + public void setEventCount(long eventCount) { + this.eventCount = eventCount; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/HistoryModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/HistoryModel.java new file mode 100644 index 00000000..cad93d6a --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/HistoryModel.java @@ -0,0 +1,20 @@ +package com.fastbee.iot.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +/** + * @author bill + */ +@Data +public class HistoryModel { + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date time; + + private String value; + + private String identity; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/IdAndName.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/IdAndName.java new file mode 100644 index 00000000..5816df6b --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/IdAndName.java @@ -0,0 +1,44 @@ +package com.fastbee.iot.model; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * id和name + * + * @author kerwincui + * @date 2021-12-16 + */ +public class IdAndName +{ + private Long id; + + private String name; + + + public void setId(Long id) + { + this.id = id; + } + public Long getId() + { + return id; + } + + public void setName(String name) + { + this.name = name; + } + public String getName() + { + return name; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("categoryId", getId()) + .append("categoryName", getName()) + .toString(); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/IdOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/IdOutput.java new file mode 100644 index 00000000..afb30f74 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/IdOutput.java @@ -0,0 +1,24 @@ +package com.fastbee.iot.model; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 产品分类的Id和名称输出 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class IdOutput +{ + private Long id; + + public void setId(Long id) + { + this.id = id; + } + public Long getId() + { + return id; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ImportThingsModelInput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ImportThingsModelInput.java new file mode 100644 index 00000000..ae4010ef --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ImportThingsModelInput.java @@ -0,0 +1,47 @@ +package com.fastbee.iot.model; + +import com.fastbee.common.annotation.Excel; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 导入产品物模型的输入对象 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ImportThingsModelInput +{ + /** 产品ID */ + private Long productId; + + /** 产品名称 */ + private String ProductName; + + /** 通用物模型ID集合 */ + private Long[] templateIds; + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getProductName() { + return ProductName; + } + + public void setProductName(String productName) { + ProductName = productName; + } + + public Long[] getTemplateIds() { + return templateIds; + } + + public void setTemplateIds(Long[] templateIds) { + this.templateIds = templateIds; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/MonitorModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/MonitorModel.java new file mode 100644 index 00000000..dda59cba --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/MonitorModel.java @@ -0,0 +1,34 @@ +package com.fastbee.iot.model; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.util.Date; + +/** + * 动作 + * @author kerwincui + * @date 2021-12-16 + */ +public class MonitorModel +{ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date time; + + private String value; + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/MqttAuthenticationModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/MqttAuthenticationModel.java new file mode 100644 index 00000000..1e44ee06 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/MqttAuthenticationModel.java @@ -0,0 +1,89 @@ +package com.fastbee.iot.model; + +/** + * 动作 + * @author kerwincui + * @date 2021-12-16 + */ +public class MqttAuthenticationModel +{ + /** Mqtt客户端ID */ + String clientId; + + /** Mqtt用户名 */ + String userName; + + /** Mqtt密码 */ + String password; + + /** 设备编号 */ + String deviceNumber; + + /** 产品ID */ + Long productId; + + /** 设备关联的用户ID */ + Long userId; + + public MqttAuthenticationModel(String clientid,String username,String password,String deviceNumber ,Long productId,Long userId){ + this.clientId=clientid; + this.userName=username; + this.password=password; + this.deviceNumber=deviceNumber; + this.productId=productId; + this.userId=userId; + } + public MqttAuthenticationModel(String clientid,String username,String password){ + this.clientId=clientid; + this.userName=username; + this.password=password; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + 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 getDeviceNumber() { + return deviceNumber; + } + + public void setDeviceNumber(String deviceNumber) { + this.deviceNumber = deviceNumber; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/MqttClientConnectModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/MqttClientConnectModel.java new file mode 100644 index 00000000..99c4ae6e --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/MqttClientConnectModel.java @@ -0,0 +1,98 @@ +package com.fastbee.iot.model; + +/** + * 客户端连接模型 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class MqttClientConnectModel +{ + /** 事件名称(固定为:"client_connected" "client_disconnected") **/ + private String action; + + /** 客户端 ClientId **/ + private String clientid; + + /** 客户端 Username,不存在时该值为 "undefined" **/ + private String username; + + /** 客户端源 IP 地址 **/ + private String ipaddress; + + /** 客户端申请的心跳保活时间 **/ + private Integer keepalive; + + /** 协议版本号 **/ + private Integer proto_ver; + + /** 时间戳(秒) **/ + private Long connected_at; + + /** 错误原因 **/ + private String reason; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getClientid() { + return clientid; + } + + public void setClientid(String clientid) { + this.clientid = clientid; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getIpaddress() { + return ipaddress; + } + + public void setIpaddress(String ipaddress) { + this.ipaddress = ipaddress; + } + + public Integer getKeepalive() { + return keepalive; + } + + public void setKeepalive(Integer keepalive) { + this.keepalive = keepalive; + } + + public Integer getProto_ver() { + return proto_ver; + } + + public void setProto_ver(Integer proto_ver) { + this.proto_ver = proto_ver; + } + + public Long getConnected_at() { + return connected_at; + } + + public void setConnected_at(Long connected_at) { + this.connected_at = connected_at; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/MqttInfoModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/MqttInfoModel.java new file mode 100644 index 00000000..9409b623 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/MqttInfoModel.java @@ -0,0 +1,43 @@ +package com.fastbee.iot.model; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * id和name + * + * @author kerwincui + * @date 2021-12-16 + */ +public class MqttInfoModel +{ + private String clientid; + + private String username; + + private String password; + + public String getClientid() { + return clientid; + } + + public void setClientid(String clientid) { + this.clientid = clientid; + } + + 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; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/NtpModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/NtpModel.java new file mode 100644 index 00000000..1b25f4bf --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/NtpModel.java @@ -0,0 +1,40 @@ +package com.fastbee.iot.model; + +/** + * 产品分类的Id和名称输出 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class NtpModel +{ + private Long deviceSendTime; + + private Long serverRecvTime; + + private Long serverSendTime; + + public Long getDeviceSendTime() { + return deviceSendTime; + } + + public void setDeviceSendTime(Long deviceSendTime) { + this.deviceSendTime = deviceSendTime; + } + + public Long getServerRecvTime() { + return serverRecvTime; + } + + public void setServerRecvTime(Long serverRecvTime) { + this.serverRecvTime = serverRecvTime; + } + + public Long getServerSendTime() { + return serverSendTime; + } + + public void setServerSendTime(Long serverSendTime) { + this.serverSendTime = serverSendTime; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ProductAuthenticateModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ProductAuthenticateModel.java new file mode 100644 index 00000000..3615b475 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ProductAuthenticateModel.java @@ -0,0 +1,137 @@ +package com.fastbee.iot.model; + + +import com.fastbee.common.annotation.Excel; + +public class ProductAuthenticateModel { + /** 产品分类ID */ + private Long deviceId; + + /** 产品分类名称 */ + private String deviceName; + + /**设备状态(1-未激活,2-禁用,3-在线,4-离线)**/ + private int status; + + /** 产品ID */ + private Long productId; + + /** 产品名称 */ + private String productName; + + /** 产品状态 1-未发布,2-已发布 */ + private int productStatus; + + /** 是否启用授权码(0-否,1-是) */ + private Integer isAuthorize; + + /** 设备编号 */ + private String serialNumber; + + /** mqtt账号 */ + private String mqttAccount; + + /** mqtt密码 */ + private String mqttPassword; + + /** 产品秘钥 */ + private String mqttSecret; + + private int vertificateMethod; + + public int getVertificateMethod() { + return vertificateMethod; + } + + public void setVertificateMethod(int vertificateMethod) { + this.vertificateMethod = vertificateMethod; + } + + public Integer getIsAuthorize() { + return isAuthorize; + } + + public void setIsAuthorize(Integer isAuthorize) { + this.isAuthorize = isAuthorize; + } + + public int getProductStatus() { + return productStatus; + } + + public void setProductStatus(int productStatus) { + this.productStatus = productStatus; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public Long getDeviceId() { + return deviceId; + } + + public void setDeviceId(Long deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public String getMqttAccount() { + return mqttAccount; + } + + public void setMqttAccount(String mqttAccount) { + this.mqttAccount = mqttAccount; + } + + public String getMqttPassword() { + return mqttPassword; + } + + public void setMqttPassword(String mqttPassword) { + this.mqttPassword = mqttPassword; + } + + public String getMqttSecret() { + return mqttSecret; + } + + public void setMqttSecret(String mqttSecret) { + this.mqttSecret = mqttSecret; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ProductAuthorizeVO.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ProductAuthorizeVO.java new file mode 100644 index 00000000..9e5fbfd6 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ProductAuthorizeVO.java @@ -0,0 +1,30 @@ +package com.fastbee.iot.model; + +/** + * 批量新增产品授权码VO + * + * @author Venus Zhang + * @create 2022-04-11 15:04 + */ + +public class ProductAuthorizeVO { + + private Long productId; + private int createNum; + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public int getCreateNum() { + return createNum; + } + + public void setCreateNum(int createNum) { + this.createNum = createNum; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ProductCode.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ProductCode.java new file mode 100644 index 00000000..7cf5765b --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ProductCode.java @@ -0,0 +1,20 @@ +package com.fastbee.iot.model; + +import lombok.Data; + +/** + * @author gsb + * @date 2023/9/4 17:23 + */ +@Data +public class ProductCode { + + /** + * 产品id + */ + private Long productId; + /** + * 协议编号 + */ + private String protocolCode; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/RegisterUserInput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/RegisterUserInput.java new file mode 100644 index 00000000..f4015801 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/RegisterUserInput.java @@ -0,0 +1,73 @@ +package com.fastbee.iot.model; + +public class RegisterUserInput { + /** + * 用户名 + */ + private String username; + + /** + * 用户密码 + */ + private String password; + + /** + * 验证码 + */ + private String code; + + private String phonenumber; + + /** + * 唯一标识 + */ + private String uuid = ""; + + public String getPhonenumber() { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) { + this.phonenumber = phonenumber; + } + + 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 getCode() + { + return code; + } + + public void setCode(String code) + { + this.code = code; + } + + public String getUuid() + { + return uuid; + } + + public void setUuid(String uuid) + { + this.uuid = uuid; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/RegisterUserOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/RegisterUserOutput.java new file mode 100644 index 00000000..583da6c5 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/RegisterUserOutput.java @@ -0,0 +1,21 @@ +package com.fastbee.iot.model; + +import lombok.Data; + +/** + * @author fastb + * @date 2023-08-17 11:46 + */ +@Data +public class RegisterUserOutput { + + /** + * 错误信息 + */ + private String msg; + + /** + * 用户id + */ + private Long sysUserId; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/Specs.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/Specs.java new file mode 100644 index 00000000..cab5b2da --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/Specs.java @@ -0,0 +1,44 @@ +package com.fastbee.iot.model; + +import lombok.Data; + +/** + * 数据定义 + * + * @author bill + */ +@Data +public class Specs { + + private String id; + private String name; + private Integer isMonitor; + private Integer slaveId; + private Integer isChart; + private Integer isHistory; + private String datatype; + /** + * 计算公式 + */ + private String formula; + + + @Data + static class Datatype { + + private String unit; + private Long min; + private Long max; + private Integer step; + private String type; + private String trueText; + private String falseText; + private EnumList enumList; + } + + @Data + static class EnumList{ + private String text; + private String value; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/ArrayModelOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/ArrayModelOutput.java new file mode 100644 index 00000000..d442c663 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/ArrayModelOutput.java @@ -0,0 +1,24 @@ +package com.fastbee.iot.model.ThingsModelItem; + +public class ArrayModelOutput extends ThingsModelItemBase +{ + private String arrayType; + + private Integer arrayCount; + + public Integer getArrayCount() { + return arrayCount; + } + + public void setArrayCount(Integer arrayCount) { + this.arrayCount = arrayCount; + } + + public String getArrayType() { + return arrayType; + } + + public void setArrayType(String arrayType) { + this.arrayType = arrayType; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/BoolModelOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/BoolModelOutput.java new file mode 100644 index 00000000..e4fdc355 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/BoolModelOutput.java @@ -0,0 +1,23 @@ +package com.fastbee.iot.model.ThingsModelItem; + +public class BoolModelOutput extends ThingsModelItemBase +{ + private String falseText; + private String trueText; + + public String getFalseText() { + return falseText; + } + + public void setFalseText(String falseText) { + this.falseText = falseText; + } + + public String getTrueText() { + return trueText; + } + + public void setTrueText(String trueText) { + this.trueText = trueText; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/Datatype.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/Datatype.java new file mode 100644 index 00000000..d473b29d --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/Datatype.java @@ -0,0 +1,161 @@ +package com.fastbee.iot.model.ThingsModelItem; + +import java.math.BigDecimal; +import java.util.List; + +public class Datatype +{ + /** 数据类型 */ + private String type; + + private String falseText; + + private String trueText; + + private BigDecimal min; + + private BigDecimal max; + + private BigDecimal step; + + private String unit; + + private String arrayType; + + private Integer arrayCount; + + private String showWay; + + private int maxLength; + + private List enumList; + + private List params; + + private List[] arrayParams; + + public Datatype(){ + falseText=""; + trueText=""; + min= BigDecimal.valueOf(0); + max= BigDecimal.valueOf(100); + step= BigDecimal.valueOf(1); + unit=""; + arrayType=""; + arrayCount=0; + maxLength=1024; + } + + public List[] getArrayParams() { + return arrayParams; + } + + public void setArrayParams(List[] arrayParams) { + this.arrayParams = arrayParams; + } + + public List getParams() { + return params; + } + + public void setParams(List params) { + this.params = params; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getFalseText() { + return falseText; + } + + public void setFalseText(String falseText) { + this.falseText = falseText; + } + + public String getTrueText() { + return trueText; + } + + public void setTrueText(String trueText) { + this.trueText = trueText; + } + + public BigDecimal getMin() { + return min; + } + + public void setMin(BigDecimal min) { + this.min = min; + } + + public BigDecimal getMax() { + return max; + } + + public void setMax(BigDecimal max) { + this.max = max; + } + + public BigDecimal getStep() { + return step; + } + + public void setStep(BigDecimal step) { + this.step = step; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public String getArrayType() { + return arrayType; + } + + public void setArrayType(String arrayType) { + this.arrayType = arrayType; + } + + public String getShowWay() { + return showWay; + } + + public void setShowWay(String showWay) { + this.showWay = showWay; + } + + public Integer getArrayCount() { + return arrayCount; + } + + public void setArrayCount(Integer arrayCount) { + this.arrayCount = arrayCount; + } + + public int getMaxLength() { + return maxLength; + } + + public void setMaxLength(int maxLength) { + this.maxLength = maxLength; + } + + public List getEnumList() { + return enumList; + } + + public void setEnumList(List enumList) { + this.enumList = enumList; + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/DecimalModelOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/DecimalModelOutput.java new file mode 100644 index 00000000..74733655 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/DecimalModelOutput.java @@ -0,0 +1,43 @@ +package com.fastbee.iot.model.ThingsModelItem; + +import java.math.BigDecimal; + +public class DecimalModelOutput extends ThingsModelItemBase +{ + private BigDecimal min; + private BigDecimal max; + private BigDecimal step; + private String unit; + + public BigDecimal getMin() { + return min; + } + + public void setMin(BigDecimal min) { + this.min = min; + } + + public BigDecimal getMax() { + return max; + } + + public void setMax(BigDecimal max) { + this.max = max; + } + + public BigDecimal getStep() { + return step; + } + + public void setStep(BigDecimal step) { + this.step = step; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/EnumItem.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/EnumItem.java new file mode 100644 index 00000000..4799b2d5 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/EnumItem.java @@ -0,0 +1,27 @@ +package com.fastbee.iot.model.ThingsModelItem; + + +public class EnumItem +{ + private String text; + private String value; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/EnumItemOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/EnumItemOutput.java new file mode 100644 index 00000000..06829e83 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/EnumItemOutput.java @@ -0,0 +1,23 @@ +package com.fastbee.iot.model.ThingsModelItem; + +public class EnumItemOutput +{ + private String text; + private String value; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/EnumModelOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/EnumModelOutput.java new file mode 100644 index 00000000..911a522d --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/EnumModelOutput.java @@ -0,0 +1,16 @@ +package com.fastbee.iot.model.ThingsModelItem; + +import java.util.List; + +public class EnumModelOutput extends ThingsModelItemBase +{ + private List enumList; + + public List getEnumList() { + return enumList; + } + + public void setEnumList(List enumList) { + this.enumList = enumList; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/IntegerModelOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/IntegerModelOutput.java new file mode 100644 index 00000000..c0507f2e --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/IntegerModelOutput.java @@ -0,0 +1,43 @@ +package com.fastbee.iot.model.ThingsModelItem; + +import java.math.BigDecimal; + +public class IntegerModelOutput extends ThingsModelItemBase +{ + private BigDecimal min; + private BigDecimal max; + private BigDecimal step; + private String unit; + + public BigDecimal getMin() { + return min; + } + + public void setMin(BigDecimal min) { + this.min = min; + } + + public BigDecimal getMax() { + return max; + } + + public void setMax(BigDecimal max) { + this.max = max; + } + + public BigDecimal getStep() { + return step; + } + + public void setStep(BigDecimal step) { + this.step = step; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/ReadOnlyModelOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/ReadOnlyModelOutput.java new file mode 100644 index 00000000..4a97ec7e --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/ReadOnlyModelOutput.java @@ -0,0 +1,44 @@ +package com.fastbee.iot.model.ThingsModelItem; + +import java.math.BigDecimal; +import java.util.List; + +public class ReadOnlyModelOutput extends ThingsModelItemBase +{ + private BigDecimal min; + private BigDecimal max; + private BigDecimal step; + private String unit; + + public BigDecimal getMin() { + return min; + } + + public void setMin(BigDecimal min) { + this.min = min; + } + + public BigDecimal getMax() { + return max; + } + + public void setMax(BigDecimal max) { + this.max = max; + } + + public BigDecimal getStep() { + return step; + } + + public void setStep(BigDecimal step) { + this.step = step; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/StringModelOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/StringModelOutput.java new file mode 100644 index 00000000..4569771a --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/StringModelOutput.java @@ -0,0 +1,14 @@ +package com.fastbee.iot.model.ThingsModelItem; + +public class StringModelOutput extends ThingsModelItemBase +{ + private int maxLength; + + public int getMaxLength() { + return maxLength; + } + + public void setMaxLength(int maxLength) { + this.maxLength = maxLength; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/ThingsModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/ThingsModel.java new file mode 100644 index 00000000..98dd8e6c --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/ThingsModel.java @@ -0,0 +1,167 @@ +package com.fastbee.iot.model.ThingsModelItem; + +public class ThingsModel +{ + /** 物模型唯一标识符 */ + private String id; + /** 物模型名称 */ + private String name; + /** 物模型值 */ + private String value; + /** 影子值 */ + private String shadow; + /** 是否首页显示(0-否,1-是) */ + private Integer isChart; + /** 是否实时监测(0-否,1-是) */ + private Integer isMonitor; + /** 是否实时监测(0-否,1-是) */ + private Integer isReadonly; + /** 是否历史存储(0-否,1-是) */ + private Integer isHistory; + /** 是否设备分享权限(0-否,1-是) */ + private Integer isSharePerm; + /** 类型 1=属性,2=功能,3=事件 */ + private Integer type; + /** 排序 */ + private Integer order; + /** 数据类型 */ + private Datatype datatype; + + /**子设备编号*/ + private Integer slaveId; + + private String regId; + + private String ts; + + public ThingsModel(){ + value=""; + shadow=""; + order=0; + isMonitor=0; + isHistory=0; + isReadonly=0; + isChart=0; + isSharePerm=0; + } + + public String getTs() { + return ts; + } + + public void setTs(String ts) { + this.ts = ts; + } + + public String getRegId() { + return regId; + } + + public void setRegId(String regId) { + this.regId = regId; + } + + public Integer getSlaveId() { + return slaveId; + } + + public void setSlaveId(Integer slaveId) { + this.slaveId = slaveId; + } + + public Integer getIsReadonly() { + return isReadonly; + } + + public void setIsReadonly(Integer isReadonly) { + this.isReadonly = isReadonly; + } + + public Integer getOrder() { + return order; + } + + public void setOrder(Integer order) { + this.order = order; + } + + 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; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getShadow() { + return shadow; + } + + public void setShadow(String shadow) { + this.shadow = shadow; + } + + public Integer getIsHistory() { + return isHistory; + } + + public void setIsHistory(Integer isHistory) { + this.isHistory = isHistory; + } + + public Integer getIsSharePerm() { + return isSharePerm; + } + + public void setIsSharePerm(Integer isSharePerm) { + this.isSharePerm = isSharePerm; + } + + public Integer getIsChart() { + return isChart; + } + + public void setIsChart(Integer isChart) { + this.isChart = isChart; + } + + public Integer getIsMonitor() { + return isMonitor; + } + + public void setIsMonitor(Integer isMonitor) { + this.isMonitor = isMonitor; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public Datatype getDatatype() { + return datatype; + } + + public void setDatatype(Datatype datatype) { + this.datatype = datatype; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/ThingsModelItemBase.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/ThingsModelItemBase.java new file mode 100644 index 00000000..3ccc4103 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelItem/ThingsModelItemBase.java @@ -0,0 +1,88 @@ +package com.fastbee.iot.model.ThingsModelItem; + +import com.fastbee.common.annotation.Excel; + +public class ThingsModelItemBase +{ + /** 物模型唯一标识符 */ + private String id; + /** 物模型名称 */ + private String name; + /** 物模型值 */ + private String value; + /** 是否首页显示(0-否,1-是) */ + private Integer isChart; + /** 是否实时监测(0-否,1-是) */ + private Integer isMonitor; + /** 类型 1=属性,2=功能,3=事件 */ + private Integer type; + /** 数据类型 */ + private String dataType; + /** 影子值 */ + private String shadow; + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public String getShadow() { + return shadow; + } + + public void setShadow(String shadow) { + this.shadow = shadow; + } + + public String getDataType() { + return dataType; + } + + public void setDataType(String dataType) { + this.dataType = dataType; + } + + 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; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Integer getIsChart() { + return isChart; + } + + public void setIsChart(Integer isChart) { + this.isChart = isChart; + } + + public Integer getIsMonitor() { + return isMonitor; + } + + public void setIsMonitor(Integer isMonitor) { + this.isMonitor = isMonitor; + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelPerm.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelPerm.java new file mode 100644 index 00000000..341f5960 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModelPerm.java @@ -0,0 +1,42 @@ +package com.fastbee.iot.model; + +/** + * + * @author kerwincui + * @date 2023-01-14 + */ +public class ThingsModelPerm +{ + /** 物模型名称 */ + private String modelName; + + /** 标识符,产品下唯一 */ + private String identifier; + + /** 备注信息 */ + private String remark; + + public String getModelName() { + return modelName; + } + + public void setModelName(String modelName) { + this.modelName = modelName; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/EventDto.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/EventDto.java new file mode 100644 index 00000000..7344819e --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/EventDto.java @@ -0,0 +1,47 @@ +package com.fastbee.iot.model.ThingsModels; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +import java.util.List; + +/** + * 产品分类的Id和名称输出 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Data +public class EventDto { + /** + * 物模型唯一标识符 + */ + private String id; + /** + * 物模型名称 + */ + private String name; + /** + * 数据定义 + */ + private JSONObject datatype; + + /** + * 从机编号 + */ + private Integer SlaveId; + /** + * 计算公式 + */ + private String formula; + + private Integer isParams; + + private Integer isHistory; + + /** + * 从机编号 + */ + private Long tempSlaveId; + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/FunctionDto.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/FunctionDto.java new file mode 100644 index 00000000..f6ab696b --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/FunctionDto.java @@ -0,0 +1,45 @@ +package com.fastbee.iot.model.ThingsModels; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +/** + * 产品分类的Id和名称输出 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Data +public class FunctionDto +{ + /** 物模型唯一标识符 */ + private String id; + /** 物模型名称 */ + private String name; + /** 是否首页显示(0-否,1-是) */ + private Integer isChart; + /** 是否只读 */ + private Integer isReadonly; + /** 是否历史存储 */ + private Integer isHistory; + /** 排序 */ + private Integer order; + /** 数据定义 */ + private JSONObject datatype; + + /** + * 从机编号 + */ + private Integer SlaveId; + /** + * 计算公式 + */ + private String formula; + + private Integer isParams; + + /** + * 从机编号 + */ + private Long tempSlaveId; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/PropertyDto.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/PropertyDto.java new file mode 100644 index 00000000..9c5b13e6 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/PropertyDto.java @@ -0,0 +1,61 @@ +package com.fastbee.iot.model.ThingsModels; + +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; + +import java.util.List; + +/** + * 产品分类的Id和名称输出 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Data +public class PropertyDto +{ + /** 物模型唯一标识符 */ + private String id; + /** 物模型名称 */ + private String name; + /** 是否图表展示(0-否,1-是) */ + private Integer isChart; + /** 是否历史存储(0-否,1-是) */ + private Integer isHistory; + /** 是否实时监测(0-否,1-是) */ + private Integer isMonitor; + /** 是否只读 */ + private Integer isReadonly; + /** 排序 */ + private Integer order; + /** 数据定义 */ + private JSONObject datatype; + /** + * 计算公式 + */ + private String formula; + + private Integer isParams; + /** + * 从机编号 + */ + private Integer tempSlaveId; + + private Integer type; + /** + * 读取寄存器数量 + */ + private Integer quantity; + /** + * 寄存器#从机 + */ + private String regId; + /** + * 功能码 + */ + private String code; + /** + * + */ + private String parseType; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsItems.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsItems.java new file mode 100644 index 00000000..a0a60ff3 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsItems.java @@ -0,0 +1,19 @@ +package com.fastbee.iot.model.ThingsModels; + +import lombok.Data; + +import java.util.List; + +/** + * @author gsb + * @date 2022/10/24 15:45 + */ +@Data +public class ThingsItems { + + private List ids; + + private Long productId; + + private Integer slaveId; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelShadow.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelShadow.java new file mode 100644 index 00000000..cd5f8dd2 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelShadow.java @@ -0,0 +1,47 @@ +package com.fastbee.iot.model.ThingsModels; + +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; + +import java.util.ArrayList; +import java.util.List; + +/** + * 产品分类的Id和名称输出 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ThingsModelShadow +{ + public ThingsModelShadow(){ + this.properties=new ArrayList<>(); + this.functions=new ArrayList<>(); + } + + public ThingsModelShadow(List properties, List functions){ + this.properties=properties; + this.functions=functions; + } + + /** 属性 */ + List properties; + + /** 功能 */ + List functions; + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } + + public List getFunctions() { + return functions; + } + + public void setFunctions(List functions) { + this.functions = functions; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelValueItem.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelValueItem.java new file mode 100644 index 00000000..18737574 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelValueItem.java @@ -0,0 +1,145 @@ +package com.fastbee.iot.model.ThingsModels; + +import com.fastbee.iot.model.ThingsModelItem.Datatype; + +/** + * 物模型值的项 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ThingsModelValueItem { + /** + * 物模型唯一标识符 + */ + private String id; + + /** + * 物模型值 + */ + private String value; + + /** + * 影子值 + **/ + private String shadow; + + /** + * 是否为监测值 + **/ + private int isMonitor; + + /** + * 是否为历史存储 + **/ + private int isHistory; + + /** + * 是否为图表展示 + **/ + private int isChart; + + /** + * 是否只读数据 + **/ + private int isReadonly; + + /** + * 物模型名称 + **/ + private String name; + + private Datatype datatype; + + private String ts; + + private Integer tempSlaveId; + + public Integer getTempSlaveId() { + return tempSlaveId; + } + + public void setTempSlaveId(Integer tempSlaveId) { + this.tempSlaveId = tempSlaveId; + } + + public int getIsHistory() { + return isHistory; + } + + public void setIsHistory(int isHistory) { + this.isHistory = isHistory; + } + + public int getIsChart() { + return isChart; + } + + public void setIsChart(int isChart) { + this.isChart = isChart; + } + + public int getIsReadonly() { + return isReadonly; + } + + public void setIsReadonly(int isReadonly) { + this.isReadonly = isReadonly; + } + + public Datatype getDatatype() { + return datatype; + } + + public void setDatatype(Datatype datatype) { + this.datatype = datatype; + } + + public int getIsMonitor() { + return isMonitor; + } + + public void setIsMonitor(int isMonitor) { + this.isMonitor = isMonitor; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getShadow() { + return shadow; + } + + public void setShadow(String shadow) { + this.shadow = shadow; + } + + 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 getTs() { + return ts; + } + + public void setTs(String ts) { + this.ts = ts; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelValueItemDto.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelValueItemDto.java new file mode 100644 index 00000000..1e5bc530 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelValueItemDto.java @@ -0,0 +1,196 @@ +package com.fastbee.iot.model.ThingsModels; + +import org.springframework.data.redis.connection.DataType; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 物模型 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ThingsModelValueItemDto +{ + /** 物模型唯一标识符 */ + private String id; + + /** 物模型名称 */ + private String name; + + /** 物模型值 */ + private String value; + + /** 是否首页显示(0-否,1-是) */ + private Integer isChart; + + /** 是否实时监测(0-否,1-是) */ + private Integer isMonitor; + + private DataType dataType; + + public Integer getIsChart() { + return isChart; + } + + public void setIsChart(Integer isChart) { + this.isChart = isChart; + } + + public Integer getIsMonitor() { + return isMonitor; + } + + public void setIsMonitor(Integer isMonitor) { + this.isMonitor = isMonitor; + } + + public DataType getDataType() { + return dataType; + } + + public void setDataType(DataType dataType) { + this.dataType = dataType; + } + + 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; + } + + public static class DataType{ + private String type; + private String falseText; + private String trueText; + private Integer maxLength; + private String arrayType; + private String unit; + private BigDecimal min; + private BigDecimal max; + private BigDecimal step; + private List enumList; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getFalseText() { + return falseText; + } + + public void setFalseText(String falseText) { + this.falseText = falseText; + } + + public String getTrueText() { + return trueText; + } + + public void setTrueText(String trueText) { + this.trueText = trueText; + } + + public Integer getMaxLength() { + return maxLength; + } + + public void setMaxLength(Integer maxLength) { + this.maxLength = maxLength; + } + + public String getArrayType() { + return arrayType; + } + + public void setArrayType(String arrayType) { + this.arrayType = arrayType; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public BigDecimal getMin() { + return min; + } + + public void setMin(BigDecimal min) { + this.min = min; + } + + public BigDecimal getMax() { + return max; + } + + public void setMax(BigDecimal max) { + this.max = max; + } + + public BigDecimal getStep() { + return step; + } + + public void setStep(BigDecimal step) { + this.step = step; + } + + public List getEnumList() { + return enumList; + } + + public void setEnumList(List enumList) { + this.enumList = enumList; + } + } + + public static class EnumItem + { + private String text; + private String value; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelValuesOutput.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelValuesOutput.java new file mode 100644 index 00000000..32235a2f --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelValuesOutput.java @@ -0,0 +1,160 @@ +package com.fastbee.iot.model.ThingsModels; + +import com.fastbee.common.annotation.Excel; + +/** + * 设备输入物模型值参数 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ThingsModelValuesOutput +{ + /** 产品ID **/ + private Long productId; + + private String productName; + + private Long deviceId; + + private String deviceName; + + private int status; + + private int isShadow; + + /** 设备ID **/ + private String serialNumber; + + /** 用户ID */ + private Long userId; + + /** 用户昵称 */ + private String userName; + + /** 租户ID */ + private Long tenantId; + + /** 租户名称 */ + private String tenantName; + + /** 设备物模型值 **/ + private String thingsModelValue; + + private int isSimulate; + + /**子设备地址*/ + private Integer slaveId; + + public Integer getSlaveId() { + return slaveId; + } + + public void setSlaveId(Integer slaveId) { + this.slaveId = slaveId; + } + + public int getIsSimulate() { + return isSimulate; + } + + public void setIsSimulate(int isSimulate) { + this.isSimulate = isSimulate; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public Long getTenantId() { + return tenantId; + } + + public void setTenantId(Long tenantId) { + this.tenantId = tenantId; + } + + public String getTenantName() { + return tenantName; + } + + public void setTenantName(String tenantName) { + this.tenantName = tenantName; + } + + public int getIsShadow() { + return isShadow; + } + + public void setIsShadow(int isShadow) { + this.isShadow = isShadow; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public Long getDeviceId() { + return deviceId; + } + + public void setDeviceId(Long deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public String getThingsModelValue() { + return thingsModelValue; + } + + public void setThingsModelValue(String thingsModelValue) { + this.thingsModelValue = thingsModelValue; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelsDto.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelsDto.java new file mode 100644 index 00000000..12cd168e --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ThingsModelsDto.java @@ -0,0 +1,50 @@ +package com.fastbee.iot.model.ThingsModels; + +import java.util.ArrayList; +import java.util.List; + +/** + * 产品分类的Id和名称输出 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ThingsModelsDto +{ + public ThingsModelsDto(){ + properties=new ArrayList<>(); + functions=new ArrayList<>(); + events=new ArrayList<>(); + } + + /** 属性 */ + private List properties; + /** 功能 */ + private List functions; + /** 事件 */ + private List events; + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } + + public List getFunctions() { + return functions; + } + + public void setFunctions(List functions) { + this.functions = functions; + } + + public List getEvents() { + return events; + } + + public void setEvents(List events) { + this.events = events; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ValueItem.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ValueItem.java new file mode 100644 index 00000000..3a2dac51 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/ThingsModels/ValueItem.java @@ -0,0 +1,62 @@ +package com.fastbee.iot.model.ThingsModels; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * redis缓存物模型值 + * @author gsb + * @date 2023/6/3 13:48 + */ +@Data +@NoArgsConstructor +public class ValueItem { + + public ValueItem(String id, Integer slaveId, String regArr) { + this.id = id; + this.slaveId = slaveId; + this.regArr = regArr; + } + + /** + * 标识符 + */ + private String id; + + /** + * 物模型值 + */ + private String value; + + /** + * 影子值 + **/ + private String shadow; + + /** + * 物模型名称 + **/ + private String name; + + /** + * 上报时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date ts; + + /** + * 从机编号 + */ + private Integer slaveId; + + /** + * 寄存器地址 + */ + private String regArr; + + private String remark; + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/TriggerParameter.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/TriggerParameter.java new file mode 100644 index 00000000..f6047255 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/TriggerParameter.java @@ -0,0 +1,92 @@ +package com.fastbee.iot.model; + +import com.fastbee.common.annotation.Excel; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.ibatis.annotations.Param; + +/** + * TriggerParameter + * + * @author kerwincui + * @date 2021-12-16 + */ +public class TriggerParameter +{ + /** 物模型标识符数组 */ + private String[] ids; + + /** 产品ID */ + private Long productId; + + /** 设备ID */ + private Long deviceId; + + /** 设备编号 */ + private String serialNumber; + + /** 触发源(1=设备触发,2=定时触发) */ + private Integer source; + + /** 类型:1=属性,2=功能,3=事件,4=设备升级,5=设备上线,6=设备下线**/ + private Integer type; + + /**场景状态 (1-启动,2-停止) **/ + private Integer status; + + public Integer getSource() { + return source; + } + + public void setSource(Integer source) { + this.source = source; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public String[] getIds() { + return ids; + } + + public void setIds(String[] ids) { + this.ids = ids; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public Long getDeviceId() { + return deviceId; + } + + public void setDeviceId(Long deviceId) { + this.deviceId = deviceId; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/UserIdDeviceIdModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/UserIdDeviceIdModel.java new file mode 100644 index 00000000..c9f05d1b --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/UserIdDeviceIdModel.java @@ -0,0 +1,36 @@ +package com.fastbee.iot.model; + +/** + * 用户ID和设备ID模型 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class UserIdDeviceIdModel +{ + private Long userId; + + private Long deviceId; + + public UserIdDeviceIdModel(Long userId, Long deviceId){ + this.userId=userId; + this.deviceId=deviceId; + } + + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getDeviceId() { + return deviceId; + } + + public void setDeviceId(Long deviceId) { + this.deviceId = deviceId; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dashBoard/DashMqttMetrics.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dashBoard/DashMqttMetrics.java new file mode 100644 index 00000000..bd599601 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dashBoard/DashMqttMetrics.java @@ -0,0 +1,29 @@ +package com.fastbee.iot.model.dashBoard; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author bill + */ +@Data +@Builder +public class DashMqttMetrics { + + /**接收总数*/ + private Integer receive_total; + /**发送总数*/ + private Integer send_total; + /**认证总数*/ + private Integer auth_total; + /**连接总数*/ + private Integer connect_total; + /**订阅总数*/ + private Integer subscribe_total; + /**今日接收总数*/ + private Integer today_received; + /**今日发送总数*/ + private Integer today_send; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dashBoard/DashMqttStat.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dashBoard/DashMqttStat.java new file mode 100644 index 00000000..069bc424 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dashBoard/DashMqttStat.java @@ -0,0 +1,31 @@ +package com.fastbee.iot.model.dashBoard; + +import lombok.Builder; +import lombok.Data; + +/** + * @author gsb + * @date 2023/3/27 17:05 + */ +@Data +@Builder +public class DashMqttStat { + + /**当前连接数*/ + private Integer connection_count; + /**连接总数*/ + private Integer connection_total; + /**会话数量*/ + private Integer session_count; + /**会话总数*/ + private Integer session_total; + /**当前订阅主题总数*/ + private Integer subscription_count; + /**总订阅总数*/ + private Integer subscription_total; + /**当前保留消息*/ + private Integer retain_count; + /**总保留信息数*/ + private Integer retain_total; + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dto/DeviceRtDto.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dto/DeviceRtDto.java new file mode 100644 index 00000000..65d820ca --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/dto/DeviceRtDto.java @@ -0,0 +1,29 @@ +package com.fastbee.iot.model.dto; + +import lombok.Data; + +/** + * 设备实时数据dto + * @author gsb + * @date 2023/2/1 15:55 + */ +@Data +public class DeviceRtDto { + + /**物模型名称*/ + private String modelName; + /**值*/ + private String value; + /**标识符*/ + private String identifier; + /**数据定义*/ + private String specs; + /**寄存器地址*/ + private String regAddr; + /**数据类型*/ + private String dataType; + /**更新时间*/ + private String platformTime; + + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/login/AuthRequestWrap.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/login/AuthRequestWrap.java new file mode 100644 index 00000000..869f8443 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/login/AuthRequestWrap.java @@ -0,0 +1,26 @@ +package com.fastbee.iot.model.login; + +import com.fastbee.iot.domain.SocialPlatform; +import me.zhyd.oauth.request.AuthRequest; + +public class AuthRequestWrap { + private AuthRequest authRequest; + + private SocialPlatform socialPlatform; + + public AuthRequest getAuthRequest() { + return authRequest; + } + + public void setAuthRequest(AuthRequest authRequest) { + this.authRequest = authRequest; + } + + public SocialPlatform getSocialPlatform() { + return socialPlatform; + } + + public void setSocialPlatform(SocialPlatform socialPlatform) { + this.socialPlatform = socialPlatform; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/login/BindIdValue.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/login/BindIdValue.java new file mode 100644 index 00000000..8d8ad55d --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/login/BindIdValue.java @@ -0,0 +1,23 @@ +package com.fastbee.iot.model.login; + +public class BindIdValue { + + private String uuid; + private String source; + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/login/LoginIdValue.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/login/LoginIdValue.java new file mode 100644 index 00000000..4dc23a57 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/login/LoginIdValue.java @@ -0,0 +1,22 @@ +package com.fastbee.iot.model.login; + +public class LoginIdValue { + String username; + String password; + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/login/WeChatLoginQrRes.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/login/WeChatLoginQrRes.java new file mode 100644 index 00000000..91f9bcc7 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/login/WeChatLoginQrRes.java @@ -0,0 +1,19 @@ +package com.fastbee.iot.model.login; + +import lombok.Data; + +/** + * @author fastb + * @date 2023-09-04 11:03 + */ +@Data +public class WeChatLoginQrRes { + + private String appid; + + private String scope; + + private String redirectUri; + + private String state; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/DeviceSlavePoint.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/DeviceSlavePoint.java new file mode 100644 index 00000000..2922fc4e --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/DeviceSlavePoint.java @@ -0,0 +1,26 @@ +package com.fastbee.iot.model.varTemp; + +import com.fastbee.common.core.iot.response.IdentityAndName; +import com.fastbee.iot.domain.ThingsModel; +import lombok.Data; + +import java.util.List; + +/** + * @author gsb + * @date 2022/12/14 14:56 + */ +@Data +public class DeviceSlavePoint { + + /**从机编号*/ + private Integer slaveId; + /**从机对应采集点数据*/ + private List pointList; + /**轮询时间*/ + private Long timer; + /**批量读取的个数*/ + private Integer packetLength; + + private Integer code; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/DeviceTemp.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/DeviceTemp.java new file mode 100644 index 00000000..60750c96 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/DeviceTemp.java @@ -0,0 +1,19 @@ +package com.fastbee.iot.model.varTemp; + +import lombok.Data; + +import java.util.List; + +/** + * @author gsb + * @date 2022/12/14 15:38 + */ +@Data +public class DeviceTemp { + + private Long productId; + + private String serialNumber; + + private List pointList; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/EnumClass.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/EnumClass.java new file mode 100644 index 00000000..f64a7b30 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/EnumClass.java @@ -0,0 +1,14 @@ +package com.fastbee.iot.model.varTemp; + +import lombok.Data; + +/** + * @author gsb + * @date 2023/1/15 10:42 + */ +@Data +public class EnumClass { + + private String text; + private String value; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/SlaveIdAndId.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/SlaveIdAndId.java new file mode 100644 index 00000000..77c7931e --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/SlaveIdAndId.java @@ -0,0 +1,14 @@ +package com.fastbee.iot.model.varTemp; + +import lombok.Data; + +/** + * @author gsb + * @date 2023/2/4 13:52 + */ +@Data +public class SlaveIdAndId { + + private Integer slaveId; + private Long id; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/SyncModel.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/SyncModel.java new file mode 100644 index 00000000..a60b3e79 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/model/varTemp/SyncModel.java @@ -0,0 +1,15 @@ +package com.fastbee.iot.model.varTemp; + +import lombok.Data; + +import java.util.List; + +/** + * @author bill + */ +@Data +public class SyncModel { + + private List productIds; + private Long templateId; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/AuthorizationServerConfig.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/AuthorizationServerConfig.java new file mode 100644 index 00000000..e001f335 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/AuthorizationServerConfig.java @@ -0,0 +1,119 @@ +package com.fastbee.iot.oauth; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.approval.ApprovalStore; +import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore; +import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; +import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; +import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices; +import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; +import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; + +import javax.sql.DataSource; + +/** + * 授权服务器配置,配置客户端id,密钥和令牌的过期时间 + */ +@Configuration +@EnableAuthorizationServer +public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + @Autowired + private DataSource dataSource; + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private UserDetailsService userDetailsService; + + /** + * 用来配置令牌端点(Token Endpoint)的安全约束 + * @param security + * @throws Exception + */ + @Override + public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { + security.allowFormAuthenticationForClients() + .authenticationEntryPoint(new OAuth2AuthenticationEntryPoint()); + } + + /** + * 用来配置客户端详情服务 + * @param clients + * @throws Exception + */ + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + + clients.withClientDetails(getClientDetailsService()); + } + + /** + * 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。 + * @param endpoints + * @throws Exception + */ + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + // 查询用户、授权、分组,可以被重写 + endpoints.userDetailsService(userDetailsService) + // 审批客户端的授权 + .userApprovalHandler(userApprovalHandler()) + // 授权审批 + .approvalStore(approvalStore()) + // 获取授权码 + .authorizationCodeServices(new JdbcAuthorizationCodeServices(dataSource)) + // 验证token + .authenticationManager(authenticationManager) + // 查询、保存、刷新token + .tokenStore(this.getJdbcTokenStore()); + } + + @Bean + public ApprovalStore approvalStore() { + return new JdbcApprovalStore(dataSource); + } + + @Bean + public UserApprovalHandler userApprovalHandler() { + return new SpeakerApprovalHandler(getClientDetailsService(), approvalStore(), oAuth2RequestFactory()); + } + + @Bean + public JdbcClientDetailsService getClientDetailsService() { + JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource); + jdbcClientDetailsService.setPasswordEncoder(passwordEncoder()); + return jdbcClientDetailsService; + } + + @Bean + public OAuth2RequestFactory oAuth2RequestFactory() { + return new DefaultOAuth2RequestFactory(getClientDetailsService()); + } + @Bean + public TokenStore getJdbcTokenStore(){ + TokenStore tokenStore = new JdbcTokenStore(dataSource); + return tokenStore; + } + + + + public BCryptPasswordEncoder passwordEncoder(){ + return new BCryptPasswordEncoder(); + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/ResourceServerConfig.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/ResourceServerConfig.java new file mode 100644 index 00000000..bb4429d5 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/ResourceServerConfig.java @@ -0,0 +1,50 @@ +package com.fastbee.iot.oauth; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; + +import javax.sql.DataSource; + +@Configuration +@EnableResourceServer +public class ResourceServerConfig extends ResourceServerConfigurerAdapter { + + @Autowired + private DataSource dataSource; + + @Override + public void configure(ResourceServerSecurityConfigurer resources) throws Exception { + TokenStore tokenStore = jdbcTokenStore(); + OAuth2AuthenticationManager auth2AuthenticationManager= new OAuth2AuthenticationManager(); + resources.authenticationManager(auth2AuthenticationManager); + resources.resourceId("speaker-service").tokenStore(tokenStore).stateless(true); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + // 限制资源服务器只接管匹配的资源 + http.requestMatchers().antMatchers("/oauth/speaker/**") + .and() + //授权的请求 + .authorizeRequests() + .anyRequest().authenticated() + //关闭跨站请求防护 + .and() + .csrf().disable(); + } + + public TokenStore jdbcTokenStore(){ + TokenStore tokenStore = new JdbcTokenStore(dataSource); + return tokenStore; + } + +} \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/SpeakerApprovalHandler.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/SpeakerApprovalHandler.java new file mode 100644 index 00000000..61fafb57 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/SpeakerApprovalHandler.java @@ -0,0 +1,83 @@ +package com.fastbee.iot.oauth; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.approval.Approval; +import org.springframework.security.oauth2.provider.approval.ApprovalStore; +import org.springframework.security.oauth2.provider.approval.ApprovalStoreUserApprovalHandler; +import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; + +import java.util.*; + +/** + * kerwincui + */ +public class SpeakerApprovalHandler extends ApprovalStoreUserApprovalHandler { + + private int approvalExpirySeconds = -1; + + @Autowired + private ApprovalStore approvalStore; + + public SpeakerApprovalHandler(JdbcClientDetailsService clientDetailsService, ApprovalStore approvalStore, OAuth2RequestFactory oAuth2RequestFactory) { + this.setApprovalStore(approvalStore); + this.setClientDetailsService(clientDetailsService); + this.setRequestFactory(oAuth2RequestFactory); + } + + @Override + public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) { + // 获取授权过的范围 + Set requestedScopes = authorizationRequest.getScope(); + Set approvedScopes = new HashSet(); + Set approvals = new HashSet(); + Date expiry = computeExpiry(); + + // 存储授权或拒绝的范围 + Map approvalParameters = authorizationRequest.getApprovalParameters(); + for (String requestedScope : requestedScopes) { + String approvalParameter = OAuth2Utils.SCOPE_PREFIX + requestedScope; + String value = approvalParameters.get(approvalParameter); + value = value == null ? "" : value.toLowerCase(); + if ("true".equals(value) || value.startsWith("approve")||value.equals("on")) { + approvedScopes.add(requestedScope); + approvals.add(new Approval(userAuthentication.getName(), authorizationRequest.getClientId(), + requestedScope, expiry, Approval.ApprovalStatus.APPROVED)); + } + else { + approvals.add(new Approval(userAuthentication.getName(), authorizationRequest.getClientId(), + requestedScope, expiry, Approval.ApprovalStatus.DENIED)); + } + } + approvalStore.addApprovals(approvals); + + boolean approved; + authorizationRequest.setScope(approvedScopes); + if (approvedScopes.isEmpty() && !requestedScopes.isEmpty()) { + approved = false; + } + else { + approved = true; + } + authorizationRequest.setApproved(approved); + return authorizationRequest; + } + + private Date computeExpiry() { + Calendar expiresAt = Calendar.getInstance(); + // 默认一个月 + if (approvalExpirySeconds == -1) { + expiresAt.add(Calendar.MONTH, 1); + } + else { + expiresAt.add(Calendar.SECOND, approvalExpirySeconds); + } + return expiresAt.getTime(); + } + +} + diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/api/ConfirmAccessController.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/api/ConfirmAccessController.java new file mode 100644 index 00000000..ea3d94fd --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/api/ConfirmAccessController.java @@ -0,0 +1,49 @@ +package com.fastbee.iot.oauth.api; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.approval.Approval; +import org.springframework.security.oauth2.provider.approval.ApprovalStore; +import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.SessionAttributes; + +import java.security.Principal; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * kerwincui + */ +@Controller +@SessionAttributes("authorizationRequest") +public class ConfirmAccessController { + @Autowired + private JdbcClientDetailsService clientDetailsService; + @Autowired + private ApprovalStore approvalStore; + + @RequestMapping("/oauth/confirm_access") + public String getAccessConfirmation(Map model, Principal principal ) { + AuthorizationRequest clientAuth = (AuthorizationRequest) model.remove("authorizationRequest"); + ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); + + Map scopes = new LinkedHashMap(); + for (String scope : clientAuth.getScope()) { + scopes.put(OAuth2Utils.SCOPE_PREFIX + scope, "false"); + } + for (Approval approval : approvalStore.getApprovals(principal.getName(), client.getClientId())) { + if (clientAuth.getScope().contains(approval.getScope())) { + scopes.put(OAuth2Utils.SCOPE_PREFIX + approval.getScope(), + approval.getStatus() == Approval.ApprovalStatus.APPROVED ? "true" : "false"); + } + } + model.put("auth_request", clientAuth); + model.put("client", client); + model.put("scopes", scopes); + return "oauth/access_confirmation"; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/api/LoginController.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/api/LoginController.java new file mode 100644 index 00000000..d8dfe0b7 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/api/LoginController.java @@ -0,0 +1,55 @@ +package com.fastbee.iot.oauth.api; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.model.LoginBody; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.framework.web.service.SysLoginService; +import com.fastbee.framework.web.service.TokenService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +@Controller +public class LoginController { + + + @Autowired + private TokenStore tokenStore; + + @Autowired + private SysLoginService loginService; + + @Autowired + private TokenService tokenService; + + @RequestMapping("/oauth/login") + public String login() { + return "oauth/login"; + } + + @RequestMapping("/oauth/index") + public String index() { + return "oauth/index"; + } + + @GetMapping("/oauth/logout") + @ResponseBody + public String logout(@RequestHeader String Authorization) { + if (!Authorization.isEmpty()){ + String token=Authorization.split(" ")[1]; + OAuth2AccessToken auth2AccessToken = tokenStore.readAccessToken(token); + tokenStore.removeAccessToken(auth2AccessToken); + return "SUCCESS"; + }else{ + return "FAIL"; + } + + } + +} \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/api/SpeakerController.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/api/SpeakerController.java new file mode 100644 index 00000000..f1f1dc70 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/oauth/api/SpeakerController.java @@ -0,0 +1,33 @@ +package com.fastbee.iot.oauth.api; + +import com.alibaba.fastjson2.JSONObject; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * kerwincui + */ +@RestController +public class SpeakerController { + @GetMapping("/oauth/speaker/get") + public JSONObject getSpeaker() { +// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + JSONObject Json = new JSONObject(); + Json.put("1", "1"); + Json.put("2", "2"); + Json.put("3", "3"); + System.out.println("调用了接口get"); + return Json; + } + @PostMapping("/oauth/speaker/post") + public JSONObject postSpeaker() { +// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + JSONObject bookJson = new JSONObject(); + bookJson.put("1", "1"); + System.out.println("调用了接口post"); + return bookJson; + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IAuthRequestFactory.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IAuthRequestFactory.java new file mode 100644 index 00000000..d9ac0ce7 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IAuthRequestFactory.java @@ -0,0 +1,14 @@ +package com.fastbee.iot.service; + +import com.fastbee.iot.model.login.AuthRequestWrap; + +/** + * AuthRequest简单工程类接口 + * + * @author json + * @date 2022-04-12 + */ +public interface IAuthRequestFactory { + + AuthRequestWrap getAuthRequest(String source); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/ICategoryService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/ICategoryService.java new file mode 100644 index 00000000..22e23d2e --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/ICategoryService.java @@ -0,0 +1,72 @@ +package com.fastbee.iot.service; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.iot.domain.Category; +import com.fastbee.iot.model.IdAndName; + +import java.util.List; + +/** + * 产品分类Service接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface ICategoryService +{ + /** + * 查询产品分类 + * + * @param categoryId 产品分类主键 + * @return 产品分类 + */ + public Category selectCategoryByCategoryId(Long categoryId); + + /** + * 查询产品分类列表 + * + * @param category 产品分类 + * @return 产品分类集合 + */ + public List selectCategoryList(Category category); + + /** + * 查询产品简短分类列表 + * + * @return 产品分类集合 + */ + public List selectCategoryShortList(); + + /** + * 新增产品分类 + * + * @param category 产品分类 + * @return 结果 + */ + public int insertCategory(Category category); + + /** + * 修改产品分类 + * + * @param category 产品分类 + * @return 结果 + */ + public int updateCategory(Category category); + + /** + * 批量删除产品分类 + * + * @param categoryIds 需要删除的产品分类主键集合 + * @return 结果 + */ + public AjaxResult deleteCategoryByCategoryIds(Long[] categoryIds); + + /** + * 删除产品分类信息 + * + * @param categoryId 产品分类主键 + * @return 结果 + */ + public int deleteCategoryByCategoryId(Long categoryId); + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceJobService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceJobService.java new file mode 100644 index 00000000..7d6f4e33 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceJobService.java @@ -0,0 +1,127 @@ +package com.fastbee.iot.service; + +import com.fastbee.common.exception.job.TaskException; +import com.fastbee.iot.domain.DeviceJob; +import org.quartz.SchedulerException; + +import java.util.List; + +/** + * 定时任务调度信息信息 服务层 + * + * @author kerwincui + */ +public interface IDeviceJobService +{ + /** + * 获取quartz调度器的计划任务 + * + * @param job 调度信息 + * @return 调度任务集合 + */ + public List selectJobList(DeviceJob job); + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + public DeviceJob selectJobById(Long jobId); + + /** + * 暂停任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int pauseJob(DeviceJob job) throws SchedulerException; + + /** + * 恢复任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int resumeJob(DeviceJob job) throws SchedulerException; + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + * @return 结果 + */ + public int deleteJob(DeviceJob job) throws SchedulerException; + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + public void deleteJobByIds(Long[] jobIds) throws SchedulerException; + + /** + * 根据设备Ids批量删除调度信息 + * + * @param deviceIds 需要删除数据的设备Ids + * @return 结果 + */ + public void deleteJobByDeviceIds(Long[] deviceIds) throws SchedulerException; + + /** + * 根据告警Ids批量删除调度信息 + * + * @param alertIds 需要删除数据的告警Ids + * @return 结果 + */ + public void deleteJobByAlertIds(Long[] alertIds) throws SchedulerException; + + /** + * 根据场景Ids批量删除调度信息 + * + * @param sceneIds 需要删除数据的设备Ids + * @return 结果 + */ + public void deleteJobBySceneIds(Long[] sceneIds) throws SchedulerException; + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + * @return 结果 + */ + public int changeStatus(DeviceJob job) throws SchedulerException; + + /** + * 立即运行任务 + * + * @param job 调度信息 + * @return 结果 + */ + public void run(DeviceJob job) throws SchedulerException; + + /** + * 新增任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int insertJob(DeviceJob job) throws SchedulerException, TaskException; + + /** + * 更新任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int updateJob(DeviceJob job) throws SchedulerException, TaskException; + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + public boolean checkCronExpressionIsValid(String cronExpression); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceLogService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceLogService.java new file mode 100644 index 00000000..7310cc80 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceLogService.java @@ -0,0 +1,27 @@ +package com.fastbee.iot.service; + +import com.fastbee.iot.domain.DeviceLog; +import com.fastbee.iot.model.HistoryModel; +import com.fastbee.iot.model.MonitorModel; + +import java.util.List; +import java.util.Map; + +/** + * 设备日志Service接口 + * + * @author kerwincui + * @date 2022-01-13 + */ +public interface IDeviceLogService +{ + + /** + * 查询设备监测数据 + * + * @param deviceLog 设备日志 + * @return 设备日志集合 + */ + public List selectMonitorList(DeviceLog deviceLog); + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceRuntimeService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceRuntimeService.java new file mode 100644 index 00000000..efb50585 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceRuntimeService.java @@ -0,0 +1,33 @@ +package com.fastbee.iot.service; + +import com.fastbee.common.enums.ThingsModelType; +import com.fastbee.iot.domain.DeviceLog; +import com.fastbee.iot.domain.FunctionLog; + +import java.util.List; + +/** + * 设备运行时数据 + * + * @author gsb + * @date 2023/2/1 15:08 + */ +public interface IDeviceRuntimeService { + + /** + * 根据设备编号查询设备实时运行状态 + * + * @param serialNumber 设备编号 + * @param type 物模型类型 + * @return 设备实时数据 + */ + public List runtimeBySerialNumber(String serialNumber, ThingsModelType type,Long productId,Integer slaveId); + + /** + * 根据设备编号查询设备服务调用日志情况 + * @param serialNumber 设备编号 + * @return 服务下发日志 + */ + public List runtimeReply(String serialNumber); + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceService.java new file mode 100644 index 00000000..cb24a156 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceService.java @@ -0,0 +1,263 @@ +package com.fastbee.iot.service; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; +import com.fastbee.common.enums.DeviceStatus; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.model.*; +import com.fastbee.iot.model.ThingsModels.ThingsModelShadow; +import com.fastbee.iot.model.ThingsModels.ThingsModelValueItem; +import com.fastbee.common.core.thingsModel.ThingsModelValuesInput; +import org.quartz.SchedulerException; + +import java.util.List; +import java.util.Map; + +/** + * 设备Service接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface IDeviceService +{ + /** + * 查询设备 + * + * @param deviceId 设备主键 + * @return 设备 + */ + public Device selectDeviceByDeviceId(Long deviceId); + + /** + * 查询设备统计信息 + * + * @return 设备 + */ + public DeviceStatistic selectDeviceStatistic(); + + /** + * 根据设备编号查询设备 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + public Device selectDeviceBySerialNumber(String serialNumber); + + /** + * 根据设备编号查询简洁设备 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + public Device selectShortDeviceBySerialNumber(String serialNumber); + + /** + * 根据设备编号查询设备认证信息 + * + * @param model 设备编号和产品ID + * @return 设备 + */ + public ProductAuthenticateModel selectProductAuthenticate(AuthenticateInputModel model); + + /** + * 查询设备和运行状态 + * + * @param deviceId 设备主键 + * @return 设备 + */ + public DeviceShortOutput selectDeviceRunningStatusByDeviceId(Long deviceId,Integer slaveId); + + /** + * 上报设备的物模型 + * @param input + * @return + */ + public List reportDeviceThingsModelValue(ThingsModelValuesInput input, int type, boolean isShadow); + + /** + * 查询设备列表 + * + * @param device 设备 + * @return 设备集合 + */ + public List selectDeviceList(Device device); + + /** + * 查询未分配授权码设备列表 + * + * @param device 设备 + * @return 设备集合 + */ + public List selectUnAuthDeviceList(Device device); + + /** + * 查询分组可添加设备分页列表 + * + * @param device 设备 + * @return 设备集合 + */ + public List selectDeviceListByGroup(Device device); + + /** + * 查询所有设备简短列表 + * + * @return 设备 + */ + public List selectAllDeviceShortList(); + + /** + * 查询设备简短列表 + * + * @param device 设备 + * @return 设备集合 + */ + public List selectDeviceShortList(Device device); + + /** + * 新增设备 + * + * @param device 设备 + * @return 结果 + */ + public Device insertDevice(Device device); + + /** + * 设备关联用户 + * + * @param deviceRelateUserInput 设备 + * @return 结果 + */ + public AjaxResult deviceRelateUser(DeviceRelateUserInput deviceRelateUserInput); + + /** + * 设备认证后自动添加设备 + * + * @return 结果 + */ + public int insertDeviceAuto(String serialNumber,Long userId,Long productId); + + /** + * 获取设备设置的影子 + * @param device + * @return + */ + public ThingsModelShadow getDeviceShadowThingsModel(Device device); + + /** + * 修改设备 + * + * @param device 设备 + * @return 结果 + */ + public AjaxResult updateDevice(Device device); + + /** + * 更新设备状态和定位 + * @param device 设备 + * @return 结果 + */ + public int updateDeviceStatusAndLocation(Device device,String ipAddress); + + /** + * 更新设备状态 + * @param device 设备 + * @return 结果 + */ + public int updateDeviceStatus(Device device); + + /** + * 更新固件版本 + * @param device + * @return + */ + public int updateDeviceFirmwareVersion(Device device); + + /** + * 上报设备信息 + * @param device 设备 + * @return 结果 + */ + public int reportDevice(Device device,Device deviceentity); + + /** + * 删除设备 + * + * @param deviceId 需要删除的设备主键集合 + * @return 结果 + */ + public int deleteDeviceByDeviceId(Long deviceId) throws SchedulerException; + + /** + * 根据网关编号删除子设备 + * @param gwCode + */ + public void deleteSubDevice(String gwCode); + + /** + * 缓存设备状态到redis + * + * @return + */ + public List cacheDeviceStatus(Long productId, String serialNumber); + + /** + * 生成设备唯一编号 + * @return 结果 + */ + public String generationDeviceNum(Integer type); + + /** + * 重置设备状态 + * @return 结果 + */ + public int resetDeviceStatus(String deviceNum); + + /** + * 根据设备编号查询协议编码 + * @param serialNumber 设备编号 + * @return + */ + public Map selectProtocolBySerialNumber(String serialNumber); + + /** + * 查询产品下所有设备,返回设备编号 + * @param productId 产品id + * @return + */ + public List selectDevicesByProductId(Long productId,Integer hasSub); + + /** + * 查询子设备总数 + * @param gwDevCode 网关编号 + * @return 数量 + */ + public Integer getSubDeviceCount(String gwDevCode); + + /** + * 批量更新设备状态 + * @param serialNumbers 设备ids + * @param status 状态 + */ + public void batchChangeStatus(List serialNumbers , DeviceStatus status); + + /** + * 查询在线的modbus网关设备 + * @return + */ + public List selectOnlineModbusDevices(); + + /** + * 根据设备编号查询设备信息 -不带缓存物模型值 + * @param serialNumber + * @return + */ + public Device selectDeviceNoModel(String serialNumber); + + /** + * 获取设备MQTT连接参数 + * @param deviceId 设备id + * @return + */ + public DeviceMqttConnectVO getMqttConnectData(Long deviceId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceUserService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceUserService.java new file mode 100644 index 00000000..5eb9ccf9 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IDeviceUserService.java @@ -0,0 +1,88 @@ +package com.fastbee.iot.service; + +import java.util.List; + +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.iot.domain.DeviceUser; + +/** + * 设备用户Service接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface IDeviceUserService +{ + /** + * 查询设备用户 + * + * @param deviceId 设备用户主键 + * @return 设备用户 + */ + public List selectDeviceUserByDeviceId(Long deviceId); + + /** + * 查询设备用户列表 + * + * @param deviceUser 设备用户 + * @return 设备用户集合 + */ + public List selectDeviceUserList(DeviceUser deviceUser); + + /** + * 查询分享设备用户 + * + * @param deviceUser 设备用户 + * @return 设备用户集合 + */ + public SysUser selectShareUser(DeviceUser deviceUser); + + /** + * 新增设备用户 + * + * @param deviceUser 设备用户 + * @return 结果 + */ + public int insertDeviceUser(DeviceUser deviceUser); + + /** + * 修改设备用户 + * + * @param deviceUser 设备用户 + * @return 结果 + */ + public int updateDeviceUser(DeviceUser deviceUser); + + /** + * 批量删除设备用户 + * + * @param deviceIds 需要删除的设备用户主键集合 + * @return 结果 + */ + public int deleteDeviceUserByDeviceIds(Long[] deviceIds); + + /** + * 删除设备用户信息 + * + * @param deviceId 设备用户主键 + * @return 结果 + */ + public int deleteDeviceUserByDeviceId(Long deviceId); + + /** + * 批量添加设备用户 + * @param deviceUsers 设备用户 + * @return 结果 + */ + public int insertDeviceUserList(List deviceUsers); + + /** + * 查询设备用户 + * + * @param deviceId 设备用户主键 + * @return 设备用户 + */ + public DeviceUser selectDeviceUserByDeviceIdAndUserId(Long deviceId, Long userId); + + public int deleteDeviceUser(DeviceUser deviceUser); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IEventLogService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IEventLogService.java new file mode 100644 index 00000000..3ee84126 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IEventLogService.java @@ -0,0 +1,76 @@ +package com.fastbee.iot.service; + +import com.fastbee.iot.domain.EventLog; + +import java.util.List; + +/** + * 事件日志Service接口 + * + * @author kerwincui + * @date 2023-03-28 + */ +public interface IEventLogService +{ + /** + * 查询事件日志 + * + * @param logId 事件日志主键 + * @return 事件日志 + */ + public EventLog selectEventLogByLogId(Long logId); + + /** + * 查询事件日志列表 + * + * @param eventLog 事件日志 + * @return 事件日志集合 + */ + public List selectEventLogList(EventLog eventLog); + + /** + * 新增事件日志 + * + * @param eventLog 事件日志 + * @return 结果 + */ + public int insertEventLog(EventLog eventLog); + + /** + * 批量存储事件日志 + * @param list + */ + public void insertBatch(List list); + + /** + * 修改事件日志 + * + * @param eventLog 事件日志 + * @return 结果 + */ + public int updateEventLog(EventLog eventLog); + + /** + * 批量删除事件日志 + * + * @param logIds 需要删除的事件日志主键集合 + * @return 结果 + */ + public int deleteEventLogByLogIds(Long[] logIds); + + /** + * 删除事件日志信息 + * + * @param logId 事件日志主键 + * @return 结果 + */ + public int deleteEventLogByLogId(Long logId); + + /** + * 通过设备ID删除设备事件日志 + * + * @param serialNumber 设备编号 + * @return 结果 + */ + public int deleteEventLogByDeviceNumber(String serialNumber); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IFunctionLogService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IFunctionLogService.java new file mode 100644 index 00000000..d701d21d --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IFunctionLogService.java @@ -0,0 +1,87 @@ +package com.fastbee.iot.service; + +import java.util.List; +import com.fastbee.iot.domain.FunctionLog; + +/** + * 设备服务下发日志Service接口 + * + * @author kerwincui + * @date 2022-10-22 + */ +public interface IFunctionLogService +{ + /** + * 查询设备服务下发日志 + * + * @param id 设备服务下发日志主键 + * @return 设备服务下发日志 + */ + public FunctionLog selectFunctionLogById(Long id); + + /** + * 查询设备服务下发日志列表 + * + * @param functionLog 设备服务下发日志 + * @return 设备服务下发日志集合 + */ + public List selectFunctionLogList(FunctionLog functionLog); + + /** + * 新增设备服务下发日志 + * + * @param functionLog 设备服务下发日志 + * @return 结果 + */ + public int insertFunctionLog(FunctionLog functionLog); + + /** + * 批量插入数据 + * @param list + */ + public void insertBatch(List list); + + /** + * 修改设备服务下发日志 + * + * @param functionLog 设备服务下发日志 + * @return 结果 + */ + public int updateFunctionLog(FunctionLog functionLog); + + /** + * 批量删除设备服务下发日志 + * + * @param ids 需要删除的设备服务下发日志主键集合 + * @return 结果 + */ + public int deleteFunctionLogByIds(Long[] ids); + + /** + * 删除设备服务下发日志信息 + * + * @param id 设备服务下发日志主键 + * @return 结果 + */ + public int deleteFunctionLogById(Long id); + + /** + * 根据设备编号删除设备服务下发日志信息 + * + * @param serialNumber 设备编号 + * @return 结果 + */ + public int deleteFunctionLogByDeviceNumber(String serialNumber); + + /** + * 批量更新日志状态值 + * @param log 参数 + */ + public void updateFuncLogBatch(FunctionLog log); + + /** + * 根据消息id更新指令下发状态 + * @param log + */ + public void updateByMessageId(FunctionLog log); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IGroupService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IGroupService.java new file mode 100644 index 00000000..bb512f1b --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IGroupService.java @@ -0,0 +1,76 @@ +package com.fastbee.iot.service; + +import com.fastbee.iot.domain.Group; +import com.fastbee.iot.model.DeviceGroupInput; + +import java.util.List; + +/** + * 设备分组Service接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface IGroupService +{ + /** + * 查询设备分组 + * + * @param groupId 设备分组主键 + * @return 设备分组 + */ + public Group selectGroupByGroupId(Long groupId); + + /** + * 通过分组ID查询关联的设备ID数组 + * @param groupId + * @return + */ + public Long[] selectDeviceIdsByGroupId(Long groupId); + + /** + * 查询设备分组列表 + * + * @param group 设备分组 + * @return 设备分组集合 + */ + public List selectGroupList(Group group); + + /** + * 新增设备分组 + * + * @param group 设备分组 + * @return 结果 + */ + public int insertGroup(Group group); + + /** + * 分组下批量添加设备分组 + * @return + */ + public int updateDeviceGroups(DeviceGroupInput input); + + /** + * 修改设备分组 + * + * @param group 设备分组 + * @return 结果 + */ + public int updateGroup(Group group); + + /** + * 批量删除分组 + * + * @param groupIds 需要删除的设备分组主键集合 + * @return 结果 + */ + public int deleteGroupByGroupIds(Long[] groupIds); + + /** + * 删除分组信息 + * + * @param groupId 设备分组主键 + * @return 结果 + */ + public int deleteGroupByGroupId(Long groupId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/INewsCategoryService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/INewsCategoryService.java new file mode 100644 index 00000000..f0113e35 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/INewsCategoryService.java @@ -0,0 +1,71 @@ +package com.fastbee.iot.service; + +import java.util.List; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.iot.domain.NewsCategory; +import com.fastbee.iot.model.IdAndName; + +/** + * 新闻分类Service接口 + * + * @author kerwincui + * @date 2022-04-09 + */ +public interface INewsCategoryService +{ + /** + * 查询新闻分类 + * + * @param categoryId 新闻分类主键 + * @return 新闻分类 + */ + public NewsCategory selectNewsCategoryByCategoryId(Long categoryId); + + /** + * 查询新闻分类列表 + * + * @param newsCategory 新闻分类 + * @return 新闻分类集合 + */ + public List selectNewsCategoryList(NewsCategory newsCategory); + + /** + * 查询新闻分类简短列表 + * + * @return 新闻分类集合 + */ + public List selectNewsCategoryShortList(); + + /** + * 新增新闻分类 + * + * @param newsCategory 新闻分类 + * @return 结果 + */ + public int insertNewsCategory(NewsCategory newsCategory); + + /** + * 修改新闻分类 + * + * @param newsCategory 新闻分类 + * @return 结果 + */ + public int updateNewsCategory(NewsCategory newsCategory); + + /** + * 批量删除新闻分类 + * + * @param categoryIds 需要删除的新闻分类主键集合 + * @return 结果 + */ + public AjaxResult deleteNewsCategoryByCategoryIds(Long[] categoryIds); + + /** + * 删除新闻分类信息 + * + * @param categoryId 新闻分类主键 + * @return 结果 + */ + public int deleteNewsCategoryByCategoryId(Long categoryId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/INewsService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/INewsService.java new file mode 100644 index 00000000..8930a3f3 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/INewsService.java @@ -0,0 +1,69 @@ +package com.fastbee.iot.service; + +import java.util.List; +import com.fastbee.iot.domain.News; +import com.fastbee.iot.model.CategoryNews; + +/** + * 新闻资讯Service接口 + * + * @author kerwincui + * @date 2022-04-09 + */ +public interface INewsService +{ + /** + * 查询新闻资讯 + * + * @param newsId 新闻资讯主键 + * @return 新闻资讯 + */ + public News selectNewsByNewsId(Long newsId); + + /** + * 查询新闻资讯列表 + * + * @param news 新闻资讯 + * @return 新闻资讯集合 + */ + public List selectNewsList(News news); + + /** + * 查询置顶新闻资讯列表 + * + * @return 新闻资讯集合 + */ + public List selectTopNewsList(); + + /** + * 新增新闻资讯 + * + * @param news 新闻资讯 + * @return 结果 + */ + public int insertNews(News news); + + /** + * 修改新闻资讯 + * + * @param news 新闻资讯 + * @return 结果 + */ + public int updateNews(News news); + + /** + * 批量删除新闻资讯 + * + * @param newsIds 需要删除的新闻资讯主键集合 + * @return 结果 + */ + public int deleteNewsByNewsIds(Long[] newsIds); + + /** + * 删除新闻资讯信息 + * + * @param newsId 新闻资讯主键 + * @return 结果 + */ + public int deleteNewsByNewsId(Long newsId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IOauthClientDetailsService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IOauthClientDetailsService.java new file mode 100644 index 00000000..701521fc --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IOauthClientDetailsService.java @@ -0,0 +1,61 @@ +package com.fastbee.iot.service; + +import java.util.List; +import com.fastbee.iot.domain.OauthClientDetails; + +/** + * 云云对接Service接口 + * + * @author kerwincui + * @date 2022-02-07 + */ +public interface IOauthClientDetailsService +{ + /** + * 查询云云对接 + * + * @param clientId 云云对接主键 + * @return 云云对接 + */ + public OauthClientDetails selectOauthClientDetailsByClientId(String clientId); + + /** + * 查询云云对接列表 + * + * @param oauthClientDetails 云云对接 + * @return 云云对接集合 + */ + public List selectOauthClientDetailsList(OauthClientDetails oauthClientDetails); + + /** + * 新增云云对接 + * + * @param oauthClientDetails 云云对接 + * @return 结果 + */ + public int insertOauthClientDetails(OauthClientDetails oauthClientDetails); + + /** + * 修改云云对接 + * + * @param oauthClientDetails 云云对接 + * @return 结果 + */ + public int updateOauthClientDetails(OauthClientDetails oauthClientDetails); + + /** + * 批量删除云云对接 + * + * @param clientIds 需要删除的云云对接主键集合 + * @return 结果 + */ + public int deleteOauthClientDetailsByClientIds(String[] clientIds); + + /** + * 删除云云对接信息 + * + * @param clientId 云云对接主键 + * @return 结果 + */ + public int deleteOauthClientDetailsByClientId(String clientId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProductAuthorizeService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProductAuthorizeService.java new file mode 100644 index 00000000..04e90abf --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProductAuthorizeService.java @@ -0,0 +1,76 @@ +package com.fastbee.iot.service; + +import java.util.List; +import com.fastbee.iot.domain.ProductAuthorize; +import com.fastbee.iot.model.ProductAuthorizeVO; + +/** + * 产品授权码Service接口 + * + * @author kami + * @date 2022-04-11 + */ +public interface IProductAuthorizeService +{ + /** + * 查询产品授权码 + * + * @param authorizeId 产品授权码主键 + * @return 产品授权码 + */ + public ProductAuthorize selectProductAuthorizeByAuthorizeId(Long authorizeId); + + /** + * 查询产品授权码列表 + * + * @param productAuthorize 产品授权码 + * @return 产品授权码集合 + */ + public List selectProductAuthorizeList(ProductAuthorize productAuthorize); + + /** + * 新增产品授权码 + * + * @param productAuthorize 产品授权码 + * @return 结果 + */ + public int insertProductAuthorize(ProductAuthorize productAuthorize); + + /** + * 修改产品授权码 + * + * @param productAuthorize 产品授权码 + * @return 结果 + */ + public int updateProductAuthorize(ProductAuthorize productAuthorize); + + /** + * 批量删除产品授权码 + * + * @param authorizeIds 需要删除的产品授权码主键集合 + * @return 结果 + */ + public int deleteProductAuthorizeByAuthorizeIds(Long[] authorizeIds); + + /** + * 删除产品授权码信息 + * + * @param authorizeId 产品授权码主键 + * @return 结果 + */ + public int deleteProductAuthorizeByAuthorizeId(Long authorizeId); + + /** + * 根据数量批量新增产品授权码 + * @param productAuthorizeVO + * @return + */ + public int addProductAuthorizeByNum(ProductAuthorizeVO productAuthorizeVO); + + /** + * 根据产品id查询产品授权码 + * @param productId 产品id + * @return + */ + List listByProductId(Long productId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProductService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProductService.java new file mode 100644 index 00000000..9c07dca7 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProductService.java @@ -0,0 +1,119 @@ +package com.fastbee.iot.service; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.iot.domain.Product; +import com.fastbee.iot.model.ChangeProductStatusModel; +import com.fastbee.iot.model.IdAndName; +import com.fastbee.iot.model.ProductCode; + +import java.util.List; + +/** + * 产品Service接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface IProductService +{ + /** + * 查询产品 + * + * @param productId 产品主键 + * @return 产品 + */ + public Product selectProductByProductId(Long productId); + + /** + * 查询产品列表 + * + * @param product 产品 + * @return 产品集合 + */ + public List selectProductList(Product product); + + /** + * 查询产品简短列表 + * + * @return 产品集合 + */ + public List selectProductShortList(); + + /** + * 新增产品 + * + * @param product 产品 + * @return 结果 + */ + public Product insertProduct(Product product); + + /** + * 修改产品 + * + * @param product 产品 + * @return 结果 + */ + public int updateProduct(Product product); + + /** + * 获取产品下面的设备数量 + * + * @param productId 产品ID + * @return 结果 + */ + public int selectDeviceCountByProductId(Long productId); + + /** + * 更新产品状态,1-未发布,2-已发布 + * + * @param model + * @return 结果 + */ + public AjaxResult changeProductStatus(ChangeProductStatusModel model); + + /** + * 批量删除产品 + * + * @param productIds 需要删除的产品主键集合 + * @return 结果 + */ + public AjaxResult deleteProductByProductIds(Long[] productIds); + + /** + * 删除产品信息 + * + * @param productId 产品主键 + * @return 结果 + */ + public int deleteProductByProductId(Long productId); + + /** + * 根据设备编号查询产品信息 + * @param serialNumber 设备编号 + * @return 结果 + */ + public Product getProductBySerialNumber(String serialNumber); + + /** + * 根据设备编号查询协议编号 + * @param serialNumber 设备编号 + * @return 协议编号 + */ + public ProductCode getProtocolBySerialNumber(String serialNumber); + + /** + * 根据产品id获取协议编号 + * @param productId 产品id + * @return 协议编号 + */ + public String getProtocolByProductId(Long productId); + + + /** + * 根据模板id查询所有使用的产品 + * @param templeId 模板id + * @return + */ + public List selectByTempleId(Long templeId); + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProtocolService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProtocolService.java new file mode 100644 index 00000000..98870751 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IProtocolService.java @@ -0,0 +1,75 @@ +package com.fastbee.iot.service; + +import com.fastbee.iot.domain.Protocol; + +import java.util.List; + +/** + * 协议管理类 + * @author gsb + * @date 2022/10/19 16:47 + */ +public interface IProtocolService { + + /** + * 查询协议 + * + * @param id 协议主键 + * @return 协议 + */ + public Protocol selectProtocolById(Long id); + + /** + * 查询协议列表 + * + * @param protocol 协议 + * @return 协议集合 + */ + public List selectProtocolList(Protocol protocol); + + /** + * 新增协议 + * + * @param protocol 协议 + * @return 结果 + */ + public int insertProtocol(Protocol protocol); + + /** + * 修改协议 + * + * @param protocol 协议 + * @return 结果 + */ + public int updateProtocol(Protocol protocol); + + /** + * 批量删除协议 + * + * @param ids 需要删除的协议主键集合 + * @return 结果 + */ + public int deleteProtocolByIds(Long[] ids); + + /** + * 删除协议信息 + * + * @param id 协议主键 + * @return 结果 + */ + public int deleteProtocolById(Long id); + + + /** + * 获取所有协议 + * @return + */ + public List selectAll(); + + /** + *获取所有可用协议 + * @param protocol + * @return + */ + public List selectByCondition(Protocol protocol); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/ISocialLoginService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/ISocialLoginService.java new file mode 100644 index 00000000..737b0321 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/ISocialLoginService.java @@ -0,0 +1,89 @@ +package com.fastbee.iot.service; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.model.BindLoginBody; +import com.fastbee.common.core.domain.model.BindRegisterBody; +import com.fastbee.iot.domain.SocialUser; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthUser; + +import javax.servlet.http.HttpServletRequest; + +/** + * 第三方登录Service接口 + * 处理登录跳转业务逻辑 + * + * @author json + * @date 2022-04-12 + */ +public interface ISocialLoginService { + + /** + * 第三方登录跳转 + * + * @param source 平台 + * @param httpServletRequest 当前请求 + * @return 跳转路径 + */ + String renderAuth(String source, HttpServletRequest httpServletRequest); + + /** + * 第三方登录callback + * + * @param source 平台 + * @param authCallback 回调参数 + * @param httpServletRequest 当前请求 + * @return 跳转路径 + */ + String callback(String source, AuthCallback authCallback, HttpServletRequest httpServletRequest); + + /** + * 检查是否bindId + * + * @param bindId 绑定id + * @return + */ + AjaxResult checkBindId(String bindId); + + /** + * 获得错误显示 + * + * @param errorId errorId + * @return + */ + AjaxResult getErrorMsg(String errorId); + + /** + * 跳转直接登录 + * + * @param loginId 登录id + * @return + */ + AjaxResult socialLogin(String loginId); + + /** + * 绑定登录api + * + * @param bindLoginBody 绑定账户参数 + * @return + */ + AjaxResult bindLogin(BindLoginBody bindLoginBody); + + /** + * 注册绑定api + * + * @param bindRegisterBody + * @return + */ + AjaxResult bindRegister(BindRegisterBody bindRegisterBody); + + String genBindId(AuthUser authUser); + + String genErrorId(String msg); + + String genWxBindId(Long userId); + + SocialUser findSocialUser(String uuid, String source); + + AjaxResult checkSocialUser(SocialUser socialUser, String bindId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/ISocialPlatformService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/ISocialPlatformService.java new file mode 100644 index 00000000..d0503bea --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/ISocialPlatformService.java @@ -0,0 +1,69 @@ +package com.fastbee.iot.service; + +import java.util.List; +import com.fastbee.iot.domain.SocialPlatform; + +/** + * 第三方登录平台控制Service接口 + * + * @author json + * @date 2022-04-12 + */ +public interface ISocialPlatformService +{ + /** + * 查询第三方登录平台控制 + * + * @param socialPlatformId 第三方登录平台控制主键 + * @return 第三方登录平台控制 + */ + public SocialPlatform selectSocialPlatformBySocialPlatformId(Long socialPlatformId); + + /** + * 查询第三方登录平台控制 + * + * @param platform 第三方登录平台名称 + * @return 第三方登录平台控制 + */ + public SocialPlatform selectSocialPlatformByPlatform(String platform); + + /** + * 查询第三方登录平台控制列表 + * + * @param socialPlatform 第三方登录平台控制 + * @return 第三方登录平台控制集合 + */ + public List selectSocialPlatformList(SocialPlatform socialPlatform); + + /** + * 新增第三方登录平台控制 + * + * @param socialPlatform 第三方登录平台控制 + * @return 结果 + */ + public int insertSocialPlatform(SocialPlatform socialPlatform); + + /** + * 修改第三方登录平台控制 + * + * @param socialPlatform 第三方登录平台控制 + * @return 结果 + */ + public int updateSocialPlatform(SocialPlatform socialPlatform); + + /** + * 批量删除第三方登录平台控制 + * + * @param socialPlatformIds 需要删除的第三方登录平台控制主键集合 + * @return 结果 + */ + public int deleteSocialPlatformBySocialPlatformIds(Long[] socialPlatformIds); + + /** + * 删除第三方登录平台控制信息 + * + * @param socialPlatformId 第三方登录平台控制主键 + * @return 结果 + */ + public int deleteSocialPlatformBySocialPlatformId(Long socialPlatformId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/ISocialUserService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/ISocialUserService.java new file mode 100644 index 00000000..4c8c4dde --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/ISocialUserService.java @@ -0,0 +1,99 @@ +package com.fastbee.iot.service; + +import com.fastbee.iot.domain.SocialUser; + +import java.util.List; + +/** + * 用户第三方用户信息Service接口 + * + * @author json + * @date 2022-04-18 + */ +public interface ISocialUserService +{ + /** + * 查询用户第三方用户信息 + * + * @param socialUserId 用户第三方用户信息主键 + * @return 用户第三方用户信息 + */ + public SocialUser selectSocialUserBySocialUserId(Long socialUserId); + + /** + * 查询用户第三方用户信息列表 + * + * @param socialUser 用户第三方用户信息 + * @return 用户第三方用户信息集合 + */ + public List selectSocialUserList(SocialUser socialUser); + + /** + * 新增用户第三方用户信息 + * + * @param socialUser 用户第三方用户信息 + * @return 结果 + */ + public int insertSocialUser(SocialUser socialUser); + + /** + * 修改用户第三方用户信息 + * + * @param socialUser 用户第三方用户信息 + * @return 结果 + */ + public int updateSocialUser(SocialUser socialUser); + + /** + * 批量删除用户第三方用户信息 + * + * @param socialUserIds 需要删除的用户第三方用户信息主键集合 + * @return 结果 + */ + public int deleteSocialUserBySocialUserIds(Long[] socialUserIds); + + /** + * 删除用户第三方用户信息信息 + * + * @param socialUserId 用户第三方用户信息主键 + * @return 结果 + */ + public int deleteSocialUserBySocialUserId(Long socialUserId); + + /** + * 根据openId或unionId获取用户第三方信息 + * @param openId + * @param unionId + * @return + */ + public SocialUser selectOneByOpenIdAndUnionId(String openId, String unionId); + + /** + * 通过unionId查询 + * + * @param unionId + * @return + */ + Long selectSysUserIdByUnionId(String unionId); + + /** + * 通过系统用户id查询已绑定信息 + * @param sysUserId 系统用户id + * @return + */ + List selectBySysUserId(Long sysUserId); + + /** + * 取消所有相关微信绑定 + * @param sysUserId 系统用户id + * @return 结果 + */ + int cancelBind(Long sysUserId, List sourceClientList); + + /** + * 取消所有相关微信绑定 + * @param sysUserIds 系统用户id集合 + * @return 结果 + */ + int batchCancelBind(Long[] sysUserIds, List sourceClientList); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IThingsModelService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IThingsModelService.java new file mode 100644 index 00000000..07957f40 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IThingsModelService.java @@ -0,0 +1,150 @@ +package com.fastbee.iot.service; + +import com.fastbee.common.core.iot.response.IdentityAndName; +import com.fastbee.iot.domain.ThingsModel; +import com.fastbee.iot.model.ImportThingsModelInput; +import com.fastbee.iot.model.ThingsModelPerm; +import com.fastbee.iot.model.ThingsModels.PropertyDto; +import com.fastbee.iot.model.ThingsModels.ThingsItems; + +import java.util.List; +import java.util.Map; + +/** + * 物模型Service接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface IThingsModelService +{ + /** + * 查询物模型 + * + * @param modelId 物模型主键 + * @return 物模型 + */ + public ThingsModel selectThingsModelByModelId(Long modelId); + + /** + * 查询单个物模型 + * @param model 物模型 + * @return 单个物模型 + */ + public ThingsModel selectSingleThingsModel(ThingsModel model); + + /** + * 查询物模型列表 + * + * @param thingsModel 物模型 + * @return 物模型集合 + */ + public List selectThingsModelList(ThingsModel thingsModel); + + /** + * 查询物模型对应设备分享用户权限列表 + * + * @param productId 产品ID + */ + public List selectThingsModelPermList(Long productId); + + /** + * 查询物模型列表-轮询使用 + * + * @param thingsModel 物模型 + * @return 物模型集合 + */ + public Map> selectThingsModelListCache(ThingsModel thingsModel); + + /** + * 新增物模型 + * + * @param thingsModel 物模型 + * @return 结果 + */ + public int insertThingsModel(ThingsModel thingsModel); + + /** + * 从模板导入物模型 + * @param input + * @return + */ + public int importByTemplateIds(ImportThingsModelInput input); + + /** + * 修改物模型 + * + * @param thingsModel 物模型 + * @return 结果 + */ + public int updateThingsModel(ThingsModel thingsModel); + + /** + * 批量删除物模型 + * + * @param modelIds 需要删除的物模型主键集合 + * @return 结果 + */ + public int deleteThingsModelByModelIds(Long[] modelIds); + + /** + * 删除物模型信息 + * + * @param modelId 物模型主键 + * @return 结果 + */ + public int deleteThingsModelByModelId(Long modelId); + + /** + * 根据产品ID获取JSON物模型 + * @param productId + * @return + */ + public String getCacheThingsModelByProductId(Long productId); + + + /** + * 获取单个物模型获取 + * @param productId + * @param identify + * @return + */ + public PropertyDto getSingleThingModels(Long productId, String identify); + + + /** + * 批量查询产品的缓存物模型 + * @param productIds + * @return + */ + public Map getBatchCacheThingsModelByProductIds(Long[] productIds); + + /** + * 导入采集点数据 + * @param lists 数据列表 + * @param tempSlaveId 从机编码 + * @return 结果 + */ + public String importData(List lists,Integer tempSlaveId); + + /** + * 根据模板id查询从机采集点列表 + * + * @return 变量模板从机采集点集合 + */ + public List selectAllByTemplateId(Long templateId); + + /** + * 根据产品id删除 产品物模型以及物模型缓存 + * @param productId + */ + public void deleteProductThingsModelAndCacheByProductId(Long productId); + + /** + * 同步采集点模板 + * @param productIds + */ + public void synchronizeVarTempToProduct(List productIds,Long templateId); + + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IThingsModelTemplateService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IThingsModelTemplateService.java new file mode 100644 index 00000000..6fb12421 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IThingsModelTemplateService.java @@ -0,0 +1,87 @@ +package com.fastbee.iot.service; + +import java.util.List; + +import com.fastbee.iot.domain.ThingsModel; +import com.fastbee.iot.domain.ThingsModelTemplate; + +/** + * 通用物模型Service接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface IThingsModelTemplateService +{ + /** + * 查询通用物模型 + * + * @param templateId 通用物模型主键 + * @return 通用物模型 + */ + public ThingsModelTemplate selectThingsModelTemplateByTemplateId(Long templateId); + + /** + * 查询通用物模型列表 + * + * @param thingsModelTemplate 通用物模型 + * @return 通用物模型集合 + */ + public List selectThingsModelTemplateList(ThingsModelTemplate thingsModelTemplate); + + /** + * 新增通用物模型 + * + * @param thingsModelTemplate 通用物模型 + * @return 结果 + */ + public int insertThingsModelTemplate(ThingsModelTemplate thingsModelTemplate); + + /** + * 修改通用物模型 + * + * @param thingsModelTemplate 通用物模型 + * @return 结果 + */ + public int updateThingsModelTemplate(ThingsModelTemplate thingsModelTemplate); + + + /** + * 根据从机关联id更新模板信息 + * @param thingsModelTemplate + * @return + */ + public int updateTemplateByTempSlaveId(ThingsModelTemplate thingsModelTemplate); + + /** + * 批量删除通用物模型 + * + * @param templateIds 需要删除的通用物模型主键集合 + * @return 结果 + */ + public int deleteThingsModelTemplateByTemplateIds(Long[] templateIds); + + /** + * 删除通用物模型信息 + * + * @param templateId 通用物模型主键 + * @return 结果 + */ + public int deleteThingsModelTemplateByTemplateId(Long templateId); + + /** + * 导入采集点数据 + * @param lists 数据列表 + * @param tempSlaveId 从机编码 + * @return 结果 + */ + public String importData(List lists,String tempSlaveId); + + /** + * 根据模板id查询从机采集点列表 + * + * @return 变量模板从机采集点集合 + */ + public List selectAllByTemplateId(Long templateId); + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IToolService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IToolService.java new file mode 100644 index 00000000..36720574 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IToolService.java @@ -0,0 +1,70 @@ +package com.fastbee.iot.service; + +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.ProductAuthorize; +import com.fastbee.iot.model.MqttAuthenticationModel; +import com.fastbee.iot.model.ProductAuthenticateModel; +import com.fastbee.iot.model.RegisterUserInput; +import com.fastbee.iot.model.RegisterUserOutput; +import com.fastbee.iot.util.AESUtils; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +/** + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface IToolService +{ + /** + * 注册 + */ + public String register(RegisterUserInput registerBody); + + /** + * 注册 + */ + public RegisterUserOutput registerNoCaptcha(RegisterUserInput registerBody); + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser user); + + /** + * 生成随机数字和字母 + */ + public String getStringRandom(int length); + + public String generateRandomHex(int length); + + /** + * 设备简单认证 + */ + public ResponseEntity simpleMqttAuthentication(MqttAuthenticationModel mqttModel, ProductAuthenticateModel productModel); + + /** + * 设备加密认证 + * + * @return + */ + public ResponseEntity encryptAuthentication(MqttAuthenticationModel mqttModel, ProductAuthenticateModel productModel)throws Exception; + + + /** + * 整合设备认证接口 + */ + public ResponseEntity clientAuth(String clientid,String username,String password) throws Exception; + public ResponseEntity clientAuthv5(String clientid,String username,String password) throws Exception; + + /** + * 返回认证信息 + */ + public ResponseEntity returnUnauthorized(MqttAuthenticationModel mqttModel, String message); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IUserSocialProfileService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IUserSocialProfileService.java new file mode 100644 index 00000000..daad1b21 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/IUserSocialProfileService.java @@ -0,0 +1,42 @@ +package com.fastbee.iot.service; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.iot.domain.UserSocialProfile; + +import java.util.List; + +public interface IUserSocialProfileService { + /** + * 找到用户绑定的社交账户 + * + * @param sysUserId 用户主键 + * @return + */ + List selectUserSocialProfile(Long sysUserId); + + /** + * 绑定用户 + * + * @param bindId 绑定的Id + * @param sysUserId 用户主键 + * @return + */ + AjaxResult bindUser(String bindId, Long sysUserId); + + /** + * 点击绑定跳转api + * + * @param platform 平台 + * @return + */ + AjaxResult bindSocialAccount(String platform); + + /** + * 解除绑定 + * + * @param socialUserId 要解除的社交账户主键 + * @param sysUserId 用户主键 + * @return + */ + AjaxResult unbindSocialAccount(Long socialUserId, Long sysUserId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/cache/IDeviceCache.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/cache/IDeviceCache.java new file mode 100644 index 00000000..80673586 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/cache/IDeviceCache.java @@ -0,0 +1,29 @@ +package com.fastbee.iot.service.cache; + +import com.fastbee.common.core.mq.DeviceStatusBo; +import com.fastbee.common.enums.DeviceStatus; +import com.fastbee.iot.domain.Device; + +import java.util.List; + +/** + * 设备缓存 + * @author bill + */ +public interface IDeviceCache { + + /** + * 更新设备状态 + * @param dto dto + */ + public Device updateDeviceStatusCache(DeviceStatusBo dto); + + /** + * 批量更新redis缓存设备状态 + * @param serialNumbers 设备列表 + * @param status 状态 + */ + void updateBatchDeviceStatusCache(List serialNumbers, DeviceStatus status); + + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/cache/IFirmwareCache.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/cache/IFirmwareCache.java new file mode 100644 index 00000000..a31b5a90 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/cache/IFirmwareCache.java @@ -0,0 +1,30 @@ +package com.fastbee.iot.service.cache; + +/** + * 固件版本缓存 + * @author gsb + * @date 2022/10/25 11:34 + */ +public interface IFirmwareCache { + + /** + * 固件版本缓存 + * @param firmwareId 固件ID + * @param urlName 升级URL + * @return + */ + public void setFirmwareCache(Long firmwareId,String urlName); + + /** + * 获取固件版本信息 + * @param firmwareId 固件ID + * @return 升级URL + */ + public String getFirmwareCache(Long firmwareId); + + /** + * 移除固件版本 + * @param firmwareId 固件id + */ + public void remove(Long firmwareId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/cache/impl/DeviceCacheImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/cache/impl/DeviceCacheImpl.java new file mode 100644 index 00000000..3ea06ece --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/cache/impl/DeviceCacheImpl.java @@ -0,0 +1,95 @@ +package com.fastbee.iot.service.cache.impl; + +import com.fastbee.common.core.mq.DeviceStatusBo; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.core.redis.RedisKeyBuilder; +import com.fastbee.common.enums.DeviceStatus; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.service.IDeviceService; +import com.fastbee.iot.service.cache.IDeviceCache; +import lombok.SneakyThrows; +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 java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * @author bill + */ +@Service +@Slf4j +public class DeviceCacheImpl implements IDeviceCache { + + @Value("${server.device.platform.expried:120}") + private Integer expireTime; + + @Autowired + private RedisCache redisCache; + @Autowired + private IDeviceService deviceService; + + + /** + * 更新设备状态 + * 如果设备状态保持不变,更新redis设备最新在线时间 + * 如果设备状态更改,更新redis同时,更新MySQL数据库设备状态 + * + * @param dto dto + */ + @Override + public Device updateDeviceStatusCache(DeviceStatusBo dto) { + + Device device = deviceService.selectDeviceBySerialNumber(dto.getSerialNumber()); + if (dto.getStatus() == DeviceStatus.ONLINE) { + /*redis设备在线列表*/ + redisCache.zSetAdd(RedisKeyBuilder.buildDeviceOnlineListKey(), dto.getSerialNumber(), DateUtils.getTimestampSeconds()); + //更新mysql的设备状态为在线,延时500ms解决状态同步问题 + try { + Thread.sleep(500); // 延迟一秒 + } catch (InterruptedException e) { + e.printStackTrace(); + } + device.setStatus(DeviceStatus.ONLINE.getType()); + } else { + /*在redis设备在线列表移除设备*/ + redisCache.zRem(RedisKeyBuilder.buildDeviceOnlineListKey(), dto.getSerialNumber()); + //更新一下mysql的设备状态为离线 + device.setStatus(DeviceStatus.OFFLINE.getType()); + } + device.setUpdateTime(DateUtils.getNowDate()); + deviceService.updateDeviceStatusAndLocation(device, dto.getIp()); + return device; + } + + + /** + * 批量更新redis缓存设备状态 + * + * @param serialNumbers 设备列表 + * @param status 状态 + */ + @Override + public void updateBatchDeviceStatusCache(List serialNumbers, DeviceStatus status) { + if (CollectionUtils.isEmpty(serialNumbers)) { + return; + } + for (String serialNumber : serialNumbers) { + DeviceStatusBo statusBo = new DeviceStatusBo(); + statusBo.setStatus(status); + statusBo.setSerialNumber(serialNumber); + this.updateDeviceStatusCache(statusBo); + } + } + + + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/cache/impl/FirmwareCacheImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/cache/impl/FirmwareCacheImpl.java new file mode 100644 index 00000000..e1e7f200 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/cache/impl/FirmwareCacheImpl.java @@ -0,0 +1,56 @@ +package com.fastbee.iot.service.cache.impl; + +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.core.redis.RedisKeyBuilder; +import com.fastbee.iot.service.cache.IFirmwareCache; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +/** + * 固件版本缓存 + * @author gsb + * @date 2022/10/25 11:37 + */ +@Service +@Slf4j +public class FirmwareCacheImpl implements IFirmwareCache { + + @Autowired + private RedisCache redisCache; + + /** + * 固件版本缓存 + * @param firmwareId 固件ID + * @param urlName 升级URL + * @return + */ + @Override + public void setFirmwareCache(Long firmwareId,String urlName){ + String cachedKey = RedisKeyBuilder.buildFirmwareCachedKey(firmwareId); + redisCache.setCacheObject(cachedKey,urlName,60*60*12, TimeUnit.SECONDS); + } + + /** + * 获取固件版本信息 + * @param firmwareId 固件ID + * @return 升级URL + */ + @Override + public String getFirmwareCache(Long firmwareId){ + String cachedKey = RedisKeyBuilder.buildFirmwareCachedKey(firmwareId); + return redisCache.getCacheObject(cachedKey); + } + + /** + * 移除固件版本 + * @param firmwareId 固件id + */ + @Override + public void remove(Long firmwareId){ + String cachedKey = RedisKeyBuilder.buildFirmwareCachedKey(firmwareId); + redisCache.deleteObject(cachedKey); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/AuthRequestFactoryImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/AuthRequestFactoryImpl.java new file mode 100644 index 00000000..382c7897 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/AuthRequestFactoryImpl.java @@ -0,0 +1,74 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.enums.SocialPlatformType; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.iot.domain.SocialPlatform; +import com.fastbee.iot.model.login.AuthRequestWrap; +import com.fastbee.iot.service.IAuthRequestFactory; +import com.fastbee.iot.service.ISocialPlatformService; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.request.AuthQqRequest; +import me.zhyd.oauth.request.AuthRequest; +import me.zhyd.oauth.request.AuthWeChatOpenRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Locale; + +@Service +public class AuthRequestFactoryImpl implements IAuthRequestFactory { + + private static final Logger log = LoggerFactory.getLogger(AuthRequestFactoryImpl.class); + + @Autowired + private ISocialPlatformService iSocialPlatformService; + + @Autowired + private AuthStateRedisCache authStateRedisCache; + + /** + * 获得对于AUthRequest + * + * @param source 登录方式 + * @return 对应AuthRequest + */ + @Override + public AuthRequestWrap getAuthRequest(String source) { + AuthRequestWrap authRequestWrap = new AuthRequestWrap(); + AuthRequest authRequest; + try { + String lowerSource = source.toLowerCase(Locale.ROOT); + SocialPlatformType socialPlatformType = SocialPlatformType.getSocialPlatformType(lowerSource); + if (socialPlatformType == null) { + throw new ServiceException("未获取到第三方平台来源"); + } + SocialPlatform socialPlatform = iSocialPlatformService.selectSocialPlatformByPlatform(lowerSource); + authRequestWrap.setSocialPlatform(socialPlatform); + AuthConfig authConfig = AuthConfig.builder() + .clientId(socialPlatform.getClientId()) + .clientSecret(socialPlatform.getSecretKey()) + .redirectUri(socialPlatform.getRedirectUri()) + .build(); + switch (socialPlatformType) { + case WECHAT_OPEN_WEB: { + authRequest = new AuthWeChatOpenRequest(authConfig, authStateRedisCache); + break; + } + case QQ_OPEN_WEB: { + authRequest = new AuthQqRequest(authConfig, authStateRedisCache); + break; + } + default: { + throw new ServiceException("source: " + source + ",暂不支持"); + } + } + authRequestWrap.setAuthRequest(authRequest); + return authRequestWrap; + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/AuthStateRedisCache.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/AuthStateRedisCache.java new file mode 100644 index 00000000..b2e21a98 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/AuthStateRedisCache.java @@ -0,0 +1,52 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.utils.sign.Md5Utils; +import me.zhyd.oauth.cache.AuthCacheConfig; +import me.zhyd.oauth.cache.AuthStateCache; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + * 扩展Redis版的state缓存 + * + * @author json + * @date 2022/04/12 + */ +@Component +public class AuthStateRedisCache implements AuthStateCache { + + @Autowired + private RedisCache redisCache; + + @Override + public void cache(String key, String value) { + redisCache.setCacheObject(key, getValue(value), (int) AuthCacheConfig.timeout, TimeUnit.MILLISECONDS); + } + + @Override + public void cache(String key, String value, long timeout) { + redisCache.setCacheObject(key, getValue(value), (int) timeout, TimeUnit.MILLISECONDS); + } + + @Override + public String get(String key) { + return redisCache.getCacheObject(key); + } + + @Override + public boolean containsKey(String key) { + return redisCache.containsKey(key); + } + + /** + * 自定义state + * @param oldState + * @return state + */ + private String getValue(String oldState) { + return Md5Utils.hash(oldState + System.currentTimeMillis()); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/CategoryServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/CategoryServiceImpl.java new file mode 100644 index 00000000..cb5c8f09 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/CategoryServiceImpl.java @@ -0,0 +1,146 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.Category; +import com.fastbee.iot.mapper.CategoryMapper; +import com.fastbee.iot.model.IdAndName; +import com.fastbee.iot.service.ICategoryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; + +/** + * 产品分类Service业务层处理 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Service +public class CategoryServiceImpl implements ICategoryService +{ + @Autowired + private CategoryMapper categoryMapper; + + /** + * 查询产品分类 + * + * @param categoryId 产品分类主键 + * @return 产品分类 + */ + @Override + public Category selectCategoryByCategoryId(Long categoryId) + { + return categoryMapper.selectCategoryByCategoryId(categoryId); + } + + /** + * 查询产品分类列表 + * + * @param category 产品分类 + * @return 产品分类 + */ + @Override + public List selectCategoryList(Category category) + { + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + // 租户 + if(roles.stream().anyMatch(a->a.getRoleKey().equals("tenant"))){ + category.setTenantId(user.getUserId()); + } + return categoryMapper.selectCategoryList(category); + } + + /** + * 查询产品分简短类列表 + * + * @return 产品分类 + */ + @Override + public List selectCategoryShortList() + { + Category category=new Category(); + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + // 租户 + if(roles.stream().anyMatch(a->a.getRoleKey().equals("tenant"))){ + category.setTenantId(user.getUserId()); + } + return categoryMapper.selectCategoryShortList(category); + } + + /** + * 新增产品分类 + * + * @param category 产品分类 + * @return 结果 + */ + @Override + public int insertCategory(Category category) + { + // 判断是否为管理员 + category.setIsSys(1); + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + for(int i=0;i0){ + return AjaxResult.error("删除失败,请先删除对应分类下的产品"); + } + if(categoryMapper.deleteCategoryByCategoryIds(categoryIds)>0){ + return AjaxResult.success("删除成功"); + } + return AjaxResult.error("删除失败"); + } + + /** + * 删除产品分类信息 + * + * @param categoryId 产品分类主键 + * @return 结果 + */ + @Override + public int deleteCategoryByCategoryId(Long categoryId) + { + return categoryMapper.deleteCategoryByCategoryId(categoryId); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceLogServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceLogServiceImpl.java new file mode 100644 index 00000000..8e033be4 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceLogServiceImpl.java @@ -0,0 +1,41 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.DeviceLog; +import com.fastbee.iot.model.HistoryModel; +import com.fastbee.iot.tdengine.service.ILogService; +import com.fastbee.iot.mapper.DeviceLogMapper; +import com.fastbee.iot.model.MonitorModel; +import com.fastbee.iot.service.IDeviceLogService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +/** + * 设备日志Service业务层处理 + * + * @author kerwincui + * @date 2022-01-13 + */ +@Service +public class DeviceLogServiceImpl implements IDeviceLogService +{ + + @Autowired + private ILogService logService; + + /** + * 查询设备监测数据 + * + * @param deviceLog 设备日志 + * @return 设备日志 + */ + @Override + public List selectMonitorList(DeviceLog deviceLog) + { + return logService.selectMonitorList(deviceLog); + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceRuntimeServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceRuntimeServiceImpl.java new file mode 100644 index 00000000..4e399022 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceRuntimeServiceImpl.java @@ -0,0 +1,169 @@ +package com.fastbee.iot.service.impl; + +import com.alibaba.fastjson.JSONObject; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.core.redis.RedisKeyBuilder; +import com.fastbee.common.enums.FunctionReplyStatus; +import com.fastbee.common.enums.ThingsModelType; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.DeviceLog; +import com.fastbee.iot.domain.FunctionLog; +import com.fastbee.iot.model.Specs; +import com.fastbee.iot.service.IDeviceRuntimeService; +import com.fastbee.iot.service.IFunctionLogService; +import com.fastbee.iot.service.IThingsModelService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 设备运行时数据 + * + * @author gsb + * @date 2023/2/1 15:11 + */ +@Service +public class DeviceRuntimeServiceImpl implements IDeviceRuntimeService { + + @Autowired + private RedisCache redisCache; + @Resource + private IFunctionLogService logService; + @Resource + private IThingsModelService thingsModelService; + + /** + * 根据设备编号查询设备实时运行状态 + * + * @param serialNumber 设备编号 + * @param type 物模型类型 + * @return 设备实时数据 + */ + @Override + public List runtimeBySerialNumber(String serialNumber, ThingsModelType type, Long productId, Integer slaveId) { + //获取redis中的物模型 + String thingsModel = thingsModelService.getCacheThingsModelByProductId(productId); + JSONObject thingModelObject = JSONObject.parseObject(thingsModel); + String key = type == ThingsModelType.PROP ? "properties" : "functions"; + String properties = thingModelObject.getString(key); + /*在redis中查找实时数据*/ + String cacheKey = RedisKeyBuilder.buildDeviceRtCacheKey(serialNumber); + /*物模型标识符 -- value*/ + Map cacheMap = redisCache.getCacheMap(cacheKey); + /*查询是否有计算公式参数*/ + String paramsKey = RedisKeyBuilder.buildDeviceRtParamsKey(serialNumber); + Map params = redisCache.getCacheMap(paramsKey); + List results = new ArrayList<>(); + List propList = JSONObject.parseArray(properties, Specs.class); + for (Specs specs : propList) { + if (specs.getSlaveId().equals(slaveId)) { + DeviceLog log = new DeviceLog(); + log.setModelName(specs.getName()); + log.setLogType(type.getCode()); + log.setSpecs(JSONObject.toJSONString(specs.getDatatype())); + log.setIdentity(specs.getId()); + log.setSerialNumber(serialNumber); + log.setSlaveId(specs.getSlaveId()); + log.setIsMonitor(specs.getIsMonitor()); + log.setFormula(specs.getFormula()); + if (!CollectionUtils.isEmpty(cacheMap) && cacheMap.containsKey(specs.getId())) { + DeviceLog valueLog = JSONObject.parseObject(JSONObject.parse(cacheMap.get(specs.getId())).toString(), DeviceLog.class); + log.setLogValue(valueLog.getLogValue()); + log.setUpdateTime(valueLog.getTs()); + log.setTs(valueLog.getTs()); + //if (log.getFormula() != null && !log.getFormula().equals("")) { + // params.put("%s", log.getLogValue()); + // BigDecimal value = CaculateUtils.execute(log.getFormula(), params); + // log.setLogValue(value.toString()); + //} + } + results.add(log); + } + } + List result = results.stream(). + filter(log -> log.getLogType() == type.getCode()).collect(Collectors.toList()); + for (DeviceLog log : result) { + parseSpecs(log); + } + return result; + } + + + /** + * 根据设备编号查询设备服务调用日志情况 + * + * @param serialNumber 设备编号 + * @return 服务下发日志 + */ + @Override + public List runtimeReply(String serialNumber) { + FunctionLog log = new FunctionLog(); + log.setSerialNumber(serialNumber); + // 1-服务下发 + log.setFunType(1); + List logList = logService.selectFunctionLogList(log); + for (FunctionLog l : logList) { + if (l.getReplyTime() == null) { + l.setReplyTime(DateUtils.getNowDate()); + } + + l.setShowValue(l.getResultMsg()); + //设备超过10s未回复,则认为下发失败 + if (l.getResultCode().equals(FunctionReplyStatus.NORELY.getCode()) && DateUtils.getTimestamp() - l.getCreateTime().getTime() >10000){ + l.setShowValue(FunctionReplyStatus.FAIl.getMessage()); + l.setResultCode(FunctionReplyStatus.FAIl.getCode()); + } + } + return logList; + } + + /** + * 定时任务,自动更新设备响应超时状态 + */ + private void autoUpdateFuncLogStatus() { + FunctionLog functionLog = new FunctionLog(); + functionLog.setResultCode(FunctionReplyStatus.NORELY.getCode()); + List logList = logService.selectFunctionLogList(functionLog); + List ids = new ArrayList<>(); + for (FunctionLog log : logList) { + //如果设备回复超过20s,自动更新设备回复状态为设备超时 + if (DateUtils.getTimestamp() - log.getCreateTime().getTime() > 20000) { + ids.add(log.getId()); + } + } + //更新状态为超时未回复 + if (!CollectionUtils.isEmpty(ids)) { + FunctionLog log = new FunctionLog(); + log.setList(ids); + log.setResultMsg(FunctionReplyStatus.UNKNOWN.getMessage()); + log.setResultCode(FunctionReplyStatus.UNKNOWN.getCode()); + log.setReplyTime(DateUtils.getNowDate()); + logService.updateFuncLogBatch(log); + } + } + + /** + * 解析数据定义specs + * + * @param log + */ + public void parseSpecs(DeviceLog log) { + DeviceLog.DataType spesc = new DeviceLog.DataType(); + if (null != log.getSpecs() && !log.getSpecs().equals("null")) { + DeviceLog.DataType dataType = JSONObject.parseObject(JSONObject.parse(log.getSpecs()).toString(), DeviceLog.DataType.class); + log.setDataType(dataType); + } else { + spesc.setType("integer"); + log.setDataType(spesc); + } + } + + + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java new file mode 100644 index 00000000..99d83678 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceServiceImpl.java @@ -0,0 +1,1312 @@ +package com.fastbee.iot.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.constant.ProductAuthConstant; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.core.redis.RedisKeyBuilder; +import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem; +import com.fastbee.common.core.thingsModel.ThingsModelValuesInput; +import com.fastbee.common.enums.DataEnum; +import com.fastbee.common.enums.DeviceStatus; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.http.HttpUtils; +import com.fastbee.common.utils.ip.IpUtils; +import com.fastbee.common.utils.json.JsonUtils; +import com.fastbee.iot.domain.*; +import com.fastbee.iot.mapper.DeviceMapper; +import com.fastbee.iot.mapper.DeviceUserMapper; +import com.fastbee.iot.mapper.EventLogMapper; +import com.fastbee.iot.model.*; +import com.fastbee.iot.model.ThingsModelItem.Datatype; +import com.fastbee.iot.model.ThingsModelItem.EnumItem; +import com.fastbee.iot.model.ThingsModelItem.ThingsModel; +import com.fastbee.iot.model.ThingsModels.PropertyDto; +import com.fastbee.iot.model.ThingsModels.ThingsModelShadow; +import com.fastbee.iot.model.ThingsModels.ThingsModelValueItem; +import com.fastbee.iot.model.ThingsModels.ValueItem; +import com.fastbee.iot.service.*; +import com.fastbee.iot.service.cache.IDeviceCache; +import com.fastbee.iot.tdengine.service.ILogService; +import com.fastbee.system.service.ISysUserService; +import org.quartz.SchedulerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; + +/** + * 设备Service业务层处理 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Service +public class DeviceServiceImpl implements IDeviceService { + private static final Logger log = LoggerFactory.getLogger(DeviceServiceImpl.class); + + @Autowired + private DeviceMapper deviceMapper; + @Autowired + private DeviceUserMapper deviceUserMapper; + @Autowired + private ThingsModelServiceImpl thingsModelService; + @Autowired + private IToolService toolService; + @Autowired + private IProductService productService; + @Autowired + private ISysUserService userService; + @Autowired + private ILogService logService; + @Autowired + private RedisCache redisCache; + @Resource + @Lazy + private IDeviceCache deviceCache; + @Autowired + private EventLogMapper eventLogMapper; + @Resource + private IFunctionLogService functionLogService; + @Resource + private IEventLogService eventLogService; + @Resource + private IProductAuthorizeService productAuthorizeService; + @Value("${server.broker.port}") + private Long brokerPort; + + + /** + * 查询设备 + * + * @param deviceId 设备主键 + * @return 设备 + */ + @Override + public Device selectDeviceByDeviceId(Long deviceId) { + Device device = deviceMapper.selectDeviceByDeviceId(deviceId); + List list = getCacheDeviceStatus(device.getProductId(), device.getSerialNumber()); + if (list != null && list.size() > 0) { + // redis中获取设备状态(物模型值) + device.setThingsModelValue(JSONObject.toJSONString(list)); + } + + /*查询子设备*/ + if (device != null) { + Device subBo = new Device(); + subBo.setGwDevCode(device.getSerialNumber()); + List subDeviceList = deviceMapper.selectDeviceList(subBo); + //获取子设备的首地址 + if (!CollectionUtils.isEmpty(subDeviceList)) { + device.setSlaveId(subDeviceList.get(0).getSlaveId()); + } + device.setSubDeviceList(subDeviceList); + } + return device; + } + + /** + * 查询设备统计信息 + * + * @return 设备 + */ + @Override + public DeviceStatistic selectDeviceStatistic() { + Device device = new Device(); + SysUser user = getLoginUser().getUser(); + List roles = user.getRoles(); + for (int i = 0; i < roles.size(); i++) { + if (roles.get(i).getRoleKey().equals("tenant")) { + // 租户查看产品下所有设备 + device.setTenantId(user.getUserId()); + } else if (roles.get(i).getRoleKey().equals("general")) { + // 用户查看自己设备 + device.setUserId(user.getUserId()); + } + } + // 获取设备、产品和告警数量 + DeviceStatistic statistic = deviceMapper.selectDeviceProductAlertCount(device); + if (statistic == null) { + statistic = new DeviceStatistic(); + return statistic; + } + // 获取属性、功能和事件 + DeviceStatistic thingsCount = logService.selectCategoryLogCount(device); + if (thingsCount == null) { + return statistic; + } + // 组合属性、功能、事件和监测数据 + statistic.setPropertyCount(thingsCount.getPropertyCount()); + statistic.setFunctionCount(thingsCount.getFunctionCount()); + statistic.setEventCount(thingsCount.getEventCount()); + statistic.setMonitorCount(thingsCount.getMonitorCount()); + return statistic; + } + + /** + * 根据设备编号查询设备 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + @Override + public Device selectDeviceBySerialNumber(String serialNumber) { + Device device = deviceMapper.selectDeviceBySerialNumber(serialNumber); + if (device != null) { + // redis中获取设备状态(物模型值) + List list = getCacheDeviceStatus(device.getProductId(), device.getSerialNumber()); + if (list != null && list.size() > 0) { + device.setThingsModelValue(JSONObject.toJSONString(list)); + } + } + return device; + } + + /** + * 根据设备编号查询简洁设备 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + @Override + public Device selectShortDeviceBySerialNumber(String serialNumber) { + Device device = deviceMapper.selectShortDeviceBySerialNumber(serialNumber); + if (device != null) { + // redis中获取设备状态(物模型值) + List list = getCacheDeviceStatus(device.getProductId(), device.getSerialNumber()); + if (list != null && list.size() > 0) { + device.setThingsModelValue(JSONObject.toJSONString(list)); + } + } + return device; + } + + /** + * 根据设备编号查询设备认证信息 + * + * @param model 设备编号和产品ID + * @return 设备 + */ + @Override + public ProductAuthenticateModel selectProductAuthenticate(AuthenticateInputModel model) { + return deviceMapper.selectProductAuthenticate(model); + } + + + + @Override + @Transactional(rollbackFor = Exception.class) + public List reportDeviceThingsModelValue(ThingsModelValuesInput input, int type, boolean isShadow) { + String key = RedisKeyBuilder.buildTSLVCacheKey(input.getProductId(), input.getDeviceNumber()); + Map maps = new HashMap(); + List list = new ArrayList<>(); + for (ThingsModelSimpleItem item : input.getThingsModelValueRemarkItem()) { + String identity = item.getId(); + Integer slaveId = input.getSlaveId() == null ? item.getSlaveId() : input.getSlaveId(); + String serialNumber = slaveId == null ? input.getDeviceNumber() : input.getDeviceNumber()+"_"+ slaveId; + if (identity.startsWith("array_")) { + identity = identity.substring(9); + } + // 查询redis中物模型 + identity = identity + (slaveId != null ? "#" + slaveId : ""); + PropertyDto dto = thingsModelService.getSingleThingModels(input.getProductId(), identity); + if (null == dto) { + continue; + } + String id = item.getId(); + String value = item.getValue(); + + + /* ★★★★★★★★★★★★★★★★★★★★★★ 处理数据 - 开始 ★★★★★★★★★★★★★★★★★★★★★★*/ + String cacheValue = redisCache.getCacheMapValue(key, identity); + ValueItem valueItem = JSON.parseObject(cacheValue, ValueItem.class); + if (null == valueItem) { + valueItem = new ValueItem(id,dto.getTempSlaveId(),id); + } + if (id.startsWith("array_")) { + // 查询是否有缓存,如果没有先进行缓存 + if (!redisCache.containsKey(key)) { + Device device = this.selectDeviceBySerialNumber(input.getDeviceNumber()); + this.selectDeviceByDeviceId(device.getDeviceId()); + } + + int index = Integer.parseInt(id.substring(6, 8)); + if (isShadow) { + String[] shadows = valueItem.getShadow().split(","); + shadows[index] = value; + valueItem.setShadow(String.join(",", shadows)); + } else { + // 设置值,获取数组值,然后替换其中元素 + valueItem.setTs(DateUtils.getNowDate()); + String[] values = valueItem.getValue().split(","); + values[index] = value; + valueItem.setValue(String.join(",", values)); + + String[] shadows = valueItem.getShadow().split(","); + shadows[index] = value; + valueItem.setShadow(String.join(",", shadows)); + } + redisCache.setCacheMapValue(key,identity,JSONObject.toJSONString(valueItem)); + //maps.put(identity, JSONObject.toJSONString(valueItem)); + } else { + if (isShadow) { + valueItem.setShadow(value); + } else { + valueItem.setValue(value); + valueItem.setShadow(value); + valueItem.setTs(DateUtils.getNowDate()); + } + maps.put(identity, JSONObject.toJSONString(valueItem)); + } + /* ★★★★★★★★★★★★★★★★★★★★★★ 处理数据 - 结束 ★★★★★★★★★★★★★★★★★★★★★★*/ + + /*★★★★★★★★★★★★★★★★★★★★★★ 存储数据 - 开始 ★★★★★★★★★★★★★★★★★★★★★★*/ + if (null != dto.getIsHistory()) { + + } + list.add(item); + } + redisCache.hashPutAll(key, maps); + /* ★★★★★★★★★★★★★★★★★★★★★★ 存储数据 - 结束 ★★★★★★★★★★★★★★★★★★★★★★*/ + return list; + } + + /** + * 查询设备列表 + * + * @param device 设备 + * @return 设备 + */ + @Override + public List selectDeviceList(Device device) { + SysUser user = getLoginUser().getUser(); + List roles = user.getRoles(); + for (int i = 0; i < roles.size(); i++) { + if (roles.get(i).getRoleKey().equals("tenant")) { + // 租户查看产品下所有设备 + device.setTenantId(user.getUserId()); + } else if (roles.get(i).getRoleKey().equals("general")) { + // 用户查看自己设备 + device.setUserId(user.getUserId()); + } + } + return deviceMapper.selectDeviceList(device); + } + + /** + * 查询未分配授权码设备列表 + * + * @param device 设备 + * @return 设备 + */ + @Override + public List selectUnAuthDeviceList(Device device) { + SysUser user = getLoginUser().getUser(); + List roles = user.getRoles(); + for (int i = 0; i < roles.size(); i++) { + if (roles.get(i).getRoleKey().equals("tenant")) { + // 租户查看产品下所有设备 + device.setTenantId(user.getUserId()); + } else if (roles.get(i).getRoleKey().equals("general")) { + // 用户查看自己设备 + device.setUserId(user.getUserId()); + } + } + return deviceMapper.selectUnAuthDeviceList(device); + } + + /** + * 查询分组可添加设备分页列表(分组用户与设备用户匹配) + * + * @param device 设备 + * @return 设备 + */ + @Override + public List selectDeviceListByGroup(Device device) { + SysUser user = getLoginUser().getUser(); + List roles = user.getRoles(); + for (int i = 0; i < roles.size(); i++) { + if (roles.get(i).getRoleKey().equals("tenant")) { + // 租户查看产品下所有设备 + device.setTenantId(user.getUserId()); + } else if (roles.get(i).getRoleKey().equals("general")) { + // 用户查看自己设备 + device.setUserId(user.getUserId()); + } + } + return deviceMapper.selectDeviceListByGroup(device); + } + + /** + * 查询所有设备简短列表 + * + * @return 设备 + */ + @Override + public List selectAllDeviceShortList() { + Device device = new Device(); + SysUser user = getLoginUser().getUser(); + List roles = user.getRoles(); + for (SysRole role : roles) { + if (role.getRoleKey().equals("tenant")) { + // 租户查看产品下所有设备 + device.setTenantId(user.getUserId()); + break; + } else if (role.getRoleKey().equals("general")) { + // 用户查看自己设备 + device.setUserId(user.getUserId()); + break; + } + } + return deviceMapper.selectAllDeviceShortList(device); + } + + /** + * 查询设备分页简短列表 + * + * @param device 设备 + * @return 设备 + */ + @Override + public List selectDeviceShortList(Device device) { + SysUser user = getLoginUser().getUser(); + List roles = user.getRoles(); + for (int i = 0; i < roles.size(); i++) { + if (roles.get(i).getRoleKey().equals("tenant")) { + // 租户查看产品下所有设备 + device.setTenantId(user.getUserId()); + break; + } else if (roles.get(i).getRoleKey().equals("general")) { + // 用户查看自己设备 + device.setUserId(user.getUserId()); + break; + } + } + List deviceList = deviceMapper.selectDeviceShortList(device); + return deviceList; + } + + /** + * 查询设备 + * + * @param deviceId 设备主键 + * @return 设备 + */ + @Override + public DeviceShortOutput selectDeviceRunningStatusByDeviceId(Long deviceId, Integer slaveId) { + DeviceShortOutput device = deviceMapper.selectDeviceRunningStatusByDeviceId(deviceId); + JSONObject thingsModelObject = JSONObject.parseObject(thingsModelService.getCacheThingsModelByProductId(device.getProductId())); + JSONArray properties = thingsModelObject.getJSONArray("properties"); + JSONArray functions = thingsModelObject.getJSONArray("functions"); + List thingsModelValueItems = getCacheDeviceStatus(device.getProductId(), device.getSerialNumber()); + // 物模型转换赋值 + List thingsList = new ArrayList<>(); + //判断一下properties 和 functions是否为空, 否则报空指针 + if (!CollectionUtils.isEmpty(properties)) { + thingsList.addAll(convertJsonToThingsList(properties, thingsModelValueItems, 1)); + } + if (!CollectionUtils.isEmpty(functions)) { + thingsList.addAll(convertJsonToThingsList(functions, thingsModelValueItems, 2)); + } + // 排序 + thingsList = thingsList.stream() + .filter(model -> { + if (null != slaveId) { + return model.getSlaveId().intValue() == slaveId.intValue(); + } + return true; + }) + .sorted(Comparator.comparing(ThingsModel::getOrder).reversed()).collect(Collectors.toList()); + device.setThingsModels(thingsList); + device.setThingsModelValue(""); + return device; + } + + /** + * 物模型基本类型转换赋值 + * + * @param jsonArray + * @param thingsModelValues + * @param type + * @return + */ + @Async + public List convertJsonToThingsList(JSONArray jsonArray, List thingsModelValues, Integer type) { + List thingsModelList = new ArrayList<>(); + for (int i = 0; i < jsonArray.size(); i++) { + ThingsModel thingsModel = new ThingsModel(); + JSONObject thingsJson = jsonArray.getJSONObject(i); + JSONObject datatypeJson = thingsJson.getJSONObject("datatype"); + thingsModel.setId(thingsJson.getString("id")); + thingsModel.setName(thingsJson.getString("name")); + thingsModel.setIsMonitor(thingsJson.getInteger("isMonitor") == null ? 0 : thingsJson.getInteger("isMonitor")); + thingsModel.setIsReadonly(thingsJson.getInteger("isReadonly") == null ? 0 : thingsJson.getInteger("isReadonly")); + thingsModel.setIsChart(thingsJson.getInteger("isChart") == null ? 0 : thingsJson.getInteger("isChart")); + thingsModel.setIsSharePerm(thingsJson.getInteger("isSharePerm") == null ? 0 : thingsJson.getInteger("isSharePerm")); + thingsModel.setIsHistory(thingsJson.getInteger("isHistory") == null ? 0 : thingsJson.getInteger("isHistory")); + thingsModel.setOrder(thingsJson.getInteger("order") == null ? 0 : thingsJson.getInteger("order")); + thingsModel.setType(type); + thingsModel.setSlaveId(thingsJson.getInteger("tempSlaveId")); + thingsModel.setRegId(thingsJson.getString("regId")); + // 获取value + for (ThingsModelValueItem valueItem : thingsModelValues) { + if (valueItem.getId().equals(thingsModel.getId()) || valueItem.getId().equals(thingsModel.getRegId())) { + thingsModel.setValue(valueItem.getValue()); + thingsModel.setShadow(valueItem.getShadow()); + thingsModel.setTs(valueItem.getTs()); + break; + } + } + // json转DataType(DataType赋值) + Datatype dataType = convertJsonToDataType(datatypeJson, thingsModelValues, type, thingsModel.getId() + "_"); + thingsModel.setDatatype(dataType); + if (JsonUtils.isJson(thingsModel.getValue())){ + JSONObject jsonObject = JSONObject.parseObject(thingsModel.getValue()); + for (EnumItem enumItem : dataType.getEnumList()) { + ThingsModel model = new ThingsModel(); + BeanUtils.copyProperties(thingsModel,model); + String val = jsonObject.getString(enumItem.getValue()); + model.setValue(val); + model.setName(enumItem.getValue()); + thingsModelList.add(model); + } + }else { + // 物模型项添加到集合 + thingsModelList.add(thingsModel); + } + } + return thingsModelList; + } + + /** + * 物模型DataType转换 + * + * @param datatypeJson + * @param thingsModelValues + * @param type + * @param parentIdentifier 上级标识符 + * @return + */ + private Datatype convertJsonToDataType(JSONObject datatypeJson, List thingsModelValues, Integer type, String parentIdentifier) { + Datatype dataType = new Datatype(); + //有些物模型数据定义为空的情况兼容 + if (datatypeJson == null) { + return dataType; + } + dataType.setType(datatypeJson.getString("type")); + if (dataType.getType().equals("decimal")) { + dataType.setMax(datatypeJson.getBigDecimal("max")); + dataType.setMin(datatypeJson.getBigDecimal("min")); + dataType.setStep(datatypeJson.getBigDecimal("step")); + dataType.setUnit(datatypeJson.getString("unit")); + } else if (dataType.getType().equals("integer")) { + dataType.setMax(datatypeJson.getBigDecimal("max")); + dataType.setMin(datatypeJson.getBigDecimal("min")); + dataType.setStep(datatypeJson.getBigDecimal("step")); + dataType.setUnit(datatypeJson.getString("unit")); + } else if (dataType.getType().equals("bool")) { + dataType.setFalseText(datatypeJson.getString("falseText")); + dataType.setTrueText(datatypeJson.getString("trueText")); + } else if (dataType.getType().equals("string")) { + dataType.setMaxLength(datatypeJson.getInteger("maxLength")); + } else if (dataType.getType().equals("enum")) { + List enumItemList = JSON.parseArray(datatypeJson.getString("enumList"), EnumItem.class); + dataType.setEnumList(enumItemList); + dataType.setShowWay(datatypeJson.getString("showWay")); + } else if (dataType.getType().equals("object")) { + JSONArray jsonArray = JSON.parseArray(datatypeJson.getString("params")); + // 物模型值过滤(parentId_开头) + thingsModelValues = thingsModelValues.stream().filter(x -> x.getId().startsWith(parentIdentifier)).collect(Collectors.toList()); + List thingsList = convertJsonToThingsList(jsonArray, thingsModelValues, type); + // 排序 + thingsList = thingsList.stream().sorted(Comparator.comparing(ThingsModel::getOrder).reversed()).collect(Collectors.toList()); + dataType.setParams(thingsList); + } else if (dataType.getType().equals("array")) { + dataType.setArrayType(datatypeJson.getString("arrayType")); + dataType.setArrayCount(datatypeJson.getInteger("arrayCount")); + if ("object".equals(dataType.getArrayType())) { + // 对象数组 + JSONArray jsonArray = datatypeJson.getJSONArray("params"); + // 物模型值过滤(parentId_开头) + thingsModelValues = thingsModelValues.stream().filter(x -> x.getId().startsWith(parentIdentifier)).collect(Collectors.toList()); + List thingsList = convertJsonToThingsList(jsonArray, thingsModelValues, type); + // 排序 + thingsList = thingsList.stream().sorted(Comparator.comparing(ThingsModel::getOrder).reversed()).collect(Collectors.toList()); + // 数组类型物模型里面对象赋值 + List[] arrayParams = new List[dataType.getArrayCount()]; + for (int i = 0; i < dataType.getArrayCount(); i++) { + List thingsModels = new ArrayList<>(); + for (int j = 0; j < thingsList.size(); j++) { + ThingsModel thingsModel = new ThingsModel(); + BeanUtils.copyProperties(thingsList.get(j), thingsModel); + String shadow = thingsList.get(j).getShadow(); + if (StringUtils.isNotEmpty(shadow) && !shadow.equals("")) { + String[] shadows = shadow.split(","); + if(i+1 > shadows.length){ + // 解决产品取消发布,增加数组长度导致设备影子和值赋值失败 + thingsModel.setShadow(" "); + }else{ + thingsModel.setShadow(shadows[i]); + } + } + String value = thingsList.get(j).getValue(); + if (StringUtils.isNotEmpty(value) && !value.equals("")) { + String[] values = value.split(","); + if(i+1 > values.length){ + thingsModel.setValue(" "); + }else{ + thingsModel.setValue(values[i]); + } + } + thingsModels.add(thingsModel); + } + arrayParams[i] = thingsModels; + } + dataType.setArrayParams(arrayParams); + } + } + return dataType; + } + + /** + * 新增设备 + * + * @param device 设备 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Device insertDevice(Device device) { + // 设备编号唯一检查 + Device existDevice = deviceMapper.selectDeviceBySerialNumber(device.getSerialNumber()); + if (existDevice != null) { + log.error("设备编号:" + device.getSerialNumber() + "已经存在了,新增设备失败"); + throw new ServiceException("设备编号:" + device.getSerialNumber() + " 已经存在,新增失败"); + } + SysUser sysUser = getLoginUser().getUser(); + //添加设备 + device.setCreateTime(DateUtils.getNowDate()); + device.setUserId(sysUser.getUserId()); + device.setUserName(sysUser.getUserName()); + device.setRssi(0); + + // 设置租户 + Product product = productService.selectProductByProductId(device.getProductId()); + device.setTenantId(product.getTenantId()); + device.setTenantName(product.getTenantName()); + device.setImgUrl(product.getImgUrl()); + // 随机经纬度和地址 + SysUser user = getLoginUser().getUser(); + device.setNetworkIp(user.getLoginIp()); + //setLocation(user.getLoginIp(), device); + + deviceMapper.insertDevice(device); + // 添加设备用户 + DeviceUser deviceUser = new DeviceUser(); + deviceUser.setUserId(sysUser.getUserId()); + deviceUser.setUserName(sysUser.getUserName()); + deviceUser.setPhonenumber(sysUser.getPhonenumber()); + deviceUser.setDeviceId(device.getDeviceId()); + deviceUser.setDeviceName(device.getDeviceName()); + deviceUser.setTenantId(product.getTenantId()); + deviceUser.setTenantName(product.getTenantName()); + deviceUser.setIsOwner(1); + deviceUser.setCreateTime(DateUtils.getNowDate()); + int result = deviceUserMapper.insertDeviceUser(deviceUser); + + // redis缓存设备默认状态(物模型值) + cacheDeviceStatus(device.getProductId(), device.getSerialNumber()); + + // 返回设备信息 + List list = getCacheDeviceStatus(device.getProductId(), device.getSerialNumber()); + if (list != null && list.size() > 0) { + device.setThingsModelValue(JSONObject.toJSONString(list)); + } + /*查询子设备*/ + Device subBo = new Device(); + subBo.setGwDevCode(device.getSerialNumber()); + List subDeviceList = deviceMapper.selectDeviceList(subBo); + //获取子设备的首地址 + if (!CollectionUtils.isEmpty(subDeviceList)) { + device.setSlaveId(subDeviceList.get(0).getSlaveId()); + } + device.setSubDeviceList(subDeviceList); + return device; + } + + /** + * 设备关联用户 + * + * @param deviceRelateUserInput + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AjaxResult deviceRelateUser(DeviceRelateUserInput deviceRelateUserInput) { + // 查询用户信息 + SysUser sysUser = userService.selectUserById(deviceRelateUserInput.getUserId()); + for (int i = 0; i < deviceRelateUserInput.getDeviceNumberAndProductIds().size(); i++) { + Device existDevice = deviceMapper.selectDeviceBySerialNumber(deviceRelateUserInput.getDeviceNumberAndProductIds().get(i).getDeviceNumber()); + if (existDevice != null) { + if (existDevice.getUserId().longValue() == deviceRelateUserInput.getUserId().longValue()) { + return AjaxResult.error("用户已经拥有设备:" + existDevice.getDeviceName() + ", 设备编号:" + existDevice.getSerialNumber()); + } + // 先删除设备的所有用户 + deviceUserMapper.deleteDeviceUserByDeviceId(new UserIdDeviceIdModel(null, existDevice.getDeviceId())); + // 添加新的设备用户 + DeviceUser deviceUser = new DeviceUser(); + deviceUser.setUserId(sysUser.getUserId()); + deviceUser.setUserName(sysUser.getUserName()); + deviceUser.setPhonenumber(sysUser.getPhonenumber()); + deviceUser.setDeviceId(existDevice.getDeviceId()); + deviceUser.setDeviceName(existDevice.getDeviceName()); + deviceUser.setTenantId(existDevice.getTenantId()); + deviceUser.setTenantName(existDevice.getTenantName()); + deviceUser.setIsOwner(1); + deviceUser.setCreateTime(DateUtils.getNowDate()); + deviceUserMapper.insertDeviceUser(deviceUser); + // 更新设备用户信息 + existDevice.setUserId(deviceRelateUserInput.getUserId()); + existDevice.setUserName(sysUser.getUserName()); + deviceMapper.updateDevice(existDevice); + } else { + // 自动添加设备 + int result = insertDeviceAuto( + deviceRelateUserInput.getDeviceNumberAndProductIds().get(i).getDeviceNumber(), + deviceRelateUserInput.getUserId(), + deviceRelateUserInput.getDeviceNumberAndProductIds().get(i).getProductId()); + if (result == 0) { + return AjaxResult.error("设备不存在,自动添加设备时失败,请检查产品编号是否正确"); + } + } + } + return AjaxResult.success("添加设备成功"); + } + + /** + * 设备认证后自动添加设备 + * + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int insertDeviceAuto(String serialNumber, Long userId, Long productId) { + // 设备编号唯一检查 + int count = deviceMapper.selectDeviceCountBySerialNumber(serialNumber); + if (count != 0) { + log.error("设备编号:" + serialNumber + "已经存在了,新增设备失败"); + return 0; + } + Device device = new Device(); + device.setSerialNumber(serialNumber); + SysUser user = userService.selectUserById(userId); + device.setUserId(userId); + device.setUserName(user.getUserName()); + device.setFirmwareVersion(BigDecimal.valueOf(1.0)); + // 设备状态(1-未激活,2-禁用,3-在线,4-离线) + device.setStatus(3); + device.setActiveTime(DateUtils.getNowDate()); + device.setIsShadow(0); + device.setRssi(0); + // 1-自动定位,2-设备定位,3-自定义位置 + device.setLocationWay(1); + device.setCreateTime(DateUtils.getNowDate()); + // 随机位置 + device.setLongitude(BigDecimal.valueOf(116.23 - (Math.random() * 15))); + device.setLatitude(BigDecimal.valueOf(39.54 - (Math.random() * 15))); + device.setNetworkAddress("中国"); + device.setNetworkIp("127.0.0.1"); + // 设置租户 + Product product = productService.selectProductByProductId(productId); + if (product == null) { + log.error("自动添加设备时,根据产品ID查找不到对应产品"); + return 0; + } + int random = (int) (Math.random() * (90)) + 10; + device.setDeviceName(product.getProductName() + random); + device.setTenantId(product.getTenantId()); + device.setTenantName(product.getTenantName()); + device.setImgUrl(product.getImgUrl()); + device.setProductId(product.getProductId()); + device.setProductName(product.getProductName()); + deviceMapper.insertDevice(device); + + // 缓存设备状态 + cacheDeviceStatus(device.getProductId(), device.getSerialNumber()); + + // 添加设备用户 + DeviceUser deviceUser = new DeviceUser(); + deviceUser.setUserId(userId); + deviceUser.setUserName(user.getUserName()); + deviceUser.setPhonenumber(user.getPhonenumber()); + deviceUser.setDeviceId(device.getDeviceId()); + deviceUser.setDeviceName(device.getDeviceName()); + deviceUser.setTenantId(product.getTenantId()); + deviceUser.setTenantName(product.getTenantName()); + deviceUser.setIsOwner(1); + return deviceUserMapper.insertDeviceUser(deviceUser); + } + + /** + * 获取用户操作设备的影子值 + * + * @param device + * @return + */ + @Override + public ThingsModelShadow getDeviceShadowThingsModel(Device device) { + // 物模型值 + List thingsModelValueItems = getCacheDeviceStatus(device.getProductId(), device.getSerialNumber()); + ThingsModelShadow shadow = new ThingsModelShadow(); + // 查询出设置的影子值 + List shadowList = new ArrayList<>(); + for (ThingsModelValueItem item : thingsModelValueItems) { + if (!item.getValue().equals(item.getShadow()) && !item.getShadow().equals("")) { + //处理id + String id = item.getId(); + //modbus类型 + if (id.contains("#")) { + id = id.split("#")[0]; + ThingsModelSimpleItem shaowItem = new ThingsModelSimpleItem(id, item.getShadow(), item.getTempSlaveId(), ""); + shadow.getProperties().add(shaowItem); + } else if (item.getShadow().contains(",")) { + String[] shadows = item.getShadow().split(","); + String[] values = item.getValue().split(","); + for (int i = 0; i < shadows.length; i++) { + String value = values[i]; + String shaVal = shadows[i]; + if (!shaVal.equals(value)) { + id = "array_0" + i + "_" + id; + ThingsModelSimpleItem shaowItem = new ThingsModelSimpleItem(id, shaVal, ""); + shadow.getProperties().add(shaowItem); + } + } + } else { + ThingsModelSimpleItem shaowItem = new ThingsModelSimpleItem(id, item.getShadow(), ""); + shadow.getProperties().add(shaowItem); + } + } + } + return shadow; + } + + + /** + * 修改设备 + * + * @param device 设备 + * @return 结果 + */ + @Override + public AjaxResult updateDevice(Device device) { + // 设备编号唯一检查 + Device oldDevice = deviceMapper.selectDeviceByDeviceId(device.getDeviceId()); + if (!oldDevice.getSerialNumber().equals(device.getSerialNumber())) { + Device existDevice = deviceMapper.selectDeviceBySerialNumber(device.getSerialNumber()); + if (existDevice != null) { + log.error("设备编号:" + device.getSerialNumber() + " 已经存在,新增设备失败"); + return AjaxResult.error("设备编号:" + device.getSerialNumber() + " 已经存在,修改失败", 0); + } + } + device.setUpdateTime(DateUtils.getNowDate()); + // 未激活状态,可以修改产品以及物模型值 + if (device.getStatus() == 1) { + // 缓存设备状态(物模型值) + cacheDeviceStatus(device.getProductId(), device.getSerialNumber()); + + } else { + device.setProductId(null); + device.setProductName(null); + } + deviceMapper.updateDevice(device); + // 更新前设备状态为禁用,启用后状态默认为离线,满足时下发获取设备最新状态指令 + if (oldDevice.getStatus() == 2 && device.getStatus() == 4) { + // 发布设备信息,设备收到该消息后上报最新状态 + // emqxService.publishInfo(oldDevice.getProductId(), oldDevice.getSerialNumber()); + } + return AjaxResult.success("修改成功", 1); + } + + /** + * 生成设备唯一编号 + * + * @return 结果 + */ + @Override + public String generationDeviceNum(Integer type) { + // 设备编号:D + userId + 15位随机字母和数字 + SysUser user = getLoginUser().getUser(); + String number; + //Hex随机字符串 + if (type == 3){ + number = toolService.generateRandomHex(12); + }else { + number = "D" + user.getUserId().toString() + toolService.getStringRandom(10); + } + int count = deviceMapper.getDeviceNumCount(number); + if (count == 0) { + return number; + } else { + generationDeviceNum(type); + } + return ""; + } + + /** + * @param device 设备状态和定位更新 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateDeviceStatusAndLocation(Device device, String ipAddress) { + // 设置自动定位和状态 + if (ipAddress != "") { + if (device.getActiveTime() == null) { + device.setActiveTime(DateUtils.getNowDate()); + } + // 定位方式(1=ip自动定位,2=设备定位,3=自定义) + if (device.getLocationWay() == 1) { + device.setNetworkIp(ipAddress); + //setLocation(ipAddress, device); + } + } + int result = deviceMapper.updateDeviceStatus(device); + // 添加到设备日志 + EventLog event = new EventLog(); + event.setDeviceId(device.getDeviceId()); + event.setDeviceName(device.getDeviceName()); + event.setSerialNumber(device.getSerialNumber()); + event.setIsMonitor(0); + event.setUserId(device.getUserId()); + event.setUserName(device.getUserName()); + event.setTenantId(device.getTenantId()); + event.setTenantName(device.getTenantName()); + event.setCreateTime(DateUtils.getNowDate()); + // 日志模式 1=影子模式,2=在线模式,3=其他 + event.setMode(3); + if (device.getStatus() == 3) { + event.setLogValue("1"); + event.setRemark("设备上线"); + event.setIdentity("online"); + event.setLogType(5); + } else if (device.getStatus() == 4) { + event.setLogValue("0"); + event.setRemark("设备离线"); + event.setIdentity("offline"); + event.setLogType(6); + } + eventLogMapper.insertEventLog(event); + return result; + } + + /** + * @param device 设备状态 + * @return 结果 + */ + @Override + public int updateDeviceStatus(Device device) { + return deviceMapper.updateDeviceStatus(device); + } + + /** + * 更新固件版本 + * + * @param device + * @return + */ + @Override + public int updateDeviceFirmwareVersion(Device device) { + return deviceMapper.updateDeviceFirmwareVersion(device); + } + + /** + * 上报设备信息 + * + * @param device 设备 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int reportDevice(Device device, Device deviceEntity) { + // 未采用设备定位则清空定位,定位方式(1=ip自动定位,2=设备定位,3=自定义) + if (deviceEntity.getLocationWay() != 2) { + device.setLatitude(null); + device.setLongitude(null); + } + int result = 0; + if (deviceEntity != null && device.getUserId() != null) { + // 通过配网或者扫码关联设备后,设备的用户信息需要变更 + if (deviceEntity.getUserId().longValue() != device.getUserId().longValue()) { + // 先删除设备的所有用户 + deviceUserMapper.deleteDeviceUserByDeviceId(new UserIdDeviceIdModel(null, deviceEntity.getDeviceId())); + // 添加新的设备用户 + SysUser sysUser = userService.selectUserById(device.getUserId()); + DeviceUser deviceUser = new DeviceUser(); + deviceUser.setUserId(sysUser.getUserId()); + deviceUser.setUserName(sysUser.getUserName()); + deviceUser.setPhonenumber(sysUser.getPhonenumber()); + deviceUser.setDeviceId(deviceEntity.getDeviceId()); + deviceUser.setDeviceName(deviceEntity.getDeviceName()); + deviceUser.setTenantId(deviceEntity.getTenantId()); + deviceUser.setTenantName(deviceEntity.getTenantName()); + deviceUser.setIsOwner(1); + deviceUser.setCreateTime(DateUtils.getNowDate()); + deviceUserMapper.insertDeviceUser(deviceUser); + // 更新设备用户信息 + device.setUserId(device.getUserId()); + device.setUserName(sysUser.getUserName()); + } + device.setUpdateTime(DateUtils.getNowDate()); + if (deviceEntity.getActiveTime() == null || deviceEntity.getActiveTime().equals("")) { + device.setActiveTime(DateUtils.getNowDate()); + } + // 不更新物模型 + device.setThingsModelValue(null); + result = deviceMapper.updateDeviceBySerialNumber(device); + } + return result; + } + + /** + * 重置设备状态 + * + * @return 结果 + */ + @Override + public int resetDeviceStatus(String deviceNum) { + int result = deviceMapper.resetDeviceStatus(deviceNum); + return result; + } + + /** + * 删除设备 + * + * @param deviceId 需要删除的设备主键 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteDeviceByDeviceId(Long deviceId) throws SchedulerException { + SysUser user = getLoginUser().getUser(); + // 是否为普通用户,普通用户如果不是设备所有者,只能删除设备用户和用户自己的设备关联分组信息 + boolean isGeneralUser = false; + List roles = user.getRoles(); + for (int i = 0; i < roles.size(); i++) { + if (roles.get(i).getRoleKey().equals("general")) { + isGeneralUser = true; + break; + } + } + Device device = deviceMapper.selectDeviceByDeviceId(deviceId); + if (isGeneralUser && device.getUserId().longValue() != user.getUserId()) { + // 删除用户分组中的设备 普通用户,且不是设备所有者。 + deviceMapper.deleteDeviceGroupByDeviceId(new UserIdDeviceIdModel(user.getUserId(), deviceId)); + // 删除用户的设备用户信息。 + deviceUserMapper.deleteDeviceUserByDeviceId(new UserIdDeviceIdModel(user.getUserId(), deviceId)); + } else { + // 删除设备分组。 租户、管理员和设备所有者 + deviceMapper.deleteDeviceGroupByDeviceId(new UserIdDeviceIdModel(null, deviceId)); + // 删除设备用户。 + deviceUserMapper.deleteDeviceUserByDeviceId(new UserIdDeviceIdModel(null, deviceId)); + // 删除定时任务 TODO - emq兼容 + // deviceJobService.deleteJobByDeviceIds(new Long[]{deviceId}); + // 批量删除设备监测日志 + logService.deleteDeviceLogByDeviceNumber(device.getSerialNumber()); + // 批量删除设备事件日志 DeviceNumber + eventLogService.deleteEventLogByDeviceNumber(device.getSerialNumber()); + // 批量删除设备功能日志 + functionLogService.deleteFunctionLogByDeviceNumber(device.getSerialNumber()); + // redis中删除设备物模型(状态) + String key = RedisKeyBuilder.buildTSLVCacheKey(device.getProductId(), device.getSerialNumber()); + redisCache.deleteObject(key); + // 删除设备 + deviceMapper.deleteDeviceByDeviceIds(new Long[]{deviceId}); + } + //查询是否有子设备 + Integer subDeviceCount = this.getSubDeviceCount(device.getSerialNumber()); + if (null != subDeviceCount && subDeviceCount > 0) { + this.deleteSubDevice(device.getSerialNumber()); + } + return 1; + } + + /** + * 根据网关编号删除子设备 + * + * @param gwCode + */ + @Override + public void deleteSubDevice(String gwCode) { + deviceMapper.deleteSubDevice(gwCode); + } + + /** + * 根据设备编号查询协议编码 + * + * @param serialNumber + * @return + */ + @Override + public Map selectProtocolBySerialNumber(String serialNumber) { + return deviceMapper.selectProtocolBySerialNumber(serialNumber); + } + + /** + * 查询产品下所有设备,返回设备编号 + * + * @param productId 产品id + * @return + */ + @Override + public List selectDevicesByProductId(Long productId, Integer hasSub) { + return deviceMapper.selectDevicesByProductId(productId, hasSub); + } + + /** + * 查询子设备总数 + * + * @param gwDevCode 网关编号 + * @return 数量 + */ + @Override + public Integer getSubDeviceCount(String gwDevCode) { + return deviceMapper.getSubDeviceCount(gwDevCode); + } + + + /** + * 批量更新设备状态 + * + * @param serialNumbers 设备ids + * @param status 状态 + */ + @Override + public void batchChangeStatus(List serialNumbers, DeviceStatus status) { + if (CollectionUtils.isEmpty(serialNumbers)) { + return; + } + //设备离线 + if (DeviceStatus.OFFLINE.equals(status)) { + deviceMapper.batchChangeOffline(serialNumbers); + } else if (DeviceStatus.ONLINE.equals(status)) { + deviceMapper.batchChangeOnline(serialNumbers); + } + deviceCache.updateBatchDeviceStatusCache(serialNumbers, status); + } + + /** + * 查询在线的modbus网关设备 + * + * @return + */ + @Override + public List selectOnlineModbusDevices() { + return deviceMapper.selectOnlineModbusDevices(); + } + + /** + * 根据设备编号查询设备信息 -不带缓存物模型值 + * + * @param serialNumber + * @return + */ + @Override + public Device selectDeviceNoModel(String serialNumber) { + return deviceMapper.selectDeviceBySerialNumber(serialNumber); + } + + /** + * 获取设备MQTT连接参数 + * @param deviceId 设备id + * @return + */ + @Override + public DeviceMqttConnectVO getMqttConnectData(Long deviceId) { + DeviceMqttConnectVO connectVO = new DeviceMqttConnectVO(); + DeviceMqttVO deviceMqttVO = deviceMapper.selectMqttConnectData(deviceId); + if (deviceMqttVO == null) { + throw new ServiceException("获取设备MQTT连接参数失败"); + } + // 不管认证方式,目前就只返回简单认证方式 + String password; + if (ProductAuthConstant.AUTHORIZE.equals(deviceMqttVO.getIsAuthorize())) { + // 查询产品授权码 + List productAuthorizeList = productAuthorizeService.listByProductId(deviceMqttVO.getProductId()); + if (CollectionUtils.isEmpty(productAuthorizeList)) { + throw new ServiceException("产品已启用授权,获取设备授权码失败,请先配置授权码"); + } + List collect = productAuthorizeList.stream().filter(p -> p.getProductId().equals(deviceMqttVO.getDeviceId())).collect(Collectors.toList()); + ProductAuthorize productAuthorize = CollectionUtils.isEmpty(collect) ? productAuthorizeList.get(0) : collect.get(0); + password = deviceMqttVO.getMqttPassword() + "&" + productAuthorize.getAuthorizeCode(); + } else { + password = deviceMqttVO.getMqttPassword(); + } + String clientId = ProductAuthConstant.CLIENT_ID_AUTH_TYPE_SIMPLE + "&" + deviceMqttVO.getSerialNumber() + "&" + deviceMqttVO.getProductId() + "&" + deviceMqttVO.getUserId(); + // 组装返回结果 + connectVO.setClientId(clientId).setUsername(deviceMqttVO.getMqttAccount()).setPasswd(password).setPort(brokerPort); + return connectVO; + } + + /** + * 根据IP获取地址 + * + * @param ip + * @return + */ + private void setLocation(String ip, Device device) { + String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp"; + String address = "未知地址"; + // 内网不查询 + if (IpUtils.internalIp(ip)) { + device.setNetworkAddress("内网IP"); + } + try { + String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK); + if (!StringUtils.isEmpty(rspStr)) { + JSONObject obj = JSONObject.parseObject(rspStr); + device.setNetworkAddress(obj.getString("addr")); + System.out.println(device.getSerialNumber() + "- 设置地址:" + obj.getString("addr")); + // 查询经纬度 + setLatitudeAndLongitude(obj.getString("city"), device); + } + } catch (Exception e) { + log.error(e.getMessage()); + } + } + + /** + * 设置经纬度 + * + * @param city + */ + private void setLatitudeAndLongitude(String city, Device device) { + String BAIDU_URL = "https://api.map.baidu.com/geocoder"; + String baiduResponse = HttpUtils.sendGet(BAIDU_URL, "address=" + city + "&output=json", Constants.GBK); + if (!StringUtils.isEmpty(baiduResponse)) { + JSONObject baiduObject = JSONObject.parseObject(baiduResponse); + JSONObject location = baiduObject.getJSONObject("result").getJSONObject("location"); + device.setLongitude(location.getBigDecimal("lng")); + device.setLatitude(location.getBigDecimal("lat")); + System.out.println(device.getSerialNumber() + "- 设置经度:" + location.getBigDecimal("lng") + ",设置纬度:" + location.getBigDecimal("lat")); + } + } + + /** + * 缓存设备状态到redis + * + * @return + */ + public List cacheDeviceStatus(Long productId, String serialNumber) { + // 获取物模型,设置默认值 + String thingsModels = thingsModelService.getCacheThingsModelByProductId(productId); + JSONObject thingsModelObject = JSONObject.parseObject(thingsModels); + JSONArray properties = thingsModelObject.getJSONArray("properties"); + JSONArray functions = thingsModelObject.getJSONArray("functions"); + List valueList = new ArrayList<>(); + if (!CollectionUtils.isEmpty(properties)) { + valueList.addAll(properties.toJavaList(ThingsModelValueItem.class)); + } + if (!CollectionUtils.isEmpty(functions)) { + valueList.addAll(functions.toJavaList(ThingsModelValueItem.class)); + } + String key = RedisKeyBuilder.buildTSLVCacheKey(productId, serialNumber); + Map maps = new HashMap<>(); + for (ThingsModelValueItem item : valueList) { + String id = item.getTempSlaveId() == null ? item.getId() : item.getId() + "#" + item.getTempSlaveId(); + List params = item.getDatatype().getParams(); + ValueItem valueItem = new ValueItem(); + valueItem.setValue(""); + valueItem.setId(id); + valueItem.setName(item.getName()); + valueItem.setShadow(""); + if (null != item.getDatatype()) { + String type = item.getDatatype().getType(); + DataEnum dataEnum = DataEnum.convert(type); + switch (dataEnum) { + case INTEGER: + case DECIMAL: + case STRING: + case ENUM: + if (null != item.getTempSlaveId()) { + valueItem.setSlaveId(item.getTempSlaveId()); + valueItem.setRegArr(item.getId()); + } + maps.put(id, JSONObject.toJSONString(valueItem)); + break; + case ARRAY: + // 数组元素赋值(英文逗号分割的字符串,包含简单类型数组和对象类型数组,数组元素ID格式:array_01_humidity) + String defaultValue = " "; + if (item.getDatatype().getArrayType().equals("object")) { + for (int i = 0; i < item.getDatatype().getArrayCount(); i++) { + // 默认值需要保留为空格,便于解析字符串为数组 + defaultValue = defaultValue + ", "; + } + for (ThingsModel param : params) { + valueItem.setValue(defaultValue); + valueItem.setShadow(defaultValue); + valueItem.setName(param.getName()); + valueItem.setId(param.getId()); + maps.put(param.getId(), JSONObject.toJSONString(valueItem)); + } + } else { + for (int i = 0; i < item.getDatatype().getArrayCount(); i++) { + defaultValue = defaultValue + ", "; + } + valueItem.setValue(defaultValue); + valueItem.setShadow(defaultValue); + valueItem.setName(item.getName()); + valueItem.setId(item.getId()); + maps.put(item.getId(), JSONObject.toJSONString(valueItem)); + } + break; + case OBJECT: + for (ThingsModel param : params) { + valueItem.setName(param.getName()); + valueItem.setId(param.getId()); + maps.put(param.getId(), JSONObject.toJSONString(valueItem)); + } + break; + } + } + } + redisCache.hashPutAll(key, maps); + return valueList; + } + + + private List getCacheDeviceStatus(Long productId, String deviceNumber) { + String key = RedisKeyBuilder.buildTSLVCacheKey(productId, deviceNumber); + Map map = redisCache.hashEntity(key); + List valueList = new ArrayList<>(); + if (map == null || map.size() == 0) { + // 缓存设备状态(物模型值)到redis + valueList = cacheDeviceStatus(productId, deviceNumber); + } else { + // 获取redis缓存的物模型值 + valueList = map.values().stream().map(s -> JSONObject.parseObject(s, ThingsModelValueItem.class)) + .collect(Collectors.toList()); + } + return valueList; + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceUserServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceUserServiceImpl.java new file mode 100644 index 00000000..b4a53c83 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/DeviceUserServiceImpl.java @@ -0,0 +1,146 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.DeviceUser; +import com.fastbee.iot.mapper.DeviceUserMapper; +import com.fastbee.iot.model.UserIdDeviceIdModel; +import com.fastbee.iot.service.IDeviceUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; + +/** + * 设备用户Service业务层处理 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Service +public class DeviceUserServiceImpl implements IDeviceUserService +{ + @Autowired + private DeviceUserMapper deviceUserMapper; + + /** + * 查询设备用户 + * + * @param deviceId 设备用户主键 + * @return 设备用户 + */ + @Override + public List selectDeviceUserByDeviceId(Long deviceId) + { + return deviceUserMapper.selectDeviceUserByDeviceId(deviceId); + } + + /** + * 查询设备用户列表 + * + * @param deviceUser 设备用户 + * @return 设备用户 + */ + @Override + public List selectDeviceUserList(DeviceUser deviceUser) + { + return deviceUserMapper.selectDeviceUserList(deviceUser); + } + + /** + * 查询设备分享用户 + * + * @param deviceUser 设备用户 + * @return 设备用户 + */ + @Override + public SysUser selectShareUser(DeviceUser deviceUser) + { + return deviceUserMapper.selectShareUser(deviceUser); + } + + /** + * 新增设备用户 + * + * @param deviceUser 设备用户 + * @return 结果 + */ + @Override + public int insertDeviceUser(DeviceUser deviceUser) + { + List deviceUsers = selectDeviceUserList(deviceUser); + if (!deviceUsers.isEmpty()) { throw new RuntimeException("该用户已添加, 禁止重复添加");} + deviceUser.setCreateTime(DateUtils.getNowDate()); + deviceUser.setIsOwner(0); + SysUser sysUser = getLoginUser().getUser(); + deviceUser.setTenantId(sysUser.getUserId()); + deviceUser.setTenantName(sysUser.getUserName()); + return deviceUserMapper.insertDeviceUser(deviceUser); + } + + /** + * 修改设备用户 + * + * @param deviceUser 设备用户 + * @return 结果 + */ + @Override + public int updateDeviceUser(DeviceUser deviceUser) + { + deviceUser.setUpdateTime(DateUtils.getNowDate()); + return deviceUserMapper.updateDeviceUser(deviceUser); + } + + /** + * 批量删除设备用户 + * + * @param deviceIds 需要删除的设备用户主键 + * @return 结果 + */ + @Override + public int deleteDeviceUserByDeviceIds(Long[] deviceIds) + { + return deviceUserMapper.deleteDeviceUserByDeviceIds(deviceIds); + } + + /** + * 删除设备用户信息 + * + * @param deviceId 设备用户主键 + * @return 结果 + */ + @Override + public int deleteDeviceUserByDeviceId(Long deviceId) + { + return deviceUserMapper.deleteDeviceUserByDeviceId(new UserIdDeviceIdModel(null,deviceId)); + } + + @Override + public int insertDeviceUserList(List deviceUsers) { + try { + deviceUsers.forEach(deviceUser -> { + deviceUser.setCreateTime(DateUtils.getNowDate()); + deviceUser.setIsOwner(0); + SysUser sysUser = getLoginUser().getUser(); + deviceUser.setTenantId(sysUser.getUserId()); + deviceUser.setTenantName(sysUser.getUserName()); + }); + return deviceUserMapper.insertDeviceUserList(deviceUsers); + } catch (DuplicateKeyException e) { + throw new RuntimeException("存在设备已经与用户绑定"); + } + } + + @Override + public DeviceUser selectDeviceUserByDeviceIdAndUserId(Long deviceId, Long userId) { + return deviceUserMapper.selectDeviceUserByDeviceIdAndUserId(deviceId, userId); + } + + @Override + public int deleteDeviceUser(DeviceUser deviceUser) { + return deviceUserMapper.deleteDeviceUser(deviceUser); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/EventLogServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/EventLogServiceImpl.java new file mode 100644 index 00000000..5cfa2c95 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/EventLogServiceImpl.java @@ -0,0 +1,116 @@ +package com.fastbee.iot.service.impl; + +import java.util.List; +import com.fastbee.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.fastbee.iot.mapper.EventLogMapper; +import com.fastbee.iot.domain.EventLog; +import com.fastbee.iot.service.IEventLogService; + +/** + * 事件日志Service业务层处理 + * + * @author kerwincui + * @date 2023-03-28 + */ +@Service +public class EventLogServiceImpl implements IEventLogService +{ + @Autowired + private EventLogMapper eventLogMapper; + + /** + * 查询事件日志 + * + * @param logId 事件日志主键 + * @return 事件日志 + */ + @Override + public EventLog selectEventLogByLogId(Long logId) + { + return eventLogMapper.selectEventLogByLogId(logId); + } + + /** + * 查询事件日志列表 + * + * @param eventLog 事件日志 + * @return 事件日志 + */ + @Override + public List selectEventLogList(EventLog eventLog) + { + return eventLogMapper.selectEventLogList(eventLog); + } + + /** + * 新增事件日志 + * + * @param eventLog 事件日志 + * @return 结果 + */ + @Override + public int insertEventLog(EventLog eventLog) + { + eventLog.setCreateTime(DateUtils.getNowDate()); + return eventLogMapper.insertEventLog(eventLog); + } + + /** + * 批量存储事件日志 + * @param list + */ + @Override + public void insertBatch(List list){ + eventLogMapper.insertBatch(list); + } + + /** + * 修改事件日志 + * + * @param eventLog 事件日志 + * @return 结果 + */ + @Override + public int updateEventLog(EventLog eventLog) + { + return eventLogMapper.updateEventLog(eventLog); + } + + /** + * 批量删除事件日志 + * + * @param logIds 需要删除的事件日志主键 + * @return 结果 + */ + @Override + public int deleteEventLogByLogIds(Long[] logIds) + { + return eventLogMapper.deleteEventLogByLogIds(logIds); + } + + /** + * 删除事件日志信息 + * + * @param logId 事件日志主键 + * @return 结果 + */ + @Override + public int deleteEventLogByLogId(Long logId) + { + return eventLogMapper.deleteEventLogByLogId(logId); + } + + /** + * 通过设备ID删除设备事件日志 + * + * @param serialNumber 设备编号 + * @return 结果 + */ + @Override + public int deleteEventLogByDeviceNumber(String serialNumber) + { + return eventLogMapper.deleteEventLogBySerialNumber(serialNumber); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/FunctionLogServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/FunctionLogServiceImpl.java new file mode 100644 index 00000000..d36a4031 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/FunctionLogServiceImpl.java @@ -0,0 +1,134 @@ +package com.fastbee.iot.service.impl; + +import java.util.List; +import com.fastbee.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.fastbee.iot.mapper.FunctionLogMapper; +import com.fastbee.iot.domain.FunctionLog; +import com.fastbee.iot.service.IFunctionLogService; + +/** + * 设备服务下发日志Service业务层处理 + * + * @author kerwincui + * @date 2022-10-22 + */ +@Service +public class FunctionLogServiceImpl implements IFunctionLogService +{ + @Autowired + private FunctionLogMapper functionLogMapper; + + /** + * 查询设备服务下发日志 + * + * @param id 设备服务下发日志主键 + * @return 设备服务下发日志 + */ + @Override + public FunctionLog selectFunctionLogById(Long id) + { + return functionLogMapper.selectFunctionLogById(id); + } + + /** + * 查询设备服务下发日志列表 + * + * @param functionLog 设备服务下发日志 + * @return 设备服务下发日志 + */ + @Override + public List selectFunctionLogList(FunctionLog functionLog) + { + return functionLogMapper.selectFunctionLogList(functionLog); + } + + /** + * 新增设备服务下发日志 + * + * @param functionLog 设备服务下发日志 + * @return 结果 + */ + @Override + public int insertFunctionLog(FunctionLog functionLog) + { + functionLog.setCreateTime(DateUtils.getNowDate()); + return functionLogMapper.insertFunctionLog(functionLog); + } + + /** + * 批量插入数据 + * @param list + */ + @Override + public void insertBatch(List list){ + functionLogMapper.insertBatch(list); + } + + /** + * 修改设备服务下发日志 + * + * @param functionLog 设备服务下发日志 + * @return 结果 + */ + @Override + public int updateFunctionLog(FunctionLog functionLog) + { + return functionLogMapper.updateFunctionLog(functionLog); + } + + /** + * 批量删除设备服务下发日志 + * + * @param ids 需要删除的设备服务下发日志主键 + * @return 结果 + */ + @Override + public int deleteFunctionLogByIds(Long[] ids) + { + return functionLogMapper.deleteFunctionLogByIds(ids); + } + + /** + * 删除设备服务下发日志信息 + * + * @param id 设备服务下发日志主键 + * @return 结果 + */ + @Override + public int deleteFunctionLogById(Long id) + { + return functionLogMapper.deleteFunctionLogById(id); + } + + /** + * 根据设备编号删除设备服务下发日志信息 + * + * @param serialNumber 设备编号 + * @return 结果 + */ + @Override + public int deleteFunctionLogByDeviceNumber(String serialNumber) + { + return functionLogMapper.deleteFunctionLogBySerialNumber(serialNumber); + } + + /** + * 批量更新日志状态值 + * @param log 参数 + */ + @Override + public void updateFuncLogBatch(FunctionLog log){ + functionLogMapper.updateFuncLogBatch(log); + } + + /** + * 根据消息id更新指令下发状态 + * @param log + */ + @Override + public void updateByMessageId(FunctionLog log){ + functionLogMapper.updateByMessageId(log); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/GroupServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/GroupServiceImpl.java new file mode 100644 index 00000000..6e0fe70a --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/GroupServiceImpl.java @@ -0,0 +1,158 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.Group; +import com.fastbee.iot.mapper.GroupMapper; +import com.fastbee.iot.model.DeviceGroupInput; +import com.fastbee.iot.model.IdOutput; +import com.fastbee.iot.service.IGroupService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; + +/** + * 设备分组Service业务层处理 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Service +public class GroupServiceImpl implements IGroupService +{ + @Autowired + private GroupMapper groupMapper; + + /** + * 查询设备分组 + * + * @param groupId 设备分组主键 + * @return 设备分组 + */ + @Override + public Group selectGroupByGroupId(Long groupId) + { + return groupMapper.selectGroupByGroupId(groupId); + } + + /** + * 通过分组ID查询关联的设备ID数组 + * @param groupId + * @return + */ + @Override + public Long[] selectDeviceIdsByGroupId(Long groupId){ + List list=groupMapper.selectDeviceIdsByGroupId(groupId); + Long[] ids=new Long[list.size()]; + for(int i=0;i selectGroupList(Group group) + { + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + if(group.getUserId()!=null && group.getUserId()!=0){ + // 筛选自己分组(主要针对管理员) + group.setUserId(group.getUserId()); + }else { + for (int i = 0; i < roles.size(); i++) { + // 租户和用户,只查看自己分组 + if (roles.get(i).getRoleKey().equals("tenant") || roles.get(i).getRoleKey().equals("general")) { + group.setUserId(user.getUserId()); + break; + } + } + } + return groupMapper.selectGroupList(group); + } + + /** + * 新增设备分组 + * + * @param group 设备分组 + * @return 结果 + */ + @Override + public int insertGroup(Group group) + { + SysUser user = getLoginUser().getUser(); + group.setUserId(user.getUserId()); + group.setUserName(user.getUserName()); + group.setCreateTime(DateUtils.getNowDate()); + return groupMapper.insertGroup(group); + } + + /** + * 修改设备分组 + * + * @param group 设备分组 + * @return 结果 + */ + @Override + public int updateGroup(Group group) + { + group.setUpdateTime(DateUtils.getNowDate()); + return groupMapper.updateGroup(group); + } + + /** + * 分组下批量添加设备分组 + * @return + */ + @Transactional(rollbackFor = Exception.class) + @Override + public int updateDeviceGroups(DeviceGroupInput input){ + //删除分组下的所有关联设备 + groupMapper.deleteDeviceGroupByGroupIds(new Long[]{input.getGroupId()}); + // 分组下添加关联设备 + if(input.getDeviceIds().length>0){ + groupMapper.insertDeviceGroups(input); + } + return 1; + } + + /** + * 批量删除分组和设备分组 + * + * @param groupIds 需要删除的设备分组主键 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteGroupByGroupIds(Long[] groupIds) + { + // 删除设备分组 + groupMapper.deleteDeviceGroupByGroupIds(groupIds); + // 删除分组 + return groupMapper.deleteGroupByGroupIds(groupIds); + } + + /** + * 删除分组信息 + * + * @param groupId 设备分组主键 + * @return 结果 + */ + @Override + public int deleteGroupByGroupId(Long groupId) + { + + return groupMapper.deleteGroupByGroupId(groupId); + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/NewsCategoryServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/NewsCategoryServiceImpl.java new file mode 100644 index 00000000..a0fcc925 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/NewsCategoryServiceImpl.java @@ -0,0 +1,117 @@ +package com.fastbee.iot.service.impl; + +import java.util.List; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.model.IdAndName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.fastbee.iot.mapper.NewsCategoryMapper; +import com.fastbee.iot.domain.NewsCategory; +import com.fastbee.iot.service.INewsCategoryService; + +/** + * 新闻分类Service业务层处理 + * + * @author kerwincui + * @date 2022-04-09 + */ +@Service +public class NewsCategoryServiceImpl implements INewsCategoryService +{ + @Autowired + private NewsCategoryMapper newsCategoryMapper; + + /** + * 查询新闻分类 + * + * @param categoryId 新闻分类主键 + * @return 新闻分类 + */ + @Override + public NewsCategory selectNewsCategoryByCategoryId(Long categoryId) + { + return newsCategoryMapper.selectNewsCategoryByCategoryId(categoryId); + } + + /** + * 查询新闻分类列表 + * + * @param newsCategory 新闻分类 + * @return 新闻分类 + */ + @Override + public List selectNewsCategoryList(NewsCategory newsCategory) + { + return newsCategoryMapper.selectNewsCategoryList(newsCategory); + } + + /** + * 查询新闻分类简短列表 + * + * @return 新闻分类 + */ + @Override + public List selectNewsCategoryShortList() + { + return newsCategoryMapper.selectNewsCategoryShortList(); + } + + /** + * 新增新闻分类 + * + * @param newsCategory 新闻分类 + * @return 结果 + */ + @Override + public int insertNewsCategory(NewsCategory newsCategory) + { + newsCategory.setCreateTime(DateUtils.getNowDate()); + return newsCategoryMapper.insertNewsCategory(newsCategory); + } + + /** + * 修改新闻分类 + * + * @param newsCategory 新闻分类 + * @return 结果 + */ + @Override + public int updateNewsCategory(NewsCategory newsCategory) + { + newsCategory.setUpdateTime(DateUtils.getNowDate()); + return newsCategoryMapper.updateNewsCategory(newsCategory); + } + + /** + * 批量删除新闻分类 + * + * @param categoryIds 需要删除的新闻分类主键 + * @return 结果 + */ + @Override + public AjaxResult deleteNewsCategoryByCategoryIds(Long[] categoryIds) + { + int productCount=newsCategoryMapper.newsCountInCategorys(categoryIds); + if(productCount>0){ + return AjaxResult.error("删除失败,请先删除对应分类下的新闻资讯"); + } + if(newsCategoryMapper.deleteNewsCategoryByCategoryIds(categoryIds)>0){ + return AjaxResult.success("删除成功"); + } + return AjaxResult.error("删除失败"); + } + + /** + * 删除新闻分类信息 + * + * @param categoryId 新闻分类主键 + * @return 结果 + */ + @Override + public int deleteNewsCategoryByCategoryId(Long categoryId) + { + return newsCategoryMapper.deleteNewsCategoryByCategoryId(categoryId); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/NewsServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/NewsServiceImpl.java new file mode 100644 index 00000000..448b77ae --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/NewsServiceImpl.java @@ -0,0 +1,131 @@ +package com.fastbee.iot.service.impl; + +import java.util.ArrayList; +import java.util.List; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.mapper.NewsCategoryMapper; +import com.fastbee.iot.model.CategoryNews; +import com.fastbee.iot.model.IdAndName; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.fastbee.iot.mapper.NewsMapper; +import com.fastbee.iot.domain.News; +import com.fastbee.iot.service.INewsService; + +/** + * 新闻资讯Service业务层处理 + * + * @author kerwincui + * @date 2022-04-09 + */ +@Service +public class NewsServiceImpl implements INewsService +{ + @Autowired + private NewsMapper newsMapper; + + /** + * 查询新闻资讯 + * + * @param newsId 新闻资讯主键 + * @return 新闻资讯 + */ + @Override + public News selectNewsByNewsId(Long newsId) + { + return newsMapper.selectNewsByNewsId(newsId); + } + + /** + * 查询新闻资讯列表 + * + * @param news 新闻资讯 + * @return 新闻资讯 + */ + @Override + public List selectNewsList(News news) + { + return newsMapper.selectNewsList(news); + } + + /** + * 查询置顶新闻资讯列表 + * + * @return 新闻资讯 + */ + @Override + public List selectTopNewsList() + { + List categoryNewsList =new ArrayList<>(); + List newsList=newsMapper.selectTopNewsList(); + for(int i=0;i selectOauthClientDetailsList(OauthClientDetails oauthClientDetails) + { + return oauthClientDetailsMapper.selectOauthClientDetailsList(oauthClientDetails); + } + + /** + * 新增云云对接 + * + * @param oauthClientDetails 云云对接 + * @return 结果 + */ + @Override + public int insertOauthClientDetails(OauthClientDetails oauthClientDetails) + { + return oauthClientDetailsMapper.insertOauthClientDetails(oauthClientDetails); + } + + /** + * 修改云云对接 + * + * @param oauthClientDetails 云云对接 + * @return 结果 + */ + @Override + public int updateOauthClientDetails(OauthClientDetails oauthClientDetails) + { + return oauthClientDetailsMapper.updateOauthClientDetails(oauthClientDetails); + } + + /** + * 批量删除云云对接 + * + * @param clientIds 需要删除的云云对接主键 + * @return 结果 + */ + @Override + public int deleteOauthClientDetailsByClientIds(String[] clientIds) + { + return oauthClientDetailsMapper.deleteOauthClientDetailsByClientIds(clientIds); + } + + /** + * 删除云云对接信息 + * + * @param clientId 云云对接主键 + * @return 结果 + */ + @Override + public int deleteOauthClientDetailsByClientId(String clientId) + { + return oauthClientDetailsMapper.deleteOauthClientDetailsByClientId(clientId); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProductAuthorizeServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProductAuthorizeServiceImpl.java new file mode 100644 index 00000000..1af3d6e8 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProductAuthorizeServiceImpl.java @@ -0,0 +1,143 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.constant.HttpStatus; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.uuid.IdUtils; +import com.fastbee.iot.domain.ProductAuthorize; +import com.fastbee.iot.mapper.ProductAuthorizeMapper; +import com.fastbee.iot.model.ProductAuthorizeVO; +import com.fastbee.iot.service.IProductAuthorizeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; + +/** + * 产品授权码Service业务层处理 + * + * @author kami + * @date 2022-04-11 + */ +@Service +public class ProductAuthorizeServiceImpl implements IProductAuthorizeService { + @Autowired + private ProductAuthorizeMapper productAuthorizeMapper; + + /** + * 查询产品授权码 + * + * @param authorizeId 产品授权码主键 + * @return 产品授权码 + */ + @Override + public ProductAuthorize selectProductAuthorizeByAuthorizeId(Long authorizeId) { + return productAuthorizeMapper.selectProductAuthorizeByAuthorizeId(authorizeId); + } + + /** + * 查询产品授权码列表 + * + * @param productAuthorize 产品授权码 + * @return 产品授权码 + */ + @Override + public List selectProductAuthorizeList(ProductAuthorize productAuthorize) { + return productAuthorizeMapper.selectProductAuthorizeList(productAuthorize); + } + + /** + * 新增产品授权码 + * + * @param productAuthorize 产品授权码 + * @return 结果 + */ + @Override + public int insertProductAuthorize(ProductAuthorize productAuthorize) { + // 1=未使用,2=使用中 + productAuthorize.setStatus(1); + productAuthorize.setCreateTime(DateUtils.getNowDate()); + return productAuthorizeMapper.insertProductAuthorize(productAuthorize); + } + + /** + * 修改产品授权码 + * + * @param productAuthorize 产品授权码 + * @return 结果 + */ + @Override + public int updateProductAuthorize(ProductAuthorize productAuthorize) { + if(productAuthorize.getDeviceId()!=null && productAuthorize.getDeviceId()!=0){ + // 1=未使用,2=使用中 + productAuthorize.setStatus(2); + productAuthorize.setUpdateTime(DateUtils.getNowDate()); + } + return productAuthorizeMapper.updateProductAuthorize(productAuthorize); + } + + /** + * 批量删除产品授权码 + * + * @param authorizeIds 需要删除的产品授权码主键 + * @return 结果 + */ + @Override + public int deleteProductAuthorizeByAuthorizeIds(Long[] authorizeIds) { + return productAuthorizeMapper.deleteProductAuthorizeByAuthorizeIds(authorizeIds); + } + + /** + * 删除产品授权码信息 + * + * @param authorizeId 产品授权码主键 + * @return 结果 + */ + @Override + public int deleteProductAuthorizeByAuthorizeId(Long authorizeId) { + return productAuthorizeMapper.deleteProductAuthorizeByAuthorizeId(authorizeId); + } + + /** + * 根据数量批量新增产品授权码 + * + * @param productAuthorizeVO + * @return + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int addProductAuthorizeByNum(ProductAuthorizeVO productAuthorizeVO) { + Long productId = productAuthorizeVO.getProductId(); + int createNum = productAuthorizeVO.getCreateNum(); + List list = new ArrayList<>(createNum); + SysUser user = getLoginUser().getUser(); + for (int i = 0; i < createNum; i++) { + ProductAuthorize authorize = new ProductAuthorize(); + // 1=未使用,2=使用中 + authorize.setStatus(1); + authorize.setProductId(productId); + authorize.setCreateBy(user.getUserName()); + authorize.setCreateTime(DateUtils.getNowDate()); + authorize.setAuthorizeCode(IdUtils.fastSimpleUUID().toUpperCase()); + list.add(authorize); + } + return productAuthorizeMapper.insertBatchAuthorize(list); + } + + /** + * 根据产品id查询产品授权码 + * @param productId 产品id + * @return + */ + @Override + public List listByProductId(Long productId) { + return productAuthorizeMapper.selectProductAuthorizeListByProductId(productId); + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProductServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProductServiceImpl.java new file mode 100644 index 00000000..7747edea --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProductServiceImpl.java @@ -0,0 +1,321 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.core.redis.RedisKeyBuilder; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.Product; +import com.fastbee.iot.domain.ThingsModelTemplate; +import com.fastbee.iot.mapper.DeviceMapper; +import com.fastbee.iot.mapper.ProductAuthorizeMapper; +import com.fastbee.iot.mapper.ProductMapper; +import com.fastbee.iot.model.ChangeProductStatusModel; +import com.fastbee.iot.model.IdAndName; +import com.fastbee.iot.model.ImportThingsModelInput; +import com.fastbee.iot.model.ProductCode; +import com.fastbee.iot.service.IProductService; +import com.fastbee.iot.service.IThingsModelTemplateService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; + +/** + * 产品Service业务层处理 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Service +public class ProductServiceImpl implements IProductService +{ + + @Autowired + private ThingsModelServiceImpl thingsModelService; + + @Autowired + private ProductMapper productMapper; + + @Autowired + private ProductAuthorizeMapper productAuthorizeMapper; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ToolServiceImpl toolService; + + @Autowired + private DeviceMapper deviceMapper; + + @Autowired + private IThingsModelTemplateService modelTemplateService; + + @Autowired + @Lazy + private DeviceServiceImpl deviceService; + + /** + * 查询产品 + * + * @param productId 产品主键 + * @return 产品 + */ + @Override + public Product selectProductByProductId(Long productId) + { + return productMapper.selectProductByProductId(productId); + } + + /** + * 查询产品列表 + * + * @param product 产品 + * @return 产品 + */ + @Override + public List selectProductList(Product product) + { + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + // 租户 + if(roles.stream().anyMatch(a->a.getRoleKey().equals("tenant"))){ + product.setTenantId(user.getUserId()); + } + return productMapper.selectProductList(product); + } + + /** + * 查询产品简短列表 + * + * @return 产品 + */ + @Override + public List selectProductShortList() + { + Product product =new Product(); + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + // 租户 + if(roles.stream().anyMatch(a->a.getRoleKey().equals("tenant"))){ + product.setTenantId(user.getUserId()); + } + return productMapper.selectProductShortList(product); + } + + /** + * 新增产品 + * + * @param product 产品 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Product insertProduct(Product product) + { + // 判断是否为管理员 + product.setIsSys(1); + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + for(int i=0;i thingsModelTemplates = modelTemplateService.selectAllByTemplateId(product.getTemplateId()); + Long[] ids = thingsModelTemplates.stream().map(ThingsModelTemplate::getTemplateId).toArray(Long[]::new); + ImportThingsModelInput input = new ImportThingsModelInput(); + input.setTemplateIds(ids); + input.setProductId(product.getProductId()); + input.setProductName(product.getProductName()); + thingsModelService.importByTemplateIds(input); + } + return product; + } + + /** + * 修改产品 + * + * @param product 产品 + * @return 结果 + */ + @Override + public int updateProduct(Product product) + { + product.setUpdateTime(DateUtils.getNowDate()); + return productMapper.updateProduct(product); + } + + /** + * 获取产品下面的设备数量 + * + * @param productId 产品ID + * @return 结果 + */ + @Override + public int selectDeviceCountByProductId(Long productId) + { + return deviceMapper.selectDeviceCountByProductId(productId); + } + + /** + * 更新产品状态,1-未发布,2-已发布 + * + * @param model + * @return 结果 + */ + @Override + @Transactional + public AjaxResult changeProductStatus(ChangeProductStatusModel model) + { + if(model.getStatus()!=1 && model.getStatus()!=2){ + return AjaxResult.error("状态更新失败,状态值有误"); + } + if(model.getStatus()==2){ + // 产品下必须包含物模型 + int thingsCount=productMapper.thingsCountInProduct(model.getProductId()); + if(thingsCount==0 && model.getDeviceType() !=3){ + return AjaxResult.error("发布失败,请先添加产品的物模型"); + } else if (thingsCount > 0){ + // 批量更新产品下所有设备的物模型值 + updateDeviceStatusByProductIdAsync(model.getProductId()); + } + // TODO 增加modbus之后,产品下子设备的物模型唯一 + //int repeatCount=productMapper.thingsRepeatCountInProduct(model.getProductId()); + //if(repeatCount>1){ + // return AjaxResult.error("发布失败,产品物模型的标识符必须唯一"); + //} + } + if(productMapper.changeProductStatus(model)>0){ + return AjaxResult.success("操作成功"); + } + return AjaxResult.error("状态更新失败"); + } + + /*** + * 更新产品下所有设备的物模型值 + * @param productId + */ + @Async + public void updateDeviceStatusByProductIdAsync(Long productId){ + List deviceNumbers=deviceMapper.selectSerialNumberByProductId(productId); + deviceNumbers.forEach(x->{ + // 缓存新的物模型值 + deviceService.cacheDeviceStatus(productId,x); + }); + } + + /** + * 批量删除产品 + * + * @param productIds 需要删除的产品主键 + * @return 结果 + */ + @Override + @Transactional + public AjaxResult deleteProductByProductIds(Long[] productIds) + { + // 删除物模型JSON缓存 + for(int i=0;i0){ + return AjaxResult.error("删除失败,请先删除对应产品下的设备"); + } + // 删除产品物模型 + productMapper.deleteProductThingsModelByProductIds(productIds); + // 删除产品的授权码 + productAuthorizeMapper.deleteProductAuthorizeByProductIds(productIds); + // 删除产品 + if(productMapper.deleteProductByProductIds(productIds)>0){ + return AjaxResult.success("删除成功"); + } + return AjaxResult.error("删除失败"); + } + + + /** + * 删除产品信息 + * + * @param productId 产品主键 + * @return 结果 + */ + @Override + public int deleteProductByProductId(Long productId) + { + // 删除物模型JSON缓存 + redisCache.deleteObject(RedisKeyBuilder.buildTSLCacheKey(productId)); + return productMapper.deleteProductByProductId(productId); + } + + /** + * 根据设备编号查询产品信息 + * @param serialNumber 设备编号 + * @return 结果 + */ + @Override + public Product getProductBySerialNumber(String serialNumber){ + return productMapper.getProductBySerialNumber(serialNumber); + } + + /** + * 根据设备编号查询协议编号 + * @param serialNumber 设备编号 + * @return 协议编号 + */ + @Override + public ProductCode getProtocolBySerialNumber(String serialNumber){ + return productMapper.getProtocolBySerialNumber(serialNumber); + } + + /** + * 根据产品id获取协议编号 + * @param productId 产品id + * @return 协议编号 + */ + @Override + public String getProtocolByProductId(Long productId){ + return productMapper.getProtocolByProductId(productId); + } + + + /** + * 根据模板id查询所有使用的产品 + * @param templeId 模板id + * @return + */ + @Override + public List selectByTempleId(Long templeId){ + return productMapper.selectByTempleId(templeId); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProtocolServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProtocolServiceImpl.java new file mode 100644 index 00000000..e3e1c2d1 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ProtocolServiceImpl.java @@ -0,0 +1,119 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.Protocol; +import com.fastbee.iot.mapper.ProtocolMapper; +import com.fastbee.iot.service.IProtocolService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 协议管理 + * @author gsb + * @date 2022/10/19 17:02 + */ +@Service +public class ProtocolServiceImpl implements IProtocolService { + + @Autowired + private ProtocolMapper protocolMapper; + + /** + * 查询协议 + * + * @param id 协议主键 + * @return 协议 + */ + @Override + public Protocol selectProtocolById(Long id) + { + return protocolMapper.selectProtocolById(id); + } + + /** + * 查询协议列表 + * + * @param protocol 协议 + * @return 协议 + */ + @Override + public List selectProtocolList(Protocol protocol) + { + return protocolMapper.selectProtocolList(protocol); + } + + /** + * 新增协议 + * + * @param protocol 协议 + * @return 结果 + */ + @Override + public int insertProtocol(Protocol protocol) + { + protocol.setCreateTime(DateUtils.getNowDate()); + if (protocol.getProtocolStatus() == null){ + protocol.setProtocolStatus(1); + } + return protocolMapper.insertProtocol(protocol); + } + + /** + * 修改协议 + * + * @param protocol 协议 + * @return 结果 + */ + @Override + public int updateProtocol(Protocol protocol) + { + protocol.setUpdateTime(DateUtils.getNowDate()); + return protocolMapper.updateProtocol(protocol); + } + + /** + * 批量删除协议 + * + * @param ids 需要删除的协议主键 + * @return 结果 + */ + @Override + public int deleteProtocolByIds(Long[] ids) + { + return protocolMapper.deleteProtocolByIds(ids); + } + + /** + * 删除协议信息 + * + * @param id 协议主键 + * @return 结果 + */ + @Override + public int deleteProtocolById(Long id) + { + return protocolMapper.deleteProtocolById(id); + } + + /** + * 获取所有协议 + * @return + */ + @Override + public List selectAll(){ + return protocolMapper.selectAll(1,0); + } + + /** + *获取所有可用协议 + * @param protocol + * @return + */ + @Override + public List selectByCondition(Protocol protocol){ + return protocolMapper.selectByUnion(protocol); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/SocialLoginServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/SocialLoginServiceImpl.java new file mode 100644 index 00000000..3d65743f --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/SocialLoginServiceImpl.java @@ -0,0 +1,432 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.constant.Constants; +import com.fastbee.common.constant.HttpStatus; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.domain.model.BindLoginBody; +import com.fastbee.common.core.domain.model.BindRegisterBody; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.sign.Md5Utils; +import com.fastbee.framework.web.service.SysLoginService; +import com.fastbee.framework.web.service.SysRegisterService; +import com.fastbee.framework.web.service.TokenService; +import com.fastbee.iot.domain.SocialPlatform; +import com.fastbee.iot.domain.SocialUser; +import com.fastbee.iot.model.RegisterUserInput; +import com.fastbee.iot.model.login.AuthRequestWrap; +import com.fastbee.iot.model.login.BindIdValue; +import com.fastbee.iot.model.login.LoginIdValue; +import com.fastbee.iot.service.IAuthRequestFactory; +import com.fastbee.iot.service.ISocialLoginService; +import com.fastbee.iot.service.ISocialUserService; +import com.fastbee.iot.service.IToolService; +import com.fastbee.system.service.ISysConfigService; +import com.fastbee.system.service.ISysUserService; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.utils.AuthStateUtils; +import me.zhyd.oauth.utils.RandomUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static com.fastbee.common.constant.HttpStatus.NO_MESSAGE_ALERT; +import static com.fastbee.common.core.domain.AjaxResult.error; + +/** + * 第三方登录Service业务层处理 + * + * @author json + * @date 2022-04-12 + */ +@Service +public class SocialLoginServiceImpl implements ISocialLoginService { + + public static final Integer BIND_EXPIRE_TIME = 60 * 60; + public static final Integer LOGIN_SOCIAL_EXPIRE_TIME = 60; + public static final String ONLINE_STATUS = "0"; //1 offline + public static final String DEL_FLAG = "0"; //1 offline + public static final Integer WX_BIND_EXPIRE_TIME = 5; + + //redis key: uuid+source + public static final String BIND_REDIS_KEY = "login:bind:user:"; + //redis key : userId+random 32 + public static final String LOGIN_SOCIAL_REDIS_KEY = "login:social:user:"; + //redis key : msg+ code+currentTime + public static final String LOGIN_ERROR_MSG_REDIS_KEY = "login:error:msg:"; + //redis key : userId+random 32 + public static final String WX_BIND_REDIS_KEY = "wx:bind:user:"; + + public static final String HTTPS = "https://"; + + private static final Logger log = LoggerFactory.getLogger(SocialLoginServiceImpl.class); + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysConfigService iSysConfigService; + + @Autowired + private TokenService tokenService; + + @Autowired + private SysRegisterService sysRegisterService; + + @Autowired + private SysLoginService sysLoginService; + + @Autowired + private ISysUserService iSysUserService; + + @Autowired + private IAuthRequestFactory iAuthRequestFactory; + + @Autowired + private ISocialUserService iSocialUserService; + + @Resource + private IToolService toolService; + + + @Override + public String renderAuth(String source, HttpServletRequest httpServletRequest) { + AuthRequestWrap authRequestWrap = null; + try { + authRequestWrap = iAuthRequestFactory.getAuthRequest(source); + checkSocialPlatform(authRequestWrap.getSocialPlatform()); + return authRequestWrap.getAuthRequest().authorize(AuthStateUtils.createState()); + } catch (AuthException authException) { + //返回错误信息 + log.error("", authException); + if (authRequestWrap != null) { + String errorId = genErrorId(authException.getMessage()); + return authRequestWrap.getSocialPlatform().getErrorMsgUri() + errorId; + } else { + return httpServletRequest.getProtocol() + httpServletRequest.getServerName() + httpServletRequest.getServerPort(); + } + } catch (Exception exception) { + //这类错误 直接不返回,重定向到主页 + log.error("", exception); + return HTTPS + httpServletRequest.getServerName(); + } + + } + + @Override + public String callback(String source, AuthCallback authCallback, HttpServletRequest httpServletRequest) { + AuthRequestWrap authRequestWrap = null; + try { + authRequestWrap = iAuthRequestFactory.getAuthRequest(source); + checkSocialPlatform(authRequestWrap.getSocialPlatform()); + AuthResponse authResponse = authRequestWrap.getAuthRequest().login(authCallback); + String bindId = null; + String loginId = null; + if (authResponse.ok()) { + + SocialUser socialUser = findSocialUser(authResponse.getData().getUuid(), authResponse.getData().getSource()); + // 同一开放平台下(unionid唯一)如果有其他应用已经微信登录绑定了账号,则直接绑定该账号登录 + Long bindSysUserId = null; + String unionId = (String) authResponse.getData().getRawUserInfo().get("unionid"); + if ((socialUser == null || socialUser.getSysUserId() == null) && StringUtils.isNotEmpty(unionId)) { + bindSysUserId = iSocialUserService.selectSysUserIdByUnionId(unionId); + } + createOrUpdateSocialUser(socialUser, authResponse.getData(), source, bindSysUserId); + if (bindSysUserId != null) { + socialUser = new SocialUser(); + socialUser.setSysUserId(bindSysUserId); + } + if (socialUser == null) { + //第一次登录 + bindId = genBindId(authResponse.getData()); + } else if (socialUser.getSysUserId() == null || socialUser.getSysUserId() <= 0) { + //初次绑定 + bindId = genBindId(authResponse.getData()); + } else { + //查看是否已经绑定 + SysUser sysUser = iSysUserService.selectUserById(socialUser.getSysUserId()); + if (sysUser == null) { + bindId = genBindId(authResponse.getData()); + } else { + //直接登录跳转 + loginId = genLoginId(sysUser); + } + } + if (StringUtils.isNotEmpty(bindId)) { + return authRequestWrap.getSocialPlatform().getBindUri() + bindId; + } else { + return authRequestWrap.getSocialPlatform().getRedirectLoginUri() + loginId; + } + } else { + log.error("登录授权异常,code:{}, msg:{}", authResponse.getCode(), authResponse.getMsg()); + String errorId = genErrorId(authResponse.getMsg()); + return authRequestWrap.getSocialPlatform().getErrorMsgUri() + errorId; + } + } catch (AuthException authException) { + //返回错误信息 + log.error("", authException); + if (authRequestWrap != null) { + String errorId = genErrorId(authException.getMessage()); + return authRequestWrap.getSocialPlatform().getErrorMsgUri() + errorId; + } else { + return httpServletRequest.getServerName() + httpServletRequest.getServerPort(); + } + } catch (Exception exception) { + log.error("", exception); + return HTTPS + httpServletRequest.getServerName(); + } + + } + + @Override + public AjaxResult checkBindId(String bindId) { + AjaxResult ajax = AjaxResult.success(); + ajax.put("bindAccount", false); + if (StringUtils.isEmpty(bindId)) { + return ajax; + } + BindIdValue bindValue = redisCache.getCacheObject(BIND_REDIS_KEY + bindId); + if (bindValue == null) { + return ajax; + } + ajax.put("bindAccount", true); + return AjaxResult.success(bindId); + } + + @Override + public AjaxResult getErrorMsg(String errorId) { + String errorMsg = redisCache.getCacheObject(LOGIN_ERROR_MSG_REDIS_KEY + errorId); + if (StringUtils.isEmpty(errorMsg)) { + return error(NO_MESSAGE_ALERT, ""); + } else { + return error(errorMsg); + } + } + + @Override + public AjaxResult socialLogin(String loginId) { + AjaxResult ajax = AjaxResult.success(); + String loginKey = LOGIN_SOCIAL_REDIS_KEY + loginId; + LoginIdValue loginIdValue = redisCache.getCacheObject(loginKey); + if (loginIdValue != null) { + //login + String token = sysLoginService.redirectLogin(loginIdValue.getUsername(), loginIdValue.getPassword()); + ajax.put(Constants.TOKEN, token); + } else { + log.info("loginId:{} ", loginId); + return error(NO_MESSAGE_ALERT, "数据错误"); + } + return ajax; + } + + @Override + public AjaxResult bindLogin(BindLoginBody bindLoginBody) { + BindIdValue bindValue = redisCache.getCacheObject(BIND_REDIS_KEY + bindLoginBody.getBindId()); + SocialUser socialUser = findSocialUser(bindValue.getUuid(), bindValue.getSource()); + AjaxResult checkAjax = checkSocialUser(socialUser, bindLoginBody.getBindId()); + if (checkAjax != null) { + return checkAjax; + } + AjaxResult ajax = AjaxResult.success(); + // 检查账号是否已经绑定微信 + SysUser sysUser = iSysUserService.selectUserByUserName(bindLoginBody.getUsername()); + if (sysUser == null) { + // 单独返回code用户不存在,给前端处理 + return AjaxResult.error(HttpStatus.USER_NO_EXIST, "用户不存在"); + } + // 自定义一下密码错误的提示 + if(!SecurityUtils.matchesPassword(bindLoginBody.getPassword(), sysUser.getPassword())){ + throw new ServiceException("密码错误"); + } + List socialUserList = iSocialUserService.selectBySysUserId(sysUser.getUserId()); + if (CollectionUtils.isNotEmpty(socialUserList)) { + throw new ServiceException("该账号已经绑定其他微信,请先解绑后重试!"); + } + // 生成令牌 + String token = sysLoginService.login(bindLoginBody.getUsername(), bindLoginBody.getPassword(), bindLoginBody.getCode(), + bindLoginBody.getUuid()); + LoginUser loginUser = tokenService.getLoginUserByToken(token); + //绑定和更新 + SocialUser updateSocialUser = new SocialUser(); + updateSocialUser.setSysUserId(loginUser.getUserId()); + updateSocialUser.setStatus("1"); + updateSocialUser.setSocialUserId(socialUser.getSocialUserId()); + iSocialUserService.updateSocialUser(updateSocialUser); + ajax.put(Constants.TOKEN, token); + redisCache.deleteObject(BIND_REDIS_KEY + bindLoginBody.getBindId()); + return ajax; + } + + @Override + public AjaxResult bindRegister(BindRegisterBody bindRegisterBody) { + if (!("true".equals(iSysConfigService.selectConfigByKey("sys.account.registerUser")))) { + return error("当前系统没有开启注册功能!"); + } + BindIdValue bindValue = redisCache.getCacheObject(BIND_REDIS_KEY + bindRegisterBody.getBindId()); + SocialUser socialUser = findSocialUser(bindValue.getUuid(), bindValue.getSource()); + AjaxResult checkAjax = checkSocialUser(socialUser, bindRegisterBody.getBindId()); + if (checkAjax != null) { + return checkAjax; + } + + AjaxResult ajax = AjaxResult.success(); + RegisterUserInput registerUserInput = new RegisterUserInput(); + BeanUtils.copyProperties(bindRegisterBody, registerUserInput); + String msg = toolService.register(registerUserInput); + if (StringUtils.isNotEmpty(msg)) { + return error(msg); + } + SysUser sysUser = iSysUserService.selectUserByUserName(bindRegisterBody.getUsername()); + //绑定和更新 + SocialUser updateSocialUser = new SocialUser(); + updateSocialUser.setSysUserId(sysUser.getUserId()); + updateSocialUser.setStatus("1"); + updateSocialUser.setSocialUserId(socialUser.getSocialUserId()); + iSocialUserService.updateSocialUser(updateSocialUser); + redisCache.deleteObject(BIND_REDIS_KEY + bindRegisterBody.getBindId()); + // 生成令牌 + String token = sysLoginService.redirectLogin(sysUser.getUserName(), sysUser.getPassword()); + ajax.put(Constants.TOKEN, token); + return ajax; + } + + private void checkSocialPlatform(SocialPlatform socialPlatform) { + if (socialPlatform != null && (!socialPlatform.getStatus().equals(ONLINE_STATUS) || !socialPlatform.getDelFlag().equals(DEL_FLAG))) { + throw new AuthException("当前第三方登录平台被禁用"); + } + } + + @Override + public SocialUser findSocialUser(String uuid, String source) { + SocialUser socialUser = new SocialUser(); + socialUser.setSource(source); + socialUser.setUuid(uuid); + List socialUserList = iSocialUserService.selectSocialUserList(socialUser); + return socialUserList == null || socialUserList.isEmpty() ? null : socialUserList.get(0); + + } + + public void createOrUpdateSocialUser(SocialUser socialUser, AuthUser authUser, String source, Long bindSysUserId) { + if (socialUser != null) { + //更新数据 + SocialUser updateSocialUser = new SocialUser(); + updateSocialUser.setSocialUserId(socialUser.getSocialUserId()); + replaceSocialUser(updateSocialUser, authUser); + updateSocialUser.setUpdateBy("System"); + updateSocialUser.setUpdateTime(DateUtils.getNowDate()); + updateSocialUser.setSourceClient(source); + if (socialUser.getSysUserId() == null && bindSysUserId != null) { + updateSocialUser.setSysUserId(bindSysUserId); + updateSocialUser.setStatus("1"); + } + iSocialUserService.updateSocialUser(updateSocialUser); + } else { + //创建 + SocialUser saveSocialUser = new SocialUser(); + replaceSocialUser(saveSocialUser, authUser); + saveSocialUser.setDelFlag("0"); + saveSocialUser.setCreateBy("System"); + saveSocialUser.setCreateTime(DateUtils.getNowDate()); + saveSocialUser.setSourceClient(source); + if (bindSysUserId != null) { + saveSocialUser.setSysUserId(bindSysUserId); + saveSocialUser.setStatus("1"); + } else { + saveSocialUser.setStatus("0"); + } + iSocialUserService.insertSocialUser(saveSocialUser); + } + } + + private void replaceSocialUser(SocialUser socialUser, AuthUser authUser) { + + socialUser.setUuid(authUser.getUuid()); + socialUser.setSource(authUser.getSource()); + socialUser.setAccessToken(authUser.getToken().getAccessToken()); + //nullable + socialUser.setExpireIn((long) authUser.getToken().getExpireIn()); + socialUser.setRefreshToken(authUser.getToken().getRefreshToken()); + socialUser.setOpenId(authUser.getToken().getOpenId()); + socialUser.setUid(authUser.getToken().getUid()); + socialUser.setAccessCode(authUser.getToken().getAccessCode()); + socialUser.setUnionId(authUser.getToken().getUnionId()); + socialUser.setCode(authUser.getToken().getCode()); + socialUser.setAvatar(authUser.getAvatar()); + socialUser.setUsername(authUser.getUsername()); + socialUser.setNickname(authUser.getNickname()); + } + + public String genBindId(AuthUser authUser) { + String bindId = Md5Utils.hash(authUser.getUuid() + authUser.getSource()); + String key = BIND_REDIS_KEY + bindId; + BindIdValue bindIdValue = new BindIdValue(); + bindIdValue.setSource(authUser.getSource()); + bindIdValue.setUuid(authUser.getUuid()); + redisCache.setCacheObject(key, bindIdValue, BIND_EXPIRE_TIME, TimeUnit.SECONDS); + return bindId; + } + + private String genLoginId(SysUser sysUser) { + String loginId = Md5Utils.hash(sysUser.getUserId() + RandomUtil.randomString(32)); + String key = LOGIN_SOCIAL_REDIS_KEY + loginId; + LoginIdValue loginIdValue = new LoginIdValue(); + loginIdValue.setPassword(sysUser.getPassword()); + loginIdValue.setUsername(sysUser.getUserName()); + redisCache.setCacheObject(key, loginIdValue, LOGIN_SOCIAL_EXPIRE_TIME, TimeUnit.SECONDS); + return loginId; + } + + public String genErrorId(String msg) { + String errorId = Md5Utils.hash(msg + RandomUtil.randomString(32)); + String key = LOGIN_ERROR_MSG_REDIS_KEY + errorId; + redisCache.setCacheObject(key, msg, LOGIN_SOCIAL_EXPIRE_TIME, TimeUnit.SECONDS); + return errorId; + } + + public String genWxBindId(Long userId) { + String wxBindId = Md5Utils.hash(userId + RandomUtil.randomString(32)); + String key = WX_BIND_REDIS_KEY + wxBindId; + redisCache.setCacheObject(key, userId, WX_BIND_EXPIRE_TIME, TimeUnit.MINUTES); + return wxBindId; + } + + @Override + public AjaxResult checkSocialUser(SocialUser socialUser, String bindId) { + if (socialUser == null) { + log.info("bindId不存在, bindId: {}", bindId); + return error("绑定账户不存在"); + } else { + return null; + } + } + + private int createSocialUser(AuthUser authUser, String sourceClient, Long sysUserId) { + //创建 + SocialUser saveSocialUser = new SocialUser(); + replaceSocialUser(saveSocialUser, authUser); + saveSocialUser.setDelFlag("0"); + saveSocialUser.setCreateBy("System"); + saveSocialUser.setCreateTime(DateUtils.getNowDate()); + saveSocialUser.setSourceClient(sourceClient); + saveSocialUser.setSysUserId(sysUserId); + saveSocialUser.setStatus("1"); + return iSocialUserService.insertSocialUser(saveSocialUser); + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/SocialPlatformServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/SocialPlatformServiceImpl.java new file mode 100644 index 00000000..8ffa12b3 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/SocialPlatformServiceImpl.java @@ -0,0 +1,103 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.SocialPlatform; +import com.fastbee.iot.mapper.SocialPlatformMapper; +import com.fastbee.iot.service.ISocialPlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 第三方登录平台控制Service业务层处理 + * + * @author kerwincui + * @date 2022-04-11 + */ +@Service +public class SocialPlatformServiceImpl implements ISocialPlatformService +{ + @Autowired + private SocialPlatformMapper socialPlatformMapper; + + /** + * 查询第三方登录平台控制 + * + * @param socialPlatformId 第三方登录平台控制主键 + * @return 第三方登录平台控制 + */ + @Override + public SocialPlatform selectSocialPlatformBySocialPlatformId(Long socialPlatformId) + { + return socialPlatformMapper.selectSocialPlatformBySocialPlatformId(socialPlatformId); + } + + @Override + public SocialPlatform selectSocialPlatformByPlatform(String platform) { + return socialPlatformMapper.selectSocialPlatformByPlatform(platform); + } + + /** + * 查询第三方登录平台控制列表 + * + * @param socialPlatform 第三方登录平台控制 + * @return 第三方登录平台控制 + */ + @Override + public List selectSocialPlatformList(SocialPlatform socialPlatform) + { + return socialPlatformMapper.selectSocialPlatformList(socialPlatform); + } + + /** + * 新增第三方登录平台控制 + * + * @param socialPlatform 第三方登录平台控制 + * @return 结果 + */ + @Override + public int insertSocialPlatform(SocialPlatform socialPlatform) + { + socialPlatform.setCreateTime(DateUtils.getNowDate()); + socialPlatform.setDelFlag("0"); + return socialPlatformMapper.insertSocialPlatform(socialPlatform); + } + + /** + * 修改第三方登录平台控制 + * + * @param socialPlatform 第三方登录平台控制 + * @return 结果 + */ + @Override + public int updateSocialPlatform(SocialPlatform socialPlatform) + { + socialPlatform.setUpdateTime(DateUtils.getNowDate()); + return socialPlatformMapper.updateSocialPlatform(socialPlatform); + } + + /** + * 批量删除第三方登录平台控制 + * + * @param socialPlatformIds 需要删除的第三方登录平台控制主键 + * @return 结果 + */ + @Override + public int deleteSocialPlatformBySocialPlatformIds(Long[] socialPlatformIds) + { + return socialPlatformMapper.deleteSocialPlatformBySocialPlatformIds(socialPlatformIds); + } + + /** + * 删除第三方登录平台控制信息 + * + * @param socialPlatformId 第三方登录平台控制主键 + * @return 结果 + */ + @Override + public int deleteSocialPlatformBySocialPlatformId(Long socialPlatformId) + { + return socialPlatformMapper.deleteSocialPlatformBySocialPlatformId(socialPlatformId); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/SocialUserServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/SocialUserServiceImpl.java new file mode 100644 index 00000000..60351ffb --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/SocialUserServiceImpl.java @@ -0,0 +1,155 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.iot.domain.SocialUser; +import com.fastbee.iot.mapper.SocialUserMapper; +import com.fastbee.iot.service.ISocialUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 用户第三方用户信息Service业务层处理 + * + * @author json + * @date 2022-04-18 + */ +@Service +public class SocialUserServiceImpl implements ISocialUserService +{ + @Autowired + private SocialUserMapper socialUserMapper; + + /** + * 查询用户第三方用户信息 + * + * @param socialUserId 用户第三方用户信息主键 + * @return 用户第三方用户信息 + */ + @Override + public SocialUser selectSocialUserBySocialUserId(Long socialUserId) + { + return socialUserMapper.selectSocialUserBySocialUserId(socialUserId); + } + + /** + * 查询用户第三方用户信息列表 + * + * @param socialUser 用户第三方用户信息 + * @return 用户第三方用户信息 + */ + @Override + public List selectSocialUserList(SocialUser socialUser) + { + return socialUserMapper.selectSocialUserList(socialUser); + } + + /** + * 新增用户第三方用户信息 + * + * @param socialUser 用户第三方用户信息 + * @return 结果 + */ + @Override + public int insertSocialUser(SocialUser socialUser) + { + socialUser.setCreateTime(DateUtils.getNowDate()); + return socialUserMapper.insertSocialUser(socialUser); + } + + /** + * 修改用户第三方用户信息 + * + * @param socialUser 用户第三方用户信息 + * @return 结果 + */ + @Override + public int updateSocialUser(SocialUser socialUser) + { + socialUser.setUpdateTime(DateUtils.getNowDate()); + return socialUserMapper.updateSocialUser(socialUser); + } + + /** + * 批量删除用户第三方用户信息 + * + * @param socialUserIds 需要删除的用户第三方用户信息主键 + * @return 结果 + */ + @Override + public int deleteSocialUserBySocialUserIds(Long[] socialUserIds) + { + return socialUserMapper.deleteSocialUserBySocialUserIds(socialUserIds); + } + + /** + * 删除用户第三方用户信息信息 + * + * @param socialUserId 用户第三方用户信息主键 + * @return 结果 + */ + @Override + public int deleteSocialUserBySocialUserId(Long socialUserId) + { + return socialUserMapper.deleteSocialUserBySocialUserId(socialUserId); + } + + /** + * 根据openId或unionId获取用户第三方信息 + * @param openId + * @param unionId + * @return + */ + @Override + public SocialUser selectOneByOpenIdAndUnionId(String openId, String unionId) { + return socialUserMapper.selectOneByOpenIdAndUnionId(openId, unionId); + } + + /** + * 通过unionId查询 + * + * @param unionId + * @return + */ + @Override + public Long selectSysUserIdByUnionId(String unionId) { + if (StringUtils.isEmpty(unionId)) { + return null; + } + return socialUserMapper.selectSysUserIdByUnionId(unionId); + } + + /** + * 通过系统用户id查询已绑定信息 + * @param sysUserId 系统用户id + * @return + */ + @Override + public List selectBySysUserId(Long sysUserId) { + return socialUserMapper.selectBySysUserId(sysUserId); + } + + /** + * 取消三方登录相关信息 + * @param sysUserId 系统用户id + * @param sourceClientList 来源具体平台 + * @return + */ + @Override + public int cancelBind(Long sysUserId, List sourceClientList) { + return socialUserMapper.deleteBySysUserIdAndSourceClient(sysUserId, sourceClientList); + } + + /** + * 取消三方登录相关信息 + * @param sysUserIds 系统用户id集合 + * @param sourceClientList 来源具体平台 + * @return + */ + @Override + public int batchCancelBind(Long[] sysUserIds, List sourceClientList) { + return socialUserMapper.deleteBySysUserIdsAndSourceClient(sysUserIds, sourceClientList); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelServiceImpl.java new file mode 100644 index 00000000..00d14821 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelServiceImpl.java @@ -0,0 +1,556 @@ +package com.fastbee.iot.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.iot.response.IdentityAndName; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.core.redis.RedisKeyBuilder; +import com.fastbee.common.enums.ThingsModelType; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.iot.domain.Product; +import com.fastbee.iot.domain.ThingsModel; +import com.fastbee.iot.domain.ThingsModelTemplate; +import com.fastbee.iot.mapper.ProductMapper; +import com.fastbee.iot.mapper.ThingsModelMapper; +import com.fastbee.iot.mapper.ThingsModelTemplateMapper; +import com.fastbee.iot.model.ImportThingsModelInput; +import com.fastbee.iot.model.ThingsModelPerm; +import com.fastbee.iot.model.ThingsModels.PropertyDto; +import com.fastbee.iot.model.varTemp.EnumClass; +import com.fastbee.iot.service.IThingsModelService; +import com.fastbee.iot.service.IThingsModelTemplateService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; + +/** + * 物模型Service业务层处理 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Service +@Slf4j +public class ThingsModelServiceImpl implements IThingsModelService { + + @Autowired + private ThingsModelMapper thingsModelMapper; + + @Autowired + private ThingsModelTemplateMapper thingsModelTemplateMapper; + + @Autowired + private ProductMapper productMapper; + + @Autowired + private RedisCache redisCache; + @Resource + private IThingsModelTemplateService modelTemplateService; + + /** + * 查询物模型 + * + * @param modelId 物模型主键 + * @return 物模型 + */ + @Override + public ThingsModel selectThingsModelByModelId(Long modelId) + { + return thingsModelMapper.selectThingsModelByModelId(modelId); + } + + /** + * 查询单个物模型 + * @param model 物模型 + * @return 单个物模型 + */ + @Override + public ThingsModel selectSingleThingsModel(ThingsModel model){ + return thingsModelMapper.selectSingleThingsModel(model); + } + + /** + * 查询物模型列表 + * + * @param thingsModel 物模型 + * @return 物模型 + */ + @Override + public List selectThingsModelList(ThingsModel thingsModel) + { + return thingsModelMapper.selectThingsModelList(thingsModel); + } + + /** + * 查询物模型对应分享设备用户权限列表 + * + * @param productId 产品编号 + * @return 物模型 + */ + @Override + public List selectThingsModelPermList(Long productId) + { + return thingsModelMapper.selectThingsModelPermList(productId); + } + + /** + * 查询物模型列表-轮询使用 + * + * @param thingsModel 物模型 + * @return 物模型集合 + */ + @Override + public Map> selectThingsModelListCache(ThingsModel thingsModel){ + String cacheThingsModel = this.getCacheThingsModelByProductId(thingsModel.getProductId()); + if (StringUtils.isEmpty(cacheThingsModel)){ + List results = thingsModelMapper.selectThingsModelListCache(thingsModel); + return results.stream().collect(Collectors.groupingBy(IdentityAndName::getTempSlaveId)); + + } + Map cacheMap = redisCache.getCacheMap(RedisKeyBuilder.buildTSLCacheKey(thingsModel.getProductId())); + return cacheMap.values().stream().map(v -> JSON.parseObject(v.toString(), IdentityAndName.class)) + .filter(item -> StringUtils.isNotEmpty(item.getId())) + .sorted((o1,o2) -> { + Integer num1 = Integer.parseInt(o1.getId()); + Integer num2 = Integer.parseInt(o2.getId()); + return num1.compareTo(num2); + }) + .collect(Collectors.groupingBy(IdentityAndName::getTempSlaveId)); + } + + /** + * 新增物模型 + * + * @param thingsModel 物模型 + * @return 结果 + */ + @Override + public int insertThingsModel(ThingsModel thingsModel) + { + // 物模型标识符不能重复 TODO 重复查询待优化 + ThingsModel input=new ThingsModel(); + input.setProductId(thingsModel.getProductId()); + List list=thingsModelMapper.selectThingsModelList(input); + Boolean isRepeat=list.stream().anyMatch(x->x.getIdentifier().equals(thingsModel.getIdentifier())); + if(!isRepeat) { + SysUser user = getLoginUser().getUser(); + thingsModel.setTenantId(user.getUserId()); + thingsModel.setTenantName(user.getUserName()); + thingsModel.setCreateTime(DateUtils.getNowDate()); + int result = thingsModelMapper.insertThingsModel(thingsModel); + // 更新redis缓存 + setCacheThingsModelByProductId(thingsModel.getProductId()); + return result; + } + return 2; + } + + /** + * 导入通用物模型 + * @param input + * @return + */ + @Override + public int importByTemplateIds(ImportThingsModelInput input){ + // 物模型标识符不能重复 TODO 重复查询待优化 + ThingsModel inputParameter=new ThingsModel(); + inputParameter.setProductId(input.getProductId()); + List dbList=thingsModelMapper.selectThingsModelList(inputParameter); + + SysUser user = getLoginUser().getUser(); + // 根据ID集合获取通用物模型列表 + List templateList=thingsModelTemplateMapper.selectThingsModelTemplateByTemplateIds(input.getTemplateIds()); + //转换为产品物模型,并批量插入 + List list=new ArrayList<>(); + int repeatCount=0; + for(ThingsModelTemplate template : templateList){ + ThingsModel thingsModel= new ThingsModel(); + BeanUtils.copyProperties(template,thingsModel); + thingsModel.setTenantId(user.getUserId()); + thingsModel.setTenantName(user.getUserName()); + thingsModel.setCreateTime(DateUtils.getNowDate()); + thingsModel.setProductId(input.getProductId()); + thingsModel.setProductName(input.getProductName()); + thingsModel.setModelId(template.getTemplateId()); + thingsModel.setModelName(template.getTemplateName()); + thingsModel.setIsReadonly(template.getIsReadonly()); + thingsModel.setIsMonitor(template.getIsMonitor()); + thingsModel.setIsChart(template.getIsChart()); + thingsModel.setIsSharePerm(template.getIsSharePerm()); + thingsModel.setIsHistory(template.getIsHistory()); + thingsModel.setModelOrder(template.getModelOrder()); + //兼容modbsu + if (StringUtils.isNotEmpty(template.getTempSlaveId())){ + thingsModel.setTempSlaveId(Integer.parseInt(template.getTempSlaveId().split("#")[1])); + } + Boolean isRepeat=dbList.stream().anyMatch(x->x.getIdentifier().equals(thingsModel.getIdentifier())); + if(isRepeat){ + repeatCount=repeatCount+1; + }else{ + list.add(thingsModel); + } + } + if(list.size()>0) { + thingsModelMapper.insertBatchThingsModel(list); + //更新redis缓存 + setCacheThingsModelByProductId(input.getProductId()); + } + return repeatCount; + } + + /** + * 修改物模型 + * + * @param thingsModel 物模型 + * @return 结果 + */ + @Override + public int updateThingsModel(ThingsModel thingsModel) + { + // 物模型标识符不能重复 TODO 重复查询待优化 + ThingsModel input=new ThingsModel(); + input.setProductId(thingsModel.getProductId()); + List list=thingsModelMapper.selectThingsModelList(input); + Boolean isRepeat=list.stream().anyMatch(x->x.getIdentifier().equals(thingsModel.getIdentifier()) && x.getModelId().longValue()!=thingsModel.getModelId()); + if(!isRepeat) { + thingsModel.setUpdateTime(DateUtils.getNowDate()); + int result = thingsModelMapper.updateThingsModel(thingsModel); + // 更新redis缓存 + setCacheThingsModelByProductId(thingsModel.getProductId()); + return result; + } + return 2; + } + + /** + * 批量删除物模型 + * + * @param modelIds 需要删除的物模型主键 + * @return 结果 + */ + @Override + public int deleteThingsModelByModelIds(Long[] modelIds) + { + ThingsModel thingsModel=thingsModelMapper.selectThingsModelByModelId(modelIds[0]); + int result=thingsModelMapper.deleteThingsModelByModelIds(modelIds); + // 更新redis缓存 + setCacheThingsModelByProductId(thingsModel.getProductId()); + + return result; + } + + /** + * 删除物模型信息 + * + * @param modelId 物模型主键 + * @return 结果 + */ + @Override + public int deleteThingsModelByModelId(Long modelId) + { + ThingsModel thingsModel=thingsModelMapper.selectThingsModelByModelId(modelId); + int result=thingsModelMapper.deleteThingsModelByModelId(modelId); + // 更新redis缓存 + setCacheThingsModelByProductId(thingsModel.getProductId()); + return result; + } + + /** + * 根据产品ID获取JSON物模型 + * + * @param productId + * @return + */ + @Override + public String getCacheThingsModelByProductId(Long productId){ + if (productId == null) { + throw new ServiceException("产品id为空"); + } + // redis获取物模型 + Map map = redisCache.getCacheMap(RedisKeyBuilder.buildTSLCacheKey(productId)); + if (!CollectionUtils.isEmpty(map)) { + //兼容原页面物模型的数据格式 + Map> listMap = map.values().stream().map(v -> JSON.parseObject(v.toString(), PropertyDto.class)) + .collect(Collectors.groupingBy(dto -> ThingsModelType.getName(dto.getType()))); + return JSON.toJSONString(listMap); + } + return setCacheThingsModelByProductId(productId); + } + + + /** + * 获取单个物模型获取 + * @param productId + * @param identify + * @return + */ + @Override + public PropertyDto getSingleThingModels(Long productId, String identify){ + PropertyDto dto = new PropertyDto(); + String cacheKey = RedisKeyBuilder.buildTSLCacheKey(productId); + String value = redisCache.getCacheMapValue(cacheKey, identify); + //缓存没有则先查询数据库 + if (StringUtils.isEmpty(value)){ + ThingsModel thingsModel = new ThingsModel(); + if (identify.contains("#")){ + String[] split = identify.split("#"); + identify = split[0]; + thingsModel.setTempSlaveId(Integer.parseInt(split[1])); + } + thingsModel.setIdentifier(identify); + thingsModel.setProductId(productId); + ThingsModel selectModel = this.selectSingleThingsModel(thingsModel); + // redis和数据库都没有则是对象或数组类型。 兼容对象类型和数组类型 + if (StringUtils.isNull(dto.getId()) && identify.contains("_")){ + String[] split = identify.split("_"); + String thingsM = redisCache.getCacheMapValue(cacheKey, split[0]); + PropertyDto subDto = JSON.parseObject(thingsM, PropertyDto.class); + JSONArray array = JSON.parseArray(String.valueOf(subDto.getDatatype().get("params"))); + String finalIdentify = identify; + PropertyDto propertyDto = array.toJavaList(PropertyDto.class).stream() + .filter(x -> x.getId().equals(finalIdentify)) + .findFirst().get(); + propertyDto.setType(subDto.getType()); + return propertyDto; + } + if (null == selectModel) { + return dto; + } + BeanUtils.copyProperties(selectModel,dto); + dto.setId(selectModel.getIdentifier()); + dto.setName(selectModel.getModelName()); + dto.setIsParams(selectModel.getIsParams()); + dto.setDatatype(JSON.parseObject(selectModel.getSpecs())); + dto.setOrder(selectModel.getModelOrder()); + dto.setFormula(selectModel.getFormula()); + dto.setTempSlaveId(selectModel.getTempSlaveId()); + this.setCacheThingsModelByProductId(productId); + }else { + dto = JSON.parseObject(value, PropertyDto.class); + } + return dto; + } + + + /** + * 根据产品ID更新JSON物模型 + * @param productId + * @return + */ + private String setCacheThingsModelByProductId(Long productId){ + // 数据库查询物模型集合 + ThingsModel model=new ThingsModel(); + model.setProductId(productId); + List thingsModels=thingsModelMapper.selectThingsModelList(model); + /*这里key 1.非modbus为 identify 2. 是modbus设备时使用 identify#设备编号*/ + Map things = thingsModels.stream().collect(Collectors.toMap(key -> { + return key.getIdentifier() + (key.getTempSlaveId() == null ? "" : "#" + key.getTempSlaveId()); + }, + value -> { + //转换数据,减少不必要数据 + PropertyDto dto = new PropertyDto(); + BeanUtils.copyProperties(value,dto); + dto.setDatatype(JSONObject.parseObject(value.getSpecs())); + dto.setId(value.getIdentifier()); + dto.setRegId(value.getIdentifier()+ (value.getTempSlaveId() == null ? "" : "#" +value.getTempSlaveId())); + dto.setName(value.getModelName()); + dto.setOrder(value.getModelOrder()); + dto.setQuantity(value.getQuantity()); + dto.setCode(value.getCode()); + return JSONObject.toJSONString(dto); + })); + + /*缓存到redis*/ + String cacheKey = RedisKeyBuilder.buildTSLCacheKey(productId); + //先删除缓存 + if ( redisCache.containsKey(cacheKey)){ + redisCache.deleteObject(cacheKey); + } + redisCache.hashPutAll(cacheKey, things); + /*组装成原格式数据*/ + Map> result = things.values().stream().map(x -> JSON.parseObject(x, PropertyDto.class)) + .collect(Collectors.groupingBy(dto -> ThingsModelType.getName(dto.getType()))); + String jsonString = JSON.toJSONString(result); + Product product = new Product(); + product.setProductId(productId); + product.setThingsModelsJson(jsonString); + productMapper.updateThingsModelJson(product); + return jsonString; + } + + /** + * 批量查询产品的缓存物模型 --方法未使用,物模型结构暂不更改 + * + * @param productIds + * @return + */ + @Override + public Map getBatchCacheThingsModelByProductIds(Long[] productIds) { + // 批量查询hkey和value + Set set = new HashSet<>(); + for (int i = 0; i < productIds.length; i++) { + set.add(RedisKeyBuilder.buildTSLCacheKey(productIds[i])); + } + Map map = redisCache.getStringAllByKeys(set); + // 如果redis键和设备不匹配,添加redis缓存 + if (map == null || map.size() != productIds.length) { + for (int i = 0; i < productIds.length; i++) { + String key = RedisKeyBuilder.buildTSLCacheKey(productIds[i]); + if (map.get(key) != null) { + continue; + } else { + map.put(key, setCacheThingsModelByProductId(productIds[i])); + } + } + } + return map; + } + + /** + * 导入采集点数据 + * + * @param lists 数据列表 + * @param tempSlaveId 从机编码 + * @return 结果 + */ + public String importData(List lists, Integer tempSlaveId) { + if (null == tempSlaveId || CollectionUtils.isEmpty(lists)) { + throw new ServiceException("导入数据异常"); + } + int success = 0; + int failure = 0; + StringBuilder succSb = new StringBuilder(); + StringBuilder failSb = new StringBuilder(); + + for (ThingsModel model : lists) { + try { + //如果标识符为空,使用寄存器地址作为标识符 + if (null == model.getIdentifier() || "".equals(model.getIdentifier())) { + model.setIdentifier(model.getRegStr()); + } + /*16进制*/ + String reg = model.getRegStr(); + if (reg.endsWith("H")) { + String hex = reg.replace("H", ""); + int address = Integer.parseInt(hex, 16); + model.setRegAddr(address); + } + model.setTempSlaveId(tempSlaveId); + //处理数据定义 + this.parseSpecs(model); + this.insertThingsModel(model); + success++; + succSb.append("
").append(success).append(",采集点: ").append(model.getModelName()); + } catch (Exception e) { + log.error("导入错误:", e); + failure++; + failSb.append("
").append(failure).append(",采集点: ").append(model.getModelName()).append("导入失败"); + } + } + if (failure > 0) { + throw new ServiceException(failSb.toString()); + } + return succSb.toString(); + } + + private void parseSpecs(ThingsModel model) { + JSONObject specs = new JSONObject(); + String datatype = model.getDatatype(); + String limitValue = model.getLimitValue(); + if (limitValue != null && !"".equals(limitValue)) { + String[] values = limitValue.split("/"); + switch (datatype) { + case "integer": + specs.put("max", values[1]); + specs.put("min", values[0]); + specs.put("type", datatype); + specs.put("unit", model.getUnit()); + specs.put("step", 0); + break; + case "bool": + specs.put("type", datatype); + specs.put("trueText", values[1]); + specs.put("falseText", values[0]); + break; + case "enum": + List list = new ArrayList<>(); + for (String value : values) { + String[] params = value.split(":"); + EnumClass enumCls = new EnumClass(); + enumCls.setText(params[1]); + enumCls.setValue(params[0]); + list.add(enumCls); + } + specs.put("type", datatype); + specs.put("enumList", list); + break; + } + model.setSpecs(specs.toJSONString()); + } + } + + /** + * 根据模板id查询从机采集点列表 + * + * @return 变量模板从机采集点集合 + */ + @Override + public List selectAllByTemplateId(Long templateId) { + return thingsModelMapper.selectAllByTemplateId(templateId); + } + + /** + * 根据产品id删除 产品物模型以及物模型缓存 + * @param productId + */ + @Override + public void deleteProductThingsModelAndCacheByProductId(Long productId){ + thingsModelMapper.deleteThingsModelByProductId(productId); + String cacheKey = RedisKeyBuilder.buildTSLCacheKey(productId); + redisCache.deleteObject(cacheKey); + } + + /** + * 同步采集点模板 + * @param productIds 产品id列表 + * @param templateId 采集点模板id + */ + @Override + public void synchronizeVarTempToProduct(List productIds,Long templateId){ + assert templateId != null : "采集点模板id不能为空"; + assert !CollectionUtils.isEmpty(productIds): "同步的产品不能为空"; + //查询产品以及产品对应的物模型,并删除 + for (Long productId : productIds) { + Product product = productMapper.selectProductByProductId(productId); + assert product != null : "同步的产品不存在:" + productId; + //删除产品的物模型以及物模型缓存 + this.deleteProductThingsModelAndCacheByProductId(productId); + //获取最新的采集点模板数据,并更新到产品以及产品缓存 + List thingsModelTemplates = modelTemplateService.selectAllByTemplateId(templateId); + Long[] ids = thingsModelTemplates.stream().map(ThingsModelTemplate::getTemplateId).toArray(Long[]::new); + ImportThingsModelInput input = new ImportThingsModelInput(); + input.setTemplateIds(ids); + input.setProductId(product.getProductId()); + input.setProductName(product.getProductName()); + this.importByTemplateIds(input); + /*更新到产品缓存*/ + setCacheThingsModelByProductId(productId); + //清除缓存轮询指令 + String cacheKey = RedisKeyBuilder.buildModbusCacheKey(productId); + redisCache.deleteObject(cacheKey); + } + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelTemplateServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelTemplateServiceImpl.java new file mode 100644 index 00000000..d82342f7 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ThingsModelTemplateServiceImpl.java @@ -0,0 +1,261 @@ +package com.fastbee.iot.service.impl; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.core.domain.entity.SysRole; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.iot.domain.ThingsModel; +import com.fastbee.iot.model.varTemp.EnumClass; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.fastbee.iot.mapper.ThingsModelTemplateMapper; +import com.fastbee.iot.domain.ThingsModelTemplate; +import com.fastbee.iot.service.IThingsModelTemplateService; +import org.springframework.util.CollectionUtils; + +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; + +/** + * 通用物模型Service业务层处理 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Service +@Slf4j +public class ThingsModelTemplateServiceImpl implements IThingsModelTemplateService +{ + @Autowired + private ThingsModelTemplateMapper thingsModelTemplateMapper; + + /** + * 查询通用物模型 + * + * @param templateId 通用物模型主键 + * @return 通用物模型 + */ + @Override + public ThingsModelTemplate selectThingsModelTemplateByTemplateId(Long templateId) + { + return thingsModelTemplateMapper.selectThingsModelTemplateByTemplateId(templateId); + } + + /** + * 查询通用物模型列表 + * + * @param thingsModelTemplate 通用物模型 + * @return 通用物模型 + */ + @Override + public List selectThingsModelTemplateList(ThingsModelTemplate thingsModelTemplate) + { + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + // 租户 + if(roles.stream().anyMatch(a->a.getRoleKey().equals("tenant"))){ + thingsModelTemplate.setTenantId(user.getUserId()); + } + return thingsModelTemplateMapper.selectThingsModelTemplateList(thingsModelTemplate); + } + + /** + * 新增通用物模型 + * + * @param template 通用物模型 + * @return 结果 + */ + @Override + public int insertThingsModelTemplate(ThingsModelTemplate template) + { + try { + // 判断是否为管理员 + template.setIsSys(1); + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + for(int i=0;i selectAllByTemplateId(Long templateId) { + return thingsModelTemplateMapper.selectAllByTemplateId(templateId); + } + + /** + * 导入采集点数据 + * + * @param lists 数据列表 + * @param tempSlaveId 从机编码 + * @return 结果 + */ + public String importData(List lists, String tempSlaveId) { + if (null == tempSlaveId || CollectionUtils.isEmpty(lists)) { + throw new ServiceException("导入数据异常"); + } + int success = 0; + int failure = 0; + StringBuilder succSb = new StringBuilder(); + StringBuilder failSb = new StringBuilder(); + + for (ThingsModelTemplate model : lists) { + try { + //处理数据定义 + this.parseSpecs(model); + model.setTempSlaveId(tempSlaveId); + this.insertThingsModelTemplate(model); + success++; + succSb.append("
").append(success).append(",采集点: ").append(model.getTemplateName()); + } catch (Exception e) { + log.error("导入错误:", e); + failure++; + failSb.append("
").append(failure).append(",采集点: ").append(model.getTemplateName()).append("导入失败"); + } + } + if (failure > 0) { + throw new ServiceException(failSb.toString()); + } + return succSb.toString(); + } + + private void parseSpecs(ThingsModelTemplate model) { + JSONObject specs = new JSONObject(); + String datatype = model.getDatatype(); + String limitValue = model.getLimitValue(); + if (limitValue != null && !"".equals(limitValue)) { + String[] values = limitValue.trim().split("/"); + switch (datatype) { + case "integer": + specs.put("max", new BigDecimal(values[1])); + specs.put("min", new BigDecimal(values[0])); + specs.put("type", datatype); + specs.put("unit", model.getUnit()); + specs.put("step", 0); + break; + case "bool": + specs.put("type",datatype); + specs.put("trueText",values[1]); + specs.put("falseText",values[0]); + break; + case "enum": + List list = new ArrayList<>(); + for (String value : values) { + String[] params = value.trim().split(":"); + EnumClass enumCls = new EnumClass(); + enumCls.setText(params[1]); + enumCls.setValue(params[0]); + list.add(enumCls); + } + specs.put("type",datatype); + specs.put("enumList",list); + break; + } + model.setSpecs(specs.toJSONString()); + } + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ToolServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ToolServiceImpl.java new file mode 100644 index 00000000..a505573b --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/ToolServiceImpl.java @@ -0,0 +1,536 @@ +package com.fastbee.iot.service.impl; + +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.annotation.DataScope; +import com.fastbee.common.constant.CacheConstants; +import com.fastbee.common.constant.Constants; +import com.fastbee.common.constant.UserConstants; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.exception.user.CaptchaException; +import com.fastbee.common.exception.user.CaptchaExpireException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.MessageUtils; +import com.fastbee.common.utils.SecurityUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.framework.manager.AsyncManager; +import com.fastbee.framework.manager.factory.AsyncFactory; +import com.fastbee.iot.domain.ProductAuthorize; +import com.fastbee.iot.mapper.ProductAuthorizeMapper; +import com.fastbee.iot.model.*; +import com.fastbee.iot.service.IDeviceService; +import com.fastbee.iot.service.IToolService; +import com.fastbee.iot.util.AESUtils; +import com.fastbee.system.mapper.SysUserMapper; +import com.fastbee.system.service.ISysConfigService; +import com.fastbee.system.service.ISysUserService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Random; + +/** + * + * @author kerwincui + * @date 2021-12-16 + */ +@Service +public class ToolServiceImpl implements IToolService +{ + private static final Logger log = LoggerFactory.getLogger(ToolServiceImpl.class); + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysConfigService configService; + + @Autowired + private ISysUserService userService; + + @Autowired + private SysUserMapper userMapper; + + @Autowired + private ProductAuthorizeMapper productAuthorizeMapper; + @Autowired + @Lazy + private IDeviceService deviceService; + + /** + * 生成随机数字和字母 + */ + @Override + public String getStringRandom(int length) { + String val = ""; + Random random = new Random(); + //参数length,表示生成几位随机数 + for(int i = 0; i < length; i++) { + String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num"; + //输出字母还是数字 + if( "char".equalsIgnoreCase(charOrNum) ) { + //输出是大写字母还是小写字母 + // int temp = random.nextInt(2) % 2 == 0 ? 65 : 97; + val += (char)(random.nextInt(26) + 65); + } else if( "num".equalsIgnoreCase(charOrNum) ) { + val += String.valueOf(random.nextInt(10)); + } + } + return val; + } + + /** + * 生成任意长度 HEX格式字符串 + * @param length + * @return + */ + public 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(); + } + + + /** + * 注册 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public String register(RegisterUserInput registerBody) + { + String msg = ""; + String username = registerBody.getUsername(); + String password = registerBody.getPassword(); + String phonenumber=registerBody.getPhonenumber(); + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + + boolean captchaEnabled = configService.selectCaptchaEnabled(); + // 验证码开关 + if (captchaEnabled) + { + validateCaptcha(username, registerBody.getCode(), registerBody.getUuid()); + } + + if (StringUtils.isEmpty(username)) + { + msg = "用户名不能为空"; + } + else if (StringUtils.isEmpty(password)) + { + msg = "用户密码不能为空"; + } + else if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + msg = "账户长度必须在2到20个字符之间"; + } + else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + msg = "密码长度必须在5到20个字符之间"; + } + else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(sysUser))) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + }else if (UserConstants.NOT_UNIQUE.equals(checkPhoneUnique(phonenumber))) + { + msg = "保存用户'" + username + "'失败,注册手机号码已存在"; + } + else + { + sysUser.setNickName(username); + sysUser.setPhonenumber(phonenumber); + sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword())); + boolean regFlag = userService.registerUser(sysUser); + //分配普通用户角色(1=超级管理员,2=设备租户,3=普通用户,4=游客) + Long[] roleIds={3L}; + userService.insertUserAuth(sysUser.getUserId(),roleIds); + if (!regFlag) + { + msg = "注册失败,请联系系统管理人员"; + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, + MessageUtils.message("user.register.success"))); + } + } + return msg; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public RegisterUserOutput registerNoCaptcha(RegisterUserInput registerBody) + { + RegisterUserOutput registerUserOutput = new RegisterUserOutput(); + String msg = ""; + String username = registerBody.getUsername(); + String password = registerBody.getPassword(); + String phonenumber=registerBody.getPhonenumber(); + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + + if (StringUtils.isEmpty(username)) + { + msg = "用户名不能为空"; + } + else if (StringUtils.isEmpty(password)) + { + msg = "用户密码不能为空"; + } + else if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + msg = "账户长度必须在2到20个字符之间"; + } + else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + msg = "密码长度必须在5到20个字符之间"; + } + else if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(sysUser))) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + }else if (UserConstants.NOT_UNIQUE.equals(checkPhoneUnique(phonenumber))) + { + msg = "保存用户'" + username + "'失败,注册手机号码已存在"; + } + else + { + sysUser.setNickName(username); + sysUser.setPhonenumber(phonenumber); + sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword())); + boolean regFlag = userService.registerUser(sysUser); + //分配普通用户角色(1=超级管理员,2=设备租户,3=普通用户,4=游客) + Long[] roleIds={3L}; + userService.insertUserAuth(sysUser.getUserId(),roleIds); + registerUserOutput.setSysUserId(sysUser.getUserId()); + if (!regFlag) + { + msg = "注册失败,请联系系统管理人员"; + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, + MessageUtils.message("user.register.success"))); + } + } + registerUserOutput.setMsg(msg); + return registerUserOutput; + } + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + public List selectUserList(SysUser user) + { + return userMapper.selectUserList(user); + } + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return + */ + public String checkPhoneUnique(String phonenumber) + { + SysUser info = userMapper.checkPhoneUnique(phonenumber); + if (StringUtils.isNotNull(info)) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + redisCache.deleteObject(verifyKey); + if (captcha == null) + { + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + throw new CaptchaException(); + } + } + + /** + * 设备简单认证 + */ + @Override + public ResponseEntity simpleMqttAuthentication(MqttAuthenticationModel mqttModel, ProductAuthenticateModel productModel) { + // 1=简单认证,2=加密认证,3=简单+加密认证 + if(productModel.getVertificateMethod()!=1 && productModel.getVertificateMethod()!=3){ + return returnUnauthorized(mqttModel, "设备简单认证,设备对应产品不支持简单认证"); + } + String[] passwordArray = mqttModel.getPassword().split("&"); + if (productModel.getIsAuthorize() == 1 && passwordArray.length != 2) { + return returnUnauthorized(mqttModel, "设备简单认证,产品启用授权码后,密码格式为:密码 & 授权码"); + } + String mqttPassword = passwordArray[0]; + String authCode = passwordArray.length == 2 ? passwordArray[1] : ""; + // 验证用户名 + if (!mqttModel.getUserName().equals(productModel.getMqttAccount())) { + return returnUnauthorized(mqttModel, "设备简单认证,设备mqtt用户名错误"); + } + // 验证密码 + if (!mqttPassword.equals(productModel.getMqttPassword())) { + return returnUnauthorized(mqttModel, "设备简单认证,设备mqtt密码错误"); + } + // 验证授权码 + if (productModel.getIsAuthorize() == 1) { + // 授权码验证和处理 + String resultMessage = authCodeProcess(authCode, mqttModel, productModel); + if (!resultMessage.equals("")) { + return returnUnauthorized(mqttModel, resultMessage); + } + } + if (productModel.getDeviceId() != null && productModel.getDeviceId() != 0) { + if (productModel.getStatus() == 2) { + return returnUnauthorized(mqttModel, "设备简单认证,设备处于禁用状态"); + } + log.info("-----------设备简单认证成功,clientId:" + mqttModel.getClientId() + "---------------"); + return ResponseEntity.ok().body("ok"); + } else { + // 自动添加设备 + int result = deviceService.insertDeviceAuto(mqttModel.getDeviceNumber(), mqttModel.getUserId(), mqttModel.getProductId()); + if (result == 1) { + log.info("-----------设备简单认证成功,并自动添加设备到系统,clientId:" + mqttModel.getClientId() + "---------------"); + return ResponseEntity.ok().body("ok"); + } + return returnUnauthorized(mqttModel, "设备简单认证,自动添加设备失败"); + } + } + + /** + * 设备加密认证 + * + * @return + */ + @Override + public ResponseEntity encryptAuthentication(MqttAuthenticationModel mqttModel, ProductAuthenticateModel productModel) throws Exception { + // 1=简单认证,2=加密认证,3=简单+加密认证 + if(productModel.getVertificateMethod()!=2 && productModel.getVertificateMethod()!=3){ + return returnUnauthorized(mqttModel, "设备加密认证,设备对应产品不支持加密认证"); + } + String decryptPassword = AESUtils.decrypt(mqttModel.getPassword(), productModel.getMqttSecret()); + if (decryptPassword == null || decryptPassword.equals("")) { + return returnUnauthorized(mqttModel, "设备加密认证,mqtt密码解密失败"); + } + String[] passwordArray = decryptPassword.split("&"); + if (passwordArray.length != 2 && passwordArray.length != 3) { + // 密码加密格式 password & expireTime (& authCode 可选) + return returnUnauthorized(mqttModel, "设备加密认证,mqtt密码加密格式为:密码 & 过期时间 & 授权码,其中授权码为可选"); + } + String mqttPassword = passwordArray[0]; + Long expireTime = Long.valueOf(passwordArray[1]); + String authCode = passwordArray.length == 3 ? passwordArray[2] : ""; + // 验证用户名 + if (!mqttModel.getUserName().equals(productModel.getMqttAccount())) { + return returnUnauthorized(mqttModel, "设备加密认证,设备mqtt用户名错误"); + } + // 验证密码 + if (!mqttPassword.equals(productModel.getMqttPassword())) { + return returnUnauthorized(mqttModel, "设备加密认证,设备mqtt密码错误"); + } + // 验证过期时间 + if (expireTime < System.currentTimeMillis()) { + return returnUnauthorized(mqttModel, "设备加密认证,设备mqtt密码已过期"); + } + // 验证授权码 + if (productModel.getIsAuthorize() == 1) { + // 授权码验证和处理 + String resultMessage = authCodeProcess(authCode, mqttModel, productModel); + if (!resultMessage.equals("")) { + return returnUnauthorized(mqttModel, resultMessage); + } + } + // 设备状态验证 (1-未激活,2-禁用,3-在线,4-离线) + if (productModel.getDeviceId() != null && productModel.getDeviceId() != 0) { + if (productModel.getStatus() == 2) { + return returnUnauthorized(mqttModel, "设备加密认证,设备处于禁用状态"); + } + log.info("-----------设备加密认证成功,clientId:" + mqttModel.getClientId() + "---------------"); + return ResponseEntity.ok().body("ok"); + } else { + // 自动添加设备 + int result = deviceService.insertDeviceAuto(mqttModel.getDeviceNumber(), mqttModel.getUserId(), mqttModel.getProductId()); + if (result == 1) { + log.info("-----------设备加密认证成功,并自动添加设备到系统,clientId:" + mqttModel.getClientId() + "---------------"); + return ResponseEntity.ok().body("ok"); + } + return returnUnauthorized(mqttModel, "设备加密认证,自动添加设备失败"); + } + } + + /** + * 整合设备认证接口 + */ + @Override + public ResponseEntity clientAuth(String clientid,String username,String password) throws Exception { + // 设备端认证:加密认证(E)和简单认证(S,配置的账号密码认证) + String[] clientArray = clientid.split("&"); + if(clientArray.length != 4 || clientArray[0].equals("") || clientArray[1].equals("") || clientArray[2].equals("") || clientArray[3].equals("")){ + return this.returnUnauthorized(new MqttAuthenticationModel(clientid, username, password), "设备mqtt客户端Id格式为:认证类型 & 设备编号 & 产品ID & 用户ID"); + } + String authType = clientArray[0]; + String deviceNumber = clientArray[1]; + Long productId = Long.valueOf(clientArray[2]); + Long userId = Long.valueOf(clientArray[3]); + // 产品认证信息 + ProductAuthenticateModel model = deviceService.selectProductAuthenticate(new AuthenticateInputModel(deviceNumber, productId)); + if (model == null) { + return this.returnUnauthorized(new MqttAuthenticationModel(clientid, username, password), "设备认证,通过产品ID查询不到信息"); + } + if (model.getProductStatus() != 2) { + // 产品必须为发布状态:1-未发布,2-已发布 + return this.returnUnauthorized(new MqttAuthenticationModel(clientid, username, password), "设备认证,设备对应产品还未发布"); + } + + if (authType.equals("S")) { + // 设备简单认证 + return this.simpleMqttAuthentication(new MqttAuthenticationModel(clientid, username, password, deviceNumber, productId, userId), model); + + } else if (authType.equals("E")) { + // 设备加密认证 + return this.encryptAuthentication(new MqttAuthenticationModel(clientid, username, password, deviceNumber, productId, userId), model); + } else { + return this.returnUnauthorized(new MqttAuthenticationModel(clientid, username, password), "设备认证,认证类型有误"); + } + } + + @Override + public ResponseEntity clientAuthv5(String clientid,String username,String password) throws Exception { + JSONObject ret = new JSONObject(); + ret.put("is_superuser", false); + // 设备端认证:加密认证(E)和简单认证(S,配置的账号密码认证) + String[] clientArray = clientid.split("&"); + if(clientArray.length != 4 || clientArray[0].equals("") || clientArray[1].equals("") || clientArray[2].equals("") || clientArray[3].equals("")){ + return this.returnUnauthorized(new MqttAuthenticationModel(clientid, username, password), "设备mqtt客户端Id格式为:认证类型 & 设备编号 & 产品ID & 用户ID"); + } + String authType = clientArray[0]; + String deviceNumber = clientArray[1]; + Long productId = Long.valueOf(clientArray[2]); + Long userId = Long.valueOf(clientArray[3]); + // 产品认证信息 + ProductAuthenticateModel model = deviceService.selectProductAuthenticate(new AuthenticateInputModel(deviceNumber, productId)); + if (model == null) { + log.error("-----------设备认证,通过产品ID查询不到信息,clientId:" + clientid + "---------------"); + ret.put("result", "deny"); + return ResponseEntity.ok().body(ret); + } + if (model.getProductStatus() != 2) { + // 产品必须为发布状态:1-未发布,2-已发布 + log.error("-----------设备认证,设备对应产品还未发布,clientId:" + clientid + "---------------"); + ret.put("result", "deny"); + return ResponseEntity.ok().body(ret); + } + + if (authType.equals("S")) { + // 设备简单认证 + ResponseEntity res = this.simpleMqttAuthentication(new MqttAuthenticationModel(clientid, username, password, deviceNumber, productId, userId), model); + if (res.getStatusCodeValue() == HttpStatus.OK.value()){ + ret.put("result", "allow"); + return ResponseEntity.ok().body(ret); + } else { + ret.put("result", "deny"); + return ResponseEntity.ok().body(ret); + } + + } else if (authType.equals("E")) { + // 设备加密认证 + ResponseEntity res = this.encryptAuthentication(new MqttAuthenticationModel(clientid, username, password, deviceNumber, productId, userId), model); + if (res.getStatusCodeValue() == HttpStatus.OK.value()){ + ret.put("result", "allow"); + return ResponseEntity.ok().body(ret); + } else { + ret.put("result", "deny"); + return ResponseEntity.ok().body(ret); + } + } else { + log.error("-----------设备认证,认证类型有误,clientId:" + clientid + "---------------"); + ret.put("result", "deny"); + return ResponseEntity.ok().body(ret); + } + } + + /** + * 授权码认证和处理 + */ + private String authCodeProcess(String authCode, MqttAuthenticationModel mqttModel, ProductAuthenticateModel productModel) { + String message = ""; + if (authCode.equals("")) { + return message = "设备认证,设备授权码不能为空"; + } + // 查询授权码是否存在 + ProductAuthorize authorize = productAuthorizeMapper.selectFirstAuthorizeByAuthorizeCode(new ProductAuthorize(authCode, productModel.getProductId())); + if (authorize == null) { + message = "设备认证,设备授权码错误"; + return message; + } + if (authorize.getSerialNumber() != null && !authorize.getSerialNumber().equals("")) { + // 授权码已关联设备 + if (!authorize.getSerialNumber().equals( productModel.getSerialNumber())) { + message = "设备认证,设备授权码已经分配给其他设备"; + return message; + } + } else { + // 授权码未关联设备 + authorize.setSerialNumber(mqttModel.getDeviceNumber()); + authorize.setUserId(mqttModel.getUserId()); + authorize.setUserName(""); + authorize.setUpdateTime(DateUtils.getNowDate()); + // 状态(1-未使用,2-使用中) + authorize.setStatus(2); + int result = productAuthorizeMapper.updateProductAuthorize(authorize); + if (result != 1) { + message = "设备认证,设备授权码关联失败"; + return message; + } + } + return message; + } + + /** + * 返回认证信息 + */ + @Override + public ResponseEntity returnUnauthorized(MqttAuthenticationModel mqttModel, String message) { + log.warn("认证失败:" + message + + "\nclientid:" + mqttModel.getClientId() + + "\nusername:" + mqttModel.getUserName() + + "\npassword:" + mqttModel.getPassword()); + return ResponseEntity.status(401).body("Unauthorized"); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/UserSocialProfileServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/UserSocialProfileServiceImpl.java new file mode 100644 index 00000000..f23a5902 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/service/impl/UserSocialProfileServiceImpl.java @@ -0,0 +1,105 @@ +package com.fastbee.iot.service.impl; + +import com.fastbee.common.constant.HttpStatus; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.enums.SocialPlatformType; +import com.fastbee.iot.domain.SocialUser; +import com.fastbee.iot.domain.UserSocialProfile; +import com.fastbee.iot.model.login.BindIdValue; +import com.fastbee.iot.service.ISocialPlatformService; +import com.fastbee.iot.service.ISocialUserService; +import com.fastbee.iot.service.IUserSocialProfileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +import static com.fastbee.iot.service.impl.SocialLoginServiceImpl.BIND_REDIS_KEY; + +@Service +public class UserSocialProfileServiceImpl implements IUserSocialProfileService { + + @Autowired + private RedisCache redisCache; + @Autowired + private ISocialUserService iSocialUserService; + @Autowired + private ISocialPlatformService iSocialPlatformService; + + @Override + public List selectUserSocialProfile(Long sysUserId) { + SocialUser selectSocialUser = new SocialUser(); + selectSocialUser.setSysUserId(sysUserId); + List socialUserList = iSocialUserService.selectSocialUserList(selectSocialUser); + List userSocialProfileList = new ArrayList<>(); + for (SocialUser socialUser : socialUserList) { + //如果是删除的标记 + if (socialUser.getDelFlag().equals("1")) { + continue; + } + UserSocialProfile userSocialProfile = new UserSocialProfile(); + userSocialProfile.setSocialUserId(socialUser.getSocialUserId()); + userSocialProfile.setAvatar(socialUser.getAvatar()); + userSocialProfile.setSource(socialUser.getSource()); + userSocialProfile.setUsername(socialUser.getUsername()); + userSocialProfile.setNickname(socialUser.getNickname()); + userSocialProfile.setStatus(socialUser.getStatus()); + userSocialProfile.setSourceClient(socialUser.getSourceClient()); + userSocialProfileList.add(userSocialProfile); + } + return userSocialProfileList; + } + + @Override + public AjaxResult bindUser(String bindId, Long sysUserId) { + BindIdValue bindValue = redisCache.getCacheObject(BIND_REDIS_KEY + bindId); + if (bindValue == null) { + //不作提示 + return AjaxResult.error(HttpStatus.NO_MESSAGE_ALERT, "未知异常"); + } + SocialUser socialUser = findSocialUser(bindValue.getUuid(), bindValue.getSource()); + SocialUser updateSocialUser = new SocialUser(); + updateSocialUser.setSocialUserId(socialUser.getSocialUserId()); + updateSocialUser.setSysUserId(sysUserId); + iSocialUserService.updateSocialUser(updateSocialUser); + redisCache.deleteObject(BIND_REDIS_KEY + bindId); + return AjaxResult.success("绑定成功!"); + } + + @Override + public AjaxResult bindSocialAccount(String platform) { + try { + SocialPlatformType.valueOf(platform); + } catch (Exception e) { + return AjaxResult.error("错误平台类型"); + } + return AjaxResult.success(); + } + + @Override + public AjaxResult unbindSocialAccount(Long socialUserId, Long sysUserId) { + SocialUser socialUser = iSocialUserService.selectSocialUserBySocialUserId(socialUserId); + if (socialUser == null) { + return AjaxResult.error("绑定账户不存在!"); + } else if (!socialUser.getSysUserId().equals(socialUserId)) { + return AjaxResult.error("用户账户和绑定账户不匹配!"); + } else { + SocialUser updateSocialUser = new SocialUser(); + updateSocialUser.setSocialUserId(socialUserId); + updateSocialUser.setSysUserId(-1L); + iSocialUserService.updateSocialUser(updateSocialUser); + return AjaxResult.success("解除绑定成功!"); + } + } + + public SocialUser findSocialUser(String uuid, String source) { + SocialUser socialUser = new SocialUser(); + socialUser.setSource(source); + socialUser.setUuid(uuid); + List socialUserList = iSocialUserService.selectSocialUserList(socialUser); + return socialUserList == null || socialUserList.isEmpty() ? null : socialUserList.get(0); + + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/ILogService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/ILogService.java new file mode 100644 index 00000000..05baa098 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/ILogService.java @@ -0,0 +1,35 @@ +package com.fastbee.iot.tdengine.service; + +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.domain.DeviceLog; + +import com.fastbee.iot.model.DeviceStatistic; +import com.fastbee.iot.model.HistoryModel; +import com.fastbee.iot.model.MonitorModel; +import com.fastbee.iot.tdengine.service.model.TdLogDto; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * @package iot.iot.log + * 类名: LogService + * 描述: 设备日志记录接口 + * 时间: 2022/5/19,0019 18:04 + * 开发人: admin + */ +public interface ILogService { + + /** 根据设备编号删除设备日志 **/ + int deleteDeviceLogByDeviceNumber(String deviceNumber); + + /** 设备属性、功能、事件总数 **/ + DeviceStatistic selectCategoryLogCount(Device device); + + /** 查询监测数据列表 **/ + List selectMonitorList(DeviceLog deviceLog); + + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/impl/MySqlLogServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/impl/MySqlLogServiceImpl.java new file mode 100644 index 00000000..80fe0bf8 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/impl/MySqlLogServiceImpl.java @@ -0,0 +1,55 @@ +package com.fastbee.iot.tdengine.service.impl; + +import com.fastbee.common.utils.DateUtils; +import com.fastbee.iot.domain.Device; +import com.fastbee.iot.domain.DeviceLog; +import com.fastbee.iot.model.DeviceStatistic; +import com.fastbee.iot.model.HistoryModel; +import com.fastbee.iot.tdengine.service.ILogService; +import com.fastbee.iot.mapper.DeviceLogMapper; +import com.fastbee.iot.model.MonitorModel; +import com.fastbee.iot.tdengine.service.model.TdLogDto; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +public class MySqlLogServiceImpl implements ILogService { + + private DeviceLogMapper deviceLogMapper; + + public MySqlLogServiceImpl(DeviceLogMapper _deviceLogMapper){ + this.deviceLogMapper=_deviceLogMapper; + } + + /*** + * 根据设备ID删除设备日志 + * @return + */ + @Override + public int deleteDeviceLogByDeviceNumber(String deviceNumber) { + return deviceLogMapper.deleteDeviceLogByDeviceNumber(deviceNumber); + } + + /*** + * 设备属性、功能、事件和监测数据总数 + * @return + */ + @Override + public DeviceStatistic selectCategoryLogCount(Device device){ + return deviceLogMapper.selectCategoryLogCount(device); + } + + + /*** + * 监测数据列表 + * @return + */ + @Override + public List selectMonitorList(DeviceLog deviceLog) { + return deviceLogMapper.selectMonitorList(deviceLog); + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/model/TdLogDto.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/model/TdLogDto.java new file mode 100644 index 00000000..54e1b91f --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/tdengine/service/model/TdLogDto.java @@ -0,0 +1,27 @@ +package com.fastbee.iot.tdengine.service.model; + +import com.fastbee.iot.domain.DeviceLog; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author gsb + * @date 2023/6/5 8:37 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TdLogDto { + + /** + * 设备编号 + */ + private String serialNumber; + /** + * 设备日志列表 + */ + private List list; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/util/AESUtils.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/util/AESUtils.java new file mode 100644 index 00000000..e42d99cb --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/util/AESUtils.java @@ -0,0 +1,120 @@ +package com.fastbee.iot.util; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import sun.misc.BASE64Decoder; +import sun.misc.BASE64Encoder; + +import java.security.MessageDigest; + +/** + * + */ +public class AESUtils { + + /** + * 加密用的Key 可以用26个字母和数字组成 使用AES-128-CBC加密模式,key需要为16位。iv 偏移量,长度16 + */ + private static final String ivString = "wumei-smart-open"; + private static final String ENCRYPT_MODE = "CBC"; // ECB和CBC两种模式 + + // 加密 + public static String encrypt(String plainText, String key) { + // 判断Key是否正确 + if (key == null || key.length() != 16) { + System.out.print("Key不能为空,长度不是16位"); + return null; + } + try { + byte[] raw = key.getBytes("utf-8"); + SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); + Cipher cipher = Cipher.getInstance("AES/" + AESUtils.ENCRYPT_MODE + "/PKCS5Padding"); + if (AESUtils.ENCRYPT_MODE.equals("ECB")) { + cipher.init(Cipher.ENCRYPT_MODE, skeySpec); + } else { + IvParameterSpec iv = new IvParameterSpec(ivString.getBytes("utf-8"));//使用CBC模式,需要一个向量iv,可增加加密算法的强度 + cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); + } + byte[] encrypted = cipher.doFinal(plainText.getBytes("utf-8")); + String encryptedStr = new String(new BASE64Encoder().encode(encrypted)); + //此处使用BASE64做转码功能,同时能起到2次加密的作用。 + return encryptedStr; + } catch (Exception ex) { + System.out.println(ex.toString()); + return null; + } + } + + // 解密 + public static String decrypt(String cipherText, String key) { + // 判断Key是否正确 + if (key == null || key.length() != 16) { + System.out.print("Key不能为空,长度不是16位"); + return null; + } + // 根据html规范,后端接口,接收参数包含+号会被替换为空格。所以这里需要还原回来,不然会造成解密失败 + cipherText=cipherText.replace(' ','+'); + + try { + byte[] raw = key.getBytes("utf-8"); + SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); + Cipher cipher = Cipher.getInstance("AES/" + AESUtils.ENCRYPT_MODE + "/PKCS5Padding"); + if (AESUtils.ENCRYPT_MODE.equals("ECB")) { + cipher.init(Cipher.DECRYPT_MODE, skeySpec); + } else { + //使用CBC模式,需要一个向量iv,可增加加密算法的强度 + IvParameterSpec iv = new IvParameterSpec(ivString.getBytes("utf-8")); + cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); + } + //先用base64解密 + byte[] encrypted = new BASE64Decoder().decodeBuffer(cipherText); + try { + byte[] original = cipher.doFinal(encrypted); + String originalString = new String(original, "utf-8"); + return originalString; + } catch (Exception e) { + System.out.println(e.toString()); + return null; + } + } catch (Exception ex) { + System.out.println(ex.toString()); + return null; + } + } + + /** + * 进行MD5加密 + * + * @param s 要进行MD5转换的字符串 + * @return 该字符串的MD5值的8-24位 + */ + public static String getMD5(String s) { + char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + try { + byte[] btInput = s.getBytes(); + // 获得MD5摘要算法的 MessageDigest 对象 + MessageDigest mdInst = MessageDigest.getInstance("MD5"); + // 使用指定的字节更新摘要 + mdInst.update(btInput); + // 获得密文 + byte[] md = mdInst.digest(); + // 把密文转换成十六进制的字符串形式 + int j = md.length; + char str[] = new char[j * 2]; + int k = 0; + for (int i = 0; i < j; i++) { + byte byte0 = md[i]; + str[k++] = hexDigits[byte0 >>> 4 & 0xf]; + str[k++] = hexDigits[byte0 & 0xf]; + } + return new String(str).substring(8, 24); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/util/SnowflakeIdWorker.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/util/SnowflakeIdWorker.java new file mode 100644 index 00000000..232cd4d4 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/util/SnowflakeIdWorker.java @@ -0,0 +1,104 @@ +package com.fastbee.iot.util; + +public class SnowflakeIdWorker { + /** + * 开始时间:2020-01-01 00:00:00 + */ + private final long beginTs = 1577808000000L; + + private final long workerIdBits = 10; + + /** + * 2^10 - 1 = 1023 + */ + private final long maxWorkerId = -1L ^ (-1L << workerIdBits); + + private final long sequenceBits = 12; + + /** + * 2^12 - 1 = 4095 + */ + private final long maxSequence = -1L ^ (-1L << sequenceBits); + + /** + * 时间戳左移22位 + */ + private final long timestampLeftOffset = workerIdBits + sequenceBits; + + /** + * 业务ID左移12位 + */ + private final long workerIdLeftOffset = sequenceBits; + + /** + * 合并了机器ID和数据标示ID,统称业务ID,10位 + */ + private long workerId; + + /** + * 毫秒内序列,12位,2^12 = 4096个数字 + */ + private long sequence = 0L; + + /** + * 上一次生成的ID的时间戳,同一个worker中 + */ + private long lastTimestamp = -1L; + + public SnowflakeIdWorker(long workerId) { + if (workerId > maxWorkerId || workerId < 0) { + throw new IllegalArgumentException(String.format("WorkerId必须大于或等于0且小于或等于%d", maxWorkerId)); + } + + this.workerId = workerId; + } + + public synchronized long nextId() { + long ts = System.currentTimeMillis(); + if (ts < lastTimestamp) { + throw new RuntimeException(String.format("系统时钟回退了%d毫秒", (lastTimestamp - ts))); + } + + // 同一时间内,则计算序列号 + if (ts == lastTimestamp) { + // 序列号溢出 + if (++sequence > maxSequence) { + ts = tilNextMillis(lastTimestamp); + sequence = 0L; + } + } else { + // 时间戳改变,重置序列号 + sequence = 0L; + } + + lastTimestamp = ts; + + // 0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000000 00 - 00000000 0000 + // 左移后,低位补0,进行按位或运算相当于二进制拼接 + // 本来高位还有个0<<63,0与任何数字按位或都是本身,所以写不写效果一样 + return (ts - beginTs) << timestampLeftOffset | workerId << workerIdLeftOffset | sequence; + } + + /** + * 阻塞到下一个毫秒 + * + * @param lastTimestamp + * @return + */ + private long tilNextMillis(long lastTimestamp) { + long ts = System.currentTimeMillis(); + while (ts <= lastTimestamp) { + ts = System.currentTimeMillis(); + } + + return ts; + } + + public static void main(String[] args) { + SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(1); + for (int i = 0; i < 10; i++) { + long id = snowflakeIdWorker.nextId(); + System.out.println(id); + } + } +} \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/util/VelocityInitializer.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/util/VelocityInitializer.java new file mode 100644 index 00000000..e21c3e4d --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/util/VelocityInitializer.java @@ -0,0 +1,35 @@ +package com.fastbee.iot.util; + +import com.fastbee.common.constant.Constants; +import org.apache.velocity.app.Velocity; + +import java.util.Properties; + +/** + * VelocityEngine工厂 + * + * @author ruoyi + */ +public class VelocityInitializer +{ + /** + * 初始化vm方法 + */ + public static void initVelocity() + { + Properties p = new Properties(); + try + { + // 加载classpath目录下的vm文件 + p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + // 定义字符集 + p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8); + // 初始化Velocity引擎,指定配置Properties + Velocity.init(p); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/util/VelocityUtils.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/util/VelocityUtils.java new file mode 100644 index 00000000..10a3c774 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/util/VelocityUtils.java @@ -0,0 +1,89 @@ +package com.fastbee.iot.util; + +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.constant.GenConstants; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; +import org.apache.velocity.VelocityContext; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +/** + * 模板处理工具类 + * + * @author ruoyi + */ +public class VelocityUtils +{ + /** 项目空间路径 */ + private static final String PROJECT_PATH = "main/java"; + + /** mybatis空间路径 */ + private static final String MYBATIS_PATH = "main/resources/mapper"; + + /** 默认上级菜单,系统工具 */ + private static final String DEFAULT_PARENT_MENU_ID = "3"; + + /** + * 设置模板变量信息 + * + * @return 模板列表 + */ + public static VelocityContext prepareContext(int deviceChip) + { + + VelocityContext velocityContext = new VelocityContext(); + return velocityContext; + } + + + /** + * 获取模板信息 + * + * @return 模板列表 + */ + public static List getTemplateList(String tplCategory) + { + List templates = new ArrayList(); + templates.add("vm/java/domain.java.vm"); + templates.add("vm/java/controller.java.vm"); + if (GenConstants.TPL_CRUD.equals(tplCategory)) + { + templates.add("vm/vue/index.vue.vm"); + } + else if (GenConstants.TPL_TREE.equals(tplCategory)) + { + templates.add("vm/vue/index-tree.vue.vm"); + } + else if (GenConstants.TPL_SUB.equals(tplCategory)) + { + templates.add("vm/vue/index.vue.vm"); + templates.add("vm/java/sub-domain.java.vm"); + } + return templates; + } + + /** + * 获取文件名 + */ + public static String getFileName(String template) + { + // 文件名称 + String fileName = ""; + String vuePath = "vue"; + + if (template.contains("domain.java.vm")) + { + fileName = StringUtils.format("{}/domain/{}.java", "test", "test"); + } else if (template.contains("controller.java.vm")) + { + fileName = StringUtils.format("{}/controller/{}Controller.java", "test", "test"); + } + return fileName; + } + + + +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/wechat/WeChatService.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/wechat/WeChatService.java new file mode 100644 index 00000000..ec3716f2 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/wechat/WeChatService.java @@ -0,0 +1,68 @@ +package com.fastbee.iot.wechat; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.wechat.WeChatLoginBody; +import com.fastbee.common.wechat.WeChatLoginResult; +import com.fastbee.iot.wechat.vo.WxBindReqVO; +import com.fastbee.iot.wechat.vo.WxCancelBindReqVO; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * 微信相关服务类 + * @author fastb + * @date 2023-07-31 11:34 + */ +public interface WeChatService { + + /** + * 移动应用微信登录 + * @param weChatLoginBody 微信登录参数 + * @return String + */ + WeChatLoginResult mobileLogin(WeChatLoginBody weChatLoginBody); + + /** + * 小程序微信登录 + * @param weChatLoginBody 微信登录参数 + * @return 登录结果 + */ + WeChatLoginResult miniLogin(WeChatLoginBody weChatLoginBody); + + /** + * 取消所有相关微信绑定 + * @param wxCancelBindReqVO 微信解绑传参类型 + * @return 结果 + */ + AjaxResult cancelBind(WxCancelBindReqVO wxCancelBindReqVO); + + /** + * 小程序、移动应用微信绑定 + * @param wxBindReqVO 微信绑定传参类型 + * @return 结果 + */ + AjaxResult bind(WxBindReqVO wxBindReqVO); + + /** + * 网站应用获取微信绑定二维码信息 + * @return 二维码信息 + */ + AjaxResult getWxBindQr(HttpServletRequest httpServletRequest); + + /** + * 网站应用内微信扫码绑定回调接口 + * @param code 用户凭证 + * @param state 时间戳 + * @param httpServletRequest 请求信息 + * @param httpServletResponse 响应信息 + */ + String wxBindCallback(String code, String state, String wxBindId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse); + + /** + * 网站应用获取微信绑定结果信息 + * @param wxBindMsgId 微信绑定结果信息id + * @return msg + */ + AjaxResult getWxBindMsg(String wxBindMsgId); +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/wechat/impl/WeChatServiceImpl.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/wechat/impl/WeChatServiceImpl.java new file mode 100644 index 00000000..3c33e17e --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/wechat/impl/WeChatServiceImpl.java @@ -0,0 +1,491 @@ +package com.fastbee.iot.wechat.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.core.domain.entity.SysUser; +import com.fastbee.common.core.domain.model.LoginUser; +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.enums.SocialPlatformType; +import com.fastbee.common.enums.VerifyTypeEnum; +import com.fastbee.common.exception.ServiceException; +import com.fastbee.common.utils.DateUtils; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.bean.BeanUtils; +import com.fastbee.common.utils.http.HttpUtils; +import com.fastbee.common.utils.uuid.IdUtils; +import com.fastbee.common.wechat.WeChatAppResult; +import com.fastbee.common.wechat.WeChatLoginBody; +import com.fastbee.common.wechat.WeChatLoginResult; +import com.fastbee.common.wechat.WeChatMiniProgramResult; +import com.fastbee.common.wechat.WeChatPhoneInfo; +import com.fastbee.common.wechat.WeChatUserInfo; +import com.fastbee.framework.web.service.SysLoginService; +import com.fastbee.iot.domain.SocialPlatform; +import com.fastbee.iot.domain.SocialUser; +import com.fastbee.iot.model.RegisterUserInput; +import com.fastbee.iot.model.RegisterUserOutput; +import com.fastbee.iot.model.login.WeChatLoginQrRes; +import com.fastbee.iot.service.ISocialLoginService; +import com.fastbee.iot.service.ISocialPlatformService; +import com.fastbee.iot.service.ISocialUserService; +import com.fastbee.iot.service.IToolService; +import com.fastbee.iot.wechat.WeChatService; +import com.fastbee.iot.wechat.vo.WxBindReqVO; +import com.fastbee.iot.wechat.vo.WxCancelBindReqVO; +import com.fastbee.system.service.ISysUserService; +import lombok.extern.slf4j.Slf4j; +import me.zhyd.oauth.model.AuthUser; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +import static com.fastbee.common.core.domain.AjaxResult.error; +import static com.fastbee.common.core.domain.AjaxResult.success; +import static com.fastbee.common.utils.SecurityUtils.getLoginUser; +import static com.fastbee.common.utils.SecurityUtils.getUserId; +import static com.fastbee.iot.service.impl.SocialLoginServiceImpl.LOGIN_ERROR_MSG_REDIS_KEY; +import static com.fastbee.iot.service.impl.SocialLoginServiceImpl.WX_BIND_REDIS_KEY; + +/** + * @author fastb + * @date 2023-08-14 9:16 + */ +@Slf4j +@Service +public class WeChatServiceImpl implements WeChatService { + + @Resource + private ISocialUserService socialUserService; + @Resource + private ISysUserService sysUserService; + @Resource + private SysLoginService sysLoginService; + @Resource + private ISocialPlatformService socialPlatformService; + @Resource + private ISocialLoginService socialLoginService; + @Resource + private IToolService toolService; + @Resource + private RedisCache redisCache; + + /** + * 移动应用登录获取用户access_token + */ + private static final String WE_CHAT_APP_API_CODE_TO_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token"; + + /** + * 小程序登录获取用户会话参数 + */ + private static final String WE_CHAT_MINI_PROGRAM_API_CODE_TO_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/jscode2session"; + + /** + * 微信小程序获取access_token + */ + private static final String WE_CHAT_MINI_PROGRAM_API_TO_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"; + + /** + * 获取用户信息 + */ + private static final String WE_CHAT_API_TO_USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo"; + + /** + * 获取用户手机号信息 + */ + private static final String WE_CHAT_API_TO_USER_PHONE_INFO_URL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token="; + + /** + * 移动应用微信登录 + * @param weChatLoginBody 微信登录参数 + * @return String + */ + public WeChatLoginResult mobileLogin(WeChatLoginBody weChatLoginBody) { + WeChatLoginResult weChatLoginResult = new WeChatLoginResult(); + SocialPlatformType socialPlatformType = SocialPlatformType.WECHAT_OPEN_MOBILE; + // 查询微信平台信息 + SocialPlatform socialPlatform = socialPlatformService.selectSocialPlatformByPlatform(socialPlatformType.getSourceClient()); + if (socialPlatform == null) { + throw new ServiceException("请先配置微信开放平台移动应用信息"); + } + // 用户凭证code只能消费一次,前端调的uni.login有时会消费,然后直接就把获取到的信息传过来,不会消费的话就这里通过code拿 + if (StringUtils.isEmpty(weChatLoginBody.getAccessToken()) || StringUtils.isEmpty(weChatLoginBody.getOpenId()) || StringUtils.isEmpty(weChatLoginBody.getUnionId())) { + WeChatAppResult weChatResult = this.getAccessTokenOpenId(weChatLoginBody.getCode(), socialPlatform); + if (weChatResult == null || weChatResult.getErrCode() != null) { + throw new ServiceException("用户凭证获取失败,请重新登录!"); + } + weChatLoginBody.setAccessToken(weChatResult.getAccessToken()).setRefreshToken(weChatResult.getRefreshToken()).setExpiresIn(weChatResult.getExpiresIn()).setOpenId(weChatResult.getOpenId()).setUnionId(weChatResult.getUnionId()).setScope(weChatResult.getScope()); + } + Long bindSysUserId; + // 查询用户第三方信息 + SocialUser socialUser = socialUserService.selectOneByOpenIdAndUnionId(weChatLoginBody.getOpenId(), weChatLoginBody.getUnionId()); + if (socialUser != null && socialUser.getSysUserId() != null) { + bindSysUserId = socialUser.getSysUserId(); + } else { + bindSysUserId = socialUserService.selectSysUserIdByUnionId(weChatLoginBody.getUnionId()); + } + Date nowDate = DateUtils.getNowDate(); + String uuid = IdUtils.randomUUID(); + if (socialUser == null) { + SocialUser addSocialUser = new SocialUser(); + addSocialUser.setSysUserId(bindSysUserId).setStatus(bindSysUserId == null ? "0" : "1").setUuid(uuid).setSource(socialPlatform.getPlatform().toUpperCase(Locale.ROOT)).setAccessToken(weChatLoginBody.getAccessToken()).setExpireIn(weChatLoginBody.getExpiresIn()) + .setRefreshToken(weChatLoginBody.getRefreshToken()).setOpenId(weChatLoginBody.getOpenId()).setUnionId(weChatLoginBody.getUnionId()).setSourceClient(socialPlatform.getPlatform()) + .setCode(weChatLoginBody.getCode()); + addSocialUser.setCreateBy("System"); + addSocialUser.setCreateTime(nowDate); + // 获取微信用户信息 + WeChatUserInfo weChatUserInfo = getWeChatUserInfo(weChatLoginBody.getAccessToken(), weChatLoginBody.getOpenId()); + if (weChatUserInfo != null) { + addSocialUser.setUnionId(weChatUserInfo.getUnionId()).setUsername(weChatUserInfo.getNickname()) + .setNickname(weChatUserInfo.getNickname()).setAvatar(weChatUserInfo.getHeadImgUrl()).setGender(weChatUserInfo.getSex()); + } + socialUserService.insertSocialUser(addSocialUser); + } else { + SocialUser updateSocialUser = new SocialUser(); + BeanUtils.copyProperties(socialUser, updateSocialUser); + updateSocialUser.setUpdateBy("System"); + updateSocialUser.setSysUserId(bindSysUserId).setStatus(bindSysUserId == null ? "0" : "1").setAccessToken(weChatLoginBody.getAccessToken()) + .setRefreshToken(weChatLoginBody.getRefreshToken()).setUuid(uuid); + socialUserService.updateSocialUser(updateSocialUser); + } + // 没有绑定账号,需要去绑定系统账号,跳转到绑定页面 + if (bindSysUserId == null) { + AuthUser authUser = new AuthUser(); + authUser.setUuid(uuid); + authUser.setSource(socialPlatform.getPlatform()); + String bindId = socialLoginService.genBindId(authUser); + weChatLoginResult.setBindId(bindId); + } else { + SysUser sysUser = sysUserService.selectUserById(bindSysUserId); + if (sysUser == null) { + throw new ServiceException("用户不存在"); + } + String token = sysLoginService.redirectLogin(sysUser.getUserName(), sysUser.getPassword()); + weChatLoginResult.setToken(token); + } + return weChatLoginResult; + } + + /** + * 小程序微信登录 + * @param weChatLoginBody 微信登录参数 + * @return 登录结果 + */ + @Override + public WeChatLoginResult miniLogin(WeChatLoginBody weChatLoginBody) { + // 使用微信手机号去登录不绑定微信,没有用户则用手机号自动注册一个登录,密码是手机号 + SocialPlatformType socialPlatformType = SocialPlatformType.WECHAT_OPEN_MINI_PROGRAM; + // 查询微信平台信息 + SocialPlatform socialPlatform = socialPlatformService.selectSocialPlatformByPlatform(socialPlatformType.getSourceClient()); + if (socialPlatform == null) { + throw new ServiceException("请先配置微信公众平台小程序信息!"); + } + if (StringUtils.isEmpty(weChatLoginBody.getPhoneCode())) { + throw new ServiceException("用户手机号凭证获取失败,请重新登录!"); + } + // 先获取token + WeChatAppResult result = getAccessToken(socialPlatform); + if (result == null || StringUtils.isEmpty(result.getAccessToken())) { + throw new ServiceException("获取用户调用凭据失败,请重新登录!"); + } + // 根据phoneCode获取用户手机号 + WeChatPhoneInfo userPhoneInfo = getWechatUserPhoneInfo(weChatLoginBody.getPhoneCode(), result.getAccessToken()); + if (userPhoneInfo == null || !userPhoneInfo.getErrCode().equals("0")) { + throw new ServiceException("获取用户手机号失败,请重新登录!"); + } + String phoneNumber = userPhoneInfo.getPhoneInfo().getPhoneNumber(); + SysUser sysUser = sysUserService.selectUserByPhoneNumber(phoneNumber); + String token; + WeChatLoginResult weChatLoginResult = new WeChatLoginResult(); + if (sysUser == null) { + // 直接用手机号注册一个新用户,密码就是手机号 + RegisterUserInput registerUserInput = new RegisterUserInput(); + registerUserInput.setUsername(phoneNumber); + registerUserInput.setPhonenumber(phoneNumber); + registerUserInput.setPassword(phoneNumber); + RegisterUserOutput registerUserOutput = toolService.registerNoCaptcha(registerUserInput); + if (StringUtils.isNotEmpty(registerUserOutput.getMsg())) { + throw new ServiceException(registerUserOutput.getMsg()); + } + token = sysLoginService.redirectLogin(phoneNumber, phoneNumber); + } else { + token = sysLoginService.redirectLogin(sysUser.getUserName(), sysUser.getPassword()); + } + weChatLoginResult.setToken(token); + return weChatLoginResult; + } + + private void miniCreateOrUpdateSocialUser(SocialUser socialUser, SocialPlatform socialPlatform, WeChatLoginBody weChatLoginBody, WeChatMiniProgramResult weChatResult, String accessToken, Long bindSysUserId) { + Date nowDate = DateUtils.getNowDate(); + String uuid = IdUtils.randomUUID(); + if (socialUser == null) { + SocialUser insertSocialUser = new SocialUser(); + String sourceClient = socialPlatform.getPlatform(); + insertSocialUser.setSysUserId(bindSysUserId).setStatus("1").setUuid(uuid).setSource(sourceClient.toUpperCase(Locale.ROOT)).setSourceClient(sourceClient) + .setAccessToken(accessToken).setOpenId(weChatResult.getOpenId()).setUnionId(weChatResult.getUnionId()) + .setCode(weChatLoginBody.getCode()).setDelFlag("0"); + insertSocialUser.setCreateBy("System"); + insertSocialUser.setCreateTime(nowDate); + socialUserService.insertSocialUser(insertSocialUser); + } else { + SocialUser updateSocialUser = new SocialUser(); + updateSocialUser.setSocialUserId(socialUser.getSocialUserId()); + updateSocialUser.setSysUserId(bindSysUserId).setStatus("1").setCode(weChatLoginBody.getCode()); + updateSocialUser.setUpdateBy("System"); + socialUserService.updateSocialUser(updateSocialUser); + } + } + + /** + * 取消所有相关微信绑定 + * @param wxCancelBindReqVO 微信解绑传参类型 + * @return 结果 + */ + @Override + public AjaxResult cancelBind(WxCancelBindReqVO wxCancelBindReqVO) { + LoginUser loginUser = getLoginUser(); + if (loginUser == null || loginUser.getUserId() == null) { + throw new ServiceException("请先登录后重试"); + } + // 密码验证 + if (VerifyTypeEnum.PASSWORD.getVerifyType().equals(wxCancelBindReqVO.getVerifyType())) { + if (StringUtils.isEmpty(wxCancelBindReqVO.getPassword())) { + throw new ServiceException("请传入用户密码"); + } + Boolean validateResult = sysUserService.validatePassword(loginUser.getUser().getPassword(), wxCancelBindReqVO.getPassword()); + if (Boolean.FALSE.equals(validateResult)) { + throw new ServiceException("密码错误,请重新输入"); + } + } + // 解绑所有微信应用 + int cancelBind = socialUserService.cancelBind(loginUser.getUserId(), SocialPlatformType.listWechatPlatform); + return cancelBind >= 1 ? success("解绑成功") : AjaxResult.error("解绑失败"); + } + + /** + * 小程序、移动应用微信绑定 + * @param wxBindReqVO 微信绑定传参类型 + * @return 结果 + */ + @Override + public AjaxResult bind(WxBindReqVO wxBindReqVO) { + Long sysUserId = getUserId(); + if (sysUserId == null) { + throw new ServiceException("请登录后重试"); + } + String openId = ""; + String unionId = ""; + // 区分小程序绑定还是移动应用绑定 + if (SocialPlatformType.WECHAT_OPEN_MOBILE.sourceClient.equals(wxBindReqVO.getSourceClient())) { + if (StringUtils.isEmpty(wxBindReqVO.getOpenId()) || StringUtils.isEmpty(wxBindReqVO.getUnionId())) { + throw new ServiceException("请传入微信用户信息"); + } + openId = wxBindReqVO.getOpenId(); + unionId = wxBindReqVO.getUnionId(); + } else if (SocialPlatformType.WECHAT_OPEN_MINI_PROGRAM.sourceClient.equals(wxBindReqVO.getSourceClient())) { + if (StringUtils.isEmpty(wxBindReqVO.getCode())) { + throw new ServiceException("请传入用户凭证"); + } + // 查询微信平台信息 + SocialPlatform socialPlatform = socialPlatformService.selectSocialPlatformByPlatform(SocialPlatformType.WECHAT_OPEN_MINI_PROGRAM.sourceClient); + if (socialPlatform == null) { + throw new ServiceException("请先配置微信开放平台小程序信息!"); + } + WeChatMiniProgramResult weChatMiniProgramResult = this.codeToSession(wxBindReqVO.getCode(), socialPlatform); + if (weChatMiniProgramResult == null + || (StringUtils.isEmpty(weChatMiniProgramResult.getOpenId()) && StringUtils.isEmpty(weChatMiniProgramResult.getUnionId()))) { + throw new ServiceException("获取微信信息失败,请重试!"); + } + openId = weChatMiniProgramResult.getOpenId(); + unionId = weChatMiniProgramResult.getUnionId(); + } + + SocialUser socialUser = socialUserService.selectOneByOpenIdAndUnionId(openId, unionId); + int bindResult; + List socialUserList = socialUserService.selectBySysUserId(sysUserId); + if (CollectionUtils.isNotEmpty(socialUserList)) { + return success("绑定成功!"); + } + if (socialUser != null) { + if (socialUser.getSysUserId() != null && !sysUserId.equals(socialUser.getSysUserId())) { + throw new ServiceException("该微信已绑定其他账号,请先使用微信登录解绑后重试!"); + } + SocialUser updateSocialUser = new SocialUser(); + updateSocialUser.setSocialUserId(socialUser.getSocialUserId()); + updateSocialUser.setSysUserId(sysUserId).setStatus("1"); + updateSocialUser.setUpdateBy("System"); + bindResult = socialUserService.updateSocialUser(updateSocialUser); + } else { + Date nowDate = DateUtils.getNowDate(); + String uuid = IdUtils.randomUUID(); + SocialUser insertSocialUser = new SocialUser(); + insertSocialUser.setSysUserId(sysUserId).setStatus("1").setUuid(uuid).setSource(wxBindReqVO.getSourceClient().toUpperCase(Locale.ROOT)).setSourceClient(wxBindReqVO.getSourceClient()) + .setOpenId(openId).setUnionId(unionId) + .setDelFlag("0"); + insertSocialUser.setCreateBy("System"); + insertSocialUser.setCreateTime(nowDate); + bindResult = socialUserService.insertSocialUser(insertSocialUser); + } + // 绑定 + return bindResult >= 1 ? success("绑定成功!") : AjaxResult.error("绑定失败"); + } + + @Override + public AjaxResult getWxBindQr(HttpServletRequest httpServletRequest) { + Long sysUserId = getUserId(); + if (sysUserId == null) { + throw new ServiceException("请先登录后重试!"); + } + WeChatLoginQrRes weChatLoginQrRes = new WeChatLoginQrRes(); + SocialPlatform socialPlatform = socialPlatformService.selectSocialPlatformByPlatform(SocialPlatformType.WECHAT_OPEN_WEB_BIND.sourceClient); + if (socialPlatform == null) { + throw new ServiceException("请先配置微信开放平台网站应用个人中心绑定信息"); + } + weChatLoginQrRes.setAppid(socialPlatform.getClientId()); + weChatLoginQrRes.setScope("snsapi_login"); + weChatLoginQrRes.setState(String.valueOf(System.currentTimeMillis())); + String wxBindId = socialLoginService.genWxBindId(sysUserId); + String url = socialPlatform.getRedirectUri() + wxBindId; + weChatLoginQrRes.setRedirectUri(url); + return success(weChatLoginQrRes); + } + + @Override + public String wxBindCallback(String code, String state, String wxBindId, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { + SocialPlatform socialPlatform = socialPlatformService.selectSocialPlatformByPlatform(SocialPlatformType.WECHAT_OPEN_WEB_BIND.sourceClient); + if (socialPlatform == null) { + String serverName = httpServletRequest.getServerName(); + String msgId = socialLoginService.genErrorId("请先配置微信开放平台网站应用个人中心绑定信息"); + return "https://" + serverName + "/user/profile?wxBindMsgId=" + msgId; + } + String url = socialPlatform.getRedirectLoginUri(); + //获取临时票据 code + log.info("code:{}", code); + if (StringUtils.isEmpty(code)) { + String msgId = socialLoginService.genErrorId("您已取消授权或未获取到授权信息"); + return url + msgId; + } + Long sysUserId = redisCache.getCacheObject(WX_BIND_REDIS_KEY + wxBindId); + if (sysUserId == null) { + String msgId = socialLoginService.genErrorId("二维码已失效,请重新点击绑定"); + return url + msgId; + } + List socialUserList = socialUserService.selectBySysUserId(sysUserId); + if (CollectionUtils.isNotEmpty(socialUserList)) { + String msgId = socialLoginService.genErrorId("您的账号已绑定微信,请先解绑"); + return url + msgId; + } + // 组装获取accessToken的url + WeChatAppResult weChatAppResult = this.getAccessTokenOpenId(code, socialPlatform); + if (weChatAppResult == null || StringUtils.isEmpty(weChatAppResult.getAccessToken()) + || StringUtils.isEmpty(weChatAppResult.getOpenId()) || StringUtils.isEmpty(weChatAppResult.getUnionId())) { + String msgId = socialLoginService.genErrorId("获取微信信息失败,请重试"); + return url + msgId; + } + Long bindUserId = socialUserService.selectSysUserIdByUnionId(weChatAppResult.getUnionId()); + if (bindUserId != null && !bindUserId.equals(sysUserId)) { + String msgId = socialLoginService.genErrorId("您的微信已绑定其他账号,请先使用微信登录解绑后重试!"); + return url + msgId; + } + SocialUser socialUser = socialUserService.selectOneByOpenIdAndUnionId(weChatAppResult.getOpenId(), weChatAppResult.getUnionId()); + if (socialUser == null) { + socialUser = new SocialUser(); + } + WeChatUserInfo weChatUserInfo = this.getWeChatUserInfo(weChatAppResult.getAccessToken(), weChatAppResult.getOpenId()); + String uuid = IdUtils.randomUUID(); + Date nowDate = DateUtils.getNowDate(); + socialUser.setUuid(uuid).setAccessToken(weChatAppResult.getAccessToken()).setRefreshToken(weChatAppResult.getRefreshToken()) + .setOpenId(weChatUserInfo.getOpenId()).setUnionId(weChatUserInfo.getUnionId()).setSysUserId(sysUserId).setStatus("1") + .setNickname(weChatUserInfo.getNickname()).setUsername(weChatUserInfo.getNickname()).setAvatar(weChatUserInfo.getHeadImgUrl()); + // 个人中心绑定这个也做了一个配置,标识设为了 wechat_open_web_bind,但其实都属于网站应用,所以这里还是改为使用wechat_open_web这个来记录来源吧,统一为网站来源,方便查询 + socialUser.setSource(SocialPlatformType.WECHAT_OPEN_WEB.sourceClient).setSourceClient(SocialPlatformType.WECHAT_OPEN_WEB.sourceClient); + if (socialUser.getCreateTime() == null) { + socialUser.setCreateTime(nowDate); + socialUser.setCreateBy("System"); + } + socialUser.setUpdateTime(nowDate); + socialUser.setUpdateBy("System"); + int updateResult = socialUser.getSocialUserId() == null ? socialUserService.insertSocialUser(socialUser) : socialUserService.updateSocialUser(socialUser); + String msg = updateResult >= 1 ? "绑定成功" : "绑定失败"; + String msgId = socialLoginService.genErrorId(msg); + return url + msgId; + } + + @Override + public AjaxResult getWxBindMsg(String wxBindMsgId) { + String errorMsg = redisCache.getCacheObject(LOGIN_ERROR_MSG_REDIS_KEY + wxBindMsgId); + if (StringUtils.isEmpty(errorMsg)) { + return success(); + } else if ("绑定成功".equals(errorMsg)) { + return success(errorMsg); + } else { + return error(errorMsg); + } + } + + /** + * 移动应用获取微信用户accessToken + * @param code 用户登录code + * @return WeChatAppResult + */ + private WeChatAppResult getAccessTokenOpenId(String code, SocialPlatform socialPlatform) { + String url = WE_CHAT_APP_API_CODE_TO_ACCESS_TOKEN_URL + "?appid=" + socialPlatform.getClientId() + "&secret=" + socialPlatform.getSecretKey() + "&code=" + code + "&grant_type=authorization_code"; + String s = HttpUtils.sendGet(url); + return JSON.parseObject(s, WeChatAppResult.class); + } + + /** + * 移动应用获取微信用户信息 + * @param accessToken 接口调用凭证 + * @param openId 用户唯一标识 + * @return WeChatUserInfo + */ + private WeChatUserInfo getWeChatUserInfo(String accessToken, String openId) { + String url = WE_CHAT_API_TO_USER_INFO_URL + "?access_token=" + accessToken + "&openid=" + openId; + String s = HttpUtils.sendGet(url); + return JSON.parseObject(s, WeChatUserInfo.class); + } + + /** + * 小程序获取微信用户登录信息 + * @param code 用户凭证 + * @param socialPlatform 登录平台 + * @return 结果 + */ + private WeChatMiniProgramResult codeToSession(String code, SocialPlatform socialPlatform) { + String url = WE_CHAT_MINI_PROGRAM_API_CODE_TO_ACCESS_TOKEN_URL + "?appid=" + socialPlatform.getClientId() + "&secret=" + socialPlatform.getSecretKey() + "&js_code=" + code + "&grant_type=authorization_code"; + String s = HttpUtils.sendGet(url); + return JSON.parseObject(s, WeChatMiniProgramResult.class); + } + + /** + * 小程序获取微信用户手机号 + * @param code 凭证 + * @return 手机号信息 + */ + private WeChatPhoneInfo getWechatUserPhoneInfo(String code, String accessToken) { + String url = WE_CHAT_API_TO_USER_PHONE_INFO_URL + 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 socialPlatform 用户登录code + * @return WeChatAppResult + */ + private WeChatAppResult getAccessToken(SocialPlatform socialPlatform) { + String url = WE_CHAT_MINI_PROGRAM_API_TO_ACCESS_TOKEN_URL + "&appid=" + socialPlatform.getClientId() + "&secret=" + socialPlatform.getSecretKey(); + String s = HttpUtils.sendGet(url); + return JSON.parseObject(s, WeChatAppResult.class); + } +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/wechat/vo/WxBindReqVO.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/wechat/vo/WxBindReqVO.java new file mode 100644 index 00000000..512a7ab4 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/wechat/vo/WxBindReqVO.java @@ -0,0 +1,19 @@ +package com.fastbee.iot.wechat.vo; + +import lombok.Data; + +/** + * @author fastb + * @date 2023-08-30 17:21 + */ +@Data +public class WxBindReqVO { + + private String sourceClient; + + private String openId; + + private String unionId; + + private String code; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/wechat/vo/WxCancelBindReqVO.java b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/wechat/vo/WxCancelBindReqVO.java new file mode 100644 index 00000000..3536e827 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/java/com/fastbee/iot/wechat/vo/WxCancelBindReqVO.java @@ -0,0 +1,32 @@ +package com.fastbee.iot.wechat.vo; + +import lombok.Data; + +/** + * 微信解绑传参类 + * @author fastb + * @date 2023-08-30 15:10 + */ +@Data +public class WxCancelBindReqVO { + + /** + * 验证类型 + */ + private Integer verifyType; + + /** + * 用户密码 + */ + private String password; + + /** + * 手机号 + */ + private String userPhone; + + /** + * 短信验证码 + */ + private String smsCode; +} diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/CategoryMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/CategoryMapper.xml new file mode 100644 index 00000000..6cdcd20c --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/CategoryMapper.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + select category_id, category_name, tenant_id, tenant_name, is_sys,order_num, create_time, update_time, remark from iot_category + + + + + + + + + + insert into iot_category + + category_name, + tenant_id, + tenant_name, + is_sys, + order_num, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{categoryName}, + #{tenantId}, + #{tenantName}, + #{isSys}, + #{orderNum}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update iot_category + + category_name = #{categoryName}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + is_sys = #{isSys}, + order_num = #{orderNum}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where category_id = #{categoryId} + + + + delete from iot_category where category_id = #{categoryId} + + + + delete from iot_category where category_id in + + #{categoryId} + + + + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/DeviceJobMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/DeviceJobMapper.xml new file mode 100644 index 00000000..b67a3519 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/DeviceJobMapper.xml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select job_id, job_name, job_group,serial_number, device_id,device_name,actions,alert_trigger,is_advance, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark, job_type, product_id, product_name, scene_id, alert_id + from iot_device_job + + + + + + + + + + + + + + + + delete from iot_device_job where job_id = #{jobId} + + + + delete from iot_device_job where job_id in + + #{jobId} + + + + + delete from iot_device_job where device_id in + + #{deviceId} + + + + + delete from iot_device_job where alert_id in + + #{alertId} + + + + + delete from iot_device_job where scene_id in + + #{sceneId} + + + + + update iot_device_job + + job_name = #{jobName}, + job_group = #{jobGroup}, + device_id = #{deviceId}, + device_name = #{deviceName}, + serial_number = #{serialNumber}, + actions = #{actions}, + alert_trigger = #{alertTrigger}, + cron_expression = #{cronExpression}, + misfire_policy = #{misfirePolicy}, + concurrent = #{concurrent}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + job_type = #{jobType}, + product_id = #{productId}, + product_name = #{productName}, + scene_id = #{sceneId}, + alert_id = #{alertId}, + is_advance =#{isAdvance}, + update_time = sysdate() + + where job_id = #{jobId} + + + + insert into iot_device_job( + job_id, + job_name, + job_group, + device_id, + device_name, + serial_number, + actions, + alert_trigger, + cron_expression, + misfire_policy, + concurrent, + status, + remark, + create_by, + job_type, + product_id, + product_name, + scene_id, + alert_id, + is_advance, + create_time + )values( + #{jobId}, + #{jobName}, + #{jobGroup}, + #{deviceId}, + #{deviceName}, + #{serialNumber}, + #{actions}, + #{alertTrigger}, + #{cronExpression}, + #{misfirePolicy}, + #{concurrent}, + #{status}, + #{remark}, + #{createBy}, + #{jobType}, + #{productId}, + #{productName}, + #{sceneId}, + #{alertId}, + #{isAdvance}, + sysdate() + ) + + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/DeviceLogMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/DeviceLogMapper.xml new file mode 100644 index 00000000..540fd56c --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/DeviceLogMapper.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select log_id, log_type, log_value, device_id, device_name,serial_number, identity, create_by, is_monitor,mode, user_id, user_name, tenant_id, tenant_name, create_time, remark from iot_device_log + + + + + + + + + + + + insert into iot_device_log (log_type,log_value,device_id,device_name,serial_number,identity,create_by, + is_monitor,mode,create_time,remark,user_id,user_name,tenant_id,tenant_name,model_name) + values + + (#{item.logType},#{item.logValue},#{item.deviceId},#{item.deviceName},#{item.serialNumber}, + #{item.identity},#{item.createBy},#{item.isMonitor},#{item.mode},#{item.createTime},#{item.remark}, + #{item.userId},#{item.userName},#{item.tenantId},#{item.tenantName},#{item.modelName}) + + + + + update iot_device_log + + log_type = #{logType}, + log_value = #{logValue}, + device_id = #{deviceId}, + device_name = #{deviceName}, + serial_number = #{serialNumber}, + identity = #{identity}, + create_by = #{createBy}, + is_monitor = #{isMonitor}, + mode = #{mode}, + create_time = #{createTime}, + remark = #{remark}, + user_id = #{userId}, + user_name = #{userName}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + + where log_id = #{logId} + + + + delete from iot_device_log where log_id = #{logId} + + + + delete from iot_device_log where log_id in + + #{logId} + + + + + delete from iot_device_log where serial_number = #{deviceNumber} + + + diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/DeviceMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/DeviceMapper.xml new file mode 100644 index 00000000..ce3b60d0 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/DeviceMapper.xml @@ -0,0 +1,639 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select device_id, device_name, product_id, product_name, user_id, user_name, tenant_id, tenant_name, serial_number,gw_dev_code, firmware_version, status, rssi,is_shadow ,is_simulate,location_way,things_model_value,network_address, network_ip, longitude, latitude, active_time, create_time, update_time, img_url,summary,remark,slave_id from iot_device + + + + select device_id, device_name, product_id, product_name, user_id, user_name, tenant_id, tenant_name, serial_number, firmware_version, status,rssi,is_shadow ,is_simulate,location_way,things_model_value, active_time,img_url,slave_id from iot_device + + + + select device_id, device_name,product_id, serial_number,user_id, user_name, tenant_id, tenant_name, status,is_shadow,is_simulate, rssi ,location_way,things_model_value, active_time from iot_device + + + + + + + + + + + + + + + + + + update iot_device set things_model_value=#{stringValue} where device_id = #{deviceId} + + + + + + + + + + + + + + + + + + + + insert into iot_device + + device_name, + product_id, + product_name, + user_id, + user_name, + tenant_id, + tenant_name, + serial_number, + firmware_version, + status, + rssi, + is_shadow, + location_way, + things_model_value, + network_address, + network_ip, + longitude, + latitude, + active_time, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + img_url, + summary, + gw_dev_code, + is_simulate, + slave_id, + + + #{deviceName}, + #{productId}, + #{productName}, + #{userId}, + #{userName}, + #{tenantId}, + #{tenantName}, + #{serialNumber}, + #{firmwareVersion}, + #{status}, + #{rssi}, + #{isShadow}, + #{locationWay}, + #{thingsModelValue}, + #{networkAddress}, + #{networkIp}, + #{longitude}, + #{latitude}, + #{activeTime}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{imgUrl}, + #{summary}, + #{gwDevCode}, + #{isSimulate}, + #{slaveId}, + + + + + update iot_device + + device_name = #{deviceName}, + product_id = #{productId}, + product_name = #{productName}, + user_id = #{userId}, + user_name = #{userName}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + serial_number = #{serialNumber}, + gw_dev_code = #{gwDevCode}, + firmware_version = #{firmwareVersion}, + status = #{status}, + rssi = #{rssi}, + is_shadow = #{isShadow}, + is_simulate = #{isSimulate}, + location_way = #{locationWay}, + things_model_value = #{thingsModelValue}, + network_address = #{networkAddress}, + network_ip = #{networkIp}, + longitude = #{longitude}, + latitude = #{latitude}, + active_time = #{activeTime}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + img_url = #{imgUrl}, + summary = #{summary}, + slave_id = #{slaveId}, + + where device_id = #{deviceId} + + + + update iot_device + + status = #{status}, + network_address = #{networkAddress}, + network_ip = #{networkIp}, + longitude = #{longitude}, + latitude = #{latitude}, + active_time = #{activeTime}, + update_time = #{updateTime,jdbcType=TIMESTAMP} + + where serial_number = #{serialNumber} or gw_dev_code = #{serialNumber} + + + + update iot_device + set firmware_version = #{firmwareVersion,jdbcType=DECIMAL} + where serial_number = #{serialNumber,jdbcType=VARCHAR} + + + + -- 设备状态(1-未激活,2-禁用,3-在线,4-离线) + update iot_device set status=4 + where serial_number = #{serialNumber} and status = 3 + + + + update iot_device + + device_name = #{deviceName}, + product_id = #{productId}, + product_name = #{productName}, + user_id = #{userId}, + user_name = #{userName}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + serial_number = #{serialNumber}, + firmware_version = #{firmwareVersion}, + status = #{status}, + rssi = #{rssi}, + is_shadow = #{isShadow}, + is_simulate = #{isSimulate}, + location_way = #{locationWay}, + things_model_value = #{thingsModelValue}, + network_address = #{networkAddress}, + network_ip = #{networkIp}, + longitude = #{longitude}, + latitude = #{latitude}, + active_time = #{activeTime}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + img_url = #{imgUrl}, + summary = #{summary}, + gw_dev_code = #{gwDevCode}, + + where serial_number = #{serialNumber} + + + + delete from iot_device where device_id = #{deviceId} + + + + delete from iot_device where device_id in + + #{deviceId} + + + + + + + delete from iot_device_group + + and device_id = #{deviceId} + and group_id in(select group_id from iot_group where user_id = #{userId}) + + + + + + + + + + + update iot_device d + set d.status = 3, + d.update_time = now() + where d.serial_number in + + #{item} + + + + + + update iot_device d + set d.status = 4, + d.update_time = now() + where d.serial_number in + + #{item} + + + + + + + + + + + + delete from iot_device where gw_dev_code = #{gwCode} + + diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/DeviceUserMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/DeviceUserMapper.xml new file mode 100644 index 00000000..fe043655 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/DeviceUserMapper.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select device_id, user_id, device_name, user_name, is_owner,tenant_id,tenant_name,phonenumber,perms, create_time, update_time, remark from iot_device_user + + + + + + + + + + + + insert into iot_device_user + + device_id, + user_id, + device_name, + user_name, + is_owner, + tenant_id, + tenant_name, + phonenumber, + perms, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{deviceId}, + #{userId}, + #{deviceName}, + #{userName}, + #{isOwner}, + #{tenantId}, + #{tenantName}, + #{phonenumber}, + #{perms}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + insert into iot_device_user + (device_id, user_id, device_name, user_name, is_owner,tenant_id,tenant_name,phonenumber,perms, create_time) values + + (#{item.deviceId},#{item.userId},#{item.deviceName}, #{item.userName}, #{item.isOwner}, #{item.tenantId},#{item.tenantName},#{item.phonenumber},#{item.perms}, #{item.createTime}) + + + + + update iot_device_user + + user_id = #{userId}, + device_name = #{deviceName}, + user_name = #{userName}, + is_owner = #{isOwner}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + phonenumber = #{phonenumber}, + perms = #{perms}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where device_id = #{deviceId} and user_id = #{userId} + + + + delete from iot_device_user + + and device_id = #{deviceId} + and user_id = #{userId} + + + + + delete from iot_device_user where device_id in + + #{deviceId} + + + + delete from iot_device_user where device_id = #{deviceId} and is_owner !=1 and user_id = #{userId} + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/EventLogMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/EventLogMapper.xml new file mode 100644 index 00000000..6696c7ce --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/EventLogMapper.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select log_id, identity, model_name, log_type, log_value, device_id, device_name, serial_number, is_monitor, mode, user_id, user_name, tenant_id, tenant_name, create_by, create_time, remark from iot_event_log + + + + + + + + insert into iot_event_log + + identity, + model_name, + log_type, + log_value, + device_id, + device_name, + serial_number, + is_monitor, + mode, + user_id, + user_name, + tenant_id, + tenant_name, + create_by, + create_time, + remark, + + + #{identity}, + #{modelName}, + #{logType}, + #{logValue}, + #{deviceId}, + #{deviceName}, + #{serialNumber}, + #{isMonitor}, + #{mode}, + #{userId}, + #{userName}, + #{tenantId}, + #{tenantName}, + #{createBy}, + #{createTime}, + #{remark}, + + + + + insert into iot_event_log (identity,model_name,log_type,log_value,device_id,device_name,serial_number,is_monitor,mode,user_id, + user_name,tenant_id,tenant_name,create_by,create_time,remark) + values + + (#{item.identity},#{item.modelName},#{item.logType},#{item.logValue},#{item.deviceId},#{item.deviceName},#{item.serialNumber},#{item.isMonitor}, + #{item.mode},#{item.userId},#{item.userName},#{item.tenantId},#{item.tenantName},#{item.createBy},#{item.createTime},#{item.remark}) + + + + + update iot_event_log + + identity = #{identity}, + model_name = #{modelName}, + log_type = #{logType}, + log_value = #{logValue}, + device_id = #{deviceId}, + device_name = #{deviceName}, + serial_number = #{serialNumber}, + is_monitor = #{isMonitor}, + mode = #{mode}, + user_id = #{userId}, + user_name = #{userName}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + create_by = #{createBy}, + create_time = #{createTime}, + remark = #{remark}, + + where log_id = #{logId} + + + + delete from iot_event_log where log_id = #{logId} + + + + delete from iot_event_log where serial_number = #{serialNumber} + + + + delete from iot_event_log where log_id in + + #{logId} + + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/FunctionLogMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/FunctionLogMapper.xml new file mode 100644 index 00000000..7a39efdf --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/FunctionLogMapper.xml @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select id, identify, fun_type, fun_value, message_id, device_name, serial_number, mode, user_id, result_msg, result_code, create_by, + create_time, remark,show_value,model_name,reply_time from iot_function_log + + + + + + + + insert into iot_function_log + + identify, + fun_type, + fun_value, + message_id, + device_name, + serial_number, + mode, + user_id, + result_msg, + result_code, + create_by, + create_time, + remark, + show_value, + model_name, + reply_time, + + + #{identify}, + #{funType}, + #{funValue}, + #{messageId}, + #{deviceName}, + #{serialNumber}, + #{mode}, + #{userId}, + #{resultMsg}, + #{resultCode}, + #{createBy}, + #{createTime}, + #{remark}, + #{showValue}, + #{modelName}, + #{replyTime}, + + + + + insert into iot_function_log (identify,fun_type,fun_value,message_id,device_name,serial_number,mode,user_id, + result_msg,result_code,create_by,create_time,remark,show_value,model_name,reply_time) + values + + (#{item.identify},#{item.funType},#{item.funValue},#{item.messageId},#{item.deviceName},#{item.serialNumber},#{item.mode},#{item.userId}, + #{item.resultMsg},#{item.resultCode},#{item.createBy},#{item.createTime},#{item.remark},#{item.showValue},#{item.modelName},#{item.replyTime}) + + + + + update iot_function_log + + identify = #{identify}, + fun_type = #{funType}, + fun_value = #{funValue}, + message_id = #{messageId}, + device_name = #{deviceName}, + serial_number = #{serialNumber}, + mode = #{mode}, + user_id = #{userId}, + result_msg = #{resultMsg}, + result_code = #{resultCode}, + create_by = #{createBy}, + create_time = #{createTime}, + remark = #{remark}, + show_value=#{showValue}, + model_name=#{modelName}, + reply_time = #{replyTime}, + + where id = #{id} + + + + delete from iot_function_log where id = #{id} + + + delete from iot_function_log where serial_number = #{serialNumber} + + + + delete from iot_function_log where identify like concat(#{prefixIdentify},'%') and serial_number = #{serialNumber} + + + + delete from iot_function_log where id in + + #{id} + + + + + update iot_function_log l + set l.result_msg = #{resultMsg}, + l.result_code = #{resultCode}, + l.reply_time = #{replyTime} + where l.id in + + #{item} + + + + + update iot_function_log + + identify = #{identify}, + fun_type = #{funType}, + fun_value = #{funValue}, + device_name = #{deviceName}, + serial_number = #{serialNumber}, + mode = #{mode}, + user_id = #{userId}, + result_msg = #{resultMsg}, + result_code = #{resultCode}, + create_by = #{createBy}, + create_time = #{createTime}, + remark = #{remark}, + show_value=#{showValue}, + model_name=#{modelName}, + reply_time = #{replyTime}, + + where message_id = #{messageId} + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/GroupMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/GroupMapper.xml new file mode 100644 index 00000000..af808817 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/GroupMapper.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + select group_id, group_name, group_order, user_id, user_name, create_time, update_time, remark from iot_group + + + + + + + + + + insert into iot_group + + group_name, + group_order, + user_id, + user_name, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{groupName}, + #{groupOrder}, + #{userId}, + #{userName}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + insert into iot_device_group (device_id,group_id) + values + + + #{deviceId},#{groupId}, + + + + + + update iot_group + + group_name = #{groupName}, + group_order = #{groupOrder}, + user_id = #{userId}, + user_name = #{userName}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where group_id = #{groupId} + + + + delete from iot_group where group_id = #{groupId} + + + + delete from iot_group where group_id in + + #{groupId} + + + + + delete from iot_device_group where group_id in + + #{groupId} + + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/NewsCategoryMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/NewsCategoryMapper.xml new file mode 100644 index 00000000..3a6fed34 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/NewsCategoryMapper.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + select category_id, category_name, order_num, del_flag, create_by, create_time, update_by, update_time, remark from news_category + + + + + + + + + + insert into news_category + + category_name, + order_num, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{categoryName}, + #{orderNum}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update news_category + + category_name = #{categoryName}, + order_num = #{orderNum}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where category_id = #{categoryId} + + + + delete from news_category where category_id = #{categoryId} + + + + delete from news_category where category_id in + + #{categoryId} + + + + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/NewsMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/NewsMapper.xml new file mode 100644 index 00000000..c4eeb2a1 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/NewsMapper.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + select news_id, title, img_url, is_top, is_banner, category_id, category_name, status, author, del_flag, create_by, create_time, update_by, update_time, remark from news + + + + + + + + + + insert into news + + title, + content, + img_url, + is_top, + is_banner, + category_id, + category_name, + status, + author, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{title}, + #{content}, + #{imgUrl}, + #{isTop}, + #{isBanner}, + #{categoryId}, + #{categoryName}, + #{status}, + #{author}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update news + + title = #{title}, + content = #{content}, + img_url = #{imgUrl}, + is_top = #{isTop}, + is_banner = #{isBanner}, + category_id = #{categoryId}, + category_name = #{categoryName}, + status = #{status}, + author = #{author}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where news_id = #{newsId} + + + + delete from news where news_id = #{newsId} + + + + delete from news where news_id in + + #{newsId} + + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/OauthClientDetailsMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/OauthClientDetailsMapper.xml new file mode 100644 index 00000000..05822fa7 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/OauthClientDetailsMapper.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + select client_id, resource_ids, client_secret, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove, type from oauth_client_details + + + + + + + + insert into oauth_client_details + + client_id, + resource_ids, + client_secret, + scope, + authorized_grant_types, + web_server_redirect_uri, + authorities, + access_token_validity, + refresh_token_validity, + additional_information, + autoapprove, + type, + + + #{clientId}, + #{resourceIds}, + #{clientSecret}, + #{scope}, + #{authorizedGrantTypes}, + #{webServerRedirectUri}, + #{authorities}, + #{accessTokenValidity}, + #{refreshTokenValidity}, + #{additionalInformation}, + #{autoapprove}, + #{type}, + + + + + update oauth_client_details + + resource_ids = #{resourceIds}, + client_secret = #{clientSecret}, + scope = #{scope}, + authorized_grant_types = #{authorizedGrantTypes}, + web_server_redirect_uri = #{webServerRedirectUri}, + authorities = #{authorities}, + access_token_validity = #{accessTokenValidity}, + refresh_token_validity = #{refreshTokenValidity}, + additional_information = #{additionalInformation}, + autoapprove = #{autoapprove}, + type = #{type}, + + where client_id = #{clientId} + + + + delete from oauth_client_details where client_id = #{clientId} + + + + delete from oauth_client_details where client_id in + + #{clientId} + + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProductAuthorizeMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProductAuthorizeMapper.xml new file mode 100644 index 00000000..d9b0500f --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProductAuthorizeMapper.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + select authorize_id, authorize_code, product_id, device_id, serial_number, user_id, user_name,status, del_flag, create_by, create_time, update_by, update_time, remark from iot_product_authorize + + + + + + + + + + + + insert into iot_product_authorize + + authorize_code, + product_id, + device_id, + serial_number, + user_id, + user_name, + status, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{authorizeCode}, + #{productId}, + #{deviceId}, + #{serialNumber}, + #{userId}, + #{userName}, + #{status}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + insert into iot_product_authorize (authorize_code,product_id,create_by,create_time,status) + values + + + #{item.authorizeCode},#{item.productId},#{item.createBy},#{item.createTime},#{item.status} + + + + + + update iot_product_authorize + + user_id = #{userId}, + device_id = #{deviceId}, + authorize_code = #{authorizeCode}, + product_id = #{productId}, + serial_number = #{serialNumber}, + user_name = #{userName}, + status = #{status}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where authorize_id = #{authorizeId} + + + + delete from iot_product_authorize where authorize_id = #{authorizeId} + + + + delete from iot_product_authorize where authorize_id in + + #{authorizeId} + + + + + delete from iot_product_authorize where product_id in + + #{productId} + + + diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProductMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProductMapper.xml new file mode 100644 index 00000000..07de4bdf --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProductMapper.xml @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select product_id, product_name,protocol_code,transport, category_id, category_name, tenant_id, tenant_name, is_sys, is_authorize, mqtt_account,mqtt_password,mqtt_secret ,status, device_type, network_method, vertificate_method, create_time, update_time, img_url,remark from iot_product + + + + + + + + + + insert into iot_product + + product_name, + category_id, + category_name, + tenant_id, + tenant_name, + is_sys, + is_authorize, + mqtt_account, + mqtt_password, + mqtt_secret, + status, + device_type, + network_method, + vertificate_method, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + img_url, + protocol_code, + transport, + + + #{productName}, + #{categoryId}, + #{categoryName}, + #{tenantId}, + #{tenantName}, + #{isSys}, + #{isAuthorize}, + #{mqttAccount}, + #{mqttPassword}, + #{mqttSecret}, + #{status}, + #{deviceType}, + #{networkMethod}, + #{vertificateMethod}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{imgUrl}, + #{protocolCode,jdbcType=VARCHAR}, + #{transport,jdbcType=VARCHAR}, + + + + + update iot_product + + product_name = #{productName}, + category_id = #{categoryId}, + category_name = #{categoryName}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + is_sys = #{isSys}, + is_authorize = #{isAuthorize}, + mqtt_account = #{mqttAccount}, + mqtt_password = #{mqttPassword}, + mqtt_secret = #{mqttSecret}, + status = #{status}, + device_type = #{deviceType}, + network_method = #{networkMethod}, + vertificate_method = #{vertificateMethod}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + img_url = #{imgUrl}, + protocol_code = #{protocolCode,jdbcType=VARCHAR}, + transport = #{transport,jdbcType=VARCHAR}, + + where product_id = #{productId} + + + + update iot_product set status=#{status} + where product_id = #{productId} + + + + update iot_product set things_models_json=#{thingsModelsJson} + where product_id = #{productId} + + + + delete from iot_product where product_id = #{productId} + + + + delete from iot_product where product_id in + + #{productId} + + + + + delete from iot_things_model where product_id in + + #{productId} + + + + + + + + + + + + + + + + + diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProtocolMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProtocolMapper.xml new file mode 100644 index 00000000..b5dd6a46 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ProtocolMapper.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + id, protocol_code, protocol_name, protocol_file_url, protocol_type, + jar_sign, create_time, update_time, protocol_status, del_flag + + + + select id, protocol_code, protocol_name, protocol_file_url, protocol_type, + jar_sign, create_time, update_time, protocol_status, del_flag from iot_protocol + + + + + + + + + + + + + insert into iot_protocol + + protocol_code, + protocol_name, + protocol_file_url, + protocol_type, + jar_sign, + create_time, + update_time, + protocol_status, + del_flag, + + + #{protocolCode}, + #{protocolName}, + #{protocolFileUrl}, + #{protocolType}, + #{jarSign}, + #{createTime}, + #{updateTime}, + #{protocolStatus}, + #{delFlag}, + + + + + update iot_protocol + + protocol_code = #{protocolCode}, + protocol_name = #{protocolName}, + protocol_file_url = #{protocolFileUrl}, + protocol_type = #{protocolType}, + jar_sign = #{jarSign}, + create_time = #{createTime}, + update_time = #{updateTime}, + protocol_status = #{protocolStatus}, + del_flag = #{delFlag}, + + where id = #{id} + + + + delete from iot_protocol where id = #{id} + + + + delete from iot_protocol where id in + + #{id} + + + diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/SocialPlatformMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/SocialPlatformMapper.xml new file mode 100644 index 00000000..30c30cb3 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/SocialPlatformMapper.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + select social_platform_id, + platform, + status, + client_id, + secret_key, + redirect_uri, + del_flag, + create_by, + create_time, + update_time, + update_by, + remark, + bind_uri, + redirect_login_uri, + error_msg_uri + from iot_social_platform + + + + + + + + + + insert into iot_social_platform + + platform, + status, + client_id, + secret_key, + redirect_uri, + del_flag, + create_by, + create_time, + update_time, + update_by, + remark, + bind_uri, + redirect_login_uri, + error_msg_uri, + + + #{platform}, + #{status}, + #{clientId}, + #{secretKey}, + #{redirectUri}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateTime}, + #{updateBy}, + #{remark}, + #{bindUri}, + #{redirectLoginUri}, + #{errorMsgUri}, + + + + + update iot_social_platform + + platform = #{platform}, + status = #{status}, + client_id = #{clientId}, + secret_key = #{secretKey}, + redirect_uri = #{redirectUri}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_time = #{updateTime}, + update_by = #{updateBy}, + remark = #{remark}, + bind_uri = #{bindUri}, + redirect_login_uri = #{redirectLoginUri}, + + error_msg_uri = #{errorMsgUri}, + + where social_platform_id = #{socialPlatformId} + + + + delete + from iot_social_platform + where social_platform_id = #{socialPlatformId} + + + + delete from iot_social_platform where social_platform_id in + + #{socialPlatformId} + + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/SocialUserMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/SocialUserMapper.xml new file mode 100644 index 00000000..0110ce38 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/SocialUserMapper.xml @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select social_user_id, + uuid, + source, + access_token, + expire_in, + refresh_token, + open_id, + uid, + access_code, + union_id, + scope, + token_type, + id_token, + mac_algorithm, + mac_key, + code, + oauth_token, + oauth_token_secret, + create_time, + create_by, + update_time, + update_by, + del_flag, + status, + sys_user_id, + username, + nickname, + avatar, + gender, + source_client + from iot_social_user + + + + + + + + + + + + + + + insert into iot_social_user + + uuid, + source, + access_token, + expire_in, + refresh_token, + open_id, + uid, + access_code, + union_id, + scope, + token_type, + id_token, + mac_algorithm, + mac_key, + code, + oauth_token, + oauth_token_secret, + create_time, + create_by, + update_time, + update_by, + del_flag, + status, + sys_user_id, + username, + nickname, + avatar, + gender, + source_client, + + + #{uuid}, + #{source}, + #{accessToken}, + #{expireIn}, + #{refreshToken}, + #{openId}, + #{uid}, + #{accessCode}, + #{unionId}, + #{scope}, + #{tokenType}, + #{idToken}, + #{macAlgorithm}, + #{macKey}, + #{code}, + #{oauthToken}, + #{oauthTokenSecret}, + #{createTime}, + #{createBy}, + #{updateTime}, + #{updateBy}, + #{delFlag}, + #{status}, + #{sysUserId}, + #{username}, + #{nickname}, + #{avatar}, + #{gender}, + #{sourceClient}, + + + + + update iot_social_user + + uuid = #{uuid}, + source = #{source}, + access_token = #{accessToken}, + expire_in = #{expireIn}, + refresh_token = #{refreshToken}, + open_id = #{openId}, + uid = #{uid}, + access_code = #{accessCode}, + union_id = #{unionId}, + scope = #{scope}, + token_type = #{tokenType}, + id_token = #{idToken}, + mac_algorithm = #{macAlgorithm}, + mac_key = #{macKey}, + code = #{code}, + oauth_token = #{oauthToken}, + oauth_token_secret = #{oauthTokenSecret}, + create_time = #{createTime}, + create_by = #{createBy}, + update_time = #{updateTime}, + update_by = #{updateBy}, + del_flag = #{delFlag}, + status = #{status}, + sys_user_id = #{sysUserId}, + username = #{username}, + nickname = #{nickname}, + avatar = #{avatar}, + gender = #{gender}, + source_client = #{sourceClient}, + + where social_user_id = #{socialUserId} + + + + delete + from iot_social_user + where social_user_id = #{socialUserId} + + + + delete from iot_social_user where social_user_id in + + #{socialUserId} + + + + + update iot_social_user + set del_flag = 1, + update_time = now() + where sys_user_id = #{sysUserId} + and source_client in + + #{sourceClient} + + + + + update iot_social_user + set del_flag = 1, + update_time = now() + where sys_user_id in + + #{sysUserId} + + and source_client in + + #{sourceClient} + + + diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ThingsModelMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ThingsModelMapper.xml new file mode 100644 index 00000000..b6486470 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ThingsModelMapper.xml @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select model_id, + model_name, + product_id, + product_name, + tenant_id, + tenant_name, + identifier, + type, + datatype, + temp_slave_id, + formula, + specs, + is_chart, + is_share_perm, + is_history, + reverse_formula, + reg_addr, + is_monitor, + del_flag, + bit_option, + value_type, + create_by, + create_time, + update_by, + update_time, + remark, + is_readonly, + model_order, + quantity, + code, + parse_type + from iot_things_model + + + + + + + + + + + + + + + + insert into iot_things_model + + + model_name, + + + product_id, + + + product_name, + + + tenant_id, + + + tenant_name, + + + identifier, + + + type, + + + datatype, + + + temp_slave_id, + + + formula, + + + specs, + + + is_chart, + + + is_share_perm, + + + is_history, + + + reverse_formula, + + + reg_addr, + + + is_monitor, + + + del_flag, + + + bit_option, + + + value_type, + + + create_by, + + + create_time, + + + update_by, + + + update_time, + + + remark, + + + is_readonly, + + + model_order, + + + quantity, + + + code, + + + parse_type, + + + + + #{modelName}, + + + #{productId}, + + + #{productName}, + + + #{tenantId}, + + + #{tenantName}, + + + #{identifier}, + + + #{type}, + + + #{datatype}, + + + #{tempSlaveId}, + + + #{formula}, + + + #{specs}, + + + #{isChart}, + + + #{isSharePerm}, + + + #{isHistory}, + + + #{reverseFormula}, + + + #{regAddr}, + + + #{isMonitor}, + + + #{delFlag}, + + + #{bitOption}, + + + #{valueType}, + + + #{createBy}, + + + #{createTime}, + + + #{updateBy}, + + + #{updateTime}, + + + #{remark}, + + + #{isReadonly}, + + + #{modelOrder}, + + + #{quantity}, + + + #{code}, + + + #{parseType}, + + + + + + insert into iot_things_model ( + model_name,product_id,product_name,tenant_id,tenant_name,identifier,type, + datatype,specs,is_chart,is_share_perm,is_history,is_monitor,is_readonly,create_by,create_time,temp_slave_id,formula, + reverse_formula,reg_addr,bit_option,value_type,quantity,code,parse_type + ) + values + + + #{model.modelName},#{model.productId},#{model.productName},#{model.tenantId},#{model.tenantName} + ,#{model.identifier},#{model.type},#{model.datatype},#{model.specs} + ,#{model.isChart},#{model.isSharePerm},#{model.isHistory},#{model.isMonitor},#{model.isReadonly},#{model.createBy},#{model.createTime}, + #{model.tempSlaveId},#{model.formula},#{model.reverseFormula},#{model.regAddr}, + #{model.bitOption},#{model.valueType},#{model.quantity},#{model.code},#{model.parseType} + + + + + + update iot_things_model + + + model_name = #{modelName}, + + + product_id = #{productId}, + + + product_name = #{productName}, + + + tenant_id = #{tenantId}, + + + tenant_name = #{tenantName}, + + + identifier = #{identifier}, + + + type = #{type}, + + + datatype = #{datatype}, + + + temp_slave_id = #{tempSlaveId}, + + + formula = #{formula}, + + + specs = #{specs}, + + + is_chart = #{isChart}, + + + is_share_perm = #{isSharePerm}, + + + is_history = #{isHistory}, + + + reverse_formula = #{reverseFormula}, + + + reg_addr = #{regAddr}, + + + is_monitor = #{isMonitor}, + + + del_flag = #{delFlag}, + + + bit_option = #{bitOption}, + + + value_type = #{valueType}, + + + create_by = #{createBy}, + + + create_time = #{createTime}, + + + update_by = #{updateBy}, + + + update_time = #{updateTime}, + + + remark = #{remark}, + + + is_readonly = #{isReadonly}, + + + model_order = #{modelOrder}, + + + quantity =#{quantity}, + + + code = #{code}, + + + parse_type= #{parseType}, + + + where model_id = #{modelId} + + + + delete + from iot_things_model + where model_id = #{modelId} + + + + delete from iot_things_model where model_id in + + #{modelId} + + + + + + + + + delete from iot_things_model where product_id = #{productId} + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ThingsModelTemplateMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ThingsModelTemplateMapper.xml new file mode 100644 index 00000000..ebdf5249 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/ThingsModelTemplateMapper.xml @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select template_id, template_name, tenant_id, tenant_name, identifier, type, datatype, specs, is_sys, is_readonly, is_chart,is_share_perm, is_history, temp_slave_id, is_monitor, formula, del_flag, reverse_formula, create_by, reg_addr, create_time, bit_option, update_by, value_type, update_time, remark, model_order,is_params,quantity,code,parse_type from iot_things_model_template + + + + + + + + + + insert into iot_things_model_template + + template_name, + tenant_id, + tenant_name, + identifier, + type, + datatype, + specs, + is_sys, + is_readonly, + is_chart, + is_share_perm, + is_history, + temp_slave_id, + is_monitor, + formula, + del_flag, + reverse_formula, + create_by, + reg_addr, + create_time, + bit_option, + update_by, + value_type, + update_time, + remark, + model_order, + is_params, + quantity, + code, + parse_type, + + + #{templateName}, + #{tenantId}, + #{tenantName}, + #{identifier}, + #{type}, + #{datatype}, + #{specs}, + #{isSys}, + #{isReadonly}, + #{isChart}, + #{isSharePerm}, + #{isHistory}, + #{tempSlaveId}, + #{isMonitor}, + #{formula}, + #{delFlag}, + #{reverseFormula}, + #{createBy}, + #{regAddr}, + #{createTime}, + #{bitOption}, + #{updateBy}, + #{valueType}, + #{updateTime}, + #{remark}, + #{modelOrder}, + #{isParams}, + #{quantity}, + #{code}, + #{parseType}, + + + + + update iot_things_model_template + + template_name = #{templateName}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + identifier = #{identifier}, + type = #{type}, + datatype = #{datatype}, + specs = #{specs}, + is_sys = #{isSys}, + is_readonly = #{isReadonly}, + is_chart = #{isChart}, + is_share_perm = #{isSharePerm}, + is_history = #{isHistory}, + temp_slave_id = #{tempSlaveId}, + is_Monitor = #{isMonitor}, + formula = #{formula}, + del_flag = #{delFlag}, + reverse_formula = #{reverseFormula}, + create_by = #{createBy}, + reg_addr = #{regAddr}, + create_time = #{createTime}, + bit_option = #{bitOption}, + update_by = #{updateBy}, + value_type = #{valueType}, + update_time = #{updateTime}, + remark = #{remark}, + model_order = #{modelOrder}, + is_params = #{isParams}, + quantity=#{quantity}, + code = #{code}, + parse_type = #{parseType}, + + where template_id = #{templateId} + + + + update iot_things_model_template + + update_time = #{updateTime}, + temp_slave_id = #{tempSlaveId}, + old_temp_slave_id = #{oldTempSlaveId} + + where temp_slave_id = #{oldTempSlaveId} + + + + delete from iot_things_model_template where template_id = #{templateId} + + + + delete from iot_things_model_template + where temp_slave_id like concat(#{templateId},'#%') + + + + delete from iot_things_model_template where template_id in + + #{templateId} + + + + + diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/VarTempMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/VarTempMapper.xml new file mode 100644 index 00000000..87cdee16 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/VarTempMapper.xml @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + select template_id, + template_name, + type, + polling_method, + slave_total, + point_total, + share, + create_time, + create_by, + update_time, + update_by, + user_id + from iot_var_temp + + + + + + + + insert into iot_var_temp + + + template_name, + + + type, + + + polling_method, + + + slave_total, + + + point_total, + + + share, + + + create_time, + + + create_by, + + + update_time, + + + update_by, + + + user_id, + + + + + #{templateName}, + + + #{type}, + + + #{pollingMethod}, + + + #{slaveTotal}, + + + #{pointTotal}, + + + #{share}, + + + #{createTime}, + + + #{createBy}, + + + #{updateTime}, + + + #{updateBy}, + + + #{userId}, + + + + + + update iot_var_temp + + + template_name = #{templateName}, + + + type = #{type}, + + + polling_method = #{pollingMethod}, + + + slave_total = #{slaveTotal}, + + + point_total = #{pointTotal}, + + + share = #{share}, + + + create_time = #{createTime}, + + + create_by = #{createBy}, + + + update_time = #{updateTime}, + + + update_by = #{updateBy}, + + + user_id = #{userId}, + + + where template_id = #{templateId} + + + + delete + from iot_var_temp + where template_id = #{templateId} + + + + delete from iot_var_temp where template_id in + + #{templateId} + + + + + + + diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/VarTempSalveMapper.xml b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/VarTempSalveMapper.xml new file mode 100644 index 00000000..d87db2be --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/mapper/iot/VarTempSalveMapper.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + select id, device_temp_id, slave_addr, slave_ip, slave_name, addr_start, addr_end,packet_length, timer, status, create_time, create_by, update_time, update_by, remark from iot_var_temp_salve + + + + + + + + insert into iot_var_temp_salve + + device_temp_id, + slave_addr, + slave_ip, + slave_name, + addr_start, + addr_end, + packet_length, + timer, + status, + create_time, + create_by, + update_time, + update_by, + remark, + + + #{deviceTempId}, + #{slaveAddr}, + #{slaveIp}, + #{slaveName}, + #{addrStart}, + #{addrEnd}, + #{packetLength}, + #{timer}, + #{status}, + #{createTime}, + #{createBy}, + #{updateTime}, + #{updateBy}, + #{remark}, + + + + + update iot_var_temp_salve + + device_temp_id = #{deviceTempId}, + slave_addr = #{slaveAddr}, + slave_ip = #{slaveIp}, + slave_name = #{slaveName}, + addr_start = #{addrStart}, + addr_end = #{addrEnd}, + packet_length = #{packetLength}, + timer = #{timer}, + status = #{status}, + create_time = #{createTime}, + create_by = #{createBy}, + update_time = #{updateTime}, + update_by = #{updateBy}, + remark = #{remark}, + + where id = #{id} + + + + delete from iot_var_temp_salve where id = #{id} + + + + delete from iot_var_temp_salve where id in + + #{id} + + + + + delete from iot_var_temp_salve where device_temp_id in + + #{id} + + + + + + + \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap-theme.css b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap-theme.css new file mode 100644 index 00000000..ea33f76a --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap-theme.css @@ -0,0 +1,587 @@ +/*! + * Bootstrap v3.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +.btn-default, +.btn-primary, +.btn-success, +.btn-info, +.btn-warning, +.btn-danger { + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); +} +.btn-default:active, +.btn-primary:active, +.btn-success:active, +.btn-info:active, +.btn-warning:active, +.btn-danger:active, +.btn-default.active, +.btn-primary.active, +.btn-success.active, +.btn-info.active, +.btn-warning.active, +.btn-danger.active { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn-default.disabled, +.btn-primary.disabled, +.btn-success.disabled, +.btn-info.disabled, +.btn-warning.disabled, +.btn-danger.disabled, +.btn-default[disabled], +.btn-primary[disabled], +.btn-success[disabled], +.btn-info[disabled], +.btn-warning[disabled], +.btn-danger[disabled], +fieldset[disabled] .btn-default, +fieldset[disabled] .btn-primary, +fieldset[disabled] .btn-success, +fieldset[disabled] .btn-info, +fieldset[disabled] .btn-warning, +fieldset[disabled] .btn-danger { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-default .badge, +.btn-primary .badge, +.btn-success .badge, +.btn-info .badge, +.btn-warning .badge, +.btn-danger .badge { + text-shadow: none; +} +.btn:active, +.btn.active { + background-image: none; +} +.btn-default { + background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); + background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); + background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #dbdbdb; + text-shadow: 0 1px 0 #fff; + border-color: #ccc; +} +.btn-default:hover, +.btn-default:focus { + background-color: #e0e0e0; + background-position: 0 -15px; +} +.btn-default:active, +.btn-default.active { + background-color: #e0e0e0; + border-color: #dbdbdb; +} +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #e0e0e0; + background-image: none; +} +.btn-primary { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); + background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #245580; +} +.btn-primary:hover, +.btn-primary:focus { + background-color: #265a88; + background-position: 0 -15px; +} +.btn-primary:active, +.btn-primary.active { + background-color: #265a88; + border-color: #245580; +} +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #265a88; + background-image: none; +} +.btn-success { + background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); + background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); + background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #3e8f3e; +} +.btn-success:hover, +.btn-success:focus { + background-color: #419641; + background-position: 0 -15px; +} +.btn-success:active, +.btn-success.active { + background-color: #419641; + border-color: #3e8f3e; +} +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #419641; + background-image: none; +} +.btn-info { + background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); + background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); + background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #28a4c9; +} +.btn-info:hover, +.btn-info:focus { + background-color: #2aabd2; + background-position: 0 -15px; +} +.btn-info:active, +.btn-info.active { + background-color: #2aabd2; + border-color: #28a4c9; +} +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #2aabd2; + background-image: none; +} +.btn-warning { + background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); + background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #e38d13; +} +.btn-warning:hover, +.btn-warning:focus { + background-color: #eb9316; + background-position: 0 -15px; +} +.btn-warning:active, +.btn-warning.active { + background-color: #eb9316; + border-color: #e38d13; +} +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #eb9316; + background-image: none; +} +.btn-danger { + background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); + background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); + background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #b92c28; +} +.btn-danger:hover, +.btn-danger:focus { + background-color: #c12e2a; + background-position: 0 -15px; +} +.btn-danger:active, +.btn-danger.active { + background-color: #c12e2a; + border-color: #b92c28; +} +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #c12e2a; + background-image: none; +} +.thumbnail, +.img-thumbnail { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); + background-repeat: repeat-x; + background-color: #e8e8e8; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); + background-repeat: repeat-x; + background-color: #2e6da4; +} +.navbar-default { + background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); + background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#f8f8f8)); + background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); + background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); + background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); + background-repeat: repeat-x; + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); +} +.navbar-brand, +.navbar-nav > li > a { + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); +} +.navbar-inverse { + background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); + background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); + background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + border-radius: 4px; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); + background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); + background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); + background-repeat: repeat-x; + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); +} +.navbar-inverse .navbar-brand, +.navbar-inverse .navbar-nav > li > a { + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} +.navbar-static-top, +.navbar-fixed-top, +.navbar-fixed-bottom { + border-radius: 0; +} +@media (max-width: 767px) { + .navbar .navbar-nav .open .dropdown-menu > .active > a, + .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); + background-repeat: repeat-x; + } +} +.alert { + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); +} +.alert-success { + background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); + background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); + background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); + background-repeat: repeat-x; + border-color: #b2dba1; +} +.alert-info { + background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); + background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); + background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); + background-repeat: repeat-x; + border-color: #9acfea; +} +.alert-warning { + background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); + background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); + background-repeat: repeat-x; + border-color: #f5e79e; +} +.alert-danger { + background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); + background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); + background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); + background-repeat: repeat-x; + border-color: #dca7a7; +} +.progress { + background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); + background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); + background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); + background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-success { + background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); + background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); + background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-info { + background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); + background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); + background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-warning { + background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); + background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-danger { + background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); + background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); + background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.list-group { + border-radius: 4px; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + text-shadow: 0 -1px 0 #286090; + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); + background-repeat: repeat-x; + border-color: #2b669a; +} +.list-group-item.active .badge, +.list-group-item.active:hover .badge, +.list-group-item.active:focus .badge { + text-shadow: none; +} +.panel { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} +.panel-default > .panel-heading { + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); + background-repeat: repeat-x; +} +.panel-primary > .panel-heading { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); + background-repeat: repeat-x; +} +.panel-success > .panel-heading { + background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); + background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); + background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); + background-repeat: repeat-x; +} +.panel-info > .panel-heading { + background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); + background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); + background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); + background-repeat: repeat-x; +} +.panel-warning > .panel-heading { + background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); + background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); + background-repeat: repeat-x; +} +.panel-danger > .panel-heading { + background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); + background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); + background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); + background-repeat: repeat-x; +} +.well { + background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); + background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); + background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); + background-repeat: repeat-x; + border-color: #dcdcdc; + -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); +} +/*# sourceMappingURL=bootstrap-theme.css.map */ \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap-theme.css.map b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap-theme.css.map new file mode 100644 index 00000000..949d0973 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap-theme.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["bootstrap-theme.css","less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"AAAA;;;;GAIG;ACiBH;;;;;;EAME,yCAAA;EC2CA,4FAAA;EACQ,oFAAA;CFzDT;ACkBC;;;;;;;;;;;;ECsCA,yDAAA;EACQ,iDAAA;CF1CT;ACQC;;;;;;;;;;;;;;;;;;ECiCA,yBAAA;EACQ,iBAAA;CFrBT;AC7BD;;;;;;EAuBI,kBAAA;CDcH;AC2BC;;EAEE,uBAAA;CDzBH;AC8BD;EEvEI,sEAAA;EACA,iEAAA;EACA,2FAAA;EAAA,oEAAA;EACA,uHAAA;EClBF,oEAAA;EH8CA,4BAAA;EACA,sBAAA;EAyCA,0BAAA;EACA,mBAAA;CDtBD;AClBC;;EAEE,0BAAA;EACA,6BAAA;CDoBH;ACjBC;;EAEE,0BAAA;EACA,sBAAA;CDmBH;ACbG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CD2BL;ACPD;EE5EI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EClBF,oEAAA;EH8CA,4BAAA;EACA,sBAAA;CD4DD;AC1DC;;EAEE,0BAAA;EACA,6BAAA;CD4DH;ACzDC;;EAEE,0BAAA;EACA,sBAAA;CD2DH;ACrDG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CDmEL;AC9CD;EE7EI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EClBF,oEAAA;EH8CA,4BAAA;EACA,sBAAA;CDoGD;AClGC;;EAEE,0BAAA;EACA,6BAAA;CDoGH;ACjGC;;EAEE,0BAAA;EACA,sBAAA;CDmGH;AC7FG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CD2GL;ACrFD;EE9EI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EClBF,oEAAA;EH8CA,4BAAA;EACA,sBAAA;CD4ID;AC1IC;;EAEE,0BAAA;EACA,6BAAA;CD4IH;ACzIC;;EAEE,0BAAA;EACA,sBAAA;CD2IH;ACrIG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CDmJL;AC5HD;EE/EI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EClBF,oEAAA;EH8CA,4BAAA;EACA,sBAAA;CDoLD;AClLC;;EAEE,0BAAA;EACA,6BAAA;CDoLH;ACjLC;;EAEE,0BAAA;EACA,sBAAA;CDmLH;AC7KG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CD2LL;ACnKD;EEhFI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EClBF,oEAAA;EH8CA,4BAAA;EACA,sBAAA;CD4ND;AC1NC;;EAEE,0BAAA;EACA,6BAAA;CD4NH;ACzNC;;EAEE,0BAAA;EACA,sBAAA;CD2NH;ACrNG;;;;;;;;;;;;;;;;;;EAME,0BAAA;EACA,uBAAA;CDmOL;ACpMD;;ECtCE,mDAAA;EACQ,2CAAA;CF8OT;AC/LD;;EEjGI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;EFgGF,0BAAA;CDqMD;ACnMD;;;EEtGI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;EFsGF,0BAAA;CDyMD;AChMD;EEnHI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;ECnBF,oEAAA;EHqIA,mBAAA;ECrEA,4FAAA;EACQ,oFAAA;CF4QT;AC3MD;;EEnHI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;ED6CF,yDAAA;EACQ,iDAAA;CFsRT;ACxMD;;EAEE,+CAAA;CD0MD;ACtMD;EEtII,sEAAA;EACA,iEAAA;EACA,2FAAA;EAAA,oEAAA;EACA,uHAAA;EACA,4BAAA;ECnBF,oEAAA;EHwJA,mBAAA;CD4MD;AC/MD;;EEtII,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;ED6CF,wDAAA;EACQ,gDAAA;CF6ST;ACzND;;EAYI,0CAAA;CDiNH;AC5MD;;;EAGE,iBAAA;CD8MD;AC1MD;EAEI;;;IAGE,YAAA;IEnKF,yEAAA;IACA,oEAAA;IACA,8FAAA;IAAA,uEAAA;IACA,uHAAA;IACA,4BAAA;GH+WD;CACF;ACrMD;EACE,8CAAA;EC/HA,2FAAA;EACQ,mFAAA;CFuUT;AC7LD;EE5LI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;EFoLF,sBAAA;CDyMD;ACpMD;EE7LI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;EFoLF,sBAAA;CDiND;AC3MD;EE9LI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;EFoLF,sBAAA;CDyND;AClND;EE/LI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;EFoLF,sBAAA;CDiOD;AClND;EEvMI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;CH4ZH;AC/MD;EEjNI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;CHmaH;ACrND;EElNI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;CH0aH;AC3ND;EEnNI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;CHibH;ACjOD;EEpNI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;CHwbH;ACvOD;EErNI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;CH+bH;AC1OD;EExLI,8MAAA;EACA,yMAAA;EACA,sMAAA;CHqaH;ACtOD;EACE,mBAAA;EClLA,mDAAA;EACQ,2CAAA;CF2ZT;ACvOD;;;EAGE,8BAAA;EEzOE,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;EFuOF,sBAAA;CD6OD;AClPD;;;EAQI,kBAAA;CD+OH;ACrOD;ECvME,kDAAA;EACQ,0CAAA;CF+aT;AC/ND;EElQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;CHoeH;ACrOD;EEnQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;CH2eH;AC3OD;EEpQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;CHkfH;ACjPD;EErQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;CHyfH;ACvPD;EEtQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;CHggBH;AC7PD;EEvQI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;CHugBH;AC7PD;EE9QI,yEAAA;EACA,oEAAA;EACA,8FAAA;EAAA,uEAAA;EACA,uHAAA;EACA,4BAAA;EF4QF,sBAAA;EC/NA,0FAAA;EACQ,kFAAA;CFmeT","file":"bootstrap-theme.css","sourcesContent":["/*!\n * Bootstrap v3.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default.disabled,\n.btn-primary.disabled,\n.btn-success.disabled,\n.btn-info.disabled,\n.btn-warning.disabled,\n.btn-danger.disabled,\n.btn-default[disabled],\n.btn-primary[disabled],\n.btn-success[disabled],\n.btn-info[disabled],\n.btn-warning[disabled],\n.btn-danger[disabled],\nfieldset[disabled] .btn-default,\nfieldset[disabled] .btn-primary,\nfieldset[disabled] .btn-success,\nfieldset[disabled] .btn-info,\nfieldset[disabled] .btn-warning,\nfieldset[disabled] .btn-danger {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n text-shadow: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n}\n.btn-default {\n background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #dbdbdb;\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n background-color: #e0e0e0;\n background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n background-color: #e0e0e0;\n border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n background-color: #e0e0e0;\n background-image: none;\n}\n.btn-primary {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n background-color: #265a88;\n background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n background-color: #265a88;\n border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n background-color: #265a88;\n background-image: none;\n}\n.btn-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n background-color: #419641;\n background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n background-color: #419641;\n border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n background-color: #419641;\n background-image: none;\n}\n.btn-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n background-color: #2aabd2;\n background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n background-color: #2aabd2;\n border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n background-color: #2aabd2;\n background-image: none;\n}\n.btn-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n background-color: #eb9316;\n background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n background-color: #eb9316;\n border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n background-color: #eb9316;\n background-image: none;\n}\n.btn-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n background-color: #c12e2a;\n background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n background-color: #c12e2a;\n border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n background-color: #c12e2a;\n background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-repeat: repeat-x;\n background-color: #e8e8e8;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-repeat: repeat-x;\n background-color: #2e6da4;\n}\n.navbar-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n background-repeat: repeat-x;\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n.navbar-inverse {\n background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);\n background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);\n background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n background-repeat: repeat-x;\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n@media (max-width: 767px) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-repeat: repeat-x;\n }\n}\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.alert-success {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n background-repeat: repeat-x;\n border-color: #b2dba1;\n}\n.alert-info {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n background-repeat: repeat-x;\n border-color: #9acfea;\n}\n.alert-warning {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n background-repeat: repeat-x;\n border-color: #f5e79e;\n}\n.alert-danger {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n background-repeat: repeat-x;\n border-color: #dca7a7;\n}\n.progress {\n background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.list-group {\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 #286090;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n background-repeat: repeat-x;\n border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n text-shadow: none;\n}\n.panel {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.panel-default > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-primary > .panel-heading {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-success > .panel-heading {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-info > .panel-heading {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-warning > .panel-heading {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-danger > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n background-repeat: repeat-x;\n}\n.well {\n background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n background-repeat: repeat-x;\n border-color: #dcdcdc;\n -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */","// stylelint-disable selector-no-qualifying-type, selector-max-compound-selectors\n\n/*!\n * Bootstrap v3.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);\n @shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0, 0, 0, .125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n .box-shadow(none);\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default {\n .btn-styles(@btn-default-bg);\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0, 0, 0, .075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0, 0, 0, .075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, .25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n border-radius: @navbar-border-radius;\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0, 0, 0, .25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, .2);\n @shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0, 0, 0, .075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0, 0, 0, .05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);\n .box-shadow(@shadow);\n}\n","// stylelint-disable indentation, property-no-vendor-prefix, selector-no-vendor-prefix\n\n// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n word-wrap: break-word;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// stylelint-disable value-no-vendor-prefix, selector-max-id\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\", argb(@start-color), argb(@end-color))); // IE9 and down\n background-repeat: repeat-x;\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\", argb(@start-color), argb(@end-color))); // IE9 and down\n background-repeat: repeat-x;\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\", argb(@start-color), argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n background-repeat: no-repeat;\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\", argb(@start-color), argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n background-repeat: no-repeat;\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255, 255, 255, .15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap-theme.min.css b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap-theme.min.css new file mode 100644 index 00000000..2a69f48c --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap-theme.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x;background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x;background-color:#2e6da4}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} +/*# sourceMappingURL=bootstrap-theme.min.css.map */ \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap-theme.min.css.map b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap-theme.min.css.map new file mode 100644 index 00000000..5d75106e --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap-theme.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["bootstrap-theme.css","dist/css/bootstrap-theme.css","less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"AAAA;;;;ACUA,YCWA,aDbA,UAFA,aACA,aAEA,aCkBE,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,qBAAA,CAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,qBAAA,CAAA,EAAA,IAAA,IAAA,iBF7CV,mBANA,mBACA,oBCWE,oBDRF,iBANA,iBAIA,oBANA,oBAOA,oBANA,oBAQA,oBANA,oBEmDE,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBFpCV,qBAMA,sBCJE,sBDDF,uBAHA,mBAMA,oBARA,sBAMA,uBALA,sBAMA,uBAJA,sBAMA,uBAOA,+BALA,gCAGA,6BAFA,gCACA,gCAEA,gCEwBE,mBAAA,KACQ,WAAA,KFfV,mBCnCA,oBDiCA,iBAFA,oBACA,oBAEA,oBCXI,YAAA,KDgBJ,YCyBE,YAEE,iBAAA,KAKJ,aEvEI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GClBF,OAAA,0DH8CA,kBAAA,SACA,aAAA,QAyCA,YAAA,EAAA,IAAA,EAAA,KACA,aAAA,KDnBF,mBCrBE,mBAEE,iBAAA,QACA,oBAAA,EAAA,MDuBJ,oBCpBE,oBAEE,iBAAA,QACA,aAAA,QAMA,sBD8BJ,6BANA,4BAGA,6BANA,4BAHA,4BAFA,uBAeA,8BANA,6BAGA,8BANA,6BAHA,6BAFA,gCAeA,uCANA,sCAGA,uCANA,sCAHA,sCCdM,iBAAA,QACA,iBAAA,KAoBN,aE5EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GClBF,OAAA,0DH8CA,kBAAA,SACA,aAAA,QDgEF,mBC9DE,mBAEE,iBAAA,QACA,oBAAA,EAAA,MDgEJ,oBC7DE,oBAEE,iBAAA,QACA,aAAA,QAMA,sBDuEJ,6BANA,4BAGA,6BANA,4BAHA,4BAFA,uBAeA,8BANA,6BAGA,8BANA,6BAHA,6BAFA,gCAeA,uCANA,sCAGA,uCANA,sCAHA,sCCvDM,iBAAA,QACA,iBAAA,KAqBN,aE7EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GClBF,OAAA,0DH8CA,kBAAA,SACA,aAAA,QDyGF,mBCvGE,mBAEE,iBAAA,QACA,oBAAA,EAAA,MDyGJ,oBCtGE,oBAEE,iBAAA,QACA,aAAA,QAMA,sBDgHJ,6BANA,4BAGA,6BANA,4BAHA,4BAFA,uBAeA,8BANA,6BAGA,8BANA,6BAHA,6BAFA,gCAeA,uCANA,sCAGA,uCANA,sCAHA,sCChGM,iBAAA,QACA,iBAAA,KAsBN,UE9EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GClBF,OAAA,0DH8CA,kBAAA,SACA,aAAA,QDkJF,gBChJE,gBAEE,iBAAA,QACA,oBAAA,EAAA,MDkJJ,iBC/IE,iBAEE,iBAAA,QACA,aAAA,QAMA,mBDyJJ,0BANA,yBAGA,0BANA,yBAHA,yBAFA,oBAeA,2BANA,0BAGA,2BANA,0BAHA,0BAFA,6BAeA,oCANA,mCAGA,oCANA,mCAHA,mCCzIM,iBAAA,QACA,iBAAA,KAuBN,aE/EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GClBF,OAAA,0DH8CA,kBAAA,SACA,aAAA,QD2LF,mBCzLE,mBAEE,iBAAA,QACA,oBAAA,EAAA,MD2LJ,oBCxLE,oBAEE,iBAAA,QACA,aAAA,QAMA,sBDkMJ,6BANA,4BAGA,6BANA,4BAHA,4BAFA,uBAeA,8BANA,6BAGA,8BANA,6BAHA,6BAFA,gCAeA,uCANA,sCAGA,uCANA,sCAHA,sCClLM,iBAAA,QACA,iBAAA,KAwBN,YEhFI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GClBF,OAAA,0DH8CA,kBAAA,SACA,aAAA,QDoOF,kBClOE,kBAEE,iBAAA,QACA,oBAAA,EAAA,MDoOJ,mBCjOE,mBAEE,iBAAA,QACA,aAAA,QAMA,qBD2OJ,4BANA,2BAGA,4BANA,2BAHA,2BAFA,sBAeA,6BANA,4BAGA,6BANA,4BAHA,4BAFA,+BAeA,sCANA,qCAGA,sCANA,qCAHA,qCC3NM,iBAAA,QACA,iBAAA,KD2ON,eC5MA,WCtCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBFsPV,0BCvMA,0BEjGI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFgGF,iBAAA,QAEF,yBD6MA,+BADA,+BGlTI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsGF,iBAAA,QASF,gBEnHI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,kBAAA,SCnBF,OAAA,0DHqIA,cAAA,ICrEA,mBAAA,MAAA,EAAA,IAAA,EAAA,qBAAA,CAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,qBAAA,CAAA,EAAA,IAAA,IAAA,iBFuRV,sCCtNA,oCEnHI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD6CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD8EV,cDoNA,iBClNE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEtII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,kBAAA,SCnBF,OAAA,0DHwJA,cAAA,IDyNF,sCC5NA,oCEtII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD6CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDoFV,8BDuOA,iCC3NI,YAAA,EAAA,KAAA,EAAA,gBDgOJ,qBADA,kBC1NA,mBAGE,cAAA,EAIF,yBAEI,mDDwNF,yDADA,yDCpNI,MAAA,KEnKF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UF2KJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC/HA,mBAAA,MAAA,EAAA,IAAA,EAAA,qBAAA,CAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,qBAAA,CAAA,EAAA,IAAA,IAAA,gBD0IV,eE5LI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoLF,aAAA,QAKF,YE7LI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoLF,aAAA,QAMF,eE9LI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoLF,aAAA,QAOF,cE/LI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoLF,aAAA,QAeF,UEvMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6MJ,cEjNI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8MJ,sBElNI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,mBEnNI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFgNJ,sBEpNI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiNJ,qBErNI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFqNJ,sBExLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKF+LJ,YACE,cAAA,IClLA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDoLV,wBDiQA,8BADA,8BC7PE,YAAA,EAAA,KAAA,EAAA,QEzOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuOF,aAAA,QALF,+BD6QA,qCADA,qCCpQI,YAAA,KAUJ,OCvME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBDgNV,8BElQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+PJ,8BEnQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFgQJ,8BEpQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiQJ,2BErQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFkQJ,8BEtQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFmQJ,6BEvQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0QJ,ME9QI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4QF,aAAA,QC/NA,mBAAA,MAAA,EAAA,IAAA,IAAA,eAAA,CAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,eAAA,CAAA,EAAA,IAAA,EAAA","sourcesContent":["/*!\n * Bootstrap v3.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default.disabled,\n.btn-primary.disabled,\n.btn-success.disabled,\n.btn-info.disabled,\n.btn-warning.disabled,\n.btn-danger.disabled,\n.btn-default[disabled],\n.btn-primary[disabled],\n.btn-success[disabled],\n.btn-info[disabled],\n.btn-warning[disabled],\n.btn-danger[disabled],\nfieldset[disabled] .btn-default,\nfieldset[disabled] .btn-primary,\nfieldset[disabled] .btn-success,\nfieldset[disabled] .btn-info,\nfieldset[disabled] .btn-warning,\nfieldset[disabled] .btn-danger {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n text-shadow: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n}\n.btn-default {\n background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #dbdbdb;\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n background-color: #e0e0e0;\n background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n background-color: #e0e0e0;\n border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n background-color: #e0e0e0;\n background-image: none;\n}\n.btn-primary {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n background-color: #265a88;\n background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n background-color: #265a88;\n border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n background-color: #265a88;\n background-image: none;\n}\n.btn-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n background-color: #419641;\n background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n background-color: #419641;\n border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n background-color: #419641;\n background-image: none;\n}\n.btn-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n background-color: #2aabd2;\n background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n background-color: #2aabd2;\n border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n background-color: #2aabd2;\n background-image: none;\n}\n.btn-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n background-color: #eb9316;\n background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n background-color: #eb9316;\n border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n background-color: #eb9316;\n background-image: none;\n}\n.btn-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n background-color: #c12e2a;\n background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n background-color: #c12e2a;\n border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n background-color: #c12e2a;\n background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-repeat: repeat-x;\n background-color: #e8e8e8;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-repeat: repeat-x;\n background-color: #2e6da4;\n}\n.navbar-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n background-repeat: repeat-x;\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n.navbar-inverse {\n background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);\n background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);\n background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n background-repeat: repeat-x;\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n@media (max-width: 767px) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-repeat: repeat-x;\n }\n}\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.alert-success {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n background-repeat: repeat-x;\n border-color: #b2dba1;\n}\n.alert-info {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n background-repeat: repeat-x;\n border-color: #9acfea;\n}\n.alert-warning {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n background-repeat: repeat-x;\n border-color: #f5e79e;\n}\n.alert-danger {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n background-repeat: repeat-x;\n border-color: #dca7a7;\n}\n.progress {\n background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.list-group {\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 #286090;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n background-repeat: repeat-x;\n border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n text-shadow: none;\n}\n.panel {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.panel-default > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-primary > .panel-heading {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-success > .panel-heading {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-info > .panel-heading {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-warning > .panel-heading {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-danger > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n background-repeat: repeat-x;\n}\n.well {\n background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n background-repeat: repeat-x;\n border-color: #dcdcdc;\n -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */","/*!\n * Bootstrap v3.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default.disabled,\n.btn-primary.disabled,\n.btn-success.disabled,\n.btn-info.disabled,\n.btn-warning.disabled,\n.btn-danger.disabled,\n.btn-default[disabled],\n.btn-primary[disabled],\n.btn-success[disabled],\n.btn-info[disabled],\n.btn-warning[disabled],\n.btn-danger[disabled],\nfieldset[disabled] .btn-default,\nfieldset[disabled] .btn-primary,\nfieldset[disabled] .btn-success,\nfieldset[disabled] .btn-info,\nfieldset[disabled] .btn-warning,\nfieldset[disabled] .btn-danger {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n text-shadow: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n}\n.btn-default {\n background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));\n background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #dbdbdb;\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n background-color: #e0e0e0;\n background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n background-color: #e0e0e0;\n border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n background-color: #e0e0e0;\n background-image: none;\n}\n.btn-primary {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));\n background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n background-color: #265a88;\n background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n background-color: #265a88;\n border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n background-color: #265a88;\n background-image: none;\n}\n.btn-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));\n background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n background-color: #419641;\n background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n background-color: #419641;\n border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n background-color: #419641;\n background-image: none;\n}\n.btn-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));\n background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n background-color: #2aabd2;\n background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n background-color: #2aabd2;\n border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n background-color: #2aabd2;\n background-image: none;\n}\n.btn-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n background-color: #eb9316;\n background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n background-color: #eb9316;\n border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n background-color: #eb9316;\n background-image: none;\n}\n.btn-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));\n background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n background-color: #c12e2a;\n background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n background-color: #c12e2a;\n border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n background-color: #c12e2a;\n background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-repeat: repeat-x;\n background-color: #e8e8e8;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-repeat: repeat-x;\n background-color: #2e6da4;\n}\n.navbar-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#f8f8f8));\n background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));\n background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n background-repeat: repeat-x;\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n.navbar-inverse {\n background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);\n background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));\n background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));\n background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n background-repeat: repeat-x;\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n@media (max-width: 767px) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-repeat: repeat-x;\n }\n}\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.alert-success {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));\n background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n background-repeat: repeat-x;\n border-color: #b2dba1;\n}\n.alert-info {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));\n background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n background-repeat: repeat-x;\n border-color: #9acfea;\n}\n.alert-warning {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n background-repeat: repeat-x;\n border-color: #f5e79e;\n}\n.alert-danger {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));\n background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n background-repeat: repeat-x;\n border-color: #dca7a7;\n}\n.progress {\n background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));\n background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));\n background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));\n background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));\n background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));\n background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n background-repeat: repeat-x;\n}\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.list-group {\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 #286090;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));\n background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n background-repeat: repeat-x;\n border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n text-shadow: none;\n}\n.panel {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.panel-default > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-primary > .panel-heading {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-success > .panel-heading {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));\n background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-info > .panel-heading {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));\n background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-warning > .panel-heading {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n background-repeat: repeat-x;\n}\n.panel-danger > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));\n background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n background-repeat: repeat-x;\n}\n.well {\n background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));\n background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n background-repeat: repeat-x;\n border-color: #dcdcdc;\n -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */","// stylelint-disable selector-no-qualifying-type, selector-max-compound-selectors\n\n/*!\n * Bootstrap v3.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);\n @shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0, 0, 0, .125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n .box-shadow(none);\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default {\n .btn-styles(@btn-default-bg);\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0, 0, 0, .075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0, 0, 0, .075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, .25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n border-radius: @navbar-border-radius;\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0, 0, 0, .25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, .2);\n @shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0, 0, 0, .075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0, 0, 0, .05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);\n .box-shadow(@shadow);\n}\n","// stylelint-disable indentation, property-no-vendor-prefix, selector-no-vendor-prefix\n\n// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n word-wrap: break-word;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// stylelint-disable value-no-vendor-prefix, selector-max-id\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\", argb(@start-color), argb(@end-color))); // IE9 and down\n background-repeat: repeat-x;\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\", argb(@start-color), argb(@end-color))); // IE9 and down\n background-repeat: repeat-x;\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\", argb(@start-color), argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n background-repeat: no-repeat;\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\", argb(@start-color), argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n background-repeat: no-repeat;\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255, 255, 255, .15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap.css b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap.css new file mode 100644 index 00000000..fcab4155 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap.css @@ -0,0 +1,6834 @@ +/*! + * Bootstrap v3.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: none; + text-decoration: underline; + -webkit-text-decoration: underline dotted; + -moz-text-decoration: underline dotted; + text-decoration: underline dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +mark { + background: #ff0; + color: #000; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-appearance: textfield; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + border: 0; + padding: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +@font-face { + font-family: "Glyphicons Halflings"; + src: url("../fonts/glyphicons-halflings-regular.eot"); + src: url("../fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"), url("../fonts/glyphicons-halflings-regular.woff2") format("woff2"), url("../fonts/glyphicons-halflings-regular.woff") format("woff"), url("../fonts/glyphicons-halflings-regular.ttf") format("truetype"), url("../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg"); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: "Glyphicons Halflings"; + font-style: normal; + font-weight: 400; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\002a"; +} +.glyphicon-plus:before { + content: "\002b"; +} +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-cd:before { + content: "\e201"; +} +.glyphicon-save-file:before { + content: "\e202"; +} +.glyphicon-open-file:before { + content: "\e203"; +} +.glyphicon-level-up:before { + content: "\e204"; +} +.glyphicon-copy:before { + content: "\e205"; +} +.glyphicon-paste:before { + content: "\e206"; +} +.glyphicon-alert:before { + content: "\e209"; +} +.glyphicon-equalizer:before { + content: "\e210"; +} +.glyphicon-king:before { + content: "\e211"; +} +.glyphicon-queen:before { + content: "\e212"; +} +.glyphicon-pawn:before { + content: "\e213"; +} +.glyphicon-bishop:before { + content: "\e214"; +} +.glyphicon-knight:before { + content: "\e215"; +} +.glyphicon-baby-formula:before { + content: "\e216"; +} +.glyphicon-tent:before { + content: "\26fa"; +} +.glyphicon-blackboard:before { + content: "\e218"; +} +.glyphicon-bed:before { + content: "\e219"; +} +.glyphicon-apple:before { + content: "\f8ff"; +} +.glyphicon-erase:before { + content: "\e221"; +} +.glyphicon-hourglass:before { + content: "\231b"; +} +.glyphicon-lamp:before { + content: "\e223"; +} +.glyphicon-duplicate:before { + content: "\e224"; +} +.glyphicon-piggy-bank:before { + content: "\e225"; +} +.glyphicon-scissors:before { + content: "\e226"; +} +.glyphicon-bitcoin:before { + content: "\e227"; +} +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} +.glyphicon-yen:before { + content: "\00a5"; +} +.glyphicon-jpy:before { + content: "\00a5"; +} +.glyphicon-ruble:before { + content: "\20bd"; +} +.glyphicon-rub:before { + content: "\20bd"; +} +.glyphicon-scale:before { + content: "\e230"; +} +.glyphicon-ice-lolly:before { + content: "\e231"; +} +.glyphicon-ice-lolly-tasted:before { + content: "\e232"; +} +.glyphicon-education:before { + content: "\e233"; +} +.glyphicon-option-horizontal:before { + content: "\e234"; +} +.glyphicon-option-vertical:before { + content: "\e235"; +} +.glyphicon-menu-hamburger:before { + content: "\e236"; +} +.glyphicon-modal-window:before { + content: "\e237"; +} +.glyphicon-oil:before { + content: "\e238"; +} +.glyphicon-grain:before { + content: "\e239"; +} +.glyphicon-sunglasses:before { + content: "\e240"; +} +.glyphicon-text-size:before { + content: "\e241"; +} +.glyphicon-text-color:before { + content: "\e242"; +} +.glyphicon-text-background:before { + content: "\e243"; +} +.glyphicon-object-align-top:before { + content: "\e244"; +} +.glyphicon-object-align-bottom:before { + content: "\e245"; +} +.glyphicon-object-align-horizontal:before { + content: "\e246"; +} +.glyphicon-object-align-left:before { + content: "\e247"; +} +.glyphicon-object-align-vertical:before { + content: "\e248"; +} +.glyphicon-object-align-right:before { + content: "\e249"; +} +.glyphicon-triangle-right:before { + content: "\e250"; +} +.glyphicon-triangle-left:before { + content: "\e251"; +} +.glyphicon-triangle-bottom:before { + content: "\e252"; +} +.glyphicon-triangle-top:before { + content: "\e253"; +} +.glyphicon-console:before { + content: "\e254"; +} +.glyphicon-superscript:before { + content: "\e255"; +} +.glyphicon-subscript:before { + content: "\e256"; +} +.glyphicon-menu-left:before { + content: "\e257"; +} +.glyphicon-menu-right:before { + content: "\e258"; +} +.glyphicon-menu-down:before { + content: "\e259"; +} +.glyphicon-menu-up:before { + content: "\e260"; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333333; + background-color: #fff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #337ab7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + display: inline-block; + max-width: 100%; + height: auto; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eeeeee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +[role="button"] { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: 400; + line-height: 1; + color: #777777; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +mark, +.mark { + padding: 0.2em; + background-color: #fcf8e3; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #777777; +} +.text-primary { + color: #337ab7; +} +a.text-primary:hover, +a.text-primary:focus { + color: #286090; +} +.text-success { + color: #3c763d; +} +a.text-success:hover, +a.text-success:focus { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover, +a.text-info:focus { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover, +a.text-warning:focus { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover, +a.text-danger:focus { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #337ab7; +} +a.bg-primary:hover, +a.bg-primary:focus { + background-color: #286090; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover, +a.bg-success:focus { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover, +a.bg-info:focus { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover, +a.bg-warning:focus { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover, +a.bg-danger:focus { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eeeeee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + list-style: none; + margin-left: -5px; +} +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: 700; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + clear: left; + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eeeeee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777777; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: "\2014 \00A0"; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eeeeee; + border-left: 0; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ""; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: "\00A0 \2014"; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 700; + -webkit-box-shadow: none; + box-shadow: none; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +.row { + margin-right: -15px; + margin-left: -15px; +} +.row-no-gutters { + margin-right: 0; + margin-left: 0; +} +.row-no-gutters [class*="col-"] { + padding-right: 0; + padding-left: 0; +} +.col-xs-1, +.col-sm-1, +.col-md-1, +.col-lg-1, +.col-xs-2, +.col-sm-2, +.col-md-2, +.col-lg-2, +.col-xs-3, +.col-sm-3, +.col-md-3, +.col-lg-3, +.col-xs-4, +.col-sm-4, +.col-md-4, +.col-lg-4, +.col-xs-5, +.col-sm-5, +.col-md-5, +.col-lg-5, +.col-xs-6, +.col-sm-6, +.col-md-6, +.col-lg-6, +.col-xs-7, +.col-sm-7, +.col-md-7, +.col-lg-7, +.col-xs-8, +.col-sm-8, +.col-md-8, +.col-lg-8, +.col-xs-9, +.col-sm-9, +.col-md-9, +.col-lg-9, +.col-xs-10, +.col-sm-10, +.col-md-10, +.col-lg-10, +.col-xs-11, +.col-sm-11, +.col-md-11, +.col-lg-11, +.col-xs-12, +.col-sm-12, +.col-md-12, +.col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9, +.col-xs-10, +.col-xs-11, +.col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0%; +} +@media (min-width: 768px) { + .col-sm-1, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-10, + .col-sm-11, + .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 992px) { + .col-md-1, + .col-md-2, + .col-md-3, + .col-md-4, + .col-md-5, + .col-md-6, + .col-md-7, + .col-md-8, + .col-md-9, + .col-md-10, + .col-md-11, + .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 1200px) { + .col-lg-1, + .col-lg-2, + .col-lg-3, + .col-lg-4, + .col-lg-5, + .col-lg-6, + .col-lg-7, + .col-lg-8, + .col-lg-9, + .col-lg-10, + .col-lg-11, + .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0%; + } +} +table { + background-color: transparent; +} +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777777; + text-align: left; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #ddd; +} +.table .table { + background-color: #fff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover { + background-color: #f5f5f5; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +.table-responsive { + min-height: 0.01%; + overflow-x: auto; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: 700; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6); +} +.form-control::-moz-placeholder { + color: #999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999; +} +.form-control::-webkit-input-placeholder { + color: #999; +} +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #eeeeee; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"].form-control, + input[type="time"].form-control, + input[type="datetime-local"].form-control, + input[type="month"].form-control { + line-height: 34px; + } + input[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .input-group-sm input[type="date"], + .input-group-sm input[type="time"], + .input-group-sm input[type="datetime-local"], + .input-group-sm input[type="month"] { + line-height: 30px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .input-group-lg input[type="date"], + .input-group-lg input[type="time"], + .input-group-lg input[type="datetime-local"], + .input-group-lg input[type="month"] { + line-height: 46px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: 400; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-top: 4px \9; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: 400; + vertical-align: middle; + cursor: pointer; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +.form-control-static { + min-height: 34px; + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-right: 0; + padding-left: 0; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.form-group-sm select.form-control { + height: 30px; + line-height: 30px; +} +.form-group-sm textarea.form-control, +.form-group-sm select[multiple].form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 30px; + min-height: 32px; + padding: 6px 10px; + font-size: 12px; + line-height: 1.5; +} +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.form-group-lg select.form-control { + height: 46px; + line-height: 46px; +} +.form-group-lg textarea.form-control, +.form-group-lg select[multiple].form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 46px; + min-height: 38px; + padding: 11px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #3c763d; +} +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d; +} +.has-success .form-control-feedback { + color: #3c763d; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #8a6d3b; +} +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #8a6d3b; +} +.has-warning .form-control-feedback { + color: #8a6d3b; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + background-color: #f2dede; + border-color: #a94442; +} +.has-error .form-control-feedback { + color: #a94442; +} +.has-feedback label ~ .form-control-feedback { + top: 25px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + padding-top: 7px; + margin-bottom: 0; + text-align: right; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 11px; + font-size: 18px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + font-size: 12px; + } +} +.btn { + display: inline-block; + margin-bottom: 0; + font-weight: normal; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + background-image: none; + border: 1px solid transparent; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + border-radius: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #333; + text-decoration: none; +} +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + filter: alpha(opacity=65); + opacity: 0.65; + -webkit-box-shadow: none; + box-shadow: none; +} +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} +.btn-default:focus, +.btn-default.focus { + color: #333; + background-color: #e6e6e6; + border-color: #8c8c8c; +} +.btn-default:hover { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #333; + background-color: #e6e6e6; + background-image: none; + border-color: #adadad; +} +.btn-default:active:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + color: #333; + background-color: #d4d4d4; + border-color: #8c8c8c; +} +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus { + background-color: #fff; + border-color: #ccc; +} +.btn-default .badge { + color: #fff; + background-color: #333; +} +.btn-primary { + color: #fff; + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary:focus, +.btn-primary.focus { + color: #fff; + background-color: #286090; + border-color: #122b40; +} +.btn-primary:hover { + color: #fff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #fff; + background-color: #286090; + background-image: none; + border-color: #204d74; +} +.btn-primary:active:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + color: #fff; + background-color: #204d74; + border-color: #122b40; +} +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus { + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary .badge { + color: #337ab7; + background-color: #fff; +} +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:focus, +.btn-success.focus { + color: #fff; + background-color: #449d44; + border-color: #255625; +} +.btn-success:hover { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #fff; + background-color: #449d44; + background-image: none; + border-color: #398439; +} +.btn-success:active:hover, +.btn-success.active:hover, +.open > .dropdown-toggle.btn-success:hover, +.btn-success:active:focus, +.btn-success.active:focus, +.open > .dropdown-toggle.btn-success:focus, +.btn-success:active.focus, +.btn-success.active.focus, +.open > .dropdown-toggle.btn-success.focus { + color: #fff; + background-color: #398439; + border-color: #255625; +} +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #fff; +} +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:focus, +.btn-info.focus { + color: #fff; + background-color: #31b0d5; + border-color: #1b6d85; +} +.btn-info:hover { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #fff; + background-color: #31b0d5; + background-image: none; + border-color: #269abc; +} +.btn-info:active:hover, +.btn-info.active:hover, +.open > .dropdown-toggle.btn-info:hover, +.btn-info:active:focus, +.btn-info.active:focus, +.open > .dropdown-toggle.btn-info:focus, +.btn-info:active.focus, +.btn-info.active.focus, +.open > .dropdown-toggle.btn-info.focus { + color: #fff; + background-color: #269abc; + border-color: #1b6d85; +} +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #fff; +} +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:focus, +.btn-warning.focus { + color: #fff; + background-color: #ec971f; + border-color: #985f0d; +} +.btn-warning:hover { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #fff; + background-color: #ec971f; + background-image: none; + border-color: #d58512; +} +.btn-warning:active:hover, +.btn-warning.active:hover, +.open > .dropdown-toggle.btn-warning:hover, +.btn-warning:active:focus, +.btn-warning.active:focus, +.open > .dropdown-toggle.btn-warning:focus, +.btn-warning:active.focus, +.btn-warning.active.focus, +.open > .dropdown-toggle.btn-warning.focus { + color: #fff; + background-color: #d58512; + border-color: #985f0d; +} +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff; +} +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:focus, +.btn-danger.focus { + color: #fff; + background-color: #c9302c; + border-color: #761c19; +} +.btn-danger:hover { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #fff; + background-color: #c9302c; + background-image: none; + border-color: #ac2925; +} +.btn-danger:active:hover, +.btn-danger.active:hover, +.open > .dropdown-toggle.btn-danger:hover, +.btn-danger:active:focus, +.btn-danger.active:focus, +.open > .dropdown-toggle.btn-danger:focus, +.btn-danger:active.focus, +.btn-danger.active.focus, +.open > .dropdown-toggle.btn-danger.focus { + color: #fff; + background-color: #ac2925; + border-color: #761c19; +} +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} +.btn-link { + font-weight: 400; + color: #337ab7; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #23527c; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777777; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + -o-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility; + -webkit-transition-duration: 0.35s; + -o-transition-duration: 0.35s; + transition-duration: 0.35s; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid \9; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: 400; + line-height: 1.42857143; + color: #333333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #fff; + text-decoration: none; + background-color: #337ab7; + outline: 0; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #777777; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + right: 0; + left: auto; +} +.dropdown-menu-left { + right: auto; + left: 0; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777777; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px dashed; + border-bottom: 4px solid \9; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + .navbar-right .dropdown-menu-left { + right: auto; + left: 0; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn, +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + display: table-cell; + float: none; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-right: 0; + padding-left: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group .form-control:focus { + z-index: 3; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: 400; + line-height: 1; + color: #555555; + text-align: center; + background-color: #eeeeee; + border: 1px solid #ccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + z-index: 2; + margin-left: -1px; +} +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} +.nav > li.disabled > a { + color: #777777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eeeeee; + border-color: #337ab7; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eeeeee #eeeeee #ddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #fff; + background-color: #337ab7; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + -webkit-overflow-scrolling: touch; +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-brand { + float: left; + height: 50px; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-right: 15px; + margin-top: 8px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} +.navbar-form { + padding: 10px 15px; + margin-right: -15px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + margin-top: 8px; + margin-bottom: 8px; +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -15px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777; +} +.navbar-default .navbar-nav > li > a { + color: #777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555; + background-color: #e7e7e7; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ccc; + background-color: transparent; + } +} +.navbar-default .navbar-toggle { + border-color: #ddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #ddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #888; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-link { + color: #777; +} +.navbar-default .navbar-link:hover { + color: #333; +} +.navbar-default .btn-link { + color: #777; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; +} +.navbar-inverse { + background-color: #222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #9d9d9d; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #fff; + background-color: #080808; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #9d9d9d; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444; + background-color: transparent; + } +} +.navbar-inverse .navbar-toggle { + border-color: #333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-link { + color: #9d9d9d; +} +.navbar-inverse .navbar-link:hover { + color: #fff; +} +.navbar-inverse .btn-link { + color: #9d9d9d; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #fff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} +.breadcrumb > .active { + color: #777777; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #337ab7; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #23527c; + background-color: #eeeeee; + border-color: #ddd; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 3; + color: #fff; + cursor: default; + background-color: #337ab7; + border-color: #337ab7; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #777777; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: 0.2em 0.6em 0.3em; + font-size: 75%; + font-weight: 700; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25em; +} +a.label:hover, +a.label:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #777777; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} +.label-primary { + background-color: #337ab7; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #286090; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: middle; + background-color: #777777; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge, +.btn-group-xs > .btn .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #337ab7; + background-color: #fff; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding-top: 30px; + padding-bottom: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eeeeee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #d5d5d5; +} +.container .jumbotron, +.container-fluid .jumbotron { + padding-right: 15px; + padding-left: 15px; + border-radius: 6px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron, + .container-fluid .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: border 0.2s ease-in-out; + -o-transition: border 0.2s ease-in-out; + transition: border 0.2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-right: auto; + margin-left: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #337ab7; +} +.thumbnail .caption { + padding: 9px; + color: #333333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #337ab7; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-transition: width 0.6s ease; + -o-transition: width 0.6s ease; + transition: width 0.6s ease; +} +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + -webkit-background-size: 40px 40px; + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media, +.media-body { + overflow: hidden; + zoom: 1; +} +.media-body { + width: 10000px; +} +.media-object { + display: block; +} +.media-object.img-thumbnail { + max-width: none; +} +.media-right, +.media > .pull-right { + padding-left: 10px; +} +.media-left, +.media > .pull-left { + padding-right: 10px; +} +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} +.media-middle { + vertical-align: middle; +} +.media-bottom { + vertical-align: bottom; +} +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + padding-left: 0; + margin-bottom: 20px; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #777777; + cursor: not-allowed; + background-color: #eeeeee; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777777; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #c7ddef; +} +a.list-group-item, +button.list-group-item { + color: #555; +} +a.list-group-item .list-group-item-heading, +button.list-group-item .list-group-item-heading { + color: #333; +} +a.list-group-item:hover, +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { + color: #555; + text-decoration: none; + background-color: #f5f5f5; +} +button.list-group-item { + width: 100%; + text-align: left; +} +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} +a.list-group-item-success, +button.list-group-item-success { + color: #3c763d; +} +a.list-group-item-success .list-group-item-heading, +button.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +button.list-group-item-success.active, +a.list-group-item-success.active:hover, +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info, +button.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading, +button.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +button.list-group-item-info.active, +a.list-group-item-info.active:hover, +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} +a.list-group-item-warning, +button.list-group-item-warning { + color: #8a6d3b; +} +a.list-group-item-warning .list-group-item-heading, +button.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +button.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} +a.list-group-item-danger, +button.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading, +button.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +button.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-right: 15px; + padding-left: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #ddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + margin-bottom: 0; + border: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid #ddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #ddd; +} +.panel-default { + border-color: #ddd; +} +.panel-default > .panel-heading { + color: #333333; + background-color: #f5f5f5; + border-color: #ddd; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ddd; +} +.panel-default > .panel-heading .badge { + color: #f5f5f5; + background-color: #333333; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ddd; +} +.panel-primary { + border-color: #337ab7; +} +.panel-primary > .panel-heading { + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #337ab7; +} +.panel-primary > .panel-heading .badge { + color: #337ab7; + background-color: #fff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #337ab7; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-heading .badge { + color: #dff0d8; + background-color: #3c763d; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-heading .badge { + color: #d9edf7; + background-color: #31708f; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-heading .badge { + color: #fcf8e3; + background-color: #8a6d3b; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-heading .badge { + color: #f2dede; + background-color: #a94442; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ebccd1; +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: 0.2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: 0.5; +} +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +.modal-open { + overflow: hidden; +} +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); + -webkit-transition: -webkit-transform 0.3s ease-out; + -o-transition: -o-transform 0.3s ease-out; + transition: -webkit-transform 0.3s ease-out; + transition: transform 0.3s ease-out; + transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out, -o-transform 0.3s ease-out; +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + outline: 0; +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0; +} +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: 0.5; +} +.modal-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 15px; +} +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-weight: 400; + line-height: 1.42857143; + line-break: auto; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + font-size: 12px; + filter: alpha(opacity=0); + opacity: 0; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: 0.9; +} +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-left .tooltip-arrow { + right: 5px; + bottom: 0; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-weight: 400; + line-height: 1.42857143; + line-break: auto; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + font-size: 14px; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999999; + border-top-color: rgba(0, 0, 0, 0.25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999999; + border-right-color: rgba(0, 0, 0, 0.25); + border-left-width: 0; +} +.popover.right > .arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999999; + border-bottom-color: rgba(0, 0, 0, 0.25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999999; + border-left-color: rgba(0, 0, 0, 0.25); +} +.popover.left > .arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: 0.6s ease-in-out left; + -o-transition: 0.6s ease-in-out left; + transition: 0.6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +@media all and (transform-3d), (-webkit-transform-3d) { + .carousel-inner > .item { + -webkit-transition: -webkit-transform 0.6s ease-in-out; + -o-transition: -o-transform 0.6s ease-in-out; + transition: -webkit-transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out, -o-transform 0.6s ease-in-out; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; + } + .carousel-inner > .item.next, + .carousel-inner > .item.active.right { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + left: 0; + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + left: 0; + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + left: 0; + } +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); + background-color: rgba(0, 0, 0, 0); + filter: alpha(opacity=50); + opacity: 0.5; +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + outline: 0; + filter: alpha(opacity=90); + opacity: 0.9; +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; + margin-top: -10px; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + font-family: serif; + line-height: 1; +} +.carousel-control .icon-prev:before { + content: "\2039"; +} +.carousel-control .icon-next:before { + content: "\203a"; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -10px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -10px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -10px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-header:before, +.modal-header:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-header:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-right: auto; + margin-left: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +/*# sourceMappingURL=bootstrap.css.map */ \ No newline at end of file diff --git a/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap.css.map b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap.css.map new file mode 100644 index 00000000..caac3e61 --- /dev/null +++ b/springboot/fastbee-service/fastbee-iot-service/src/main/resources/static/oauth/css/bootstrap.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["bootstrap.css","less/normalize.less","less/print.less","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/mixins/reset-text.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,4EAA4E;ACK5E;EACE,wBAAA;EACA,2BAAA;EACA,+BAAA;CDHD;ACUD;EACE,UAAA;CDRD;ACqBD;;;;;;;;;;;;;EAaE,eAAA;CDnBD;AC2BD;;;;EAIE,sBAAA;EACA,yBAAA;CDzBD;ACiCD;EACE,cAAA;EACA,UAAA;CD/BD;ACuCD;;EAEE,cAAA;CDrCD;AC+CD;EACE,8BAAA;CD7CD;ACqDD;;EAEE,WAAA;CDnDD;AC8DD;EACE,oBAAA;EACA,2BAAA;EACA,0CAAA;EAAA,uCAAA;EAAA,kCAAA;CD5DD;ACmED;;EAEE,kBAAA;CDjED;ACwED;EACE,mBAAA;CDtED;AC8ED;EACE,eAAA;EACA,iBAAA;CD5ED;ACmFD;EACE,iBAAA;EACA,YAAA;CDjFD;ACwFD;EACE,eAAA;CDtFD;AC6FD;;EAEE,eAAA;EACA,eAAA;EACA,mBAAA;EACA,yBAAA;CD3FD;AC8FD;EACE,YAAA;CD5FD;AC+FD;EACE,gBAAA;CD7FD;ACuGD;EACE,UAAA;CDrGD;AC4GD;EACE,iBAAA;CD1GD;ACoHD;EACE,iBAAA;CDlHD;ACyHD;EACE,gCAAA;EAAA,6BAAA;EAAA,wBAAA;EACA,UAAA;CDvHD;AC8HD;EACE,eAAA;CD5HD;ACmID;;;;EAIE,kCAAA;EACA,eAAA;CDjID;ACmJD;;;;;EAKE,eAAA;EACA,cAAA;EACA,UAAA;CDjJD;ACwJD;EACE,kBAAA;CDtJD;ACgKD;;EAEE,qBAAA;CD9JD;ACyKD;;;;EAIE,2BAAA;EACA,gBAAA;CDvKD;AC8KD;;EAEE,gBAAA;CD5KD;ACmLD;;EAEE,UAAA;EACA,WAAA;CDjLD;ACyLD;EACE,oBAAA;CDvLD;ACkMD;;EAEE,+BAAA;EAAA,4BAAA;EAAA,uBAAA;EACA,WAAA;CDhMD;ACyMD;;EAEE,aAAA;CDvMD;AC+MD;EACE,8BAAA;EACA,gCAAA;EAAA,6BAAA;EAAA,wBAAA;CD7MD;ACsND;;EAEE,yBAAA;CDpND;AC2ND;EACE,0BAAA;EACA,cAAA;EACA,+BAAA;CDzND;ACiOD;EACE,UAAA;EACA,WAAA;CD/ND;ACsOD;EACE,eAAA;CDpOD;AC4OD;EACE,kBAAA;CD1OD;ACoPD;EACE,0BAAA;EACA,kBAAA;CDlPD;ACqPD;;EAEE,WAAA;CDnPD;AACD,qFAAqF;AEhLrF;EACE;;;IAGE,uBAAA;IACA,6BAAA;IACA,mCAAA;IACA,oCAAA;IAAA,4BAAA;GFkLD;EE/KD;;IAEE,2BAAA;GFiLD;EE9KD;IACE,6BAAA;GFgLD;EE7KD;IACE,8BAAA;GF+KD;EE1KD;;IAEE,YAAA;GF4KD;EEzKD;;IAEE,uBAAA;IACA,yBAAA;GF2KD;EExKD;IACE,4BAAA;GF0KD;EEvKD;;IAEE,yBAAA;GFyKD;EEtKD;IACE,2BAAA;GFwKD;EErKD;;;IAGE,WAAA;IACA,UAAA;GFuKD;EEpKD;;IAEE,wBAAA;GFsKD;EEhKD;IACE,cAAA;GFkKD;EEhKD;;IAGI,kCAAA;GFiKH;EE9JD;IACE,uBAAA;GFgKD;EE7JD;IACE,qCAAA;GF+JD;EEhKD;;IAKI,kCAAA;GF+JH;EE5JD;;IAGI,kCAAA;GF6JH;CACF;AGnPD;EACE,oCAAA;EACA,sDAAA;EACA,gYAAA;CHqPD;AG7OD;EACE,mBAAA;EACA,SAAA;EACA,sBAAA;EACA,oCAAA;EACA,mBAAA;EACA,iBAAA;EACA,eAAA;EACA,oCAAA;EACA,mCAAA;CH+OD;AG3OmC;EAAW,iBAAA;CH8O9C;AG7OmC;EAAW,iBAAA;CHgP9C;AG9OmC;;EAAW,iBAAA;CHkP9C;AGjPmC;EAAW,iBAAA;CHoP9C;AGnPmC;EAAW,iBAAA;CHsP9C;AGrPmC;EAAW,iBAAA;CHwP9C;AGvPmC;EAAW,iBAAA;CH0P9C;AGzPmC;EAAW,iBAAA;CH4P9C;AG3PmC;EAAW,iBAAA;CH8P9C;AG7PmC;EAAW,iBAAA;CHgQ9C;AG/PmC;EAAW,iBAAA;CHkQ9C;AGjQmC;EAAW,iBAAA;CHoQ9C;AGnQmC;EAAW,iBAAA;CHsQ9C;AGrQmC;EAAW,iBAAA;CHwQ9C;AGvQmC;EAAW,iBAAA;CH0Q9C;AGzQmC;EAAW,iBAAA;CH4Q9C;AG3QmC;EAAW,iBAAA;CH8Q9C;AG7QmC;EAAW,iBAAA;CHgR9C;AG/QmC;EAAW,iBAAA;CHkR9C;AGjRmC;EAAW,iBAAA;CHoR9C;AGnRmC;EAAW,iBAAA;CHsR9C;AGrRmC;EAAW,iBAAA;CHwR9C;AGvRmC;EAAW,iBAAA;CH0R9C;AGzRmC;EAAW,iBAAA;CH4R9C;AG3RmC;EAAW,iBAAA;CH8R9C;AG7RmC;EAAW,iBAAA;CHgS9C;AG/RmC;EAAW,iBAAA;CHkS9C;AGjSmC;EAAW,iBAAA;CHoS9C;AGnSmC;EAAW,iBAAA;CHsS9C;AGrSmC;EAAW,iBAAA;CHwS9C;AGvSmC;EAAW,iBAAA;CH0S9C;AGzSmC;EAAW,iBAAA;CH4S9C;AG3SmC;EAAW,iBAAA;CH8S9C;AG7SmC;EAAW,iBAAA;CHgT9C;AG/SmC;EAAW,iBAAA;CHkT9C;AGjTmC;EAAW,iBAAA;CHoT9C;AGnTmC;EAAW,iBAAA;CHsT9C;AGrTmC;EAAW,iBAAA;CHwT9C;AGvTmC;EAAW,iBAAA;CH0T9C;AGzTmC;EAAW,iBAAA;CH4T9C;AG3TmC;EAAW,iBAAA;CH8T9C;AG7TmC;EAAW,iBAAA;CHgU9C;AG/TmC;EAAW,iBAAA;CHkU9C;AGjUmC;EAAW,iBAAA;CHoU9C;AGnUmC;EAAW,iBAAA;CHsU9C;AGrUmC;EAAW,iBAAA;CHwU9C;AGvUmC;EAAW,iBAAA;CH0U9C;AGzUmC;EAAW,iBAAA;CH4U9C;AG3UmC;EAAW,iBAAA;CH8U9C;AG7UmC;EAAW,iBAAA;CHgV9C;AG/UmC;EAAW,iBAAA;CHkV9C;AGjVmC;EAAW,iBAAA;CHoV9C;AGnVmC;EAAW,iBAAA;CHsV9C;AGrVmC;EAAW,iBAAA;CHwV9C;AGvVmC;EAAW,iBAAA;CH0V9C;AGzVmC;EAAW,iBAAA;CH4V9C;AG3VmC;EAAW,iBAAA;CH8V9C;AG7VmC;EAAW,iBAAA;CHgW9C;AG/VmC;EAAW,iBAAA;CHkW9C;AGjWmC;EAAW,iBAAA;CHoW9C;AGnWmC;EAAW,iBAAA;CHsW9C;AGrWmC;EAAW,iBAAA;CHwW9C;AGvWmC;EAAW,iBAAA;CH0W9C;AGzWmC;EAAW,iBAAA;CH4W9C;AG3WmC;EAAW,iBAAA;CH8W9C;AG7WmC;EAAW,iBAAA;CHgX9C;AG/WmC;EAAW,iBAAA;CHkX9C;AGjXmC;EAAW,iBAAA;CHoX9C;AGnXmC;EAAW,iBAAA;CHsX9C;AGrXmC;EAAW,iBAAA;CHwX9C;AGvXmC;EAAW,iBAAA;CH0X9C;AGzXmC;EAAW,iBAAA;CH4X9C;AG3XmC;EAAW,iBAAA;CH8X9C;AG7XmC;EAAW,iBAAA;CHgY9C;AG/XmC;EAAW,iBAAA;CHkY9C;AGjYmC;EAAW,iBAAA;CHoY9C;AGnYmC;EAAW,iBAAA;CHsY9C;AGrYmC;EAAW,iBAAA;CHwY9C;AGvYmC;EAAW,iBAAA;CH0Y9C;AGzYmC;EAAW,iBAAA;CH4Y9C;AG3YmC;EAAW,iBAAA;CH8Y9C;AG7YmC;EAAW,iBAAA;CHgZ9C;AG/YmC;EAAW,iBAAA;CHkZ9C;AGjZmC;EAAW,iBAAA;CHoZ9C;AGnZmC;EAAW,iBAAA;CHsZ9C;AGrZmC;EAAW,iBAAA;CHwZ9C;AGvZmC;EAAW,iBAAA;CH0Z9C;AGzZmC;EAAW,iBAAA;CH4Z9C;AG3ZmC;EAAW,iBAAA;CH8Z9C;AG7ZmC;EAAW,iBAAA;CHga9C;AG/ZmC;EAAW,iBAAA;CHka9C;AGjamC;EAAW,iBAAA;CHoa9C;AGnamC;EAAW,iBAAA;CHsa9C;AGramC;EAAW,iBAAA;CHwa9C;AGvamC;EAAW,iBAAA;CH0a9C;AGzamC;EAAW,iBAAA;CH4a9C;AG3amC;EAAW,iBAAA;CH8a9C;AG7amC;EAAW,iBAAA;CHgb9C;AG/amC;EAAW,iBAAA;CHkb9C;AGjbmC;EAAW,iBAAA;CHob9C;AGnbmC;EAAW,iBAAA;CHsb9C;AGrbmC;EAAW,iBAAA;CHwb9C;AGvbmC;EAAW,iBAAA;CH0b9C;AGzbmC;EAAW,iBAAA;CH4b9C;AG3bmC;EAAW,iBAAA;CH8b9C;AG7bmC;EAAW,iBAAA;CHgc9C;AG/bmC;EAAW,iBAAA;CHkc9C;AGjcmC;EAAW,iBAAA;CHoc9C;AGncmC;EAAW,iBAAA;CHsc9C;AGrcmC;EAAW,iBAAA;CHwc9C;AGvcmC;EAAW,iBAAA;CH0c9C;AGzcmC;EAAW,iBAAA;CH4c9C;AG3cmC;EAAW,iBAAA;CH8c9C;AG7cmC;EAAW,iBAAA;CHgd9C;AG/cmC;EAAW,iBAAA;CHkd9C;AGjdmC;EAAW,iBAAA;CHod9C;AGndmC;EAAW,iBAAA;CHsd9C;AGrdmC;EAAW,iBAAA;CHwd9C;AGvdmC;EAAW,iBAAA;CH0d9C;AGzdmC;EAAW,iBAAA;CH4d9C;AG3dmC;EAAW,iBAAA;CH8d9C;AG7dmC;EAAW,iBAAA;CHge9C;AG/dmC;EAAW,iBAAA;CHke9C;AGjemC;EAAW,iBAAA;CHoe9C;AGnemC;EAAW,iBAAA;CHse9C;AGremC;EAAW,iBAAA;CHwe9C;AGvemC;EAAW,iBAAA;CH0e9C;AGzemC;EAAW,iBAAA;CH4e9C;AG3emC;EAAW,iBAAA;CH8e9C;AG7emC;EAAW,iBAAA;CHgf9C;AG/emC;EAAW,iBAAA;CHkf9C;AGjfmC;EAAW,iBAAA;CHof9C;AGnfmC;EAAW,iBAAA;CHsf9C;AGrfmC;EAAW,iBAAA;CHwf9C;AGvfmC;EAAW,iBAAA;CH0f9C;AGzfmC;EAAW,iBAAA;CH4f9C;AG3fmC;EAAW,iBAAA;CH8f9C;AG7fmC;EAAW,iBAAA;CHggB9C;AG/fmC;EAAW,iBAAA;CHkgB9C;AGjgBmC;EAAW,iBAAA;CHogB9C;AGngBmC;EAAW,iBAAA;CHsgB9C;AGrgBmC;EAAW,iBAAA;CHwgB9C;AGvgBmC;EAAW,iBAAA;CH0gB9C;AGzgBmC;EAAW,iBAAA;CH4gB9C;AG3gBmC;EAAW,iBAAA;CH8gB9C;AG7gBmC;EAAW,iBAAA;CHghB9C;AG/gBmC;EAAW,iBAAA;CHkhB9C;AGjhBmC;EAAW,iBAAA;CHohB9C;AGnhBmC;EAAW,iBAAA;CHshB9C;AGrhBmC;EAAW,iBAAA;CHwhB9C;AGvhBmC;EAAW,iBAAA;CH0hB9C;AGzhBmC;EAAW,iBAAA;CH4hB9C;AG3hBmC;EAAW,iBAAA;CH8hB9C;AG7hBmC;EAAW,iBAAA;CHgiB9C;AG/hBmC;EAAW,iBAAA;CHkiB9C;AGjiBmC;EAAW,iBAAA;CHoiB9C;AGniBmC;EAAW,iBAAA;CHsiB9C;AGriBmC;EAAW,iBAAA;CHwiB9C;AGviBmC;EAAW,iBAAA;CH0iB9C;AGziBmC;EAAW,iBAAA;CH4iB9C;AG3iBmC;EAAW,iBAAA;CH8iB9C;AG7iBmC;EAAW,iBAAA;CHgjB9C;AG/iBmC;EAAW,iBAAA;CHkjB9C;AGjjBmC;EAAW,iBAAA;CHojB9C;AGnjBmC;EAAW,iBAAA;CHsjB9C;AGrjBmC;EAAW,iBAAA;CHwjB9C;AGvjBmC;EAAW,iBAAA;CH0jB9C;AGzjBmC;EAAW,iBAAA;CH4jB9C;AG3jBmC;EAAW,iBAAA;CH8jB9C;AG7jBmC;EAAW,iBAAA;CHgkB9C;AG/jBmC;EAAW,iBAAA;CHkkB9C;AGjkBmC;EAAW,iBAAA;CHokB9C;AGnkBmC;EAAW,iBAAA;CHskB9C;AGrkBmC;EAAW,iBAAA;CHwkB9C;AGvkBmC;EAAW,iBAAA;CH0kB9C;AGzkBmC;EAAW,iBAAA;CH4kB9C;AG3kBmC;EAAW,iBAAA;CH8kB9C;AG7kBmC;EAAW,iBAAA;CHglB9C;AG/kBmC;EAAW,iBAAA;CHklB9C;AGjlBmC;EAAW,iBAAA;CHolB9C;AGnlBmC;EAAW,iBAAA;CHslB9C;AGrlBmC;EAAW,iBAAA;CHwlB9C;AGvlBmC;EAAW,iBAAA;CH0lB9C;AGzlBmC;EAAW,iBAAA;CH4lB9C;AG3lBmC;EAAW,iBAAA;CH8lB9C;AG7lBmC;EAAW,iBAAA;CHgmB9C;AG/lBmC;EAAW,iBAAA;CHkmB9C;AGjmBmC;EAAW,iBAAA;CHomB9C;AGnmBmC;EAAW,iBAAA;CHsmB9C;AGrmBmC;EAAW,iBAAA;CHwmB9C;AGvmBmC;EAAW,iBAAA;CH0mB9C;AGzmBmC;EAAW,iBAAA;CH4mB9C;AG3mBmC;EAAW,iBAAA;CH8mB9C;AG7mBmC;EAAW,iBAAA;CHgnB9C;AG/mBmC;EAAW,iBAAA;CHknB9C;AGjnBmC;EAAW,iBAAA;CHonB9C;AGnnBmC;EAAW,iBAAA;CHsnB9C;AGrnBmC;EAAW,iBAAA;CHwnB9C;AGvnBmC;EAAW,iBAAA;CH0nB9C;AGznBmC;EAAW,iBAAA;CH4nB9C;AG3nBmC;EAAW,iBAAA;CH8nB9C;AG7nBmC;EAAW,iBAAA;CHgoB9C;AG/nBmC;EAAW,iBAAA;CHkoB9C;AGjoBmC;EAAW,iBAAA;CHooB9C;AGnoBmC;EAAW,iBAAA;CHsoB9C;AGroBmC;EAAW,iBAAA;CHwoB9C;AG/nBmC;EAAW,iBAAA;CHkoB9C;AGjoBmC;EAAW,iBAAA;CHooB9C;AGnoBmC;EAAW,iBAAA;CHsoB9C;AGroBmC;EAAW,iBAAA;CHwoB9C;AGvoBmC;EAAW,iBAAA;CH0oB9C;AGzoBmC;EAAW,iBAAA;CH4oB9C;AG3oBmC;EAAW,iBAAA;CH8oB9C;AG7oBmC;EAAW,iBAAA;CHgpB9C;AG/oBmC;EAAW,iBAAA;CHkpB9C;AGjpBmC;EAAW,iBAAA;CHopB9C;AGnpBmC;EAAW,iBAAA;CHspB9C;AGrpBmC;EAAW,iBAAA;CHwpB9C;AGvpBmC;EAAW,iBAAA;CH0pB9C;AGzpBmC;EAAW,iBAAA;CH4pB9C;AG3pBmC;EAAW,iBAAA;CH8pB9C;AG7pBmC;EAAW,iBAAA;CHgqB9C;AG/pBmC;EAAW,iBAAA;CHkqB9C;AGjqBmC;EAAW,iBAAA;CHoqB9C;AGnqBmC;EAAW,iBAAA;CHsqB9C;AGrqBmC;EAAW,iBAAA;CHwqB9C;AGvqBmC;EAAW,iBAAA;CH0qB9C;AGzqBmC;EAAW,iBAAA;CH4qB9C;AG3qBmC;EAAW,iBAAA;CH8qB9C;AG7qBmC;EAAW,iBAAA;CHgrB9C;AG/qBmC;EAAW,iBAAA;CHkrB9C;AGjrBmC;EAAW,iBAAA;CHorB9C;AGnrBmC;EAAW,iBAAA;CHsrB9C;AGrrBmC;EAAW,iBAAA;CHwrB9C;AGvrBmC;EAAW,iBAAA;CH0rB9C;AGzrBmC;EAAW,iBAAA;CH4rB9C;AG3rBmC;EAAW,iBAAA;CH8rB9C;AG7rBmC;EAAW,iBAAA;CHgsB9C;AG/rBmC;EAAW,iBAAA;CHksB9C;AGjsBmC;EAAW,iBAAA;CHosB9C;AGnsBmC;EAAW,iBAAA;CHssB9C;AGrsBmC;EAAW,iBAAA;CHwsB9C;AGvsBmC;EAAW,iBAAA;CH0sB9C;AGzsBmC;EAAW,iBAAA;CH4sB9C;AG3sBmC;EAAW,iBAAA;CH8sB9C;AG7sBmC;EAAW,iBAAA;CHgtB9C;AG/sBmC;EAAW,iBAAA;CHktB9C;AGjtBmC;EAAW,iBAAA;CHotB9C;AGntBmC;EAAW,iBAAA;CHstB9C;AGrtBmC;EAAW,iBAAA;CHwtB9C;AGvtBmC;EAAW,iBAAA;CH0tB9C;AGztBmC;EAAW,iBAAA;CH4tB9C;AG3tBmC;EAAW,iBAAA;CH8tB9C;AG7tBmC;EAAW,iBAAA;CHguB9C;AG/tBmC;EAAW,iBAAA;CHkuB9C;AGjuBmC;EAAW,iBAAA;CHouB9C;AGnuBmC;EAAW,iBAAA;CHsuB9C;AGruBmC;EAAW,iBAAA;CHwuB9C;AGvuBmC;EAAW,iBAAA;CH0uB9C;AGzuBmC;EAAW,iBAAA;CH4uB9C;AG3uBmC;EAAW,iBAAA;CH8uB9C;AG7uBmC;EAAW,iBAAA;CHgvB9C;AIxhCD;ECkEE,+BAAA;EACG,4BAAA;EACK,uBAAA;CLy9BT;AI1hCD;;EC+DE,+BAAA;EACG,4BAAA;EACK,uBAAA;CL+9BT;AIxhCD;EACE,gBAAA;EACA,8CAAA;CJ0hCD;AIvhCD;EACE,4DAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,uBAAA;CJyhCD;AIrhCD;;;;EAIE,qBAAA;EACA,mBAAA;EACA,qBAAA;CJuhCD;AIjhCD;EACE,eAAA;EACA,sBAAA;CJmhCD;AIjhCC;;EAEE,eAAA;EACA,2BAAA;CJmhCH;AIhhCC;EEnDA,2CAAA;EACA,qBAAA;CNskCD;AIzgCD;EACE,UAAA;CJ2gCD;AIrgCD;EACE,uBAAA;CJugCD;AIngCD;;;;;EG1EE,eAAA;EACA,gBAAA;EACA,aAAA;CPolCD;AIvgCD;EACE,mBAAA;CJygCD;AIngCD;EACE,aAAA;EACA,wBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;EC+FA,yCAAA;EACK,oCAAA;EACG,iCAAA;EE5LR,sBAAA;EACA,gBAAA;EACA,aAAA;CPomCD;AIngCD;EACE,mBAAA;CJqgCD;AI//BD;EACE,iBAAA;EACA,oBAAA;EACA,UAAA;EACA,8BAAA;CJigCD;AIz/BD;EACE,mBAAA;EACA,WAAA;EACA,YAAA;EACA,WAAA;EACA,aAAA;EACA,iBAAA;EACA,uBAAA;EACA,UAAA;CJ2/BD;AIn/BC;;EAEE,iBAAA;EACA,YAAA;EACA,aAAA;EACA,UAAA;EACA,kBAAA;EACA,WAAA;CJq/BH;AI1+BD;EACE,gBAAA;CJ4+BD;AQjoCD;;;;;;;;;;;;EAEE,qBAAA;EACA,iBAAA;EACA,iBAAA;EACA,eAAA;CR6oCD;AQlpCD;;;;;;;;;;;;;;;;;;;;;;;;EASI,iBAAA;EACA,eAAA;EACA,eAAA;CRmqCH;AQ/pCD;;;;;;EAGE,iBAAA;EACA,oBAAA;CRoqCD;AQxqCD;;;;;;;;;;;;EAQI,eAAA;CR8qCH;AQ3qCD;;;;;;EAGE,iBAAA;EACA,oBAAA;CRgrCD;AQprCD;;;;;;;;;;;;EAQI,eAAA;CR0rCH;AQtrCD;;EAAU,gBAAA;CR0rCT;AQzrCD;;EAAU,gBAAA;CR6rCT;AQ5rCD;;EAAU,gBAAA;CRgsCT;AQ/rCD;;EAAU,gBAAA;CRmsCT;AQlsCD;;EAAU,gBAAA;CRssCT;AQrsCD;;EAAU,gBAAA;CRysCT;AQnsCD;EACE,iBAAA;CRqsCD;AQlsCD;EACE,oBAAA;EACA,gBAAA;EACA,iBAAA;EACA,iBAAA;CRosCD;AQlsCC;EAAA;IACE,gBAAA;GRqsCD;CACF;AQ7rCD;;EAEE,eAAA;CR+rCD;AQ5rCD;;EAEE,eAAA;EACA,0BAAA;CR8rCD;AQ1rCD;EAAuB,iBAAA;CR6rCtB;AQ5rCD;EAAuB,kBAAA;CR+rCtB;AQ9rCD;EAAuB,mBAAA;CRisCtB;AQhsCD;EAAuB,oBAAA;CRmsCtB;AQlsCD;EAAuB,oBAAA;CRqsCtB;AQlsCD;EAAuB,0BAAA;CRqsCtB;AQpsCD;EAAuB,0BAAA;CRusCtB;AQtsCD;EAAuB,2BAAA;CRysCtB;AQtsCD;EACE,eAAA;CRwsCD;AQtsCD;ECvGE,eAAA;CTgzCD;AS/yCC;;EAEE,eAAA;CTizCH;AQ1sCD;EC1GE,eAAA;CTuzCD;AStzCC;;EAEE,eAAA;CTwzCH;AQ9sCD;EC7GE,eAAA;CT8zCD;AS7zCC;;EAEE,eAAA;CT+zCH;AQltCD;EChHE,eAAA;CTq0CD;ASp0CC;;EAEE,eAAA;CTs0CH;AQttCD;ECnHE,eAAA;CT40CD;AS30CC;;EAEE,eAAA;CT60CH;AQttCD;EAGE,YAAA;EE7HA,0BAAA;CVo1CD;AUn1CC;;EAEE,0BAAA;CVq1CH;AQxtCD;EEhIE,0BAAA;CV21CD;AU11CC;;EAEE,0BAAA;CV41CH;AQ5tCD;EEnIE,0BAAA;CVk2CD;AUj2CC;;EAEE,0BAAA;CVm2CH;AQhuCD;EEtIE,0BAAA;CVy2CD;AUx2CC;;EAEE,0BAAA;CV02CH;AQpuCD;EEzIE,0BAAA;CVg3CD;AU/2CC;;EAEE,0BAAA;CVi3CH;AQnuCD;EACE,oBAAA;EACA,oBAAA;EACA,iCAAA;CRquCD;AQ7tCD;;EAEE,cAAA;EACA,oBAAA;CR+tCD;AQluCD;;;;EAMI,iBAAA;CRkuCH;AQ3tCD;EACE,gBAAA;EACA,iBAAA;CR6tCD;AQztCD;EALE,gBAAA;EACA,iBAAA;EAMA,kBAAA;CR4tCD;AQ9tCD;EAKI,sBAAA;EACA,mBAAA;EACA,kBAAA;CR4tCH;AQvtCD;EACE,cAAA;EACA,oBAAA;CRytCD;AQvtCD;;EAEE,wBAAA;CRytCD;AQvtCD;EACE,iBAAA;CRytCD;AQvtCD;EACE,eAAA;CRytCD;AQ5sCC;EAAA;IAEI,YAAA;IACA,aAAA;IACA,YAAA;IACA,kBAAA;IGxNJ,iBAAA;IACA,wBAAA;IACA,oBAAA;GXu6CC;EQttCD;IASI,mBAAA;GRgtCH;CACF;AQtsCD;;EAEE,aAAA;CRwsCD;AQrsCD;EACE,eAAA;EA9IqB,0BAAA;CRs1CtB;AQnsCD;EACE,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,+BAAA;CRqsCD;AQhsCG;;;EACE,iBAAA;CRosCL;AQ9sCD;;;EAmBI,eAAA;EACA,eAAA;EACA,wBAAA;EACA,eAAA;CRgsCH;AQ9rCG;;;EACE,uBAAA;CRksCL;AQ1rCD;;EAEE,oBAAA;EACA,gBAAA;EACA,kBAAA;EACA,gCAAA;EACA,eAAA;CR4rCD;AQtrCG;;;;;;EAAW,YAAA;CR8rCd;AQ7rCG;;;;;;EACE,uBAAA;CRosCL;AQ9rCD;EACE,oBAAA;EACA,mBAAA;EACA,wBAAA;CRgsCD;AYx+CD;;;;EAIE,+DAAA;CZ0+CD;AYt+CD;EACE,iBAAA;EACA,eAAA;EACA,eAAA;EACA,0BAAA;EACA,mBAAA;CZw+CD;AYp+CD;EACE,iBAAA;EACA,eAAA;EACA,YAAA;EACA,uBAAA;EACA,mBAAA;EACA,uDAAA;EAAA,+CAAA;CZs+CD;AY5+CD;EASI,WAAA;EACA,gBAAA;EACA,iBAAA;EACA,yBAAA;EAAA,iBAAA;CZs+CH;AYj+CD;EACE,eAAA;EACA,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,sBAAA;EACA,sBAAA;EACA,0BAAA;EACA,uBAAA;EACA,mBAAA;CZm+CD;AY9+CD;EAeI,WAAA;EACA,mBAAA;EACA,eAAA;EACA,sBAAA;EACA,8BAAA;EACA,iBAAA;CZk+CH;AY79CD;EACE,kBAAA;EACA,mBAAA;CZ+9CD;AazhDD;ECHE,oBAAA;EACA,mBAAA;EACA,mBAAA;EACA,kBAAA;Cd+hDD;Aa5hDC;EAAA;IACE,aAAA;Gb+hDD;CACF;Aa9hDC;EAAA;IACE,aAAA;GbiiDD;CACF;AahiDC;EAAA;IACE,cAAA;GbmiDD;CACF;Aa1hDD;ECvBE,oBAAA;EACA,mBAAA;EACA,mBAAA;EACA,kBAAA;CdojDD;AavhDD;ECvBE,oBAAA;EACA,mBAAA;CdijDD;AavhDD;EACE,gBAAA;EACA,eAAA;CbyhDD;Aa3hDD;EAKI,iBAAA;EACA,gBAAA;CbyhDH;AczkDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECiBK,mBAAA;EAEA,gBAAA;EAEA,oBAAA;EACA,mBAAA;CfwmDL;Ac9nDA;;;;;;;;;;;;ECuCK,YAAA;CfqmDL;Ac5oDA;EC+CG,YAAA;CfgmDH;Ac/oDA;EC+CG,oBAAA;CfmmDH;AclpDA;EC+CG,oBAAA;CfsmDH;AcrpDA;EC+CG,WAAA;CfymDH;AcxpDA;EC+CG,oBAAA;Cf4mDH;Ac3pDA;EC+CG,oBAAA;Cf+mDH;Ac9pDA;EC+CG,WAAA;CfknDH;AcjqDA;EC+CG,oBAAA;CfqnDH;AcpqDA;EC+CG,oBAAA;CfwnDH;AcvqDA;EC+CG,WAAA;Cf2nDH;Ac1qDA;EC+CG,oBAAA;Cf8nDH;Ac7qDA;EC+CG,mBAAA;CfioDH;AchrDA;EC8DG,YAAA;CfqnDH;AcnrDA;EC8DG,oBAAA;CfwnDH;ActrDA;EC8DG,oBAAA;Cf2nDH;AczrDA;EC8DG,WAAA;Cf8nDH;Ac5rDA;EC8DG,oBAAA;CfioDH;Ac/rDA;EC8DG,oBAAA;CfooDH;AclsDA;EC8DG,WAAA;CfuoDH;AcrsDA;EC8DG,oBAAA;Cf0oDH;AcxsDA;EC8DG,oBAAA;Cf6oDH;Ac3sDA;EC8DG,WAAA;CfgpDH;Ac9sDA;EC8DG,oBAAA;CfmpDH;AcjtDA;EC8DG,mBAAA;CfspDH;AcptDA;ECmEG,YAAA;CfopDH;AcvtDA;ECoDG,WAAA;CfsqDH;Ac1tDA;ECoDG,mBAAA;CfyqDH;Ac7tDA;ECoDG,mBAAA;Cf4qDH;AchuDA;ECoDG,UAAA;Cf+qDH;AcnuDA;ECoDG,mBAAA;CfkrDH;ActuDA;ECoDG,mBAAA;CfqrDH;AczuDA;ECoDG,UAAA;CfwrDH;Ac5uDA;ECoDG,mBAAA;Cf2rDH;Ac/uDA;ECoDG,mBAAA;Cf8rDH;AclvDA;ECoDG,UAAA;CfisDH;AcrvDA;ECoDG,mBAAA;CfosDH;AcxvDA;ECoDG,kBAAA;CfusDH;Ac3vDA;ECyDG,WAAA;CfqsDH;Ac9vDA;ECwEG,kBAAA;CfyrDH;AcjwDA;ECwEG,0BAAA;Cf4rDH;AcpwDA;ECwEG,0BAAA;Cf+rDH;AcvwDA;ECwEG,iBAAA;CfksDH;Ac1wDA;ECwEG,0BAAA;CfqsDH;Ac7wDA;ECwEG,0BAAA;CfwsDH;AchxDA;ECwEG,iBAAA;Cf2sDH;AcnxDA;ECwEG,0BAAA;Cf8sDH;ActxDA;ECwEG,0BAAA;CfitDH;AczxDA;ECwEG,iBAAA;CfotDH;Ac5xDA;ECwEG,0BAAA;CfutDH;Ac/xDA;ECwEG,yBAAA;Cf0tDH;AclyDA;ECwEG,gBAAA;Cf6tDH;Aa5tDD;ECzEC;;;;;;;;;;;;ICuCK,YAAA;Gf6wDH;EcpzDF;IC+CG,YAAA;GfwwDD;EcvzDF;IC+CG,oBAAA;Gf2wDD;Ec1zDF;IC+CG,oBAAA;Gf8wDD;Ec7zDF;IC+CG,WAAA;GfixDD;Ech0DF;IC+CG,oBAAA;GfoxDD;Ecn0DF;IC+CG,oBAAA;GfuxDD;Ect0DF;IC+CG,WAAA;Gf0xDD;Ecz0DF;IC+CG,oBAAA;Gf6xDD;Ec50DF;IC+CG,oBAAA;GfgyDD;Ec/0DF;IC+CG,WAAA;GfmyDD;Ecl1DF;IC+CG,oBAAA;GfsyDD;Ecr1DF;IC+CG,mBAAA;GfyyDD;Ecx1DF;IC8DG,YAAA;Gf6xDD;Ec31DF;IC8DG,oBAAA;GfgyDD;Ec91DF;IC8DG,oBAAA;GfmyDD;Ecj2DF;IC8DG,WAAA;GfsyDD;Ecp2DF;IC8DG,oBAAA;GfyyDD;Ecv2DF;IC8DG,oBAAA;Gf4yDD;Ec12DF;IC8DG,WAAA;Gf+yDD;Ec72DF;IC8DG,oBAAA;GfkzDD;Ech3DF;IC8DG,oBAAA;GfqzDD;Ecn3DF;IC8DG,WAAA;GfwzDD;Ect3DF;IC8DG,oBAAA;Gf2zDD;Ecz3DF;IC8DG,mBAAA;Gf8zDD;Ec53DF;ICmEG,YAAA;Gf4zDD;Ec/3DF;ICoDG,WAAA;Gf80DD;Ecl4DF;ICoDG,mBAAA;Gfi1DD;Ecr4DF;ICoDG,mBAAA;Gfo1DD;Ecx4DF;ICoDG,UAAA;Gfu1DD;Ec34DF;ICoDG,mBAAA;Gf01DD;Ec94DF;ICoDG,mBAAA;Gf61DD;Ecj5DF;ICoDG,UAAA;Gfg2DD;Ecp5DF;ICoDG,mBAAA;Gfm2DD;Ecv5DF;ICoDG,mBAAA;Gfs2DD;Ec15DF;ICoDG,UAAA;Gfy2DD;Ec75DF;ICoDG,mBAAA;Gf42DD;Ech6DF;ICoDG,kBAAA;Gf+2DD;Ecn6DF;ICyDG,WAAA;Gf62DD;Ect6DF;ICwEG,kBAAA;Gfi2DD;Ecz6DF;ICwEG,0BAAA;Gfo2DD;Ec56DF;ICwEG,0BAAA;Gfu2DD;Ec/6DF;ICwEG,iBAAA;Gf02DD;Ecl7DF;ICwEG,0BAAA;Gf62DD;Ecr7DF;ICwEG,0BAAA;Gfg3DD;Ecx7DF;ICwEG,iBAAA;Gfm3DD;Ec37DF;ICwEG,0BAAA;Gfs3DD;Ec97DF;ICwEG,0BAAA;Gfy3DD;Ecj8DF;ICwEG,iBAAA;Gf43DD;Ecp8DF;ICwEG,0BAAA;Gf+3DD;Ecv8DF;ICwEG,yBAAA;Gfk4DD;Ec18DF;ICwEG,gBAAA;Gfq4DD;CACF;Aa53DD;EClFC;;;;;;;;;;;;ICuCK,YAAA;Gfs7DH;Ec79DF;IC+CG,YAAA;Gfi7DD;Ech+DF;IC+CG,oBAAA;Gfo7DD;Ecn+DF;IC+CG,oBAAA;Gfu7DD;Ect+DF;IC+CG,WAAA;Gf07DD;Ecz+DF;IC+CG,oBAAA;Gf67DD;Ec5+DF;IC+CG,oBAAA;Gfg8DD;Ec/+DF;IC+CG,WAAA;Gfm8DD;Ecl/DF;IC+CG,oBAAA;Gfs8DD;Ecr/DF;IC+CG,oBAAA;Gfy8DD;Ecx/DF;IC+CG,WAAA;Gf48DD;Ec3/DF;IC+CG,oBAAA;Gf+8DD;Ec9/DF;IC+CG,mBAAA;Gfk9DD;EcjgEF;IC8DG,YAAA;Gfs8DD;EcpgEF;IC8DG,oBAAA;Gfy8DD;EcvgEF;IC8DG,oBAAA;Gf48DD;Ec1gEF;IC8DG,WAAA;Gf+8DD;Ec7gEF;IC8DG,oBAAA;Gfk9DD;EchhEF;IC8DG,oBAAA;Gfq9DD;EcnhEF;IC8DG,WAAA;Gfw9DD;EcthEF;IC8DG,oBAAA;Gf29DD;EczhEF;IC8DG,oBAAA;Gf89DD;Ec5hEF;IC8DG,WAAA;Gfi+DD;Ec/hEF;IC8DG,oBAAA;Gfo+DD;EcliEF;IC8DG,mBAAA;Gfu+DD;EcriEF;ICmEG,YAAA;Gfq+DD;EcxiEF;ICoDG,WAAA;Gfu/DD;Ec3iEF;ICoDG,mBAAA;Gf0/DD;Ec9iEF;ICoDG,mBAAA;Gf6/DD;EcjjEF;ICoDG,UAAA;GfggED;EcpjEF;ICoDG,mBAAA;GfmgED;EcvjEF;ICoDG,mBAAA;GfsgED;Ec1jEF;ICoDG,UAAA;GfygED;Ec7jEF;ICoDG,mBAAA;Gf4gED;EchkEF;ICoDG,mBAAA;Gf+gED;EcnkEF;ICoDG,UAAA;GfkhED;EctkEF;ICoDG,mBAAA;GfqhED;EczkEF;ICoDG,kBAAA;GfwhED;Ec5kEF;ICyDG,WAAA;GfshED;Ec/kEF;ICwEG,kBAAA;Gf0gED;EcllEF;ICwEG,0BAAA;Gf6gED;EcrlEF;ICwEG,0BAAA;GfghED;EcxlEF;ICwEG,iBAAA;GfmhED;Ec3lEF;ICwEG,0BAAA;GfshED;Ec9lEF;ICwEG,0BAAA;GfyhED;EcjmEF;ICwEG,iBAAA;Gf4hED;EcpmEF;ICwEG,0BAAA;Gf+hED;EcvmEF;ICwEG,0BAAA;GfkiED;Ec1mEF;ICwEG,iBAAA;GfqiED;Ec7mEF;ICwEG,0BAAA;GfwiED;EchnEF;ICwEG,yBAAA;Gf2iED;EcnnEF;ICwEG,gBAAA;Gf8iED;CACF;Aa5hED;EC3FC;;;;;;;;;;;;ICuCK,YAAA;Gf+lEH;EctoEF;IC+CG,YAAA;Gf0lED;EczoEF;IC+CG,oBAAA;Gf6lED;Ec5oEF;IC+CG,oBAAA;GfgmED;Ec/oEF;IC+CG,WAAA;GfmmED;EclpEF;IC+CG,oBAAA;GfsmED;EcrpEF;IC+CG,oBAAA;GfymED;EcxpEF;IC+CG,WAAA;Gf4mED;Ec3pEF;IC+CG,oBAAA;Gf+mED;Ec9pEF;IC+CG,oBAAA;GfknED;EcjqEF;IC+CG,WAAA;GfqnED;EcpqEF;IC+CG,oBAAA;GfwnED;EcvqEF;IC+CG,mBAAA;Gf2nED;Ec1qEF;IC8DG,YAAA;Gf+mED;Ec7qEF;IC8DG,oBAAA;GfknED;EchrEF;IC8DG,oBAAA;GfqnED;EcnrEF;IC8DG,WAAA;GfwnED;EctrEF;IC8DG,oBAAA;Gf2nED;EczrEF;IC8DG,oBAAA;Gf8nED;Ec5rEF;IC8DG,WAAA;GfioED;Ec/rEF;IC8DG,oBAAA;GfooED;EclsEF;IC8DG,oBAAA;GfuoED;EcrsEF;IC8DG,WAAA;Gf0oED;EcxsEF;IC8DG,oBAAA;Gf6oED;Ec3sEF;IC8DG,mBAAA;GfgpED;Ec9sEF;ICmEG,YAAA;Gf8oED;EcjtEF;ICoDG,WAAA;GfgqED;EcptEF;ICoDG,mBAAA;GfmqED;EcvtEF;ICoDG,mBAAA;GfsqED;Ec1tEF;ICoDG,UAAA;GfyqED;Ec7tEF;ICoDG,mBAAA;Gf4qED;EchuEF;ICoDG,mBAAA;Gf+qED;EcnuEF;ICoDG,UAAA;GfkrED;EctuEF;ICoDG,mBAAA;GfqrED;EczuEF;ICoDG,mBAAA;GfwrED;Ec5uEF;ICoDG,UAAA;Gf2rED;Ec/uEF;ICoDG,mBAAA;Gf8rED;EclvEF;ICoDG,kBAAA;GfisED;EcrvEF;ICyDG,WAAA;Gf+rED;EcxvEF;ICwEG,kBAAA;GfmrED;Ec3vEF;ICwEG,0BAAA;GfsrED;Ec9vEF;ICwEG,0BAAA;GfyrED;EcjwEF;ICwEG,iBAAA;Gf4rED;EcpwEF;ICwEG,0BAAA;Gf+rED;EcvwEF;ICwEG,0BAAA;GfksED;Ec1wEF;ICwEG,iBAAA;GfqsED;Ec7wEF;ICwEG,0BAAA;GfwsED;EchxEF;ICwEG,0BAAA;Gf2sED;EcnxEF;ICwEG,iBAAA;Gf8sED;EctxEF;ICwEG,0BAAA;GfitED;EczxEF;ICwEG,yBAAA;GfotED;Ec5xEF;ICwEG,gBAAA;GfutED;CACF;AgBzxED;EACE,8BAAA;ChB2xED;AgB5xED;EAQI,iBAAA;EACA,sBAAA;EACA,YAAA;ChBuxEH;AgBlxEG;;EACE,iBAAA;EACA,oBAAA;EACA,YAAA;ChBqxEL;AgBhxED;EACE,iBAAA;EACA,oBAAA;EACA,eAAA;EACA,iBAAA;ChBkxED;AgB/wED;EACE,iBAAA;ChBixED;AgB3wED;EACE,YAAA;EACA,gBAAA;EACA,oBAAA;ChB6wED;AgBhxED;;;;;;EAWQ,aAAA;EACA,wBAAA;EACA,oBAAA;EACA,2BAAA;ChB6wEP;AgB3xED;EAoBI,uBAAA;EACA,8BAAA;ChB0wEH;AgB/xED;;;;;;EA8BQ,cAAA;ChBywEP;AgBvyED;EAoCI,2BAAA;ChBswEH;AgB1yED;EAyCI,uBAAA;ChBowEH;AgB7vED;;;;;;EAOQ,aAAA;ChB8vEP;AgBnvED;EACE,uBAAA;ChBqvED;AgBtvED;;;;;;EAQQ,uBAAA;ChBsvEP;AgB9vED;;EAeM,yBAAA;ChBmvEL;AgBzuED;EAEI,0BAAA;ChB0uEH;AgBjuED;EAEI,0BAAA;ChBkuEH;AiBj3EC;;;;;;;;;;;;EAOI,0BAAA;CjBw3EL;AiBl3EC;;;;;EAMI,0BAAA;CjBm3EL;AiBt4EC;;;;;;;;;;;;EAOI,0BAAA;CjB64EL;AiBv4EC;;;;;EAMI,0BAAA;CjBw4EL;AiB35EC;;;;;;;;;;;;EAOI,0BAAA;CjBk6EL;AiB55EC;;;;;EAMI,0BAAA;CjB65EL;AiBh7EC;;;;;;;;;;;;EAOI,0BAAA;CjBu7EL;AiBj7EC;;;;;EAMI,0BAAA;CjBk7EL;AiBr8EC;;;;;;;;;;;;EAOI,0BAAA;CjB48EL;AiBt8EC;;;;;EAMI,0BAAA;CjBu8EL;AgBnzED;EACE,kBAAA;EACA,iBAAA;ChBqzED;AgBnzEC;EAAA;IACE,YAAA;IACA,oBAAA;IACA,mBAAA;IACA,6CAAA;IACA,uBAAA;GhBszED;EgB3zED;IASI,iBAAA;GhBqzEH;EgB9zED;;;;;;IAkBU,oBAAA;GhBozET;EgBt0ED;IA0BI,UAAA;GhB+yEH;EgBz0ED;;;;;;IAmCU,eAAA;GhB8yET;EgBj1ED;;;;;;IAuCU,gBAAA;GhBkzET;EgBz1ED;;;;IAoDU,iBAAA;GhB2yET;CACF;AkBrgFD;EAIE,aAAA;EACA,WAAA;EACA,UAAA;EACA,UAAA;ClBogFD;AkBjgFD;EACE,eAAA;EACA,YAAA;EACA,WAAA;EACA,oBAAA;EACA,gBAAA;EACA,qBAAA;EACA,eAAA;EACA,UAAA;EACA,iCAAA;ClBmgFD;AkBhgFD;EACE,sBAAA;EACA,gBAAA;EACA,mBAAA;EACA,iBAAA;ClBkgFD;AkBx/ED;Eb6BE,+BAAA;EACG,4BAAA;EACK,uBAAA;EarBR,yBAAA;EACA,sBAAA;EAAA,iBAAA;ClBo/ED;AkBh/ED;;EAEE,gBAAA;EACA,mBAAA;EACA,oBAAA;ClBk/ED;AkB5+EC;;;;;;EAGE,oBAAA;ClBi/EH;AkB7+ED;EACE,eAAA;ClB++ED;AkB3+ED;EACE,eAAA;EACA,YAAA;ClB6+ED;AkBz+ED;;EAEE,aAAA;ClB2+ED;AkBv+ED;;;EZ1FE,2CAAA;EACA,qBAAA;CNskFD;AkBt+ED;EACE,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;ClBw+ED;AkB98ED;EACE,eAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,uBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;Eb3EA,yDAAA;EACQ,iDAAA;EAyHR,+EAAA;EACK,0EAAA;EACG,uFAAA;EAAA,+EAAA;EAAA,uEAAA;EAAA,4GAAA;CLo6ET;AmB9iFC;EACE,sBAAA;EACA,WAAA;EdYF,0FAAA;EACQ,kFAAA;CLqiFT;AKpgFC;EACE,YAAA;EACA,WAAA;CLsgFH;AKpgFC;EAA0B,YAAA;CLugF3B;AKtgFC;EAAgC,YAAA;CLygFjC;AkB19EC;EACE,8BAAA;EACA,UAAA;ClB49EH;AkBp9EC;;;EAGE,0BAAA;EACA,WAAA;ClBs9EH;AkBn9EC;;EAEE,oBAAA;ClBq9EH;AkBj9EC;EACE,aAAA;ClBm9EH;AkBr8ED;EAKI;;;;IACE,kBAAA;GlBs8EH;EkBn8EC;;;;;;;;IAEE,kBAAA;GlB28EH;EkBx8EC;;;;;;;;IAEE,kBAAA;GlBg9EH;CACF;AkBt8ED;EACE,oBAAA;ClBw8ED;AkBh8ED;;EAEE,mBAAA;EACA,eAAA;EACA,iBAAA;EACA,oBAAA;ClBk8ED;AkB/7EC;;;;EAGI,oBAAA;ClBk8EL;AkB78ED;;EAgBI,iBAAA;EACA,mBAAA;EACA,iBAAA;EACA,iBAAA;EACA,gBAAA;ClBi8EH;AkB97ED;;;;EAIE,mBAAA;EACA,mBAAA;EACA,mBAAA;ClBg8ED;AkB77ED;;EAEE,iBAAA;ClB+7ED;AkB37ED;;EAEE,mBAAA;EACA,sBAAA;EACA,mBAAA;EACA,iBAAA;EACA,iBAAA;EACA,uBAAA;EACA,gBAAA;ClB67ED;AkB17EC;;;;EAEE,oBAAA;ClB87EH;AkB37ED;;EAEE,cAAA;EACA,kBAAA;ClB67ED;AkBp7ED;EACE,iBAAA;EAEA,iBAAA;EACA,oBAAA;EAEA,iBAAA;ClBo7ED;AkBl7EC;;EAEE,iBAAA;EACA,gBAAA;ClBo7EH;AkBv6ED;EC3PE,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CnBqqFD;AmBnqFC;EACE,aAAA;EACA,kBAAA;CnBqqFH;AmBlqFC;;EAEE,aAAA;CnBoqFH;AkBn7ED;EAEI,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;ClBo7EH;AkB17ED;EASI,aAAA;EACA,kBAAA;ClBo7EH;AkB97ED;;EAcI,aAAA;ClBo7EH;AkBl8ED;EAiBI,aAAA;EACA,iBAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;ClBo7EH;AkBh7ED;ECvRE,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CnB0sFD;AmBxsFC;EACE,aAAA;EACA,kBAAA;CnB0sFH;AmBvsFC;;EAEE,aAAA;CnBysFH;AkB57ED;EAEI,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;ClB67EH;AkBn8ED;EASI,aAAA;EACA,kBAAA;ClB67EH;AkBv8ED;;EAcI,aAAA;ClB67EH;AkB38ED;EAiBI,aAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;ClB67EH;AkBp7ED;EAEE,mBAAA;ClBq7ED;AkBv7ED;EAMI,sBAAA;ClBo7EH;AkBh7ED;EACE,mBAAA;EACA,OAAA;EACA,SAAA;EACA,WAAA;EACA,eAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;EACA,mBAAA;EACA,qBAAA;ClBk7ED;AkBh7ED;;;EAGE,YAAA;EACA,aAAA;EACA,kBAAA;ClBk7ED;AkBh7ED;;;EAGE,YAAA;EACA,aAAA;EACA,kBAAA;ClBk7ED;AkB96ED;;;;;;;;;;EClZI,eAAA;CnB40FH;AkB17ED;EC9YI,sBAAA;EdiDF,yDAAA;EACQ,iDAAA;CL2xFT;AmB30FG;EACE,sBAAA;Ed8CJ,0EAAA;EACQ,kEAAA;CLgyFT;AkBp8ED;ECpYI,eAAA;EACA,0BAAA;EACA,sBAAA;CnB20FH;AkBz8ED;EC9XI,eAAA;CnB00FH;AkBz8ED;;;;;;;;;;ECrZI,eAAA;CnB02FH;AkBr9ED;ECjZI,sBAAA;EdiDF,yDAAA;EACQ,iDAAA;CLyzFT;AmBz2FG;EACE,sBAAA;Ed8CJ,0EAAA;EACQ,kEAAA;CL8zFT;AkB/9ED;ECvYI,eAAA;EACA,0BAAA;EACA,sBAAA;CnBy2FH;AkBp+ED;ECjYI,eAAA;CnBw2FH;AkBp+ED;;;;;;;;;;ECxZI,eAAA;CnBw4FH;AkBh/ED;ECpZI,sBAAA;EdiDF,yDAAA;EACQ,iDAAA;CLu1FT;AmBv4FG;EACE,sBAAA;Ed8CJ,0EAAA;EACQ,kEAAA;CL41FT;AkB1/ED;EC1YI,eAAA;EACA,0BAAA;EACA,sBAAA;CnBu4FH;AkB//ED;ECpYI,eAAA;CnBs4FH;AkB3/EC;EACE,UAAA;ClB6/EH;AkB3/EC;EACE,OAAA;ClB6/EH;AkBn/ED;EACE,eAAA;EACA,gBAAA;EACA,oBAAA;EACA,eAAA;ClBq/ED;AkBn+EC;EAAA;IAGI,sBAAA;IACA,iBAAA;IACA,uBAAA;GlBo+EH;EkBz+ED;IAUI,sBAAA;IACA,YAAA;IACA,uBAAA;GlBk+EH;EkB9+ED;IAiBI,sBAAA;GlBg+EH;EkBj/ED;IAqBI,sBAAA;IACA,uBAAA;GlB+9EH;EkBr/ED;;;IA2BM,YAAA;GlB+9EL;EkB1/ED;IAiCI,YAAA;GlB49EH;EkB7/ED;IAqCI,iBAAA;IACA,uBAAA;GlB29EH;EkBjgFD;;IA6CI,sBAAA;IACA,cAAA;IACA,iBAAA;IACA,uBAAA;GlBw9EH;EkBxgFD;;IAmDM,gBAAA;GlBy9EL;EkB5gFD;;IAwDI,mBAAA;IACA,eAAA;GlBw9EH;EkBjhFD;IA8DI,OAAA;GlBs9EH;CACF;AkB58ED;;;;EASI,iBAAA;EACA,cAAA;EACA,iBAAA;ClBy8EH;AkBp9ED;;EAiBI,iBAAA;ClBu8EH;AkBx9ED;EJ9gBE,oBAAA;EACA,mBAAA;Cdy+FD;AkBj8EC;EAAA;IAEI,iBAAA;IACA,iBAAA;IACA,kBAAA;GlBm8EH;CACF;AkBn+ED;EAwCI,YAAA;ClB87EH;AkBt7EG;EAAA;IAEI,kBAAA;IACA,gBAAA;GlBw7EL;CACF;AkBp7EG;EAAA;IAEI,iBAAA;IACA,gBAAA;GlBs7EL;CACF;AoBrgGD;EACE,sBAAA;EACA,iBAAA;EACA,oBAAA;EACA,mBAAA;EACA,oBAAA;EACA,uBAAA;EACA,+BAAA;EAAA,2BAAA;EACA,gBAAA;EACA,uBAAA;EACA,8BAAA;ECoCA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,mBAAA;EhBqKA,0BAAA;EACG,uBAAA;EACC,sBAAA;EACI,kBAAA;CLg0FT;AoBxgGG;;;;;;EdrBF,2CAAA;EACA,qBAAA;CNqiGD;AoB3gGC;;;EAGE,YAAA;EACA,sBAAA;CpB6gGH;AoB1gGC;;EAEE,uBAAA;EACA,WAAA;Ef2BF,yDAAA;EACQ,iDAAA;CLk/FT;AoB1gGC;;;EAGE,oBAAA;EE9CF,0BAAA;EACA,cAAA;EjBiEA,yBAAA;EACQ,iBAAA;CL2/FT;AoB1gGG;;EAEE,qBAAA;CpB4gGL;AoBngGD;EC7DE,YAAA;EACA,uBAAA;EACA,mBAAA;CrBmkGD;AqBjkGC;;EAEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBmkGH;AqBjkGC;EACE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBmkGH;AqBjkGC;;;EAGE,YAAA;EACA,0BAAA;EACA,uBAAA;EACA,sBAAA;CrBmkGH;AqBjkGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBykGL;AqBnkGG;;;;;;;;;EAGE,uBAAA;EACA,mBAAA;CrB2kGL;AoBpjGD;EClBI,YAAA;EACA,uBAAA;CrBykGH;AoBrjGD;EChEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBwnGD;AqBtnGC;;EAEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBwnGH;AqBtnGC;EACE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBwnGH;AqBtnGC;;;EAGE,YAAA;EACA,0BAAA;EACA,uBAAA;EACA,sBAAA;CrBwnGH;AqBtnGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB8nGL;AqBxnGG;;;;;;;;;EAGE,0BAAA;EACA,sBAAA;CrBgoGL;AoBtmGD;ECrBI,eAAA;EACA,uBAAA;CrB8nGH;AoBtmGD;ECpEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB6qGD;AqB3qGC;;EAEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB6qGH;AqB3qGC;EACE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB6qGH;AqB3qGC;;;EAGE,YAAA;EACA,0BAAA;EACA,uBAAA;EACA,sBAAA;CrB6qGH;AqB3qGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBmrGL;AqB7qGG;;;;;;;;;EAGE,0BAAA;EACA,sBAAA;CrBqrGL;AoBvpGD;ECzBI,eAAA;EACA,uBAAA;CrBmrGH;AoBvpGD;ECxEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBkuGD;AqBhuGC;;EAEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBkuGH;AqBhuGC;EACE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBkuGH;AqBhuGC;;;EAGE,YAAA;EACA,0BAAA;EACA,uBAAA;EACA,sBAAA;CrBkuGH;AqBhuGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBwuGL;AqBluGG;;;;;;;;;EAGE,0BAAA;EACA,sBAAA;CrB0uGL;AoBxsGD;EC7BI,eAAA;EACA,uBAAA;CrBwuGH;AoBxsGD;EC5EE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBuxGD;AqBrxGC;;EAEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBuxGH;AqBrxGC;EACE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBuxGH;AqBrxGC;;;EAGE,YAAA;EACA,0BAAA;EACA,uBAAA;EACA,sBAAA;CrBuxGH;AqBrxGG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB6xGL;AqBvxGG;;;;;;;;;EAGE,0BAAA;EACA,sBAAA;CrB+xGL;AoBzvGD;ECjCI,eAAA;EACA,uBAAA;CrB6xGH;AoBzvGD;EChFE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB40GD;AqB10GC;;EAEE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB40GH;AqB10GC;EACE,YAAA;EACA,0BAAA;EACA,sBAAA;CrB40GH;AqB10GC;;;EAGE,YAAA;EACA,0BAAA;EACA,uBAAA;EACA,sBAAA;CrB40GH;AqB10GG;;;;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;CrBk1GL;AqB50GG;;;;;;;;;EAGE,0BAAA;EACA,sBAAA;CrBo1GL;AoB1yGD;ECrCI,eAAA;EACA,uBAAA;CrBk1GH;AoBryGD;EACE,iBAAA;EACA,eAAA;EACA,iBAAA;CpBuyGD;AoBryGC;;;;;EAKE,8BAAA;EfnCF,yBAAA;EACQ,iBAAA;CL20GT;AoBtyGC;;;;EAIE,0BAAA;CpBwyGH;AoBtyGC;;EAEE,eAAA;EACA,2BAAA;EACA,8BAAA;CpBwyGH;AoBpyGG;;;;EAEE,eAAA;EACA,sBAAA;CpBwyGL;AoB/xGD;;EC9EE,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CrBi3GD;AoBlyGD;;EClFE,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CrBw3GD;AoBryGD;;ECtFE,iBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CrB+3GD;AoBpyGD;EACE,eAAA;EACA,YAAA;CpBsyGD;AoBlyGD;EACE,gBAAA;CpBoyGD;AoB7xGC;;;EACE,YAAA;CpBiyGH;AuB37GD;EACE,WAAA;ElBoLA,yCAAA;EACK,oCAAA;EACG,iCAAA;CL0wGT;AuB77GC;EACE,WAAA;CvB+7GH;AuB37GD;EACE,cAAA;CvB67GD;AuB37GC;EAAY,eAAA;CvB87Gb;AuB77GC;EAAY,mBAAA;CvBg8Gb;AuB/7GC;EAAY,yBAAA;CvBk8Gb;AuB/7GD;EACE,mBAAA;EACA,UAAA;EACA,iBAAA;ElBsKA,gDAAA;EACQ,2CAAA;EAAA,wCAAA;EAOR,mCAAA;EACQ,8BAAA;EAAA,2BAAA;EAGR,yCAAA;EACQ,oCAAA;EAAA,iCAAA;CLoxGT;AwBh+GD;EACE,sBAAA;EACA,SAAA;EACA,UAAA;EACA,iBAAA;EACA,uBAAA;EACA,uBAAA;EACA,yBAAA;EACA,oCAAA;EACA,mCAAA;CxBk+GD;AwB99GD;;EAEE,mBAAA;CxBg+GD;AwB59GD;EACE,WAAA;CxB89GD;AwB19GD;EACE,mBAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,cAAA;EACA,YAAA;EACA,iBAAA;EACA,eAAA;EACA,gBAAA;EACA,gBAAA;EACA,iBAAA;EACA,iBAAA;EACA,uBAAA;EACA,6BAAA;EACA,uBAAA;EACA,sCAAA;EACA,mBAAA;EnBuBA,oDAAA;EACQ,4CAAA;CLs8GT;AwBx9GC;EACE,SAAA;EACA,WAAA;CxB09GH;AwBn/GD;ECzBE,YAAA;EACA,cAAA;EACA,iBAAA;EACA,0BAAA;CzB+gHD;AwBz/GD;EAmCI,eAAA;EACA,kBAAA;EACA,YAAA;EACA,iBAAA;EACA,wBAAA;EACA,eAAA;EACA,oBAAA;CxBy9GH;AwBv9GG;;EAEE,eAAA;EACA,sBAAA;EACA,0BAAA;CxBy9GL;AwBl9GC;;;EAGE,YAAA;EACA,sBAAA;EACA,0BAAA;EACA,WAAA;CxBo9GH;AwB38GC;;;EAGE,eAAA;CxB68GH;AwBz8GC;;EAEE,sBAAA;EACA,oBAAA;EACA,8BAAA;EACA,uBAAA;EEzGF,oEAAA;C1BqjHD;AwBt8GD;EAGI,eAAA;CxBs8GH;AwBz8GD;EAQI,WAAA;CxBo8GH;AwB57GD;EACE,SAAA;EACA,WAAA;CxB87GD;AwBt7GD;EACE,YAAA;EACA,QAAA;CxBw7GD;AwBp7GD;EACE,eAAA;EACA,kBAAA;EACA,gBAAA;EACA,wBAAA;EACA,eAAA;EACA,oBAAA;CxBs7GD;AwBl7GD;EACE,gBAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,aAAA;CxBo7GD;AwBh7GD;EACE,SAAA;EACA,WAAA;CxBk7GD;AwB16GD;;EAII,YAAA;EACA,cAAA;EACA,0BAAA;EACA,4BAAA;CxB06GH;AwBj7GD;;EAWI,UAAA;EACA,aAAA;EACA,mBAAA;CxB06GH;AwBj6GD;EACE;IApEA,SAAA;IACA,WAAA;GxBw+GC;EwBr6GD;IA1DA,YAAA;IACA,QAAA;GxBk+GC;CACF;A2B7mHD;;EAEE,mBAAA;EACA,sBAAA;EACA,uBAAA;C3B+mHD;A2BnnHD;;EAMI,mBAAA;EACA,YAAA;C3BinHH;A2B/mHG;;;;;;;;EAIE,WAAA;C3BqnHL;A2B/mHD;;;;EAKI,kBAAA;C3BgnHH;A2B3mHD;EACE,kBAAA;C3B6mHD;A2B9mHD;;;EAOI,YAAA;C3B4mHH;A2BnnHD;;;EAYI,iBAAA;C3B4mHH;A2BxmHD;EACE,iBAAA;C3B0mHD;A2BtmHD;EACE,eAAA;C3BwmHD;A2BvmHC;ECpDA,2BAAA;EACA,8BAAA;C5B8pHD;A2BtmHD;;ECjDE,0BAAA;EACA,6BAAA;C5B2pHD;A2BrmHD;EACE,YAAA;C3BumHD;A2BrmHD;EACE,iBAAA;C3BumHD;A2BrmHD;;ECrEE,2BAAA;EACA,8BAAA;C5B8qHD;A2BpmHD;ECnEE,0BAAA;EACA,6BAAA;C5B0qHD;A2BnmHD;;EAEE,WAAA;C3BqmHD;A2BplHD;EACE,mBAAA;EACA,kBAAA;C3BslHD;A2BplHD;EACE,oBAAA;EACA,mBAAA;C3BslHD;A2BjlHD;EtB/CE,yDAAA;EACQ,iDAAA;CLmoHT;A2BjlHC;EtBnDA,yBAAA;EACQ,iBAAA;CLuoHT;A2B9kHD;EACE,eAAA;C3BglHD;A2B7kHD;EACE,wBAAA;EACA,uBAAA;C3B+kHD;A2B5kHD;EACE,wBAAA;C3B8kHD;A2BvkHD;;;EAII,eAAA;EACA,YAAA;EACA,YAAA;EACA,gBAAA;C3BwkHH;A2B/kHD;EAcM,YAAA;C3BokHL;A2BllHD;;;;EAsBI,iBAAA;EACA,eAAA;C3BkkHH;A2B7jHC;EACE,iBAAA;C3B+jHH;A2B7jHC;EC7KA,4BAAA;EACA,6BAAA;EAOA,8BAAA;EACA,6BAAA;C5BuuHD;A2B/jHC;ECjLA,0BAAA;EACA,2BAAA;EAOA,gCAAA;EACA,+BAAA;C5B6uHD;A2BhkHD;EACE,iBAAA;C3BkkHD;A2BhkHD;;ECjLE,8BAAA;EACA,6BAAA;C5BqvHD;A2B/jHD;EC/LE,0BAAA;EACA,2BAAA;C5BiwHD;A2B3jHD;EACE,eAAA;EACA,YAAA;EACA,oBAAA;EACA,0BAAA;C3B6jHD;A2BjkHD;;EAOI,oBAAA;EACA,YAAA;EACA,UAAA;C3B8jHH;A2BvkHD;EAYI,YAAA;C3B8jHH;A2B1kHD;EAgBI,WAAA;C3B6jHH;A2B5iHD;;;;EAKM,mBAAA;EACA,uBAAA;EACA,qBAAA;C3B6iHL;A6BvxHD;EACE,mBAAA;EACA,eAAA;EACA,0BAAA;C7ByxHD;A6BtxHC;EACE,YAAA;EACA,iBAAA;EACA,gBAAA;C7BwxHH;A6BjyHD;EAeI,mBAAA;EACA,WAAA;EAKA,YAAA;EAEA,YAAA;EACA,iBAAA;C7BgxHH;A6B9wHG;EACE,WAAA;C7BgxHL;A6BtwHD;;;EVwBE,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,uBAAA;EACA,mBAAA;CnBmvHD;AmBjvHC;;;EACE,aAAA;EACA,kBAAA;CnBqvHH;AmBlvHC;;;;;;EAEE,aAAA;CnBwvHH;A6BxxHD;;;EVmBE,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,mBAAA;CnB0wHD;AmBxwHC;;;EACE,aAAA;EACA,kBAAA;CnB4wHH;AmBzwHC;;;;;;EAEE,aAAA;CnB+wHH;A6BtyHD;;;EAGE,oBAAA;C7BwyHD;A6BtyHC;;;EACE,iBAAA;C7B0yHH;A6BtyHD;;EAEE,UAAA;EACA,oBAAA;EACA,uBAAA;C7BwyHD;A6BnyHD;EACE,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,eAAA;EACA,eAAA;EACA,mBAAA;EACA,0BAAA;EACA,uBAAA;EACA,mBAAA;C7BqyHD;A6BlyHC;EACE,kBAAA;EACA,gBAAA;EACA,mBAAA;C7BoyHH;A6BlyHC;EACE,mBAAA;EACA,gBAAA;EACA,mBAAA;C7BoyHH;A6BxzHD;;EA0BI,cAAA;C7BkyHH;A6B7xHD;;;;;;;EDtGE,2BAAA;EACA,8BAAA;C5B44HD;A6B9xHD;EACE,gBAAA;C7BgyHD;A6B9xHD;;;;;;;ED1GE,0BAAA;EACA,6BAAA;C5Bi5HD;A6B/xHD;EACE,eAAA;C7BiyHD;A6B5xHD;EACE,mBAAA;EAGA,aAAA;EACA,oBAAA;C7B4xHD;A6BjyHD;EAUI,mBAAA;C7B0xHH;A6BpyHD;EAYM,kBAAA;C7B2xHL;A6BxxHG;;;EAGE,WAAA;C7B0xHL;A6BrxHC;;EAGI,mBAAA;C7BsxHL;A6BnxHC;;EAGI,WAAA;EACA,kBAAA;C7BoxHL;A8Bn7HD;EACE,gBAAA;EACA,iBAAA;EACA,iBAAA;C9Bq7HD;A8Bx7HD;EAOI,mBAAA;EACA,eAAA;C9Bo7HH;A8B57HD;EAWM,mBAAA;EACA,eAAA;EACA,mBAAA;C9Bo7HL;A8Bn7HK;;EAEE,sBAAA;EACA,0BAAA;C9Bq7HP;A8Bh7HG;EACE,eAAA;C9Bk7HL;A8Bh7HK;;EAEE,eAAA;EACA,sBAAA;EACA,oBAAA;EACA,8BAAA;C9Bk7HP;A8B36HG;;;EAGE,0BAAA;EACA,sBAAA;C9B66HL;A8Bt9HD;ELLE,YAAA;EACA,cAAA;EACA,iBAAA;EACA,0BAAA;CzB89HD;A8B59HD;EA0DI,gBAAA;C9Bq6HH;A8B55HD;EACE,8BAAA;C9B85HD;A8B/5HD;EAGI,YAAA;EAEA,oBAAA;C9B85HH;A8Bn6HD;EASM,kBAAA;EACA,wBAAA;EACA,8BAAA;EACA,2BAAA;C9B65HL;A8B55HK;EACE,mCAAA;C9B85HP;A8Bx5HK;;;EAGE,eAAA;EACA,gBAAA;EACA,uBAAA;EACA,uBAAA;EACA,iCAAA;C9B05HP;A8Br5HC;EAqDA,YAAA;EA8BA,iBAAA;C9Bs0HD;A8Bz5HC;EAwDE,YAAA;C9Bo2HH;A8B55HC;EA0DI,mBAAA;EACA,mBAAA;C9Bq2HL;A8Bh6HC;EAgEE,UAAA;EACA,WAAA;C9Bm2HH;A8Bh2HC;EAAA;IAEI,oBAAA;IACA,UAAA;G9Bk2HH;E8Br2HD;IAKM,iBAAA;G9Bm2HL;CACF;A8B76HC;EAuFE,gBAAA;EACA,mBAAA;C9By1HH;A8Bj7HC;;;EA8FE,uBAAA;C9Bw1HH;A8Br1HC;EAAA;IAEI,8BAAA;IACA,2BAAA;G9Bu1HH;E8B11HD;;;IAQI,0BAAA;G9Bu1HH;CACF;A8Bx7HD;EAEI,YAAA;C9By7HH;A8B37HD;EAMM,mBAAA;C9Bw7HL;A8B97HD;EASM,iBAAA;C9Bw7HL;A8Bn7HK;;;EAGE,YAAA;EACA,0BAAA;C9Bq7HP;A8B76HD;EAEI,YAAA;C9B86HH;A8Bh7HD;EAIM,gBAAA;EACA,eAAA;C9B+6HL;A8Bn6HD;EACE,YAAA;C9Bq6HD;A8Bt6HD;EAII,YAAA;C9Bq6HH;A8Bz6HD;EAMM,mBAAA;EACA,mBAAA;C9Bs6HL;A8B76HD;EAYI,UAAA;EACA,WAAA;C9Bo6HH;A8Bj6HC;EAAA;IAEI,oBAAA;IACA,UAAA;G9Bm6HH;E8Bt6HD;IAKM,iBAAA;G9Bo6HL;CACF;A8B55HD;EACE,iBAAA;C9B85HD;A8B/5HD;EAKI,gBAAA;EACA,mBAAA;C9B65HH;A8Bn6HD;;;EAYI,uBAAA;C9B45HH;A8Bz5HC;EAAA;IAEI,8BAAA;IACA,2BAAA;G9B25HH;E8B95HD;;;IAQI,0BAAA;G9B25HH;CACF;A8Bl5HD;EAEI,cAAA;C9Bm5HH;A8Br5HD;EAKI,eAAA;C9Bm5HH;A8B14HD;EAEE,iBAAA;EF7OA,0BAAA;EACA,2BAAA;C5BynID;A+BjnID;EACE,mBAAA;EACA,iBAAA;EACA,oBAAA;EACA,8BAAA;C/BmnID;A+B9mIC;EAAA;IACE,mBAAA;G/BinID;CACF;A+BrmIC;EAAA;IACE,YAAA;G/BwmID;CACF;A+B1lID;EACE,oBAAA;EACA,mBAAA;EACA,oBAAA;EACA,kCAAA;EACA,2DAAA;EAAA,mDAAA;EAEA,kCAAA;C/B2lID;A+BzlIC;EACE,iBAAA;C/B2lIH;A+BxlIC;EAAA;IACE,YAAA;IACA,cAAA;IACA,yBAAA;IAAA,iBAAA;G/B2lID;E+BzlIC;IACE,0BAAA;IACA,wBAAA;IACA,kBAAA;IACA,6BAAA;G/B2lIH;E+BxlIC;IACE,oBAAA;G/B0lIH;E+BrlIC;;;IAGE,iBAAA;IACA,gBAAA;G/BulIH;CACF;A+BnlID;;EAWE,gBAAA;EACA,SAAA;EACA,QAAA;EACA,cAAA;C/B4kID;A+B1lID;;EAGI,kBAAA;C/B2lIH;A+BzlIG;EAAA;;IACE,kBAAA;G/B6lIH;CACF;A+BnlIC;EAAA;;IACE,iBAAA;G/BulID;CACF;A+BplID;EACE,OAAA;EACA,sBAAA;C/BslID;A+BplID;EACE,UAAA;EACA,iBAAA;EACA,sBAAA;C/BslID;A+B9kID;;;;EAII,oBAAA;EACA,mBAAA;C/BglIH;A+B9kIG;EAAA;;;;IACE,gBAAA;IACA,eAAA;G/BolIH;CACF;A+BxkID;EACE,cAAA;EACA,sBAAA;C/B0kID;A+BxkIC;EAAA;IACE,iBAAA;G/B2kID;CACF;A+BrkID;EACE,YAAA;EACA,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,kBAAA;C/BukID;A+BrkIC;;EAEE,sBAAA;C/BukIH;A+BhlID;EAaI,eAAA;C/BskIH;A+BnkIC;EACE;;IAEE,mBAAA;G/BqkIH;CACF;A+B3jID;EACE,mBAAA;EACA,aAAA;EACA,kBAAA;EACA,mBAAA;EC9LA,gBAAA;EACA,mBAAA;ED+LA,8BAAA;EACA,uBAAA;EACA,8BAAA;EACA,mBAAA;C/B8jID;A+B1jIC;EACE,WAAA;C/B4jIH;A+B1kID;EAmBI,eAAA;EACA,YAAA;EACA,YAAA;EACA,mBAAA;C/B0jIH;A+BhlID;EAyBI,gBAAA;C/B0jIH;A+BvjIC;EAAA;IACE,cAAA;G/B0jID;CACF;A+BjjID;EACE,oBAAA;C/BmjID;A+BpjID;EAII,kBAAA;EACA,qBAAA;EACA,kBAAA;C/BmjIH;A+BhjIC;EAAA;IAGI,iBAAA;IACA,YAAA;IACA,YAAA;IACA,cAAA;IACA,8BAAA;IACA,UAAA;IACA,yBAAA;IAAA,iBAAA;G/BijIH;E+B1jID;;IAYM,2BAAA;G/BkjIL;E+B9jID;IAeM,kBAAA;G/BkjIL;E+BjjIK;;IAEE,uBAAA;G/BmjIP;CACF;A+B7iIC;EAAA;IACE,YAAA;IACA,UAAA;G/BgjID;E+BljID;IAKI,YAAA;G/BgjIH;E+BrjID;IAOM,kBAAA;IACA,qBAAA;G/BijIL;CACF;A+BtiID;EACE,mBAAA;EACA,oBAAA;EACA,mBAAA;EACA,kCAAA;EACA,qCAAA;E1B5NA,6FAAA;EACQ,qFAAA;E2BjER,gBAAA;EACA,mBAAA;ChCu0ID;AkB13HC;EAAA;IAGI,sBAAA;IACA,iBAAA;IACA,uBAAA;GlB23HH;EkBh4HD;IAUI,sBAAA;IACA,YAAA;IACA,uBAAA;GlBy3HH;EkBr4HD;IAiBI,sBAAA;GlBu3HH;EkBx4HD;IAqBI,sBAAA;IACA,uBAAA;GlBs3HH;EkB54HD;;;IA2BM,YAAA;GlBs3HL;EkBj5HD;IAiCI,YAAA;GlBm3HH;EkBp5HD;IAqCI,iBAAA;IACA,uBAAA;GlBk3HH;EkBx5HD;;IA6CI,sBAAA;IACA,cAAA;IACA,iBAAA;IACA,uBAAA;GlB+2HH;EkB/5HD;;IAmDM,gBAAA;GlBg3HL;EkBn6HD;;IAwDI,mBAAA;IACA,eAAA;GlB+2HH;EkBx6HD;IA8DI,OAAA;GlB62HH;CACF;A+BtlIG;EAAA;IACE,mBAAA;G/BylIH;E+BvlIG;IACE,iBAAA;G/BylIL;CACF;A+BjlIC;EAAA;IACE,YAAA;IACA,eAAA;IACA,kBAAA;IACA,gBAAA;IACA,eAAA;IACA,UAAA;I1BvPF,yBAAA;IACQ,iBAAA;GL40IP;CACF;A+B9kID;EACE,cAAA;EHpUA,0BAAA;EACA,2BAAA;C5Bq5ID;A+B9kID;EACE,iBAAA;EHzUA,4BAAA;EACA,6BAAA;EAOA,8BAAA;EACA,6BAAA;C5Bo5ID;A+B1kID;EChVE,gBAAA;EACA,mBAAA;ChC65ID;A+B3kIC;ECnVA,iBAAA;EACA,oBAAA;ChCi6ID;A+B5kIC;ECtVA,iBAAA;EACA,oBAAA;ChCq6ID;A+BtkID;EChWE,iBAAA;EACA,oBAAA;ChCy6ID;A+BvkIC;EAAA;IACE,YAAA;IACA,mBAAA;IACA,kBAAA;G/B0kID;CACF;A+B9jID;EACE;IEtWA,uBAAA;GjCu6IC;E+BhkID;IE1WA,wBAAA;IF4WE,oBAAA;G/BkkID;E+BpkID;IAKI,gBAAA;G/BkkIH;CACF;A+BzjID;EACE,0BAAA;EACA,sBAAA;C/B2jID;A+B7jID;EAKI,YAAA;C/B2jIH;A+B1jIG;;EAEE,eAAA;EACA,8BAAA;C/B4jIL;A+BrkID;EAcI,YAAA;C/B0jIH;A+BxkID;EAmBM,YAAA;C/BwjIL;A+BtjIK;;EAEE,YAAA;EACA,8BAAA;C/BwjIP;A+BpjIK;;;EAGE,YAAA;EACA,0BAAA;C/BsjIP;A+BljIK;;;EAGE,YAAA;EACA,8BAAA;C/BojIP;A+B7iIK;;;EAGE,YAAA;EACA,0BAAA;C/B+iIP;A+B3iIG;EAAA;IAIM,YAAA;G/B2iIP;E+B1iIO;;IAEE,YAAA;IACA,8BAAA;G/B4iIT;E+BxiIO;;;IAGE,YAAA;IACA,0BAAA;G/B0iIT;E+BtiIO;;;IAGE,YAAA;IACA,8BAAA;G/BwiIT;CACF;A+BxnID;EAuFI,mBAAA;C/BoiIH;A+BniIG;;EAEE,uBAAA;C/BqiIL;A+B/nID;EA6FM,uBAAA;C/BqiIL;A+BloID;;EAmGI,sBAAA;C/BmiIH;A+BtoID;EA4GI,YAAA;C/B6hIH;A+B5hIG;EACE,YAAA;C/B8hIL;A+B5oID;EAmHI,YAAA;C/B4hIH;A+B3hIG;;EAEE,YAAA;C/B6hIL;A+BzhIK;;;;EAEE,YAAA;C/B6hIP;A+BrhID;EACE,uBAAA;EACA,sBAAA;C/BuhID;A+BzhID;EAKI,eAAA;C/BuhIH;A+BthIG;;EAEE,YAAA;EACA,8BAAA;C/BwhIL;A+BjiID;EAcI,eAAA;C/BshIH;A+BpiID;EAmBM,eAAA;C/BohIL;A+BlhIK;;EAEE,YAAA;EACA,8BAAA;C/BohIP;A+BhhIK;;;EAGE,YAAA;EACA,0BAAA;C/BkhIP;A+B9gIK;;;EAGE,YAAA;EACA,8BAAA;C/BghIP;A+B1gIK;;;EAGE,YAAA;EACA,0BAAA;C/B4gIP;A+BxgIG;EAAA;IAIM,sBAAA;G/BwgIP;E+B5gIC;IAOM,0BAAA;G/BwgIP;E+B/gIC;IAUM,eAAA;G/BwgIP;E+BvgIO;;IAEE,YAAA;IACA,8BAAA;G/BygIT;E+BrgIO;;;IAGE,YAAA;IACA,0BAAA;G/BugIT;E+BngIO;;;IAGE,YAAA;IACA,8BAAA;G/BqgIT;CACF;A+B1lID;EA6FI,mBAAA;C/BggIH;A+B//HG;;EAEE,uBAAA;C/BigIL;A+BjmID;EAmGM,uBAAA;C/BigIL;A+BpmID;;EAyGI,sBAAA;C/B+/HH;A+BxmID;EA6GI,eAAA;C/B8/HH;A+B7/HG;EACE,YAAA;C/B+/HL;A+B9mID;EAoHI,eAAA;C/B6/HH;A+B5/HG;;EAEE,YAAA;C/B8/HL;A+B1/HK;;;;EAEE,YAAA;C/B8/HP;AkCpoJD;EACE,kBAAA;EACA,oBAAA;EACA,iBAAA;EACA,0BAAA;EACA,mBAAA;ClCsoJD;AkC3oJD;EAQI,sBAAA;ClCsoJH;AkC9oJD;EAWM,eAAA;EACA,YAAA;EACA,kBAAA;ClCsoJL;AkCnpJD;EAkBI,eAAA;ClCooJH;AmCxpJD;EACE,sBAAA;EACA,gBAAA;EACA,eAAA;EACA,mBAAA;CnC0pJD;AmC9pJD;EAOI,gBAAA;CnC0pJH;AmCjqJD;;EAUM,mBAAA;EACA,YAAA;EACA,kBAAA;EACA,kBAAA;EACA,wBAAA;EACA,eAAA;EACA,sBAAA;EACA,uBAAA;EACA,uBAAA;CnC2pJL;AmCzpJK;;;;EAEE,WAAA;EACA,eAAA;EACA,0BAAA;EACA,mBAAA;CnC6pJP;AmC1pJG;;EAGI,eAAA;EPnBN,4BAAA;EACA,+BAAA;C5B+qJD;AmCzpJG;;EP/BF,6BAAA;EACA,gCAAA;C5B4rJD;AmCppJG;;;;;;EAGE,WAAA;EACA,YAAA;EACA,gBAAA;EACA,0BAAA;EACA,sBAAA;CnCypJL;AmC7sJD;;;;;;EA+DM,eAAA;EACA,oBAAA;EACA,uBAAA;EACA,mBAAA;CnCspJL;AmC7oJD;;ECxEM,mBAAA;EACA,gBAAA;EACA,uBAAA;CpCytJL;AoCvtJG;;ERKF,4BAAA;EACA,+BAAA;C5BstJD;AoCttJG;;ERTF,6BAAA;EACA,gCAAA;C5BmuJD;AmCxpJD;;EC7EM,kBAAA;EACA,gBAAA;EACA,iBAAA;CpCyuJL;AoCvuJG;;ERKF,4BAAA;EACA,+BAAA;C5BsuJD;AoCtuJG;;ERTF,6BAAA;EACA,gCAAA;C5BmvJD;AqCtvJD;EACE,gBAAA;EACA,eAAA;EACA,mBAAA;EACA,iBAAA;CrCwvJD;AqC5vJD;EAOI,gBAAA;CrCwvJH;AqC/vJD;;EAUM,sBAAA;EACA,kBAAA;EACA,uBAAA;EACA,uBAAA;EACA,oBAAA;CrCyvJL;AqCvwJD;;EAmBM,sBAAA;EACA,0BAAA;CrCwvJL;AqC5wJD;;EA2BM,aAAA;CrCqvJL;AqChxJD;;EAkCM,YAAA;CrCkvJL;AqCpxJD;;;;EA2CM,eAAA;EACA,oBAAA;EACA,uBAAA;CrC+uJL;AsC7xJD;EACE,gBAAA;EACA,2BAAA;EACA,eAAA;EACA,iBAAA;EACA,eAAA;EACA,YAAA;EACA,mBAAA;EACA,oBAAA;EACA,yBAAA;EACA,sBAAA;CtC+xJD;AsC3xJG;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;CtC6xJL;AsCxxJC;EACE,cAAA;CtC0xJH;AsCtxJC;EACE,mBAAA;EACA,UAAA;CtCwxJH;AsCjxJD;ECtCE,0BAAA;CvC0zJD;AuCvzJG;;EAEE,0BAAA;CvCyzJL;AsCpxJD;EC1CE,0BAAA;CvCi0JD;AuC9zJG;;EAEE,0BAAA;CvCg0JL;AsCvxJD;EC9CE,0BAAA;CvCw0JD;AuCr0JG;;EAEE,0BAAA;CvCu0JL;AsC1xJD;EClDE,0BAAA;CvC+0JD;AuC50JG;;EAEE,0BAAA;CvC80JL;AsC7xJD;ECtDE,0BAAA;CvCs1JD;AuCn1JG;;EAEE,0BAAA;CvCq1JL;AsChyJD;EC1DE,0BAAA;CvC61JD;AuC11JG;;EAEE,0BAAA;CvC41JL;AwC91JD;EACE,sBAAA;EACA,gBAAA;EACA,iBAAA;EACA,gBAAA;EACA,kBAAA;EACA,eAAA;EACA,YAAA;EACA,mBAAA;EACA,oBAAA;EACA,uBAAA;EACA,0BAAA;EACA,oBAAA;CxCg2JD;AwC71JC;EACE,cAAA;CxC+1JH;AwC31JC;EACE,mBAAA;EACA,UAAA;CxC61JH;AwC11JC;;EAEE,OAAA;EACA,iBAAA;CxC41JH;AwCv1JG;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;CxCy1JL;AwCp1JC;;EAEE,eAAA;EACA,uBAAA;CxCs1JH;AwCn1JC;EACE,aAAA;CxCq1JH;AwCl1JC;EACE,kBAAA;CxCo1JH;AwCj1JC;EACE,iBAAA;CxCm1JH;AyC74JD;EACE,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,eAAA;EACA,0BAAA;CzC+4JD;AyCp5JD;;EASI,eAAA;CzC+4JH;AyCx5JD;EAaI,oBAAA;EACA,gBAAA;EACA,iBAAA;CzC84JH;AyC75JD;EAmBI,0BAAA;CzC64JH;AyC14JC;;EAEE,oBAAA;EACA,mBAAA;EACA,mBAAA;CzC44JH;AyCt6JD;EA8BI,gBAAA;CzC24JH;AyCx4JC;EAAA;IACE,kBAAA;IACA,qBAAA;GzC24JD;EyCz4JC;;IAEE,oBAAA;IACA,mBAAA;GzC24JH;EyCl5JD;;IAYI,gBAAA;GzC04JH;CACF;A0Cr7JD;EACE,eAAA;EACA,aAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;EACA,uBAAA;EACA,mBAAA;ErCiLA,4CAAA;EACK,uCAAA;EACG,oCAAA;CLuwJT;A0Cj8JD;;EAaI,mBAAA;EACA,kBAAA;C1Cw7JH;A0Cp7JC;;;EAGE,sBAAA;C1Cs7JH;A0C38JD;EA0BI,aAAA;EACA,eAAA;C1Co7JH;A2C/8JD;EACE,cAAA;EACA,oBAAA;EACA,8BAAA;EACA,mBAAA;C3Ci9JD;A2Cr9JD;EAQI,cAAA;EACA,eAAA;C3Cg9JH;A2Cz9JD;EAcI,kBAAA;C3C88JH;A2C59JD;;EAoBI,iBAAA;C3C48JH;A2Ch+JD;EAwBI,gBAAA;C3C28JH;A2Cl8JD;;EAEE,oBAAA;C3Co8JD;A2Ct8JD;;EAMI,mBAAA;EACA,UAAA;EACA,aAAA;EACA,eAAA;C3Co8JH;A2C57JD;ECvDE,eAAA;EACA,0BAAA;EACA,sBAAA;C5Cs/JD;A2Cj8JD;EClDI,0BAAA;C5Cs/JH;A2Cp8JD;EC9CI,eAAA;C5Cq/JH;A2Cn8JD;EC3DE,eAAA;EACA,0BAAA;EACA,sBAAA;C5CigKD;A2Cx8JD;ECtDI,0BAAA;C5CigKH;A2C38JD;EClDI,eAAA;C5CggKH;A2C18JD;EC/DE,eAAA;EACA,0BAAA;EACA,sBAAA;C5C4gKD;A2C/8JD;EC1DI,0BAAA;C5C4gKH;A2Cl9JD;ECtDI,eAAA;C5C2gKH;A2Cj9JD;ECnEE,eAAA;EACA,0BAAA;EACA,sBAAA;C5CuhKD;A2Ct9JD;EC9DI,0BAAA;C5CuhKH;A2Cz9JD;EC1DI,eAAA;C5CshKH;A6CvhKD;EACE;IAAQ,4BAAA;G7C0hKP;E6CzhKD;IAAQ,yBAAA;G7C4hKP;CACF;A6CzhKD;EACE;IAAQ,4BAAA;G7C4hKP;E6C3hKD;IAAQ,yBAAA;G7C8hKP;CACF;A6CjiKD;EACE;IAAQ,4BAAA;G7C4hKP;E6C3hKD;IAAQ,yBAAA;G7C8hKP;CACF;A6CvhKD;EACE,aAAA;EACA,oBAAA;EACA,iBAAA;EACA,0BAAA;EACA,mBAAA;ExCsCA,uDAAA;EACQ,+CAAA;CLo/JT;A6CthKD;EACE,YAAA;EACA,UAAA;EACA,aAAA;EACA,gBAAA;EACA,kBAAA;EACA,YAAA;EACA,mBAAA;EACA,0BAAA;ExCyBA,uDAAA;EACQ,+CAAA;EAyHR,oCAAA;EACK,+BAAA;EACG,4BAAA;CLw4JT;A6CnhKD;;ECDI,8MAAA;EACA,yMAAA;EACA,sMAAA;EDEF,mCAAA;EAAA,2BAAA;C7CuhKD;A6ChhKD;;ExC5CE,2DAAA;EACK,sDAAA;EACG,mDAAA;CLgkKT;A6C7gKD;EEvEE,0BAAA;C/CulKD;A+CplKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9CuiKH;A6CjhKD;EE3EE,0BAAA;C/C+lKD;A+C5lKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9C+iKH;A6CrhKD;EE/EE,0BAAA;C/CumKD;A+CpmKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9CujKH;A6CzhKD;EEnFE,0BAAA;C/C+mKD;A+C5mKC;EDgDE,8MAAA;EACA,yMAAA;EACA,sMAAA;C9C+jKH;AgDvnKD;EAEE,iBAAA;ChDwnKD;AgDtnKC;EACE,cAAA;ChDwnKH;AgDpnKD;;EAEE,iBAAA;EACA,QAAA;ChDsnKD;AgDnnKD;EACE,eAAA;ChDqnKD;AgDlnKD;EACE,eAAA;ChDonKD;AgDjnKC;EACE,gBAAA;ChDmnKH;AgD/mKD;;EAEE,mBAAA;ChDinKD;AgD9mKD;;EAEE,oBAAA;ChDgnKD;AgD7mKD;;;EAGE,oBAAA;EACA,oBAAA;ChD+mKD;AgD5mKD;EACE,uBAAA;ChD8mKD;AgD3mKD;EACE,uBAAA;ChD6mKD;AgDzmKD;EACE,cAAA;EACA,mBAAA;ChD2mKD;AgDrmKD;EACE,gBAAA;EACA,iBAAA;ChDumKD;AiD5pKD;EAEE,gBAAA;EACA,oBAAA;CjD6pKD;AiDrpKD;EACE,mBAAA;EACA,eAAA;EACA,mBAAA;EAEA,oBAAA;EACA,uBAAA;EACA,uBAAA;CjDspKD;AiDnpKC;ErB7BA,4BAAA;EACA,6BAAA;C5BmrKD;AiDppKC;EACE,iBAAA;ErBzBF,gCAAA;EACA,+BAAA;C5BgrKD;AiDnpKC;;;EAGE,eAAA;EACA,oBAAA;EACA,0BAAA;CjDqpKH;AiD1pKC;;;EASI,eAAA;CjDspKL;AiD/pKC;;;EAYI,eAAA;CjDwpKL;AiDnpKC;;;EAGE,WAAA;EACA,YAAA;EACA,0BAAA;EACA,sBAAA;CjDqpKH;AiD3pKC;;;;;;;;;EAYI,eAAA;CjD0pKL;AiDtqKC;;;EAeI,eAAA;CjD4pKL;AiDjpKD;;EAEE,YAAA;CjDmpKD;AiDrpKD;;EAKI,YAAA;CjDopKH;AiDhpKC;;;;EAEE,YAAA;EACA,sBAAA;EACA,0BAAA;CjDopKH;AiDhpKD;EACE,YAAA;EACA,iBAAA;CjDkpKD;AczvKA;EoCIG,eAAA;EACA,0BAAA;ClDwvKH;AkDtvKG;;EAEE,eAAA;ClDwvKL;AkD1vKG;;EAKI,eAAA;ClDyvKP;AkDtvKK;;;;EAEE,eAAA;EACA,0BAAA;ClD0vKP;AkDxvKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClD6vKP;ActxKA;EoCIG,eAAA;EACA,0BAAA;ClDqxKH;AkDnxKG;;EAEE,eAAA;ClDqxKL;AkDvxKG;;EAKI,eAAA;ClDsxKP;AkDnxKK;;;;EAEE,eAAA;EACA,0BAAA;ClDuxKP;AkDrxKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClD0xKP;AcnzKA;EoCIG,eAAA;EACA,0BAAA;ClDkzKH;AkDhzKG;;EAEE,eAAA;ClDkzKL;AkDpzKG;;EAKI,eAAA;ClDmzKP;AkDhzKK;;;;EAEE,eAAA;EACA,0BAAA;ClDozKP;AkDlzKK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClDuzKP;Ach1KA;EoCIG,eAAA;EACA,0BAAA;ClD+0KH;AkD70KG;;EAEE,eAAA;ClD+0KL;AkDj1KG;;EAKI,eAAA;ClDg1KP;AkD70KK;;;;EAEE,eAAA;EACA,0BAAA;ClDi1KP;AkD/0KK;;;;;;EAGE,YAAA;EACA,0BAAA;EACA,sBAAA;ClDo1KP;AiDnvKD;EACE,cAAA;EACA,mBAAA;CjDqvKD;AiDnvKD;EACE,iBAAA;EACA,iBAAA;CjDqvKD;AmD72KD;EACE,oBAAA;EACA,uBAAA;EACA,8BAAA;EACA,mBAAA;E9C0DA,kDAAA;EACQ,0CAAA;CLszKT;AmD52KD;EACE,cAAA;CnD82KD;AmDz2KD;EACE,mBAAA;EACA,qCAAA;EvBtBA,4BAAA;EACA,6BAAA;C5Bk4KD;AmD/2KD;EAMI,eAAA;CnD42KH;AmDv2KD;EACE,cAAA;EACA,iBAAA;EACA,gBAAA;EACA,eAAA;CnDy2KD;AmD72KD;;;;;EAWI,eAAA;CnDy2KH;AmDp2KD;EACE,mBAAA;EACA,0BAAA;EACA,2BAAA;EvB1CA,gCAAA;EACA,+BAAA;C5Bi5KD;AmD91KD;;EAGI,iBAAA;CnD+1KH;AmDl2KD;;EAMM,oBAAA;EACA,iBAAA;CnDg2KL;AmD51KG;;EAEI,cAAA;EvBzEN,4BAAA;EACA,6BAAA;C5Bw6KD;AmD11KG;;EAEI,iBAAA;EvBzEN,gCAAA;EACA,+BAAA;C5Bs6KD;AmDn3KD;EvB5DE,0BAAA;EACA,2BAAA;C5Bk7KD;AmDt1KD;EAEI,oBAAA;CnDu1KH;AmDp1KD;EACE,oBAAA;CnDs1KD;AmD90KD;;;EAII,iBAAA;CnD+0KH;AmDn1KD;;;EAOM,oBAAA;EACA,mBAAA;CnDi1KL;AmDz1KD;;EvB3GE,4BAAA;EACA,6BAAA;C5Bw8KD;AmD91KD;;;;EAmBQ,4BAAA;EACA,6BAAA;CnDi1KP;AmDr2KD;;;;;;;;EAwBU,4BAAA;CnDu1KT;AmD/2KD;;;;;;;;EA4BU,6BAAA;CnD61KT;AmDz3KD;;EvBnGE,gCAAA;EACA,+BAAA;C5Bg+KD;AmD93KD;;;;EAyCQ,gCAAA;EACA,+BAAA;CnD21KP;AmDr4KD;;;;;;;;EA8CU,+BAAA;CnDi2KT;AmD/4KD;;;;;;;;EAkDU,gCAAA;CnDu2KT;AmDz5KD;;;;EA2DI,2BAAA;CnDo2KH;AmD/5KD;;EA+DI,cAAA;CnDo2KH;AmDn6KD;;EAmEI,UAAA;CnDo2KH;AmDv6KD;;;;;;;;;;;;EA0EU,eAAA;CnD22KT;AmDr7KD;;;;;;;;;;;;EA8EU,gBAAA;CnDq3KT;AmDn8KD;;;;;;;;EAuFU,iBAAA;CnDs3KT;AmD78KD;;;;;;;;EAgGU,iBAAA;CnDu3KT;AmDv9KD;EAsGI,iBAAA;EACA,UAAA;CnDo3KH;AmD12KD;EACE,oBAAA;CnD42KD;AmD72KD;EAKI,iBAAA;EACA,mBAAA;CnD22KH;AmDj3KD;EASM,gBAAA;CnD22KL;AmDp3KD;EAcI,iBAAA;CnDy2KH;AmDv3KD;;EAkBM,2BAAA;CnDy2KL;AmD33KD;EAuBI,cAAA;CnDu2KH;AmD93KD;EAyBM,8BAAA;CnDw2KL;AmDj2KD;EC5PE,mBAAA;CpDgmLD;AoD9lLC;EACE,eAAA;EACA,0BAAA;EACA,mBAAA;CpDgmLH;AoDnmLC;EAMI,uBAAA;CpDgmLL;AoDtmLC;EASI,eAAA;EACA,0BAAA;CpDgmLL;AoD7lLC;EAEI,0BAAA;CpD8lLL;AmDh3KD;EC/PE,sBAAA;CpDknLD;AoDhnLC;EACE,YAAA;EACA,0BAAA;EACA,sBAAA;CpDknLH;AoDrnLC;EAMI,0BAAA;CpDknLL;AoDxnLC;EASI,eAAA;EACA,uBAAA;CpDknLL;AoD/mLC;EAEI,6BAAA;CpDgnLL;AmD/3KD;EClQE,sBAAA;CpDooLD;AoDloLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpDooLH;AoDvoLC;EAMI,0BAAA;CpDooLL;AoD1oLC;EASI,eAAA;EACA,0BAAA;CpDooLL;AoDjoLC;EAEI,6BAAA;CpDkoLL;AmD94KD;ECrQE,sBAAA;CpDspLD;AoDppLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpDspLH;AoDzpLC;EAMI,0BAAA;CpDspLL;AoD5pLC;EASI,eAAA;EACA,0BAAA;CpDspLL;AoDnpLC;EAEI,6BAAA;CpDopLL;AmD75KD;ECxQE,sBAAA;CpDwqLD;AoDtqLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpDwqLH;AoD3qLC;EAMI,0BAAA;CpDwqLL;AoD9qLC;EASI,eAAA;EACA,0BAAA;CpDwqLL;AoDrqLC;EAEI,6BAAA;CpDsqLL;AmD56KD;EC3QE,sBAAA;CpD0rLD;AoDxrLC;EACE,eAAA;EACA,0BAAA;EACA,sBAAA;CpD0rLH;AoD7rLC;EAMI,0BAAA;CpD0rLL;AoDhsLC;EASI,eAAA;EACA,0BAAA;CpD0rLL;AoDvrLC;EAEI,6BAAA;CpDwrLL;AqDxsLD;EACE,mBAAA;EACA,eAAA;EACA,UAAA;EACA,WAAA;EACA,iBAAA;CrD0sLD;AqD/sLD;;;;;EAYI,mBAAA;EACA,OAAA;EACA,UAAA;EACA,QAAA;EACA,YAAA;EACA,aAAA;EACA,UAAA;CrD0sLH;AqDrsLD;EACE,uBAAA;CrDusLD;AqDnsLD;EACE,oBAAA;CrDqsLD;AsDhuLD;EACE,iBAAA;EACA,cAAA;EACA,oBAAA;EACA,0BAAA;EACA,0BAAA;EACA,mBAAA;EjD0DA,wDAAA;EACQ,gDAAA;CLyqLT;AsD1uLD;EASI,mBAAA;EACA,kCAAA;CtDouLH;AsD/tLD;EACE,cAAA;EACA,mBAAA;CtDiuLD;AsD/tLD;EACE,aAAA;EACA,mBAAA;CtDiuLD;AuDrvLD;EACE,aAAA;EACA,gBAAA;EACA,kBAAA;EACA,eAAA;EACA,YAAA;EACA,0BAAA;EjCTA,0BAAA;EACA,aAAA;CtBiwLD;AuDtvLC;;EAEE,YAAA;EACA,sBAAA;EACA,gBAAA;EjChBF,0BAAA;EACA,aAAA;CtBywLD;AuDlvLC;EACE,WAAA;EACA,gBAAA;EACA,wBAAA;EACA,UAAA;EACA,yBAAA;EACA,sBAAA;EAAA,iBAAA;CvDovLH;AwD5wLD;EACE,iBAAA;CxD8wLD;AwD1wLD;EACE,gBAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,cAAA;EACA,iBAAA;EACA,kCAAA;EAIA,WAAA;CxDywLD;AwDtwLC;EnDiHA,sCAAA;EACI,kCAAA;EACC,iCAAA;EACG,8BAAA;EAkER,oDAAA;EAEK,0CAAA;EACG,4CAAA;EAAA,oCAAA;EAAA,iGAAA;CLulLT;AwD5wLC;EnD6GA,mCAAA;EACI,+BAAA;EACC,8BAAA;EACG,2BAAA;CLkqLT;AwDhxLD;EACE,mBAAA;EACA,iBAAA;CxDkxLD;AwD9wLD;EACE,mBAAA;EACA,YAAA;EACA,aAAA;CxDgxLD;AwD5wLD;EACE,mBAAA;EACA,uBAAA;EACA,6BAAA;EACA,uBAAA;EACA,qCAAA;EACA,mBAAA;EnDcA,iDAAA;EACQ,yCAAA;EmDZR,WAAA;CxD8wLD;AwD1wLD;EACE,gBAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,QAAA;EACA,cAAA;EACA,uBAAA;CxD4wLD;AwD1wLC;ElCpEA,yBAAA;EACA,WAAA;CtBi1LD;AwD7wLC;ElCrEA,0BAAA;EACA,aAAA;CtBq1LD;AwD5wLD;EACE,cAAA;EACA,iCAAA;CxD8wLD;AwD1wLD;EACE,iBAAA;CxD4wLD;AwDxwLD;EACE,UAAA;EACA,wBAAA;CxD0wLD;AwDrwLD;EACE,mBAAA;EACA,cAAA;CxDuwLD;AwDnwLD;EACE,cAAA;EACA,kBAAA;EACA,8BAAA;CxDqwLD;AwDxwLD;EAQI,iBAAA;EACA,iBAAA;CxDmwLH;AwD5wLD;EAaI,kBAAA;CxDkwLH;AwD/wLD;EAiBI,eAAA;CxDiwLH;AwD5vLD;EACE,mBAAA;EACA,aAAA;EACA,YAAA;EACA,aAAA;EACA,iBAAA;CxD8vLD;AwD1vLD;EAEE;IACE,aAAA;IACA,kBAAA;GxD2vLD;EwDzvLD;InDrEA,kDAAA;IACQ,0CAAA;GLi0LP;EwDxvLD;IAAY,aAAA;GxD2vLX;CACF;AwDzvLD;EACE;IAAY,aAAA;GxD4vLX;CACF;AyD34LD;EACE,mBAAA;EACA,cAAA;EACA,eAAA;ECRA,4DAAA;EAEA,mBAAA;EACA,iBAAA;EACA,wBAAA;EACA,iBAAA;EACA,iBAAA;EACA,kBAAA;EACA,sBAAA;EACA,kBAAA;EACA,qBAAA;EACA,uBAAA;EACA,mBAAA;EACA,qBAAA;EACA,kBAAA;EACA,oBAAA;EDHA,gBAAA;EnCTA,yBAAA;EACA,WAAA;CtBm6LD;AyDv5LC;EnCbA,0BAAA;EACA,aAAA;CtBu6LD;AyD15LC;EACE,eAAA;EACA,iBAAA;CzD45LH;AyD15LC;EACE,eAAA;EACA,iBAAA;CzD45LH;AyD15LC;EACE,eAAA;EACA,gBAAA;CzD45LH;AyD15LC;EACE,eAAA;EACA,kBAAA;CzD45LH;AyDx5LC;EACE,UAAA;EACA,UAAA;EACA,kBAAA;EACA,wBAAA;EACA,uBAAA;CzD05LH;AyDx5LC;EACE,WAAA;EACA,UAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;CzD05LH;AyDx5LC;EACE,UAAA;EACA,UAAA;EACA,oBAAA;EACA,wBAAA;EACA,uBAAA;CzD05LH;AyDx5LC;EACE,SAAA;EACA,QAAA;EACA,iBAAA;EACA,4BAAA;EACA,yBAAA;CzD05LH;AyDx5LC;EACE,SAAA;EACA,SAAA;EACA,iBAAA;EACA,4BAAA;EACA,wBAAA;CzD05LH;AyDx5LC;EACE,OAAA;EACA,UAAA;EACA,kBAAA;EACA,wBAAA;EACA,0BAAA;CzD05LH;AyDx5LC;EACE,OAAA;EACA,WAAA;EACA,iBAAA;EACA,wBAAA;EACA,0BAAA;CzD05LH;AyDx5LC;EACE,OAAA;EACA,UAAA;EACA,iBAAA;EACA,wBAAA;EACA,0BAAA;CzD05LH;AyDr5LD;EACE,iBAAA;EACA,iBAAA;EACA,YAAA;EACA,mBAAA;EACA,uBAAA;EACA,mBAAA;CzDu5LD;AyDn5LD;EACE,mBAAA;EACA,SAAA;EACA,UAAA;EACA,0BAAA;EACA,oBAAA;CzDq5LD;A2D9/LD;EACE,mBAAA;EACA,OAAA;EACA,QAAA;EACA,cAAA;EACA,cAAA;EACA,iBAAA;EACA,aAAA;EDXA,4DAAA;EAEA,mBAAA;EACA,iBAAA;EACA,wBAAA;EACA,iBAAA;EACA,iBAAA;EACA,kBAAA;EACA,sBAAA;EACA,kBAAA;EACA,qBAAA;EACA,uBAAA;EACA,mBAAA;EACA,qBAAA;EACA,kBAAA;EACA,oBAAA;ECAA,gBAAA;EACA,uBAAA;EACA,6BAAA;EACA,uBAAA;EACA,qCAAA;EACA,mBAAA;EtDiDA,kDAAA;EACQ,0CAAA;CL49LT;A2D1gMC;EAAQ,kBAAA;C3D6gMT;A2D5gMC;EAAU,kBAAA;C3D+gMX;A2D9gMC;EAAW,iBAAA;C3DihMZ;A2DhhMC;EAAS,mBAAA;C3DmhMV;A2D1iMD;EA4BI,mBAAA;C3DihMH;A2D/gMG;;EAEE,mBAAA;EACA,eAAA;EACA,SAAA;EACA,UAAA;EACA,0BAAA;EACA,oBAAA;C3DihML;A2D9gMG;EACE,YAAA;EACA,mBAAA;C3DghML;A2D5gMC;EACE,cAAA;EACA,UAAA;EACA,mBAAA;EACA,0BAAA;EACA,sCAAA;EACA,uBAAA;C3D8gMH;A2D7gMG;EACE,YAAA;EACA,mBAAA;EACA,aAAA;EACA,uBAAA;EACA,uBAAA;C3D+gML;A2D5gMC;EACE,SAAA;EACA,YAAA;EACA,kBAAA;EACA,4BAAA;EACA,wCAAA;EACA,qBAAA;C3D8gMH;A2D7gMG;EACE,cAAA;EACA,UAAA;EACA,aAAA;EACA,yBAAA;EACA,qBAAA;C3D+gML;A2D5gMC;EACE,WAAA;EACA,UAAA;EACA,mBAAA;EACA,oBAAA;EACA,6BAAA;EACA,yCAAA;C3D8gMH;A2D7gMG;EACE,SAAA;EACA,mBAAA;EACA,aAAA;EACA,oBAAA;EACA,0BAAA;C3D+gML;A2D3gMC;EACE,SAAA;EACA,aAAA;EACA,kBAAA;EACA,sBAAA;EACA,2BAAA;EACA,uCAAA;C3D6gMH;A2D5gMG;EACE,WAAA;EACA,cAAA;EACA,aAAA;EACA,sBAAA;EACA,wBAAA;C3D8gML;A2DzgMD;EACE,kBAAA;EACA,UAAA;EACA,gBAAA;EACA,0BAAA;EACA,iCAAA;EACA,2BAAA;C3D2gMD;A2DxgMD;EACE,kBAAA;C3D0gMD;A4D9nMD;EACE,mBAAA;C5DgoMD;A4D7nMD;EACE,mBAAA;EACA,YAAA;EACA,iBAAA;C5D+nMD;A4DloMD;EAMI,mBAAA;EACA,cAAA;EvD6KF,0CAAA;EACK,qCAAA;EACG,kCAAA;CLm9LT;A4DzoMD;;EAcM,eAAA;C5D+nML;A4D3nMG;EAAA;IvDuLF,uDAAA;IAEK,6CAAA;IACG,+CAAA;IAAA,uCAAA;IAAA,0GAAA;IA7JR,oCAAA;IAEQ,4BAAA;IA+GR,4BAAA;IAEQ,oBAAA;GLw/LP;E4DnoMG;;IvDmHJ,2CAAA;IACQ,mCAAA;IuDjHF,QAAA;G5DsoML;E4DpoMG;;IvD8GJ,4CAAA;IACQ,oCAAA;IuD5GF,QAAA;G5DuoML;E4DroMG;;;IvDyGJ,wCAAA;IACQ,gCAAA;IuDtGF,QAAA;G5DwoML;CACF;A4D9qMD;;;EA6CI,eAAA;C5DsoMH;A4DnrMD;EAiDI,QAAA;C5DqoMH;A4DtrMD;;EAsDI,mBAAA;EACA,OAAA;EACA,YAAA;C5DooMH;A4D5rMD;EA4DI,WAAA;C5DmoMH;A4D/rMD;EA+DI,YAAA;C5DmoMH;A4DlsMD;;EAmEI,QAAA;C5DmoMH;A4DtsMD;EAuEI,YAAA;C5DkoMH;A4DzsMD;EA0EI,WAAA;C5DkoMH;A4D1nMD;EACE,mBAAA;EACA,OAAA;EACA,UAAA;EACA,QAAA;EACA,WAAA;EACA,gBAAA;EACA,YAAA;EACA,mBAAA;EACA,0CAAA;EACA,mCAAA;EtCpGA,0BAAA;EACA,aAAA;CtBiuMD;A4DxnMC;EdrGE,mGAAA;EACA,8FAAA;EACA,qHAAA;EAAA,+FAAA;EACA,uHAAA;EACA,4BAAA;C9CguMH;A4D5nMC;EACE,SAAA;EACA,WAAA;Ed1GA,mGAAA;EACA,8FAAA;EACA,qHAAA;EAAA,+FAAA;EACA,uHAAA;EACA,4BAAA;C9CyuMH;A4D9nMC;;EAEE,YAAA;EACA,sBAAA;EACA,WAAA;EtCxHF,0BAAA;EACA,aAAA;CtByvMD;A4DhqMD;;;;EAuCI,mBAAA;EACA,SAAA;EACA,WAAA;EACA,sBAAA;EACA,kBAAA;C5D+nMH;A4D1qMD;;EA+CI,UAAA;EACA,mBAAA;C5D+nMH;A4D/qMD;;EAoDI,WAAA;EACA,oBAAA;C5D+nMH;A4DprMD;;EAyDI,YAAA;EACA,aAAA;EACA,mBAAA;EACA,eAAA;C5D+nMH;A4D3nMG;EACE,iBAAA;C5D6nML;A4DznMG;EACE,iBAAA;C5D2nML;A4DjnMD;EACE,mBAAA;EACA,aAAA;EACA,UAAA;EACA,YAAA;EACA,WAAA;EACA,gBAAA;EACA,kBAAA;EACA,mBAAA;EACA,iBAAA;C5DmnMD;A4D5nMD;EAYI,sBAAA;EACA,YAAA;EACA,aAAA;EACA,YAAA;EACA,oBAAA;EACA,gBAAA;EAUA,0BAAA;EACA,mCAAA;EAEA,uBAAA;EACA,oBAAA;C5DymMH;A4DxoMD;EAmCI,YAAA;EACA,aAAA;EACA,UAAA;EACA,uBAAA;C5DwmMH;A4DjmMD;EACE,mBAAA;EACA,WAAA;EACA,aAAA;EACA,UAAA;EACA,YAAA;EACA,kBAAA;EACA,qBAAA;EACA,YAAA;EACA,mBAAA;EACA,0CAAA;C5DmmMD;A4DjmMC;EACE,kBAAA;C5DmmMH;A4D7lMD;EAGE;;;;IAKI,YAAA;IACA,aAAA;IACA,kBAAA;IACA,gBAAA;G5D4lMH;E4DpmMD;;IAYI,mBAAA;G5D4lMH;E4DxmMD;;IAgBI,oBAAA;G5D4lMH;E4DvlMD;IACE,WAAA;IACA,UAAA;IACA,qBAAA;G5DylMD;E4DrlMD;IACE,aAAA;G5DulMD;CACF;A6Dz1MC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEE,eAAA;EACA,aAAA;C7Dy3MH;A6Dv3MC;;;;;;;;;;;;;;;;EACE,YAAA;C7Dw4MH;AiC94MD;E6BVE,eAAA;EACA,mBAAA;EACA,kBAAA;C9D25MD;AiCh5MD;EACE,wBAAA;CjCk5MD;AiCh5MD;EACE,uBAAA;CjCk5MD;AiC14MD;EACE,yBAAA;CjC44MD;AiC14MD;EACE,0BAAA;CjC44MD;AiC14MD;EACE,mBAAA;CjC44MD;AiC14MD;E8BzBE,YAAA;EACA,mBAAA;EACA,kBAAA;EACA,8BAAA;EACA,UAAA;C/Ds6MD;AiCx4MD;EACE,yBAAA;CjC04MD;AiCn4MD;EACE,gBAAA;CjCq4MD;AgEt6MD;EACE,oBAAA;ChEw6MD;AgEl6MD;;;;EClBE,yBAAA;CjE07MD;AgEj6MD;;;;;;;;;;;;EAYE,yBAAA;ChEm6MD;AgE/5MC;EAAA;ICjDA,0BAAA;GjEo9MC;EiEn9MD;IAAU,0BAAA;GjEs9MT;EiEr9MD;IAAU,8BAAA;GjEw9MT;EiEv9MD;;IACU,+BAAA;GjE09MT;CACF;AgEz6MC;EAAA;IACE,0BAAA;GhE46MD;CACF;AgEz6MC;EAAA;IACE,2BAAA;GhE46MD;CACF;AgEz6MC;EAAA;IACE,iCAAA;GhE46MD;CACF;AgEx6MC;EAAA;ICtEA,0BAAA;GjEk/MC;EiEj/MD;IAAU,0BAAA;GjEo/MT;EiEn/MD;IAAU,8BAAA;GjEs/MT;EiEr/MD;;IACU,+BAAA;GjEw/MT;CACF;AgEl7MC;EAAA;IACE,0BAAA;GhEq7MD;CACF;AgEl7MC;EAAA;IACE,2BAAA;GhEq7MD;CACF;AgEl7MC;EAAA;IACE,iCAAA;GhEq7MD;CACF;AgEj7MC;EAAA;IC3FA,0BAAA;GjEghNC;EiE/gND;IAAU,0BAAA;GjEkhNT;EiEjhND;IAAU,8BAAA;GjEohNT;EiEnhND;;IACU,+BAAA;GjEshNT;CACF;AgE37MC;EAAA;IACE,0BAAA;GhE87MD;CACF;AgE37MC;EAAA;IACE,2BAAA;GhE87MD;CACF;AgE37MC;EAAA;IACE,iCAAA;GhE87MD;CACF;AgE17MC;EAAA;IChHA,0BAAA;GjE8iNC;EiE7iND;IAAU,0BAAA;GjEgjNT;EiE/iND;IAAU,8BAAA;GjEkjNT;EiEjjND;;IACU,+BAAA;GjEojNT;CACF;AgEp8MC;EAAA;IACE,0BAAA;GhEu8MD;CACF;AgEp8MC;EAAA;IACE,2BAAA;GhEu8MD;CACF;AgEp8MC;EAAA;IACE,iCAAA;GhEu8MD;CACF;AgEn8MC;EAAA;IC7HA,yBAAA;GjEokNC;CACF;AgEn8MC;EAAA;IClIA,yBAAA;GjEykNC;CACF;AgEn8MC;EAAA;ICvIA,yBAAA;GjE8kNC;CACF;AgEn8MC;EAAA;IC5IA,yBAAA;GjEmlNC;CACF;AgE77MD;ECvJE,yBAAA;CjEulND;AgE77MC;EAAA;IClKA,0BAAA;GjEmmNC;EiElmND;IAAU,0BAAA;GjEqmNT;EiEpmND;IAAU,8BAAA;GjEumNT;EiEtmND;;IACU,+BAAA;GjEymNT;CACF;AgEx8MD;EACE,yBAAA;ChE08MD;AgEx8MC;EAAA;IACE,0BAAA;GhE28MD;CACF;AgEz8MD;EACE,yBAAA;ChE28MD;AgEz8MC;EAAA;IACE,2BAAA;GhE48MD;CACF;AgE18MD;EACE,yBAAA;ChE48MD;AgE18MC;EAAA;IACE,iCAAA;GhE68MD;CACF;AgEz8MC;EAAA;ICrLA,yBAAA;GjEkoNC;CACF","file":"bootstrap.css","sourcesContent":["/*!\n * Bootstrap v3.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\nhtml {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block;\n vertical-align: baseline;\n}\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n[hidden],\ntemplate {\n display: none;\n}\na {\n background-color: transparent;\n}\na:active,\na:hover {\n outline: 0;\n}\nabbr[title] {\n border-bottom: none;\n text-decoration: underline;\n text-decoration: underline dotted;\n}\nb,\nstrong {\n font-weight: bold;\n}\ndfn {\n font-style: italic;\n}\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\nmark {\n background: #ff0;\n color: #000;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsup {\n top: -0.5em;\n}\nsub {\n bottom: -0.25em;\n}\nimg {\n border: 0;\n}\nsvg:not(:root) {\n overflow: hidden;\n}\nfigure {\n margin: 1em 40px;\n}\nhr {\n box-sizing: content-box;\n height: 0;\n}\npre {\n overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit;\n font: inherit;\n margin: 0;\n}\nbutton {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button;\n cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\ninput {\n line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box;\n padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: textfield;\n box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n border: 0;\n padding: 0;\n}\ntextarea {\n overflow: auto;\n}\noptgroup {\n font-weight: bold;\n}\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\ntd,\nth {\n padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n *,\n *:before,\n *:after {\n color: #000 !important;\n text-shadow: none !important;\n background: transparent !important;\n box-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n img {\n max-width: 100% !important;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n .navbar {\n display: none;\n }\n .btn > .caret,\n .dropup > .btn > .caret {\n border-top-color: #000 !important;\n }\n .label {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n@font-face {\n font-family: \"Glyphicons Halflings\";\n src: url(\"../fonts/glyphicons-halflings-regular.eot\");\n src: url(\"../fonts/glyphicons-halflings-regular.eot?#iefix\") format(\"embedded-opentype\"), url(\"../fonts/glyphicons-halflings-regular.woff2\") format(\"woff2\"), url(\"../fonts/glyphicons-halflings-regular.woff\") format(\"woff\"), url(\"../fonts/glyphicons-halflings-regular.ttf\") format(\"truetype\"), url(\"../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular\") format(\"svg\");\n}\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: \"Glyphicons Halflings\";\n font-style: normal;\n font-weight: 400;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n content: \"\\002a\";\n}\n.glyphicon-plus:before {\n content: \"\\002b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n content: \"\\270f\";\n}\n.glyphicon-glass:before {\n content: \"\\e001\";\n}\n.glyphicon-music:before {\n content: \"\\e002\";\n}\n.glyphicon-search:before {\n content: \"\\e003\";\n}\n.glyphicon-heart:before {\n content: \"\\e005\";\n}\n.glyphicon-star:before {\n content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n content: \"\\e007\";\n}\n.glyphicon-user:before {\n content: \"\\e008\";\n}\n.glyphicon-film:before {\n content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n content: \"\\e010\";\n}\n.glyphicon-th:before {\n content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n content: \"\\e012\";\n}\n.glyphicon-ok:before {\n content: \"\\e013\";\n}\n.glyphicon-remove:before {\n content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n content: \"\\e016\";\n}\n.glyphicon-off:before {\n content: \"\\e017\";\n}\n.glyphicon-signal:before {\n content: \"\\e018\";\n}\n.glyphicon-cog:before {\n content: \"\\e019\";\n}\n.glyphicon-trash:before {\n content: \"\\e020\";\n}\n.glyphicon-home:before {\n content: \"\\e021\";\n}\n.glyphicon-file:before {\n content: \"\\e022\";\n}\n.glyphicon-time:before {\n content: \"\\e023\";\n}\n.glyphicon-road:before {\n content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n content: \"\\e025\";\n}\n.glyphicon-download:before {\n content: \"\\e026\";\n}\n.glyphicon-upload:before {\n content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n content: \"\\e032\";\n}\n.glyphicon-lock:before {\n content: \"\\e033\";\n}\n.glyphicon-flag:before {\n content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n content: \"\\e040\";\n}\n.glyphicon-tag:before {\n content: \"\\e041\";\n}\n.glyphicon-tags:before {\n content: \"\\e042\";\n}\n.glyphicon-book:before {\n content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n content: \"\\e044\";\n}\n.glyphicon-print:before {\n content: \"\\e045\";\n}\n.glyphicon-camera:before {\n content: \"\\e046\";\n}\n.glyphicon-font:before {\n content: \"\\e047\";\n}\n.glyphicon-bold:before {\n content: \"\\e048\";\n}\n.glyphicon-italic:before {\n content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n content: \"\\e055\";\n}\n.glyphicon-list:before {\n content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n content: \"\\e059\";\n}\n.glyphicon-picture:before {\n content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n content: \"\\e063\";\n}\n.glyphicon-tint:before {\n content: \"\\e064\";\n}\n.glyphicon-edit:before {\n content: \"\\e065\";\n}\n.glyphicon-share:before {\n content: \"\\e066\";\n}\n.glyphicon-check:before {\n content: \"\\e067\";\n}\n.glyphicon-move:before {\n content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n content: \"\\e070\";\n}\n.glyphicon-backward:before {\n content: \"\\e071\";\n}\n.glyphicon-play:before {\n content: \"\\e072\";\n}\n.glyphicon-pause:before {\n content: \"\\e073\";\n}\n.glyphicon-stop:before {\n content: \"\\e074\";\n}\n.glyphicon-forward:before {\n content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n content: \"\\e077\";\n}\n.glyphicon-eject:before {\n content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n content: \"\\e101\";\n}\n.glyphicon-gift:before {\n content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n content: \"\\e103\";\n}\n.glyphicon-fire:before {\n content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n content: \"\\e107\";\n}\n.glyphicon-plane:before {\n content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n content: \"\\e109\";\n}\n.glyphicon-random:before {\n content: \"\\e110\";\n}\n.glyphicon-comment:before {\n content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n content: \"\\e122\";\n}\n.glyphicon-bell:before {\n content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n content: \"\\e134\";\n}\n.glyphicon-globe:before {\n content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n content: \"\\e137\";\n}\n.glyphicon-filter:before {\n content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n content: \"\\e143\";\n}\n.glyphicon-link:before {\n content: \"\\e144\";\n}\n.glyphicon-phone:before {\n content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n content: \"\\e146\";\n}\n.glyphicon-usd:before {\n content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n content: \"\\e149\";\n}\n.glyphicon-sort:before {\n content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n content: \"\\e157\";\n}\n.glyphicon-expand:before {\n content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n content: \"\\e161\";\n}\n.glyphicon-flash:before {\n content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n content: \"\\e164\";\n}\n.glyphicon-record:before {\n content: \"\\e165\";\n}\n.glyphicon-save:before {\n content: \"\\e166\";\n}\n.glyphicon-open:before {\n content: \"\\e167\";\n}\n.glyphicon-saved:before {\n content: \"\\e168\";\n}\n.glyphicon-import:before {\n content: \"\\e169\";\n}\n.glyphicon-export:before {\n content: \"\\e170\";\n}\n.glyphicon-send:before {\n content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n content: \"\\e179\";\n}\n.glyphicon-header:before {\n content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n content: \"\\e183\";\n}\n.glyphicon-tower:before {\n content: \"\\e184\";\n}\n.glyphicon-stats:before {\n content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n content: \"\\e200\";\n}\n.glyphicon-cd:before {\n content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n content: \"\\e204\";\n}\n.glyphicon-copy:before {\n content: \"\\e205\";\n}\n.glyphicon-paste:before {\n content: \"\\e206\";\n}\n.glyphicon-alert:before {\n content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n content: \"\\e210\";\n}\n.glyphicon-king:before {\n content: \"\\e211\";\n}\n.glyphicon-queen:before {\n content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n content: \"\\e214\";\n}\n.glyphicon-knight:before {\n content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n content: \"\\e216\";\n}\n.glyphicon-tent:before {\n content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n content: \"\\e218\";\n}\n.glyphicon-bed:before {\n content: \"\\e219\";\n}\n.glyphicon-apple:before {\n content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n content: \"\\e227\";\n}\n.glyphicon-btc:before {\n content: \"\\e227\";\n}\n.glyphicon-xbt:before {\n content: \"\\e227\";\n}\n.glyphicon-yen:before {\n content: \"\\00a5\";\n}\n.glyphicon-jpy:before {\n content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n content: \"\\20bd\";\n}\n.glyphicon-rub:before {\n content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n content: \"\\e232\";\n}\n.glyphicon-education:before {\n content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n content: \"\\e237\";\n}\n.glyphicon-oil:before {\n content: \"\\e238\";\n}\n.glyphicon-grain:before {\n content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n content: \"\\e253\";\n}\n.glyphicon-console:before {\n content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n content: \"\\e260\";\n}\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n*:before,\n*:after {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.42857143;\n color: #333333;\n background-color: #fff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\na {\n color: #337ab7;\n text-decoration: none;\n}\na:hover,\na:focus {\n color: #23527c;\n text-decoration: underline;\n}\na:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\nfigure {\n margin: 0;\n}\nimg {\n vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.img-rounded {\n border-radius: 6px;\n}\n.img-thumbnail {\n padding: 4px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease-in-out;\n -o-transition: all 0.2s ease-in-out;\n transition: all 0.2s ease-in-out;\n display: inline-block;\n max-width: 100%;\n height: auto;\n}\n.img-circle {\n border-radius: 50%;\n}\nhr {\n margin-top: 20px;\n margin-bottom: 20px;\n border: 0;\n border-top: 1px solid #eeeeee;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n}\n[role=\"button\"] {\n cursor: pointer;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n font-weight: 400;\n line-height: 1;\n color: #777777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n margin-top: 20px;\n margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n margin-top: 10px;\n margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n font-size: 75%;\n}\nh1,\n.h1 {\n font-size: 36px;\n}\nh2,\n.h2 {\n font-size: 30px;\n}\nh3,\n.h3 {\n font-size: 24px;\n}\nh4,\n.h4 {\n font-size: 18px;\n}\nh5,\n.h5 {\n font-size: 14px;\n}\nh6,\n.h6 {\n font-size: 12px;\n}\np {\n margin: 0 0 10px;\n}\n.lead {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 300;\n line-height: 1.4;\n}\n@media (min-width: 768px) {\n .lead {\n font-size: 21px;\n }\n}\nsmall,\n.small {\n font-size: 85%;\n}\nmark,\n.mark {\n padding: 0.2em;\n background-color: #fcf8e3;\n}\n.text-left {\n text-align: left;\n}\n.text-right {\n text-align: right;\n}\n.text-center {\n text-align: center;\n}\n.text-justify {\n text-align: justify;\n}\n.text-nowrap {\n white-space: nowrap;\n}\n.text-lowercase {\n text-transform: lowercase;\n}\n.text-uppercase {\n text-transform: uppercase;\n}\n.text-capitalize {\n text-transform: capitalize;\n}\n.text-muted {\n color: #777777;\n}\n.text-primary {\n color: #337ab7;\n}\na.text-primary:hover,\na.text-primary:focus {\n color: #286090;\n}\n.text-success {\n color: #3c763d;\n}\na.text-success:hover,\na.text-success:focus {\n color: #2b542c;\n}\n.text-info {\n color: #31708f;\n}\na.text-info:hover,\na.text-info:focus {\n color: #245269;\n}\n.text-warning {\n color: #8a6d3b;\n}\na.text-warning:hover,\na.text-warning:focus {\n color: #66512c;\n}\n.text-danger {\n color: #a94442;\n}\na.text-danger:hover,\na.text-danger:focus {\n color: #843534;\n}\n.bg-primary {\n color: #fff;\n background-color: #337ab7;\n}\na.bg-primary:hover,\na.bg-primary:focus {\n background-color: #286090;\n}\n.bg-success {\n background-color: #dff0d8;\n}\na.bg-success:hover,\na.bg-success:focus {\n background-color: #c1e2b3;\n}\n.bg-info {\n background-color: #d9edf7;\n}\na.bg-info:hover,\na.bg-info:focus {\n background-color: #afd9ee;\n}\n.bg-warning {\n background-color: #fcf8e3;\n}\na.bg-warning:hover,\na.bg-warning:focus {\n background-color: #f7ecb5;\n}\n.bg-danger {\n background-color: #f2dede;\n}\na.bg-danger:hover,\na.bg-danger:focus {\n background-color: #e4b9b9;\n}\n.page-header {\n padding-bottom: 9px;\n margin: 40px 0 20px;\n border-bottom: 1px solid #eeeeee;\n}\nul,\nol {\n margin-top: 0;\n margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n margin-bottom: 0;\n}\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n.list-inline {\n padding-left: 0;\n list-style: none;\n margin-left: -5px;\n}\n.list-inline > li {\n display: inline-block;\n padding-right: 5px;\n padding-left: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: 700;\n}\ndd {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .dl-horizontal dt {\n float: left;\n width: 160px;\n clear: left;\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dl-horizontal dd {\n margin-left: 180px;\n }\n}\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\nblockquote {\n padding: 10px 20px;\n margin: 0 0 20px;\n font-size: 17.5px;\n border-left: 5px solid #eeeeee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n display: block;\n font-size: 80%;\n line-height: 1.42857143;\n color: #777777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n content: \"\\2014 \\00A0\";\n}\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n text-align: right;\n border-right: 5px solid #eeeeee;\n border-left: 0;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n content: \"\";\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n content: \"\\00A0 \\2014\";\n}\naddress {\n margin-bottom: 20px;\n font-style: normal;\n line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: #c7254e;\n background-color: #f9f2f4;\n border-radius: 4px;\n}\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: #fff;\n background-color: #333;\n border-radius: 3px;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: 700;\n box-shadow: none;\n}\npre {\n display: block;\n padding: 9.5px;\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 1.42857143;\n color: #333333;\n word-break: break-all;\n word-wrap: break-word;\n background-color: #f5f5f5;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n}\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n.container {\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n@media (min-width: 768px) {\n .container {\n width: 750px;\n }\n}\n@media (min-width: 992px) {\n .container {\n width: 970px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n width: 1170px;\n }\n}\n.container-fluid {\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n.row {\n margin-right: -15px;\n margin-left: -15px;\n}\n.row-no-gutters {\n margin-right: 0;\n margin-left: 0;\n}\n.row-no-gutters [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n}\n.col-xs-1,\n.col-sm-1,\n.col-md-1,\n.col-lg-1,\n.col-xs-2,\n.col-sm-2,\n.col-md-2,\n.col-lg-2,\n.col-xs-3,\n.col-sm-3,\n.col-md-3,\n.col-lg-3,\n.col-xs-4,\n.col-sm-4,\n.col-md-4,\n.col-lg-4,\n.col-xs-5,\n.col-sm-5,\n.col-md-5,\n.col-lg-5,\n.col-xs-6,\n.col-sm-6,\n.col-md-6,\n.col-lg-6,\n.col-xs-7,\n.col-sm-7,\n.col-md-7,\n.col-lg-7,\n.col-xs-8,\n.col-sm-8,\n.col-md-8,\n.col-lg-8,\n.col-xs-9,\n.col-sm-9,\n.col-md-9,\n.col-lg-9,\n.col-xs-10,\n.col-sm-10,\n.col-md-10,\n.col-lg-10,\n.col-xs-11,\n.col-sm-11,\n.col-md-11,\n.col-lg-11,\n.col-xs-12,\n.col-sm-12,\n.col-md-12,\n.col-lg-12 {\n position: relative;\n min-height: 1px;\n padding-right: 15px;\n padding-left: 15px;\n}\n.col-xs-1,\n.col-xs-2,\n.col-xs-3,\n.col-xs-4,\n.col-xs-5,\n.col-xs-6,\n.col-xs-7,\n.col-xs-8,\n.col-xs-9,\n.col-xs-10,\n.col-xs-11,\n.col-xs-12 {\n float: left;\n}\n.col-xs-12 {\n width: 100%;\n}\n.col-xs-11 {\n width: 91.66666667%;\n}\n.col-xs-10 {\n width: 83.33333333%;\n}\n.col-xs-9 {\n width: 75%;\n}\n.col-xs-8 {\n width: 66.66666667%;\n}\n.col-xs-7 {\n width: 58.33333333%;\n}\n.col-xs-6 {\n width: 50%;\n}\n.col-xs-5 {\n width: 41.66666667%;\n}\n.col-xs-4 {\n width: 33.33333333%;\n}\n.col-xs-3 {\n width: 25%;\n}\n.col-xs-2 {\n width: 16.66666667%;\n}\n.col-xs-1 {\n width: 8.33333333%;\n}\n.col-xs-pull-12 {\n right: 100%;\n}\n.col-xs-pull-11 {\n right: 91.66666667%;\n}\n.col-xs-pull-10 {\n right: 83.33333333%;\n}\n.col-xs-pull-9 {\n right: 75%;\n}\n.col-xs-pull-8 {\n right: 66.66666667%;\n}\n.col-xs-pull-7 {\n right: 58.33333333%;\n}\n.col-xs-pull-6 {\n right: 50%;\n}\n.col-xs-pull-5 {\n right: 41.66666667%;\n}\n.col-xs-pull-4 {\n right: 33.33333333%;\n}\n.col-xs-pull-3 {\n right: 25%;\n}\n.col-xs-pull-2 {\n right: 16.66666667%;\n}\n.col-xs-pull-1 {\n right: 8.33333333%;\n}\n.col-xs-pull-0 {\n right: auto;\n}\n.col-xs-push-12 {\n left: 100%;\n}\n.col-xs-push-11 {\n left: 91.66666667%;\n}\n.col-xs-push-10 {\n left: 83.33333333%;\n}\n.col-xs-push-9 {\n left: 75%;\n}\n.col-xs-push-8 {\n left: 66.66666667%;\n}\n.col-xs-push-7 {\n left: 58.33333333%;\n}\n.col-xs-push-6 {\n left: 50%;\n}\n.col-xs-push-5 {\n left: 41.66666667%;\n}\n.col-xs-push-4 {\n left: 33.33333333%;\n}\n.col-xs-push-3 {\n left: 25%;\n}\n.col-xs-push-2 {\n left: 16.66666667%;\n}\n.col-xs-push-1 {\n left: 8.33333333%;\n}\n.col-xs-push-0 {\n left: auto;\n}\n.col-xs-offset-12 {\n margin-left: 100%;\n}\n.col-xs-offset-11 {\n margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n margin-left: 75%;\n}\n.col-xs-offset-8 {\n margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n margin-left: 50%;\n}\n.col-xs-offset-5 {\n margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n margin-left: 25%;\n}\n.col-xs-offset-2 {\n margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n margin-left: 0%;\n}\n@media (min-width: 768px) {\n .col-sm-1,\n .col-sm-2,\n .col-sm-3,\n .col-sm-4,\n .col-sm-5,\n .col-sm-6,\n .col-sm-7,\n .col-sm-8,\n .col-sm-9,\n .col-sm-10,\n .col-sm-11,\n .col-sm-12 {\n float: left;\n }\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666667%;\n }\n .col-sm-10 {\n width: 83.33333333%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666667%;\n }\n .col-sm-7 {\n width: 58.33333333%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666667%;\n }\n .col-sm-4 {\n width: 33.33333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.66666667%;\n }\n .col-sm-1 {\n width: 8.33333333%;\n }\n .col-sm-pull-12 {\n right: 100%;\n }\n .col-sm-pull-11 {\n right: 91.66666667%;\n }\n .col-sm-pull-10 {\n right: 83.33333333%;\n }\n .col-sm-pull-9 {\n right: 75%;\n }\n .col-sm-pull-8 {\n right: 66.66666667%;\n }\n .col-sm-pull-7 {\n right: 58.33333333%;\n }\n .col-sm-pull-6 {\n right: 50%;\n }\n .col-sm-pull-5 {\n right: 41.66666667%;\n }\n .col-sm-pull-4 {\n right: 33.33333333%;\n }\n .col-sm-pull-3 {\n right: 25%;\n }\n .col-sm-pull-2 {\n right: 16.66666667%;\n }\n .col-sm-pull-1 {\n right: 8.33333333%;\n }\n .col-sm-pull-0 {\n right: auto;\n }\n .col-sm-push-12 {\n left: 100%;\n }\n .col-sm-push-11 {\n left: 91.66666667%;\n }\n .col-sm-push-10 {\n left: 83.33333333%;\n }\n .col-sm-push-9 {\n left: 75%;\n }\n .col-sm-push-8 {\n left: 66.66666667%;\n }\n .col-sm-push-7 {\n left: 58.33333333%;\n }\n .col-sm-push-6 {\n left: 50%;\n }\n .col-sm-push-5 {\n left: 41.66666667%;\n }\n .col-sm-push-4 {\n left: 33.33333333%;\n }\n .col-sm-push-3 {\n left: 25%;\n }\n .col-sm-push-2 {\n left: 16.66666667%;\n }\n .col-sm-push-1 {\n left: 8.33333333%;\n }\n .col-sm-push-0 {\n left: auto;\n }\n .col-sm-offset-12 {\n margin-left: 100%;\n }\n .col-sm-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-sm-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-sm-offset-9 {\n margin-left: 75%;\n }\n .col-sm-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-sm-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-sm-offset-6 {\n margin-left: 50%;\n }\n .col-sm-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-sm-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-sm-offset-3 {\n margin-left: 25%;\n }\n .col-sm-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-sm-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-sm-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 992px) {\n .col-md-1,\n .col-md-2,\n .col-md-3,\n .col-md-4,\n .col-md-5,\n .col-md-6,\n .col-md-7,\n .col-md-8,\n .col-md-9,\n .col-md-10,\n .col-md-11,\n .col-md-12 {\n float: left;\n }\n .col-md-12 {\n width: 100%;\n }\n .col-md-11 {\n width: 91.66666667%;\n }\n .col-md-10 {\n width: 83.33333333%;\n }\n .col-md-9 {\n width: 75%;\n }\n .col-md-8 {\n width: 66.66666667%;\n }\n .col-md-7 {\n width: 58.33333333%;\n }\n .col-md-6 {\n width: 50%;\n }\n .col-md-5 {\n width: 41.66666667%;\n }\n .col-md-4 {\n width: 33.33333333%;\n }\n .col-md-3 {\n width: 25%;\n }\n .col-md-2 {\n width: 16.66666667%;\n }\n .col-md-1 {\n width: 8.33333333%;\n }\n .col-md-pull-12 {\n right: 100%;\n }\n .col-md-pull-11 {\n right: 91.66666667%;\n }\n .col-md-pull-10 {\n right: 83.33333333%;\n }\n .col-md-pull-9 {\n right: 75%;\n }\n .col-md-pull-8 {\n right: 66.66666667%;\n }\n .col-md-pull-7 {\n right: 58.33333333%;\n }\n .col-md-pull-6 {\n right: 50%;\n }\n .col-md-pull-5 {\n right: 41.66666667%;\n }\n .col-md-pull-4 {\n right: 33.33333333%;\n }\n .col-md-pull-3 {\n right: 25%;\n }\n .col-md-pull-2 {\n right: 16.66666667%;\n }\n .col-md-pull-1 {\n right: 8.33333333%;\n }\n .col-md-pull-0 {\n right: auto;\n }\n .col-md-push-12 {\n left: 100%;\n }\n .col-md-push-11 {\n left: 91.66666667%;\n }\n .col-md-push-10 {\n left: 83.33333333%;\n }\n .col-md-push-9 {\n left: 75%;\n }\n .col-md-push-8 {\n left: 66.66666667%;\n }\n .col-md-push-7 {\n left: 58.33333333%;\n }\n .col-md-push-6 {\n left: 50%;\n }\n .col-md-push-5 {\n left: 41.66666667%;\n }\n .col-md-push-4 {\n left: 33.33333333%;\n }\n .col-md-push-3 {\n left: 25%;\n }\n .col-md-push-2 {\n left: 16.66666667%;\n }\n .col-md-push-1 {\n left: 8.33333333%;\n }\n .col-md-push-0 {\n left: auto;\n }\n .col-md-offset-12 {\n margin-left: 100%;\n }\n .col-md-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-md-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-md-offset-9 {\n margin-left: 75%;\n }\n .col-md-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-md-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-md-offset-6 {\n margin-left: 50%;\n }\n .col-md-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-md-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-md-offset-3 {\n margin-left: 25%;\n }\n .col-md-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-md-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-md-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 1200px) {\n .col-lg-1,\n .col-lg-2,\n .col-lg-3,\n .col-lg-4,\n .col-lg-5,\n .col-lg-6,\n .col-lg-7,\n .col-lg-8,\n .col-lg-9,\n .col-lg-10,\n .col-lg-11,\n .col-lg-12 {\n float: left;\n }\n .col-lg-12 {\n width: 100%;\n }\n .col-lg-11 {\n width: 91.66666667%;\n }\n .col-lg-10 {\n width: 83.33333333%;\n }\n .col-lg-9 {\n width: 75%;\n }\n .col-lg-8 {\n width: 66.66666667%;\n }\n .col-lg-7 {\n width: 58.33333333%;\n }\n .col-lg-6 {\n width: 50%;\n }\n .col-lg-5 {\n width: 41.66666667%;\n }\n .col-lg-4 {\n width: 33.33333333%;\n }\n .col-lg-3 {\n width: 25%;\n }\n .col-lg-2 {\n width: 16.66666667%;\n }\n .col-lg-1 {\n width: 8.33333333%;\n }\n .col-lg-pull-12 {\n right: 100%;\n }\n .col-lg-pull-11 {\n right: 91.66666667%;\n }\n .col-lg-pull-10 {\n right: 83.33333333%;\n }\n .col-lg-pull-9 {\n right: 75%;\n }\n .col-lg-pull-8 {\n right: 66.66666667%;\n }\n .col-lg-pull-7 {\n right: 58.33333333%;\n }\n .col-lg-pull-6 {\n right: 50%;\n }\n .col-lg-pull-5 {\n right: 41.66666667%;\n }\n .col-lg-pull-4 {\n right: 33.33333333%;\n }\n .col-lg-pull-3 {\n right: 25%;\n }\n .col-lg-pull-2 {\n right: 16.66666667%;\n }\n .col-lg-pull-1 {\n right: 8.33333333%;\n }\n .col-lg-pull-0 {\n right: auto;\n }\n .col-lg-push-12 {\n left: 100%;\n }\n .col-lg-push-11 {\n left: 91.66666667%;\n }\n .col-lg-push-10 {\n left: 83.33333333%;\n }\n .col-lg-push-9 {\n left: 75%;\n }\n .col-lg-push-8 {\n left: 66.66666667%;\n }\n .col-lg-push-7 {\n left: 58.33333333%;\n }\n .col-lg-push-6 {\n left: 50%;\n }\n .col-lg-push-5 {\n left: 41.66666667%;\n }\n .col-lg-push-4 {\n left: 33.33333333%;\n }\n .col-lg-push-3 {\n left: 25%;\n }\n .col-lg-push-2 {\n left: 16.66666667%;\n }\n .col-lg-push-1 {\n left: 8.33333333%;\n }\n .col-lg-push-0 {\n left: auto;\n }\n .col-lg-offset-12 {\n margin-left: 100%;\n }\n .col-lg-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-lg-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-lg-offset-9 {\n margin-left: 75%;\n }\n .col-lg-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-lg-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-lg-offset-6 {\n margin-left: 50%;\n }\n .col-lg-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-lg-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-lg-offset-3 {\n margin-left: 25%;\n }\n .col-lg-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-lg-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-lg-offset-0 {\n margin-left: 0%;\n }\n}\ntable {\n background-color: transparent;\n}\ntable col[class*=\"col-\"] {\n position: static;\n display: table-column;\n float: none;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n display: table-cell;\n float: none;\n}\ncaption {\n padding-top: 8px;\n padding-bottom: 8px;\n color: #777777;\n text-align: left;\n}\nth {\n text-align: left;\n}\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n padding: 8px;\n line-height: 1.42857143;\n vertical-align: top;\n border-top: 1px solid #ddd;\n}\n.table > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid #ddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n border-top: 0;\n}\n.table > tbody + tbody {\n border-top: 2px solid #ddd;\n}\n.table .table {\n background-color: #fff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n padding: 5px;\n}\n.table-bordered {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #ddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n background-color: #f5f5f5;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n background-color: #ebcccc;\n}\n.table-responsive {\n min-height: 0.01%;\n overflow-x: auto;\n}\n@media screen and (max-width: 767px) {\n .table-responsive {\n width: 100%;\n margin-bottom: 15px;\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid #ddd;\n }\n .table-responsive > .table {\n margin-bottom: 0;\n }\n .table-responsive > .table > thead > tr > th,\n .table-responsive > .table > tbody > tr > th,\n .table-responsive > .table > tfoot > tr > th,\n .table-responsive > .table > thead > tr > td,\n .table-responsive > .table > tbody > tr > td,\n .table-responsive > .table > tfoot > tr > td {\n white-space: nowrap;\n }\n .table-responsive > .table-bordered {\n border: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:first-child,\n .table-responsive > .table-bordered > tbody > tr > th:first-child,\n .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n .table-responsive > .table-bordered > thead > tr > td:first-child,\n .table-responsive > .table-bordered > tbody > tr > td:first-child,\n .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:last-child,\n .table-responsive > .table-bordered > tbody > tr > th:last-child,\n .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n .table-responsive > .table-bordered > thead > tr > td:last-child,\n .table-responsive > .table-bordered > tbody > tr > td:last-child,\n .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n }\n .table-responsive > .table-bordered > tbody > tr:last-child > th,\n .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n .table-responsive > .table-bordered > tbody > tr:last-child > td,\n .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n border-bottom: 0;\n }\n}\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: 20px;\n font-size: 21px;\n line-height: inherit;\n color: #333333;\n border: 0;\n border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: 700;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n -webkit-appearance: none;\n appearance: none;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n cursor: not-allowed;\n}\ninput[type=\"file\"] {\n display: block;\n}\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\nselect[multiple],\nselect[size] {\n height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\noutput {\n display: block;\n padding-top: 7px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n}\n.form-control {\n display: block;\n width: 100%;\n height: 34px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n background-color: #fff;\n background-image: none;\n border: 1px solid #ccc;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n border-color: #66afe9;\n outline: 0;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);\n}\n.form-control::-moz-placeholder {\n color: #999;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #999;\n}\n.form-control::-webkit-input-placeholder {\n color: #999;\n}\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: #eeeeee;\n opacity: 1;\n}\n.form-control[disabled],\nfieldset[disabled] .form-control {\n cursor: not-allowed;\n}\ntextarea.form-control {\n height: auto;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"].form-control,\n input[type=\"time\"].form-control,\n input[type=\"datetime-local\"].form-control,\n input[type=\"month\"].form-control {\n line-height: 34px;\n }\n input[type=\"date\"].input-sm,\n input[type=\"time\"].input-sm,\n input[type=\"datetime-local\"].input-sm,\n input[type=\"month\"].input-sm,\n .input-group-sm input[type=\"date\"],\n .input-group-sm input[type=\"time\"],\n .input-group-sm input[type=\"datetime-local\"],\n .input-group-sm input[type=\"month\"] {\n line-height: 30px;\n }\n input[type=\"date\"].input-lg,\n input[type=\"time\"].input-lg,\n input[type=\"datetime-local\"].input-lg,\n input[type=\"month\"].input-lg,\n .input-group-lg input[type=\"date\"],\n .input-group-lg input[type=\"time\"],\n .input-group-lg input[type=\"datetime-local\"],\n .input-group-lg input[type=\"month\"] {\n line-height: 46px;\n }\n}\n.form-group {\n margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.radio label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: 400;\n cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-top: 4px \\9;\n margin-left: -20px;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n position: relative;\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: 400;\n vertical-align: middle;\n cursor: pointer;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\n}\n.form-control-static {\n min-height: 34px;\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-right: 0;\n padding-left: 0;\n}\n.input-sm {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-sm {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n height: auto;\n}\n.form-group-sm .form-control {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.form-group-sm select.form-control {\n height: 30px;\n line-height: 30px;\n}\n.form-group-sm textarea.form-control,\n.form-group-sm select[multiple].form-control {\n height: auto;\n}\n.form-group-sm .form-control-static {\n height: 30px;\n min-height: 32px;\n padding: 6px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.input-lg {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-lg {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n height: auto;\n}\n.form-group-lg .form-control {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.form-group-lg select.form-control {\n height: 46px;\n line-height: 46px;\n}\n.form-group-lg textarea.form-control,\n.form-group-lg select[multiple].form-control {\n height: auto;\n}\n.form-group-lg .form-control-static {\n height: 46px;\n min-height: 38px;\n padding: 11px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.has-feedback {\n position: relative;\n}\n.has-feedback .form-control {\n padding-right: 42.5px;\n}\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback,\n.input-group-lg + .form-control-feedback,\n.form-group-lg .form-control + .form-control-feedback {\n width: 46px;\n height: 46px;\n line-height: 46px;\n}\n.input-sm + .form-control-feedback,\n.input-group-sm + .form-control-feedback,\n.form-group-sm .form-control + .form-control-feedback {\n width: 30px;\n height: 30px;\n line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n color: #3c763d;\n}\n.has-success .form-control {\n border-color: #3c763d;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-success .form-control:focus {\n border-color: #2b542c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #3c763d;\n}\n.has-success .form-control-feedback {\n color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n color: #8a6d3b;\n}\n.has-warning .form-control {\n border-color: #8a6d3b;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-warning .form-control:focus {\n border-color: #66512c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #8a6d3b;\n}\n.has-warning .form-control-feedback {\n color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n color: #a94442;\n}\n.has-error .form-control {\n border-color: #a94442;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-error .form-control:focus {\n border-color: #843534;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n color: #a94442;\n background-color: #f2dede;\n border-color: #a94442;\n}\n.has-error .form-control-feedback {\n color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n top: 0;\n}\n.help-block {\n display: block;\n margin-top: 5px;\n margin-bottom: 10px;\n color: #737373;\n}\n@media (min-width: 768px) {\n .form-inline .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-static {\n display: inline-block;\n }\n .form-inline .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .form-inline .input-group .input-group-addon,\n .form-inline .input-group .input-group-btn,\n .form-inline .input-group .form-control {\n width: auto;\n }\n .form-inline .input-group > .form-control {\n width: 100%;\n }\n .form-inline .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio,\n .form-inline .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio label,\n .form-inline .checkbox label {\n padding-left: 0;\n }\n .form-inline .radio input[type=\"radio\"],\n .form-inline .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n padding-top: 7px;\n margin-top: 0;\n margin-bottom: 0;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n padding-top: 7px;\n margin-bottom: 0;\n text-align: right;\n }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n right: 15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-lg .control-label {\n padding-top: 11px;\n font-size: 18px;\n }\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-sm .control-label {\n padding-top: 6px;\n font-size: 12px;\n }\n}\n.btn {\n display: inline-block;\n margin-bottom: 0;\n font-weight: normal;\n text-align: center;\n white-space: nowrap;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none;\n border: 1px solid transparent;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n border-radius: 4px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n color: #333;\n text-decoration: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n outline: 0;\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n cursor: not-allowed;\n filter: alpha(opacity=65);\n opacity: 0.65;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\na.btn.disabled,\nfieldset[disabled] a.btn {\n pointer-events: none;\n}\n.btn-default {\n color: #333;\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default:focus,\n.btn-default.focus {\n color: #333;\n background-color: #e6e6e6;\n border-color: #8c8c8c;\n}\n.btn-default:hover {\n color: #333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n color: #333;\n background-color: #e6e6e6;\n background-image: none;\n border-color: #adadad;\n}\n.btn-default:active:hover,\n.btn-default.active:hover,\n.open > .dropdown-toggle.btn-default:hover,\n.btn-default:active:focus,\n.btn-default.active:focus,\n.open > .dropdown-toggle.btn-default:focus,\n.btn-default:active.focus,\n.btn-default.active.focus,\n.open > .dropdown-toggle.btn-default.focus {\n color: #333;\n background-color: #d4d4d4;\n border-color: #8c8c8c;\n}\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus {\n background-color: #fff;\n border-color: #ccc;\n}\n.btn-default .badge {\n color: #fff;\n background-color: #333;\n}\n.btn-primary {\n color: #fff;\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary:focus,\n.btn-primary.focus {\n color: #fff;\n background-color: #286090;\n border-color: #122b40;\n}\n.btn-primary:hover {\n color: #fff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n color: #fff;\n background-color: #286090;\n background-image: none;\n border-color: #204d74;\n}\n.btn-primary:active:hover,\n.btn-primary.active:hover,\n.open > .dropdown-toggle.btn-primary:hover,\n.btn-primary:active:focus,\n.btn-primary.active:focus,\n.open > .dropdown-toggle.btn-primary:focus,\n.btn-primary:active.focus,\n.btn-primary.active.focus,\n.open > .dropdown-toggle.btn-primary.focus {\n color: #fff;\n background-color: #204d74;\n border-color: #122b40;\n}\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.btn-success {\n color: #fff;\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success:focus,\n.btn-success.focus {\n color: #fff;\n background-color: #449d44;\n border-color: #255625;\n}\n.btn-success:hover {\n color: #fff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n color: #fff;\n background-color: #449d44;\n background-image: none;\n border-color: #398439;\n}\n.btn-success:active:hover,\n.btn-success.active:hover,\n.open > .dropdown-toggle.btn-success:hover,\n.btn-success:active:focus,\n.btn-success.active:focus,\n.open > .dropdown-toggle.btn-success:focus,\n.btn-success:active.focus,\n.btn-success.active.focus,\n.open > .dropdown-toggle.btn-success.focus {\n color: #fff;\n background-color: #398439;\n border-color: #255625;\n}\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success .badge {\n color: #5cb85c;\n background-color: #fff;\n}\n.btn-info {\n color: #fff;\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info:focus,\n.btn-info.focus {\n color: #fff;\n background-color: #31b0d5;\n border-color: #1b6d85;\n}\n.btn-info:hover {\n color: #fff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n color: #fff;\n background-color: #31b0d5;\n background-image: none;\n border-color: #269abc;\n}\n.btn-info:active:hover,\n.btn-info.active:hover,\n.open > .dropdown-toggle.btn-info:hover,\n.btn-info:active:focus,\n.btn-info.active:focus,\n.open > .dropdown-toggle.btn-info:focus,\n.btn-info:active.focus,\n.btn-info.active.focus,\n.open > .dropdown-toggle.btn-info.focus {\n color: #fff;\n background-color: #269abc;\n border-color: #1b6d85;\n}\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info .badge {\n color: #5bc0de;\n background-color: #fff;\n}\n.btn-warning {\n color: #fff;\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning:focus,\n.btn-warning.focus {\n color: #fff;\n background-color: #ec971f;\n border-color: #985f0d;\n}\n.btn-warning:hover {\n color: #fff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n color: #fff;\n background-color: #ec971f;\n background-image: none;\n border-color: #d58512;\n}\n.btn-warning:active:hover,\n.btn-warning.active:hover,\n.open > .dropdown-toggle.btn-warning:hover,\n.btn-warning:active:focus,\n.btn-warning.active:focus,\n.open > .dropdown-toggle.btn-warning:focus,\n.btn-warning:active.focus,\n.btn-warning.active.focus,\n.open > .dropdown-toggle.btn-warning.focus {\n color: #fff;\n background-color: #d58512;\n border-color: #985f0d;\n}\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning .badge {\n color: #f0ad4e;\n background-color: #fff;\n}\n.btn-danger {\n color: #fff;\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger:focus,\n.btn-danger.focus {\n color: #fff;\n background-color: #c9302c;\n border-color: #761c19;\n}\n.btn-danger:hover {\n color: #fff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n color: #fff;\n background-color: #c9302c;\n background-image: none;\n border-color: #ac2925;\n}\n.btn-danger:active:hover,\n.btn-danger.active:hover,\n.open > .dropdown-toggle.btn-danger:hover,\n.btn-danger:active:focus,\n.btn-danger.active:focus,\n.open > .dropdown-toggle.btn-danger:focus,\n.btn-danger:active.focus,\n.btn-danger.active.focus,\n.open > .dropdown-toggle.btn-danger.focus {\n color: #fff;\n background-color: #ac2925;\n border-color: #761c19;\n}\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger .badge {\n color: #d9534f;\n background-color: #fff;\n}\n.btn-link {\n font-weight: 400;\n color: #337ab7;\n border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n background-color: transparent;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n color: #23527c;\n text-decoration: underline;\n background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n color: #777777;\n text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-block {\n display: block;\n width: 100%;\n}\n.btn-block + .btn-block {\n margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n.fade {\n opacity: 0;\n -webkit-transition: opacity 0.15s linear;\n -o-transition: opacity 0.15s linear;\n transition: opacity 0.15s linear;\n}\n.fade.in {\n opacity: 1;\n}\n.collapse {\n display: none;\n}\n.collapse.in {\n display: block;\n}\ntr.collapse.in {\n display: table-row;\n}\ntbody.collapse.in {\n display: table-row-group;\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n -webkit-transition-property: height, visibility;\n transition-property: height, visibility;\n -webkit-transition-duration: 0.35s;\n transition-duration: 0.35s;\n -webkit-transition-timing-function: ease;\n transition-timing-function: ease;\n}\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: 4px dashed;\n border-top: 4px solid \\9;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n position: relative;\n}\n.dropdown-toggle:focus {\n outline: 0;\n}\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0;\n font-size: 14px;\n text-align: left;\n list-style: none;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n}\n.dropdown-menu.pull-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: 400;\n line-height: 1.42857143;\n color: #333333;\n white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n color: #262626;\n text-decoration: none;\n background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n color: #fff;\n text-decoration: none;\n background-color: #337ab7;\n outline: 0;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n color: #777777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n text-decoration: none;\n cursor: not-allowed;\n background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu-left {\n right: auto;\n left: 0;\n}\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: 12px;\n line-height: 1.42857143;\n color: #777777;\n white-space: nowrap;\n}\n.dropdown-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 990;\n}\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n content: \"\";\n border-top: 0;\n border-bottom: 4px dashed;\n border-bottom: 4px solid \\9;\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n .navbar-right .dropdown-menu {\n right: 0;\n left: auto;\n }\n .navbar-right .dropdown-menu-left {\n right: auto;\n left: 0;\n }\n}\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n margin-left: -1px;\n}\n.btn-toolbar {\n margin-left: -5px;\n}\n.btn-toolbar .btn,\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n padding-right: 8px;\n padding-left: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-right: 12px;\n padding-left: 12px;\n}\n.btn-group.open .dropdown-toggle {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn .caret {\n margin-left: 0;\n}\n.btn-lg .caret {\n border-width: 5px 5px 0;\n border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n display: table-cell;\n float: none;\n width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.input-group {\n position: relative;\n display: table;\n border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n float: none;\n padding-right: 0;\n padding-left: 0;\n}\n.input-group .form-control {\n position: relative;\n z-index: 2;\n float: left;\n width: 100%;\n margin-bottom: 0;\n}\n.input-group .form-control:focus {\n z-index: 3;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle;\n}\n.input-group-addon {\n padding: 6px 12px;\n font-size: 14px;\n font-weight: 400;\n line-height: 1;\n color: #555555;\n text-align: center;\n background-color: #eeeeee;\n border: 1px solid #ccc;\n border-radius: 4px;\n}\n.input-group-addon.input-sm {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 3px;\n}\n.input-group-addon.input-lg {\n padding: 10px 16px;\n font-size: 18px;\n border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.input-group-btn > .btn {\n position: relative;\n}\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n z-index: 2;\n margin-left: -1px;\n}\n.nav {\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n.nav > li {\n position: relative;\n display: block;\n}\n.nav > li > a {\n position: relative;\n display: block;\n padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.nav > li.disabled > a {\n color: #777777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n color: #777777;\n text-decoration: none;\n cursor: not-allowed;\n background-color: transparent;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n background-color: #eeeeee;\n border-color: #337ab7;\n}\n.nav .nav-divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.nav > li > a > img {\n max-width: none;\n}\n.nav-tabs {\n border-bottom: 1px solid #ddd;\n}\n.nav-tabs > li {\n float: left;\n margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n margin-right: 2px;\n line-height: 1.42857143;\n border: 1px solid transparent;\n border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n border-color: #eeeeee #eeeeee #ddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n color: #555555;\n cursor: default;\n background-color: #fff;\n border: 1px solid #ddd;\n border-bottom-color: transparent;\n}\n.nav-tabs.nav-justified {\n width: 100%;\n border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n float: none;\n}\n.nav-tabs.nav-justified > li > a {\n margin-bottom: 5px;\n text-align: center;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-tabs.nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs.nav-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs.nav-justified > .active > a,\n .nav-tabs.nav-justified > .active > a:hover,\n .nav-tabs.nav-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.nav-pills > li {\n float: left;\n}\n.nav-pills > li > a {\n border-radius: 4px;\n}\n.nav-pills > li + li {\n margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n color: #fff;\n background-color: #337ab7;\n}\n.nav-stacked > li {\n float: none;\n}\n.nav-stacked > li + li {\n margin-top: 2px;\n margin-left: 0;\n}\n.nav-justified {\n width: 100%;\n}\n.nav-justified > li {\n float: none;\n}\n.nav-justified > li > a {\n margin-bottom: 5px;\n text-align: center;\n}\n.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs-justified {\n border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n border: 1px solid #ddd;\n}\n@media (min-width: 768px) {\n .nav-tabs-justified > li > a {\n border-bottom: 1px solid #ddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs-justified > .active > a,\n .nav-tabs-justified > .active > a:hover,\n .nav-tabs-justified > .active > a:focus {\n border-bottom-color: #fff;\n }\n}\n.tab-content > .tab-pane {\n display: none;\n}\n.tab-content > .active {\n display: block;\n}\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.navbar {\n position: relative;\n min-height: 50px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n .navbar {\n border-radius: 4px;\n }\n}\n@media (min-width: 768px) {\n .navbar-header {\n float: left;\n }\n}\n.navbar-collapse {\n padding-right: 15px;\n padding-left: 15px;\n overflow-x: visible;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n -webkit-overflow-scrolling: touch;\n}\n.navbar-collapse.in {\n overflow-y: auto;\n}\n@media (min-width: 768px) {\n .navbar-collapse {\n width: auto;\n border-top: 0;\n box-shadow: none;\n }\n .navbar-collapse.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0;\n overflow: visible !important;\n }\n .navbar-collapse.in {\n overflow-y: visible;\n }\n .navbar-fixed-top .navbar-collapse,\n .navbar-static-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n padding-right: 0;\n padding-left: 0;\n }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n .navbar-fixed-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n max-height: 200px;\n }\n}\n@media (min-width: 768px) {\n .navbar-fixed-top,\n .navbar-fixed-bottom {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0;\n border-width: 1px 0 0;\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .container > .navbar-header,\n .container-fluid > .navbar-header,\n .container > .navbar-collapse,\n .container-fluid > .navbar-collapse {\n margin-right: 0;\n margin-left: 0;\n }\n}\n.navbar-static-top {\n z-index: 1000;\n border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n .navbar-static-top {\n border-radius: 0;\n }\n}\n.navbar-brand {\n float: left;\n height: 50px;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n text-decoration: none;\n}\n.navbar-brand > img {\n display: block;\n}\n@media (min-width: 768px) {\n .navbar > .container .navbar-brand,\n .navbar > .container-fluid .navbar-brand {\n margin-left: -15px;\n }\n}\n.navbar-toggle {\n position: relative;\n float: right;\n padding: 9px 10px;\n margin-right: 15px;\n margin-top: 8px;\n margin-bottom: 8px;\n background-color: transparent;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.navbar-toggle:focus {\n outline: 0;\n}\n.navbar-toggle .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n margin-top: 4px;\n}\n@media (min-width: 768px) {\n .navbar-toggle {\n display: none;\n }\n}\n.navbar-nav {\n margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: 20px;\n}\n@media (max-width: 767px) {\n .navbar-nav .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n }\n .navbar-nav .open .dropdown-menu > li > a,\n .navbar-nav .open .dropdown-menu .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n .navbar-nav .open .dropdown-menu > li > a {\n line-height: 20px;\n }\n .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-nav .open .dropdown-menu > li > a:focus {\n background-image: none;\n }\n}\n@media (min-width: 768px) {\n .navbar-nav {\n float: left;\n margin: 0;\n }\n .navbar-nav > li {\n float: left;\n }\n .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n}\n.navbar-form {\n padding: 10px 15px;\n margin-right: -15px;\n margin-left: -15px;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n margin-top: 8px;\n margin-bottom: 8px;\n}\n@media (min-width: 768px) {\n .navbar-form .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .navbar-form .form-control-static {\n display: inline-block;\n }\n .navbar-form .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .navbar-form .input-group .input-group-addon,\n .navbar-form .input-group .input-group-btn,\n .navbar-form .input-group .form-control {\n width: auto;\n }\n .navbar-form .input-group > .form-control {\n width: 100%;\n }\n .navbar-form .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio,\n .navbar-form .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio label,\n .navbar-form .checkbox label {\n padding-left: 0;\n }\n .navbar-form .radio input[type=\"radio\"],\n .navbar-form .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .navbar-form .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n@media (max-width: 767px) {\n .navbar-form .form-group {\n margin-bottom: 5px;\n }\n .navbar-form .form-group:last-child {\n margin-bottom: 0;\n }\n}\n@media (min-width: 768px) {\n .navbar-form {\n width: auto;\n padding-top: 0;\n padding-bottom: 0;\n margin-right: 0;\n margin-left: 0;\n border: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n}\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.navbar-btn {\n margin-top: 8px;\n margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n margin-top: 14px;\n margin-bottom: 14px;\n}\n.navbar-text {\n margin-top: 15px;\n margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n .navbar-text {\n float: left;\n margin-right: 15px;\n margin-left: 15px;\n }\n}\n@media (min-width: 768px) {\n .navbar-left {\n float: left !important;\n }\n .navbar-right {\n float: right !important;\n margin-right: -15px;\n }\n .navbar-right ~ .navbar-right {\n margin-right: 0;\n }\n}\n.navbar-default {\n background-color: #f8f8f8;\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n color: #777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n color: #5e5e5e;\n background-color: transparent;\n}\n.navbar-default .navbar-text {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a {\n color: #777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n color: #333;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n color: #555;\n background-color: #e7e7e7;\n}\n@media (max-width: 767px) {\n .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n color: #777;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #333;\n background-color: transparent;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #555;\n background-color: #e7e7e7;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #ccc;\n background-color: transparent;\n }\n}\n.navbar-default .navbar-toggle {\n border-color: #ddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n background-color: #ddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n background-color: #888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-link {\n color: #777;\n}\n.navbar-default .navbar-link:hover {\n color: #333;\n}\n.navbar-default .btn-link {\n color: #777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n color: #333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n color: #ccc;\n}\n.navbar-inverse {\n background-color: #222;\n border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n color: #fff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n color: #fff;\n background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n color: #fff;\n background-color: #080808;\n}\n@media (max-width: 767px) {\n .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n border-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n color: #9d9d9d;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #fff;\n background-color: transparent;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #444;\n background-color: transparent;\n }\n}\n.navbar-inverse .navbar-toggle {\n border-color: #333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n background-color: #333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n background-color: #fff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n border-color: #101010;\n}\n.navbar-inverse .navbar-link {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n color: #fff;\n}\n.navbar-inverse .btn-link {\n color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n color: #fff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n color: #444;\n}\n.breadcrumb {\n padding: 8px 15px;\n margin-bottom: 20px;\n list-style: none;\n background-color: #f5f5f5;\n border-radius: 4px;\n}\n.breadcrumb > li {\n display: inline-block;\n}\n.breadcrumb > li + li:before {\n padding: 0 5px;\n color: #ccc;\n content: \"/\\00a0\";\n}\n.breadcrumb > .active {\n color: #777777;\n}\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: 20px 0;\n border-radius: 4px;\n}\n.pagination > li {\n display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n position: relative;\n float: left;\n padding: 6px 12px;\n margin-left: -1px;\n line-height: 1.42857143;\n color: #337ab7;\n text-decoration: none;\n background-color: #fff;\n border: 1px solid #ddd;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n z-index: 2;\n color: #23527c;\n background-color: #eeeeee;\n border-color: #ddd;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-top-left-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-top-right-radius: 4px;\n border-bottom-right-radius: 4px;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n z-index: 3;\n color: #fff;\n cursor: default;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n color: #777777;\n cursor: not-allowed;\n background-color: #fff;\n border-color: #ddd;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n border-top-left-radius: 6px;\n border-bottom-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-top-right-radius: 6px;\n border-bottom-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n border-top-left-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-top-right-radius: 3px;\n border-bottom-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n text-align: center;\n list-style: none;\n}\n.pager li {\n display: inline;\n}\n.pager li > a,\n.pager li > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.pager .next > a,\n.pager .next > span {\n float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n color: #777777;\n cursor: not-allowed;\n background-color: #fff;\n}\n.label {\n display: inline;\n padding: 0.2em 0.6em 0.3em;\n font-size: 75%;\n font-weight: 700;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.25em;\n}\na.label:hover,\na.label:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.label:empty {\n display: none;\n}\n.btn .label {\n position: relative;\n top: -1px;\n}\n.label-default {\n background-color: #777777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n background-color: #5e5e5e;\n}\n.label-primary {\n background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n background-color: #286090;\n}\n.label-success {\n background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n background-color: #449d44;\n}\n.label-info {\n background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n background-color: #31b0d5;\n}\n.label-warning {\n background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n background-color: #ec971f;\n}\n.label-danger {\n background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n background-color: #c9302c;\n}\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: 12px;\n font-weight: bold;\n line-height: 1;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n vertical-align: middle;\n background-color: #777777;\n border-radius: 10px;\n}\n.badge:empty {\n display: none;\n}\n.btn .badge {\n position: relative;\n top: -1px;\n}\n.btn-xs .badge,\n.btn-group-xs > .btn .badge {\n top: 0;\n padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n color: #fff;\n text-decoration: none;\n cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.list-group-item > .badge {\n float: right;\n}\n.list-group-item > .badge + .badge {\n margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n.jumbotron {\n padding-top: 30px;\n padding-bottom: 30px;\n margin-bottom: 30px;\n color: inherit;\n background-color: #eeeeee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n color: inherit;\n}\n.jumbotron p {\n margin-bottom: 15px;\n font-size: 21px;\n font-weight: 200;\n}\n.jumbotron > hr {\n border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n padding-right: 15px;\n padding-left: 15px;\n border-radius: 6px;\n}\n.jumbotron .container {\n max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n .jumbotron {\n padding-top: 48px;\n padding-bottom: 48px;\n }\n .container .jumbotron,\n .container-fluid .jumbotron {\n padding-right: 60px;\n padding-left: 60px;\n }\n .jumbotron h1,\n .jumbotron .h1 {\n font-size: 63px;\n }\n}\n.thumbnail {\n display: block;\n padding: 4px;\n margin-bottom: 20px;\n line-height: 1.42857143;\n background-color: #fff;\n border: 1px solid #ddd;\n border-radius: 4px;\n -webkit-transition: border 0.2s ease-in-out;\n -o-transition: border 0.2s ease-in-out;\n transition: border 0.2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n margin-right: auto;\n margin-left: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n border-color: #337ab7;\n}\n.thumbnail .caption {\n padding: 9px;\n color: #333333;\n}\n.alert {\n padding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.alert h4 {\n margin-top: 0;\n color: inherit;\n}\n.alert .alert-link {\n font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n margin-bottom: 0;\n}\n.alert > p + p {\n margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n}\n.alert-success {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.alert-danger hr {\n border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.progress {\n height: 20px;\n margin-bottom: 20px;\n overflow: hidden;\n background-color: #f5f5f5;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #fff;\n text-align: center;\n background-color: #337ab7;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n -webkit-transition: width 0.6s ease;\n -o-transition: width 0.6s ease;\n transition: width 0.6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n -webkit-animation: progress-bar-stripes 2s linear infinite;\n -o-animation: progress-bar-stripes 2s linear infinite;\n animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n.media,\n.media-body {\n overflow: hidden;\n zoom: 1;\n}\n.media-body {\n width: 10000px;\n}\n.media-object {\n display: block;\n}\n.media-object.img-thumbnail {\n max-width: none;\n}\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n.media-middle {\n vertical-align: middle;\n}\n.media-bottom {\n vertical-align: bottom;\n}\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n.list-group {\n padding-left: 0;\n margin-bottom: 20px;\n}\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid #ddd;\n}\n.list-group-item:first-child {\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n}\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n color: #777777;\n cursor: not-allowed;\n background-color: #eeeeee;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n color: #777777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n z-index: 2;\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n color: #c7ddef;\n}\na.list-group-item,\nbutton.list-group-item {\n color: #555;\n}\na.list-group-item .list-group-item-heading,\nbutton.list-group-item .list-group-item-heading {\n color: #333;\n}\na.list-group-item:hover,\nbutton.list-group-item:hover,\na.list-group-item:focus,\nbutton.list-group-item:focus {\n color: #555;\n text-decoration: none;\n background-color: #f5f5f5;\n}\nbutton.list-group-item {\n width: 100%;\n text-align: left;\n}\n.list-group-item-success {\n color: #3c763d;\n background-color: #dff0d8;\n}\na.list-group-item-success,\nbutton.list-group-item-success {\n color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading,\nbutton.list-group-item-success .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-success:hover,\nbutton.list-group-item-success:hover,\na.list-group-item-success:focus,\nbutton.list-group-item-success:focus {\n color: #3c763d;\n background-color: #d0e9c6;\n}\na.list-group-item-success.active,\nbutton.list-group-item-success.active,\na.list-group-item-success.active:hover,\nbutton.list-group-item-success.active:hover,\na.list-group-item-success.active:focus,\nbutton.list-group-item-success.active:focus {\n color: #fff;\n background-color: #3c763d;\n border-color: #3c763d;\n}\n.list-group-item-info {\n color: #31708f;\n background-color: #d9edf7;\n}\na.list-group-item-info,\nbutton.list-group-item-info {\n color: #31708f;\n}\na.list-group-item-info .list-group-item-heading,\nbutton.list-group-item-info .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-info:hover,\nbutton.list-group-item-info:hover,\na.list-group-item-info:focus,\nbutton.list-group-item-info:focus {\n color: #31708f;\n background-color: #c4e3f3;\n}\na.list-group-item-info.active,\nbutton.list-group-item-info.active,\na.list-group-item-info.active:hover,\nbutton.list-group-item-info.active:hover,\na.list-group-item-info.active:focus,\nbutton.list-group-item-info.active:focus {\n color: #fff;\n background-color: #31708f;\n border-color: #31708f;\n}\n.list-group-item-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n}\na.list-group-item-warning,\nbutton.list-group-item-warning {\n color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading,\nbutton.list-group-item-warning .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-warning:hover,\nbutton.list-group-item-warning:hover,\na.list-group-item-warning:focus,\nbutton.list-group-item-warning:focus {\n color: #8a6d3b;\n background-color: #faf2cc;\n}\na.list-group-item-warning.active,\nbutton.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\nbutton.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus,\nbutton.list-group-item-warning.active:focus {\n color: #fff;\n background-color: #8a6d3b;\n border-color: #8a6d3b;\n}\n.list-group-item-danger {\n color: #a94442;\n background-color: #f2dede;\n}\na.list-group-item-danger,\nbutton.list-group-item-danger {\n color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading,\nbutton.list-group-item-danger .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-danger:hover,\nbutton.list-group-item-danger:hover,\na.list-group-item-danger:focus,\nbutton.list-group-item-danger:focus {\n color: #a94442;\n background-color: #ebcccc;\n}\na.list-group-item-danger.active,\nbutton.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\nbutton.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus,\nbutton.list-group-item-danger.active:focus {\n color: #fff;\n background-color: #a94442;\n border-color: #a94442;\n}\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n.panel {\n margin-bottom: 20px;\n background-color: #fff;\n border: 1px solid transparent;\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.panel-body {\n padding: 15px;\n}\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n color: inherit;\n}\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: 16px;\n color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n color: inherit;\n}\n.panel-footer {\n padding: 10px 15px;\n background-color: #f5f5f5;\n border-top: 1px solid #ddd;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n border-top: 0;\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n border-bottom: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n border-top-width: 0;\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n padding-right: 15px;\n padding-left: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n border-top: 1px solid #ddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n border-bottom: 0;\n}\n.panel > .table-responsive {\n margin-bottom: 0;\n border: 0;\n}\n.panel-group {\n margin-bottom: 20px;\n}\n.panel-group .panel {\n margin-bottom: 0;\n border-radius: 4px;\n}\n.panel-group .panel + .panel {\n margin-top: 5px;\n}\n.panel-group .panel-heading {\n border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n border-top: 1px solid #ddd;\n}\n.panel-group .panel-footer {\n border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n border-bottom: 1px solid #ddd;\n}\n.panel-default {\n border-color: #ddd;\n}\n.panel-default > .panel-heading {\n color: #333333;\n background-color: #f5f5f5;\n border-color: #ddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ddd;\n}\n.panel-default > .panel-heading .badge {\n color: #f5f5f5;\n background-color: #333333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ddd;\n}\n.panel-primary {\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n color: #fff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n color: #337ab7;\n background-color: #fff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #337ab7;\n}\n.panel-success {\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n color: #dff0d8;\n background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #d6e9c6;\n}\n.panel-info {\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n color: #d9edf7;\n background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #bce8f1;\n}\n.panel-warning {\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n color: #fcf8e3;\n background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #faebcc;\n}\n.panel-danger {\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n color: #f2dede;\n background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border: 1px solid #e3e3e3;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.well blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, 0.15);\n}\n.well-lg {\n padding: 24px;\n border-radius: 6px;\n}\n.well-sm {\n padding: 9px;\n border-radius: 3px;\n}\n.close {\n float: right;\n font-size: 21px;\n font-weight: bold;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n filter: alpha(opacity=20);\n opacity: 0.2;\n}\n.close:hover,\n.close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n filter: alpha(opacity=50);\n opacity: 0.5;\n}\nbutton.close {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n appearance: none;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\n display: none;\n overflow: hidden;\n -webkit-overflow-scrolling: touch;\n outline: 0;\n}\n.modal.fade .modal-dialog {\n -webkit-transform: translate(0, -25%);\n -ms-transform: translate(0, -25%);\n -o-transform: translate(0, -25%);\n transform: translate(0, -25%);\n -webkit-transition: -webkit-transform 0.3s ease-out;\n -moz-transition: -moz-transform 0.3s ease-out;\n -o-transition: -o-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n}\n.modal.in .modal-dialog {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n.modal-content {\n position: relative;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #999;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n outline: 0;\n}\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n background-color: #000;\n}\n.modal-backdrop.fade {\n filter: alpha(opacity=0);\n opacity: 0;\n}\n.modal-backdrop.in {\n filter: alpha(opacity=50);\n opacity: 0.5;\n}\n.modal-header {\n padding: 15px;\n border-bottom: 1px solid #e5e5e5;\n}\n.modal-header .close {\n margin-top: -2px;\n}\n.modal-title {\n margin: 0;\n line-height: 1.42857143;\n}\n.modal-body {\n position: relative;\n padding: 15px;\n}\n.modal-footer {\n padding: 15px;\n text-align: right;\n border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n margin-bottom: 0;\n margin-left: 5px;\n}\n.modal-footer .btn-group .btn + .btn {\n margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n margin-left: 0;\n}\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n@media (min-width: 768px) {\n .modal-dialog {\n width: 600px;\n margin: 30px auto;\n }\n .modal-content {\n -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n }\n .modal-sm {\n width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg {\n width: 900px;\n }\n}\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: 400;\n line-height: 1.42857143;\n line-break: auto;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n white-space: normal;\n font-size: 12px;\n filter: alpha(opacity=0);\n opacity: 0;\n}\n.tooltip.in {\n filter: alpha(opacity=90);\n opacity: 0.9;\n}\n.tooltip.top {\n padding: 5px 0;\n margin-top: -3px;\n}\n.tooltip.right {\n padding: 0 5px;\n margin-left: 3px;\n}\n.tooltip.bottom {\n padding: 5px 0;\n margin-top: 3px;\n}\n.tooltip.left {\n padding: 0 5px;\n margin-left: -3px;\n}\n.tooltip.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-left .tooltip-arrow {\n right: 5px;\n bottom: 0;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.top-right .tooltip-arrow {\n bottom: 0;\n left: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000;\n}\n.tooltip.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #000;\n}\n.tooltip.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -5px;\n border-width: 5px 0 5px 5px;\n border-left-color: #000;\n}\n.tooltip.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n top: 0;\n right: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n top: 0;\n left: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000;\n}\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 4px;\n}\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: none;\n max-width: 276px;\n padding: 1px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-style: normal;\n font-weight: 400;\n line-height: 1.42857143;\n line-break: auto;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n word-wrap: normal;\n white-space: normal;\n font-size: 14px;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ccc;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n}\n.popover.top {\n margin-top: -10px;\n}\n.popover.right {\n margin-left: 10px;\n}\n.popover.bottom {\n margin-top: 10px;\n}\n.popover.left {\n margin-left: -10px;\n}\n.popover > .arrow {\n border-width: 11px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover > .arrow:after {\n content: \"\";\n border-width: 10px;\n}\n.popover.top > .arrow {\n bottom: -11px;\n left: 50%;\n margin-left: -11px;\n border-top-color: #999999;\n border-top-color: rgba(0, 0, 0, 0.25);\n border-bottom-width: 0;\n}\n.popover.top > .arrow:after {\n bottom: 1px;\n margin-left: -10px;\n content: \" \";\n border-top-color: #fff;\n border-bottom-width: 0;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-right-color: #999999;\n border-right-color: rgba(0, 0, 0, 0.25);\n border-left-width: 0;\n}\n.popover.right > .arrow:after {\n bottom: -10px;\n left: 1px;\n content: \" \";\n border-right-color: #fff;\n border-left-width: 0;\n}\n.popover.bottom > .arrow {\n top: -11px;\n left: 50%;\n margin-left: -11px;\n border-top-width: 0;\n border-bottom-color: #999999;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n.popover.bottom > .arrow:after {\n top: 1px;\n margin-left: -10px;\n content: \" \";\n border-top-width: 0;\n border-bottom-color: #fff;\n}\n.popover.left > .arrow {\n top: 50%;\n right: -11px;\n margin-top: -11px;\n border-right-width: 0;\n border-left-color: #999999;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n.popover.left > .arrow:after {\n right: 1px;\n bottom: -10px;\n content: \" \";\n border-right-width: 0;\n border-left-color: #fff;\n}\n.popover-title {\n padding: 8px 14px;\n margin: 0;\n font-size: 14px;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-radius: 5px 5px 0 0;\n}\n.popover-content {\n padding: 9px 14px;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n.carousel-inner > .item {\n position: relative;\n display: none;\n -webkit-transition: 0.6s ease-in-out left;\n -o-transition: 0.6s ease-in-out left;\n transition: 0.6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n .carousel-inner > .item {\n -webkit-transition: -webkit-transform 0.6s ease-in-out;\n -moz-transition: -moz-transform 0.6s ease-in-out;\n -o-transition: -o-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000px;\n -moz-perspective: 1000px;\n perspective: 1000px;\n }\n .carousel-inner > .item.next,\n .carousel-inner > .item.active.right {\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.prev,\n .carousel-inner > .item.active.left {\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.next.left,\n .carousel-inner > .item.prev.right,\n .carousel-inner > .item.active {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n left: 0;\n }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n display: block;\n}\n.carousel-inner > .active {\n left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n}\n.carousel-inner > .next {\n left: 100%;\n}\n.carousel-inner > .prev {\n left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n left: 0;\n}\n.carousel-inner > .active.left {\n left: -100%;\n}\n.carousel-inner > .active.right {\n left: 100%;\n}\n.carousel-control {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 15%;\n font-size: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n background-color: rgba(0, 0, 0, 0);\n filter: alpha(opacity=50);\n opacity: 0.5;\n}\n.carousel-control.left {\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n background-repeat: repeat-x;\n}\n.carousel-control.right {\n right: 0;\n left: auto;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n background-repeat: repeat-x;\n}\n.carousel-control:hover,\n.carousel-control:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n filter: alpha(opacity=90);\n opacity: 0.9;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n margin-top: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n width: 20px;\n height: 20px;\n font-family: serif;\n line-height: 1;\n}\n.carousel-control .icon-prev:before {\n content: \"\\2039\";\n}\n.carousel-control .icon-next:before {\n content: \"\\203a\";\n}\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n padding-left: 0;\n margin-left: -30%;\n text-align: center;\n list-style: none;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n border: 1px solid #fff;\n border-radius: 10px;\n}\n.carousel-indicators .active {\n width: 12px;\n height: 12px;\n margin: 0;\n background-color: #fff;\n}\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-caption .btn {\n text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-prev,\n .carousel-control .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -10px;\n font-size: 30px;\n }\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .icon-prev {\n margin-left: -10px;\n }\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-next {\n margin-right: -10px;\n }\n .carousel-caption {\n right: 20%;\n left: 20%;\n padding-bottom: 30px;\n }\n .carousel-indicators {\n bottom: 20px;\n }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-header:before,\n.modal-header:after,\n.modal-footer:before,\n.modal-footer:after {\n display: table;\n content: \" \";\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-header:after,\n.modal-footer:after {\n clear: both;\n}\n.center-block {\n display: block;\n margin-right: auto;\n margin-left: auto;\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n.hidden {\n display: none !important;\n}\n.affix {\n position: fixed;\n}\n@-ms-viewport {\n width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n@media (max-width: 767px) {\n .visible-xs {\n display: block !important;\n }\n table.visible-xs {\n display: table !important;\n }\n tr.visible-xs {\n display: table-row !important;\n }\n th.visible-xs,\n td.visible-xs {\n display: table-cell !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-block {\n display: block !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline {\n display: inline !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm {\n display: block !important;\n }\n table.visible-sm {\n display: table !important;\n }\n tr.visible-sm {\n display: table-row !important;\n }\n th.visible-sm,\n td.visible-sm {\n display: table-cell !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-block {\n display: block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline {\n display: inline !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md {\n display: block !important;\n }\n table.visible-md {\n display: table !important;\n }\n tr.visible-md {\n display: table-row !important;\n }\n th.visible-md,\n td.visible-md {\n display: table-cell !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-block {\n display: block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline {\n display: inline !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg {\n display: block !important;\n }\n table.visible-lg {\n display: table !important;\n }\n tr.visible-lg {\n display: table-row !important;\n }\n th.visible-lg,\n td.visible-lg {\n display: table-cell !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-block {\n display: block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline {\n display: inline !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline-block {\n display: inline-block !important;\n }\n}\n@media (max-width: 767px) {\n .hidden-xs {\n display: none !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .hidden-sm {\n display: none !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .hidden-md {\n display: none !important;\n }\n}\n@media (min-width: 1200px) {\n .hidden-lg {\n display: none !important;\n }\n}\n.visible-print {\n display: none !important;\n}\n@media print {\n .visible-print {\n display: block !important;\n }\n table.visible-print {\n display: table !important;\n }\n tr.visible-print {\n display: table-row !important;\n }\n th.visible-print,\n td.visible-print {\n display: table-cell !important;\n }\n}\n.visible-print-block {\n display: none !important;\n}\n@media print {\n .visible-print-block {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n}\n@media print {\n .visible-print-inline {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n}\n@media print {\n .visible-print-inline-block {\n display: inline-block !important;\n }\n}\n@media print {\n .hidden-print {\n display: none !important;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","// stylelint-disable\n\n/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS and IE text size adjust after device orientation change,\n// without disabling user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11\n// and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background-color: transparent;\n}\n\n//\n// Improve readability of focused elements when they are also in an\n// active/hover state.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// 1. Remove the bottom border in Chrome 57- and Firefox 39-.\n// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n//\n\nabbr[title] {\n border-bottom: none; // 1\n text-decoration: underline; // 2\n text-decoration: underline dotted; // 2\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome.\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n box-sizing: content-box; //2\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n","// stylelint-disable declaration-no-important, selector-no-qualifying-type\n\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request: h5bp.com/r\n// ==========================================================================\n\n@media print {\n *,\n *:before,\n *:after {\n color: #000 !important; // Black prints faster: h5bp.com/s\n text-shadow: none !important;\n background: transparent !important;\n box-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n}\n","// stylelint-disable value-list-comma-newline-after, value-list-comma-space-after, indentation, declaration-colon-newline-after, font-family-no-missing-generic-family-keyword\n\n//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// Star\n\n// Import the fonts\n@font-face {\n font-family: \"Glyphicons Halflings\";\n src: url(\"@{icon-font-path}@{icon-font-name}.eot\");\n src: url(\"@{icon-font-path}@{icon-font-name}.eot?#iefix\") format(\"embedded-opentype\"),\n url(\"@{icon-font-path}@{icon-font-name}.woff2\") format(\"woff2\"),\n url(\"@{icon-font-path}@{icon-font-name}.woff\") format(\"woff\"),\n url(\"@{icon-font-path}@{icon-font-name}.ttf\") format(\"truetype\"),\n url(\"@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}\") format(\"svg\");\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: \"Glyphicons Halflings\";\n font-style: normal;\n font-weight: 400;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\002a\"; } }\n.glyphicon-plus { &:before { content: \"\\002b\"; } }\n.glyphicon-euro,\n.glyphicon-eur { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n.glyphicon-cd { &:before { content: \"\\e201\"; } }\n.glyphicon-save-file { &:before { content: \"\\e202\"; } }\n.glyphicon-open-file { &:before { content: \"\\e203\"; } }\n.glyphicon-level-up { &:before { content: \"\\e204\"; } }\n.glyphicon-copy { &:before { content: \"\\e205\"; } }\n.glyphicon-paste { &:before { content: \"\\e206\"; } }\n// The following 2 Glyphicons are omitted for the time being because\n// they currently use Unicode codepoints that are outside the\n// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle\n// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.\n// Notably, the bug affects some older versions of the Android Browser.\n// More info: https://github.com/twbs/bootstrap/issues/10106\n// .glyphicon-door { &:before { content: \"\\1f6aa\"; } }\n// .glyphicon-key { &:before { content: \"\\1f511\"; } }\n.glyphicon-alert { &:before { content: \"\\e209\"; } }\n.glyphicon-equalizer { &:before { content: \"\\e210\"; } }\n.glyphicon-king { &:before { content: \"\\e211\"; } }\n.glyphicon-queen { &:before { content: \"\\e212\"; } }\n.glyphicon-pawn { &:before { content: \"\\e213\"; } }\n.glyphicon-bishop { &:before { content: \"\\e214\"; } }\n.glyphicon-knight { &:before { content: \"\\e215\"; } }\n.glyphicon-baby-formula { &:before { content: \"\\e216\"; } }\n.glyphicon-tent { &:before { content: \"\\26fa\"; } }\n.glyphicon-blackboard { &:before { content: \"\\e218\"; } }\n.glyphicon-bed { &:before { content: \"\\e219\"; } }\n.glyphicon-apple { &:before { content: \"\\f8ff\"; } }\n.glyphicon-erase { &:before { content: \"\\e221\"; } }\n.glyphicon-hourglass { &:before { content: \"\\231b\"; } }\n.glyphicon-lamp { &:before { content: \"\\e223\"; } }\n.glyphicon-duplicate { &:before { content: \"\\e224\"; } }\n.glyphicon-piggy-bank { &:before { content: \"\\e225\"; } }\n.glyphicon-scissors { &:before { content: \"\\e226\"; } }\n.glyphicon-bitcoin { &:before { content: \"\\e227\"; } }\n.glyphicon-btc { &:before { content: \"\\e227\"; } }\n.glyphicon-xbt { &:before { content: \"\\e227\"; } }\n.glyphicon-yen { &:before { content: \"\\00a5\"; } }\n.glyphicon-jpy { &:before { content: \"\\00a5\"; } }\n.glyphicon-ruble { &:before { content: \"\\20bd\"; } }\n.glyphicon-rub { &:before { content: \"\\20bd\"; } }\n.glyphicon-scale { &:before { content: \"\\e230\"; } }\n.glyphicon-ice-lolly { &:before { content: \"\\e231\"; } }\n.glyphicon-ice-lolly-tasted { &:before { content: \"\\e232\"; } }\n.glyphicon-education { &:before { content: \"\\e233\"; } }\n.glyphicon-option-horizontal { &:before { content: \"\\e234\"; } }\n.glyphicon-option-vertical { &:before { content: \"\\e235\"; } }\n.glyphicon-menu-hamburger { &:before { content: \"\\e236\"; } }\n.glyphicon-modal-window { &:before { content: \"\\e237\"; } }\n.glyphicon-oil { &:before { content: \"\\e238\"; } }\n.glyphicon-grain { &:before { content: \"\\e239\"; } }\n.glyphicon-sunglasses { &:before { content: \"\\e240\"; } }\n.glyphicon-text-size { &:before { content: \"\\e241\"; } }\n.glyphicon-text-color { &:before { content: \"\\e242\"; } }\n.glyphicon-text-background { &:before { content: \"\\e243\"; } }\n.glyphicon-object-align-top { &:before { content: \"\\e244\"; } }\n.glyphicon-object-align-bottom { &:before { content: \"\\e245\"; } }\n.glyphicon-object-align-horizontal{ &:before { content: \"\\e246\"; } }\n.glyphicon-object-align-left { &:before { content: \"\\e247\"; } }\n.glyphicon-object-align-vertical { &:before { content: \"\\e248\"; } }\n.glyphicon-object-align-right { &:before { content: \"\\e249\"; } }\n.glyphicon-triangle-right { &:before { content: \"\\e250\"; } }\n.glyphicon-triangle-left { &:before { content: \"\\e251\"; } }\n.glyphicon-triangle-bottom { &:before { content: \"\\e252\"; } }\n.glyphicon-triangle-top { &:before { content: \"\\e253\"; } }\n.glyphicon-console { &:before { content: \"\\e254\"; } }\n.glyphicon-superscript { &:before { content: \"\\e255\"; } }\n.glyphicon-subscript { &:before { content: \"\\e256\"; } }\n.glyphicon-menu-left { &:before { content: \"\\e257\"; } }\n.glyphicon-menu-right { &:before { content: \"\\e258\"; } }\n.glyphicon-menu-down { &:before { content: \"\\e259\"; } }\n.glyphicon-menu-up { &:before { content: \"\\e260\"; } }\n","//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// https://getbootstrap.com/docs/3.4/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: https://a11yproject.com/posts/how-to-hide-content\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see https://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n\n\n// iOS \"clickable elements\" fix for role=\"button\"\n//\n// Fixes \"clickability\" issue (and more generally, the firing of events such as focus as well)\n// for traditionally non-focusable elements with role=\"button\"\n// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n\n[role=\"button\"] {\n cursor: pointer;\n}\n","// stylelint-disable indentation, property-no-vendor-prefix, selector-no-vendor-prefix\n\n// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They have been removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility) {\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n word-wrap: break-word;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// WebKit-style focus\n\n.tab-focus() {\n // WebKit-specific. Other browsers will keep their default outline style.\n // (Initially tried to also force default via `outline: initial`,\n // but that seems to erroneously remove the outline in Firefox altogether.)\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n","// stylelint-disable media-feature-name-no-vendor-prefix, media-feature-parentheses-space-inside, media-feature-name-no-unknown, indentation, at-rule-name-space-after\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n","// stylelint-disable selector-list-comma-newline-after, selector-no-qualifying-type\n\n//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: 400;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\nmark,\n.mark {\n padding: .2em;\n background-color: @state-warning-bg;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-right: 5px;\n padding-left: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: 700;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @dl-horizontal-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n}\n\n.initialism {\n font-size: 90%;\n .text-uppercase();\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: \"\\2014 \\00A0\"; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n text-align: right;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: \"\"; }\n &:after {\n content: \"\\00A0 \\2014\"; // nbsp, em dash\n }\n }\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n","// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover,\n a&:focus {\n color: darken(@color, 10%);\n }\n}\n","// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover,\n a&:focus {\n background-color: darken(@color, 10%);\n }\n}\n","// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: 700;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n color: @pre-color;\n word-break: break-all;\n word-wrap: break-word;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n.row-no-gutters {\n margin-right: 0;\n margin-left: 0;\n\n [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n padding-right: ceil((@gutter / 2));\n padding-left: floor((@gutter / 2));\n margin-right: auto;\n margin-left: auto;\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-right: floor((@gutter / -2));\n margin-left: ceil((@gutter / -2));\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-right: (@gutter / 2);\n padding-left: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-right: (@gutter / 2);\n padding-left: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-right: (@gutter / 2);\n padding-left: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-right: (@gutter / 2);\n padding-left: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-right: floor((@grid-gutter-width / 2));\n padding-left: ceil((@grid-gutter-width / 2));\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n .col-@{class}-push-0 {\n left: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n .col-@{class}-pull-0 {\n right: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n","// stylelint-disable selector-max-type, selector-max-compound-selectors, selector-no-qualifying-type\n\n//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n\n // Table cell sizing\n //\n // Reset default table behavior\n\n col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n display: table-column;\n float: none;\n }\n\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n display: table-cell;\n float: none;\n }\n }\n}\n\ncaption {\n padding-top: @table-cell-padding;\n padding-bottom: @table-cell-padding;\n color: @text-muted;\n text-align: left;\n}\n\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-of-type(odd) {\n background-color: @table-bg-accent;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n background-color: @table-bg-hover;\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n min-height: .01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n overflow-x: auto;\n\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * .75);\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n","// Tables\n\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &:hover > .@{state},\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n","// stylelint-disable selector-no-qualifying-type, property-no-vendor-prefix, media-feature-name-no-vendor-prefix\n\n//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n margin-bottom: 5px;\n font-weight: 700;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\ninput[type=\"search\"] {\n // Override content-box in Normalize (* isn't specific enough)\n .box-sizing(border-box);\n\n // Search inputs in iOS\n //\n // This overrides the extra rounded corners on search inputs in iOS so that our\n // `.form-control` class can properly style them. Note that this cannot simply\n // be added to `.form-control` as it's not specific enough. For details, see\n // https://github.com/twbs/bootstrap/issues/11586.\n -webkit-appearance: none;\n appearance: none;\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; // IE8-9\n line-height: normal;\n\n // Apply same disabled cursor tweak as for inputs\n // Some special care is needed because