diff --git a/sdk/arduino/wumei/.vscode/settings.json b/sdk/arduino/wumei/.vscode/settings.json new file mode 100644 index 00000000..691a8f68 --- /dev/null +++ b/sdk/arduino/wumei/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.errorSquiggles": "Disabled" +} \ No newline at end of file diff --git a/sdk/arduino/wumei/Base64.cpp b/sdk/arduino/wumei/Base64.cpp new file mode 100644 index 00000000..71c40863 --- /dev/null +++ b/sdk/arduino/wumei/Base64.cpp @@ -0,0 +1,137 @@ +#include "Base64.h" + +#if (defined(__AVR__)) +#include +#else +#include +#endif + +const char PROGMEM b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +/* 'Private' declarations */ +inline void a3_to_a4(unsigned char * a4, unsigned char * a3); +inline void a4_to_a3(unsigned char * a3, unsigned char * a4); +inline unsigned char b64_lookup(char c); + +int base64_encode(char *output, char *input, int inputLen) { + int i = 0, j = 0; + int encLen = 0; + unsigned char a3[3]; + unsigned char a4[4]; + + while(inputLen--) { + a3[i++] = *(input++); + if(i == 3) { + a3_to_a4(a4, a3); + + for(i = 0; i < 4; i++) { + output[encLen++] = pgm_read_byte(&b64_alphabet[a4[i]]); + } + + i = 0; + } + } + + if(i) { + for(j = i; j < 3; j++) { + a3[j] = '\0'; + } + + a3_to_a4(a4, a3); + + for(j = 0; j < i + 1; j++) { + output[encLen++] = pgm_read_byte(&b64_alphabet[a4[j]]); + } + + while((i++ < 3)) { + output[encLen++] = '='; + } + } + output[encLen] = '\0'; + return encLen; +} + +int base64_decode(char * output, char * input, int inputLen) { + int i = 0, j = 0; + int decLen = 0; + unsigned char a3[3]; + unsigned char a4[4]; + + + while (inputLen--) { + if(*input == '=') { + break; + } + + a4[i++] = *(input++); + if (i == 4) { + for (i = 0; i <4; i++) { + a4[i] = b64_lookup(a4[i]); + } + + a4_to_a3(a3,a4); + + for (i = 0; i < 3; i++) { + output[decLen++] = a3[i]; + } + i = 0; + } + } + + if (i) { + for (j = i; j < 4; j++) { + a4[j] = '\0'; + } + + for (j = 0; j <4; j++) { + a4[j] = b64_lookup(a4[j]); + } + + a4_to_a3(a3,a4); + + for (j = 0; j < i - 1; j++) { + output[decLen++] = a3[j]; + } + } + output[decLen] = '\0'; + return decLen; +} + +int base64_enc_len(int plainLen) { + int n = plainLen; + return (n + 2 - ((n + 2) % 3)) / 3 * 4; +} + +int base64_dec_len(char * input, int inputLen) { + int i = 0; + int numEq = 0; + for(i = inputLen - 1; input[i] == '='; i--) { + numEq++; + } + + return ((6 * inputLen) / 8) - numEq; +} + +inline void a3_to_a4(unsigned char * a4, unsigned char * a3) { + a4[0] = (a3[0] & 0xfc) >> 2; + a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); + a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); + a4[3] = (a3[2] & 0x3f); +} + +inline void a4_to_a3(unsigned char * a3, unsigned char * a4) { + a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4); + a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2); + a3[2] = ((a4[2] & 0x3) << 6) + a4[3]; +} + +inline unsigned char b64_lookup(char c) { + if(c >='A' && c <='Z') return c - 'A'; + if(c >='a' && c <='z') return c - 71; + if(c >='0' && c <='9') return c + 4; + if(c == '+') return 62; + if(c == '/') return 63; + return -1; +} diff --git a/sdk/arduino/wumei/Base64.h b/sdk/arduino/wumei/Base64.h new file mode 100644 index 00000000..56d3271e --- /dev/null +++ b/sdk/arduino/wumei/Base64.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2013 Adam Rudd. + * See LICENSE for more information + */ +#ifndef _BASE64_H +#define _BASE64_H + +/* b64_alphabet: + * Description: Base64 alphabet table, a mapping between integers + * and base64 digits + * Notes: This is an extern here but is defined in Base64.c + */ +extern const char b64_alphabet[]; + +/* base64_encode: + * Description: + * Encode a string of characters as base64 + * Parameters: + * output: the output buffer for the encoding, stores the encoded string + * input: the input buffer for the encoding, stores the binary to be encoded + * inputLen: the length of the input buffer, in bytes + * Return value: + * Returns the length of the encoded string + * Requirements: + * 1. output must not be null or empty + * 2. input must not be null + * 3. inputLen must be greater than or equal to 0 + */ +int base64_encode(char *output, char *input, int inputLen); + +/* base64_decode: + * Description: + * Decode a base64 encoded string into bytes + * Parameters: + * output: the output buffer for the decoding, + * stores the decoded binary + * input: the input buffer for the decoding, + * stores the base64 string to be decoded + * inputLen: the length of the input buffer, in bytes + * Return value: + * Returns the length of the decoded string + * Requirements: + * 1. output must not be null or empty + * 2. input must not be null + * 3. inputLen must be greater than or equal to 0 + */ +int base64_decode(char *output, char *input, int inputLen); + +/* base64_enc_len: + * Description: + * Returns the length of a base64 encoded string whose decoded + * form is inputLen bytes long + * Parameters: + * inputLen: the length of the decoded string + * Return value: + * The length of a base64 encoded string whose decoded form + * is inputLen bytes long + * Requirements: + * None + */ +int base64_enc_len(int inputLen); + +/* base64_dec_len: + * Description: + * Returns the length of the decoded form of a + * base64 encoded string + * Parameters: + * input: the base64 encoded string to be measured + * inputLen: the length of the base64 encoded string + * Return value: + * Returns the length of the decoded form of a + * base64 encoded string + * Requirements: + * 1. input must not be null + * 2. input must be greater than or equal to zero + */ +int base64_dec_len(char *input, int inputLen); + +#endif // _BASE64_H diff --git a/sdk/arduino/wumei/Helper.cpp b/sdk/arduino/wumei/Helper.cpp new file mode 100644 index 00000000..15078231 --- /dev/null +++ b/sdk/arduino/wumei/Helper.cpp @@ -0,0 +1,487 @@ +/*********************************************************** + * author: kerwincui [物美智能 wumei-smart] + * create: 2022-02-20 + * email:164770707@qq.com + * source:https://github.com/kerwincui/wumei-smart + * board:esp8266 core for arduino v3.0.2 + ***********************************************************/ + +#include "Helper.h" + +WiFiClient wifiClient; +PubSubClient mqttClient; +float rssi = 0; +char wumei_iv[17] = "wumei-smart-open"; +int monitorCount = 0; +long monitorInterval = 1000; + +//==================================== 这是需要配置的项 =============================== +// Wifi配置 +char *wifiSsid = "wifi账号"; +char *wifiPwd = "wifi密码"; + +// 设备信息配置 +String deviceNum = "D6329VL54419L1Y0"; +String userId = "1"; +String productId = "2"; +String firmwareVersion = "1.0"; + +// Mqtt配置 +char *mqttHost = "wumei.live"; +int mqttPort = 1883; +char *mqttUserName = "wumei-smart"; +char *mqttPwd = "P5FJKZJHIR82GNB2"; +char mqttSecret[17] = "K63C4EA3AI5TER97"; + +// NTP地址(用于获取时间,可选的修改为自己部署项目的地址) +String ntpServer = "http://wumei.live:8080/iot/tool/ntp?deviceSendTime="; +//==================================================================================== + +// 订阅的主题 +String prefix = "/" + productId + "/" + deviceNum; +String sOtaTopic = prefix + "/ota/get"; +String sNtpTopic = prefix + "/ntp/get"; +String sPropertyTopic = prefix + "/property/get"; +String sFunctionTopic = prefix + "/function/get"; +String sPropertyOnline = prefix + "/property-online/get"; +String sFunctionOnline = prefix + "/function-online/get"; +String sMonitorTopic = prefix + "/monitor/get"; +// 发布的主题 +String pInfoTopic = prefix + "/info/post"; +String pNtpTopic = prefix + "/ntp/post"; +String pPropertyTopic = prefix + "/property/post"; +String pFunctionTopic = prefix + "/function/post"; +String pMonitorTopic = prefix + "/monitor/post"; +String pEventTopic = prefix + "/event/post"; + +// 物模型-属性处理 +void processProperty(String payload) +{ + StaticJsonDocument<1024> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + return; + } + for (JsonObject object : doc.as()) + { + // 匹配云端定义的属性(不包含属性中的监测数据) + const char* id = object["id"]; + const char* value = object["value"]; + printMsg((String)id + ":" + (String)value); + } + // 最后发布属性,服务端订阅存储(重要) + publishProperty(payload); +} + +// 物模型-功能处理 +void processFunction(String payload) +{ + StaticJsonDocument<1024> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + return; + } + for (JsonObject object : doc.as()) + { + // 匹配云端定义的功能 + const char* id = object["id"]; + const char* value = object["value"]; + if (strcmp(id, "switch") == 0) + { + printMsg("开关 switch:" + (String) value); + } + else if (strcmp(id, "gear") == 0) + { + printMsg("档位 gear:" + (String)value); + } + else if (strcmp(id, "light_color") == 0) + { + printMsg("灯光颜色 light_color:" + (String)value); + } + else if (strcmp(id, "message") == 0) + { + printMsg("屏显消息 message:" + (String)value); + }else if(strcmp(id,"report_monitor")==0){ + String msg=randomPropertyData(); + printMsg("订阅到上报监测数据指令,上报数据:"); + printMsg(msg); + publishProperty(msg); + } + } + // 最后发布功能,服务端订阅存储(重要) + publishFunction(payload); +} + +// Mqtt回调 +void callback(char *topic, byte *payload, unsigned int length) +{ + blink(); + printMsg("接收数据:"); + String data = ""; + for (int i = 0; i < length; i++) + { + Serial.print((char)payload[i]); + data += (char)payload[i]; + } + + if (strcmp(topic, sOtaTopic.c_str()) == 0) + { + printMsg("订阅到设备升级指令..."); + } + else if (strcmp(topic, sNtpTopic.c_str()) == 0) + { + printMsg("订阅到NTP时间..."); + StaticJsonDocument<256> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) + { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + return; + } + // 计算设备当前时间:(${serverRecvTime} + ${serverSendTime} + ${deviceRecvTime} - ${deviceSendTime}) / 2 + float deviceSendTime = doc["deviceSendTime"]; + float serverSendTime = doc["serverSendTime"]; + float serverRecvTime = doc["serverRecvTime"]; + float deviceRecvTime = millis(); + float now = (serverSendTime + serverRecvTime + deviceRecvTime - deviceSendTime) / 2; + printMsg("当前时间:" + String(now, 0)); + } + else if (strcmp(topic, sPropertyTopic.c_str()) == 0 || strcmp(topic, sPropertyOnline.c_str()) == 0) + { + printMsg("订阅到属性指令..."); + processProperty(data); + } + else if (strcmp(topic, sFunctionTopic.c_str()) == 0 || strcmp(topic, sFunctionOnline.c_str()) == 0) + { + printMsg("订阅到功能指令..."); + processFunction(data); + } + else if (strcmp(topic, sMonitorTopic.c_str()) == 0) + { + printMsg("订阅到实时监测指令..."); + StaticJsonDocument<128> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) + { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + return; + } + monitorCount = doc["count"]; + monitorInterval = doc["interval"]; + } +} + +// 连接wifi +void connectWifi() +{ + printMsg("连接 "); + Serial.print(wifiSsid); + WiFi.mode(WIFI_STA); + WiFi.begin(wifiSsid, wifiPwd); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print("."); + } + printMsg("WiFi连接成功"); + printMsg("IP地址: "); + Serial.print(WiFi.localIP()); +} + +// 连接mqtt +void connectMqtt() +{ + printMsg("连接Mqtt服务器..."); + // 生成mqtt认证密码(密码 = mqtt密码 & 用户ID & 过期时间) + String password = generationPwd(); + String encryptPassword = encrypt(password, mqttSecret, wumei_iv); + printMsg("密码(已加密):" + encryptPassword); + mqttClient.setClient(wifiClient); + mqttClient.setServer(mqttHost, mqttPort); + mqttClient.setCallback(callback); + mqttClient.setBufferSize(1024); + mqttClient.setKeepAlive(10); + //连接(客户端ID = 设备编号 & 产品ID) + String clientId = deviceNum + "&" + productId; + bool connectResult = mqttClient.connect(clientId.c_str(), mqttUserName, encryptPassword.c_str()); + if (connectResult) + { + printMsg("连接成功"); + // 订阅(OTA、NTP、属性、功能、实时监测) + mqttClient.subscribe(sOtaTopic.c_str(), 1); + mqttClient.subscribe(sNtpTopic.c_str(), 1); + mqttClient.subscribe(sPropertyTopic.c_str(), 1); + mqttClient.subscribe(sFunctionTopic.c_str(), 1); + mqttClient.subscribe(sPropertyOnline.c_str(), 1); + mqttClient.subscribe(sFunctionOnline.c_str(), 1); + mqttClient.subscribe(sMonitorTopic.c_str(), 1); + printMsg("订阅主题:" + sOtaTopic); + printMsg("订阅主题:" + sNtpTopic); + printMsg("订阅主题:" + sPropertyTopic); + printMsg("订阅主题:" + sFunctionTopic); + printMsg("订阅主题:" + sPropertyOnline); + printMsg("订阅主题:" + sFunctionOnline); + printMsg("订阅主题:" + sMonitorTopic); + // 发布设备信息 + publishInfo(); + } + else + { + printMsg("连接失败, rc="); + Serial.print(mqttClient.state()); + } +} + +// 1.发布设备信息 +void publishInfo() +{ + StaticJsonDocument<256> doc; + doc["rssi"] = WiFi.RSSI(); + doc["firmwareVersion"] = firmwareVersion; + doc["status"] = 3; // (1-未激活,2-禁用,3-在线,4-离线) + doc["userId"] = (String)userId; + + printMsg("发布设备信息:"); + serializeJson(doc, Serial); + String output; + serializeJson(doc, output); + mqttClient.publish(pInfoTopic.c_str(), output.c_str()); +} + +// 2.发布时钟同步信,用于获取当前时间(可选) +void publishNtp() +{ + StaticJsonDocument<128> doc; + doc["deviceSendTime"] = millis(); + + printMsg("发布NTP信息:"); + serializeJson(doc, Serial); + String output; + serializeJson(doc, output); + mqttClient.publish(pNtpTopic.c_str(), output.c_str()); +} + +// 3.发布属性 +void publishProperty(String msg) +{ + printMsg("发布属性:" + msg); + mqttClient.publish(pPropertyTopic.c_str(), msg.c_str()); +} + +// 4.发布功能 +void publishFunction(String msg) +{ + printMsg("发布功能:" + msg); + mqttClient.publish(pFunctionTopic.c_str(), msg.c_str()); +} + +// 5.发布事件 +void publishEvent() +{ + // 匹配云端的事件 + StaticJsonDocument<512> doc; + JsonObject objTmeperature = doc.createNestedObject(); + objTmeperature["id"] = "height_temperature"; + objTmeperature["value"] = "40"; + objTmeperature["remark"] = "温度过高警告"; + + JsonObject objException = doc.createNestedObject(); + objException["id"] = "exception"; + objException["value"] = "异常消息,消息内容XXXXXXXX"; + objException["remark"] = "设备发生错误"; + + printMsg("发布事件:"); + serializeJson(doc, Serial); + String output; + serializeJson(doc, output); + mqttClient.publish(pEventTopic.c_str(), output.c_str()); +} + +// 6.发布实时监测数据 +void publishMonitor() +{ + String msg=randomPropertyData(); + // 发布为实时监测数据,不会存储 + printMsg("发布实时监测数据:"+msg); + mqttClient.publish(pMonitorTopic.c_str(), msg.c_str()); +} + +// 随机生成监测值 +String randomPropertyData(){ + // 匹配云端定义的监测数据,随机数代替监测结果 + float randFloat = 0; + int randInt=0; + StaticJsonDocument<1024> doc; + JsonObject objTmeperature = doc.createNestedObject(); + objTmeperature["id"] = "temperature"; + randFloat = random(1000, 3000) ; + objTmeperature["value"] = (String)(randFloat/100); + objTmeperature["remark"] = (String)millis(); + + JsonObject objHumidity = doc.createNestedObject(); + objHumidity["id"] = "humidity"; + randFloat = random(3000, 6000); + objHumidity["value"] = (String)(randFloat/100); + objHumidity["remark"] = (String)millis(); + + JsonObject objCo2 = doc.createNestedObject(); + objCo2["id"] = "co2"; + randInt = random(400, 1000); + objCo2["value"] = (String)(randInt); + objCo2["remark"] = (String)millis(); + + JsonObject objBrightness = doc.createNestedObject(); + objBrightness["id"] = "brightness"; + randInt = random(1000, 10000); + objBrightness["value"] = (String)(randInt); + objBrightness["remark"] = (String)millis(); + + printMsg("随机生成监测数据值:"); + serializeJson(doc, Serial); + String output; + serializeJson(doc, output); + return output; +} + +// 生成密码 +String generationPwd() +{ + String jsonTime = getTime(); + // 128字节内存池容量 + StaticJsonDocument<128> doc; + // 解析JSON + DeserializationError error = deserializeJson(doc, jsonTime); + if (error) + { + printMsg("Json解析失败:"); + Serial.print(error.f_str()); + return ""; + } + // 获取当前时间 + float deviceSendTime = doc["deviceSendTime"]; + float serverSendTime = doc["serverSendTime"]; + float serverRecvTime = doc["serverRecvTime"]; + float deviceRecvTime = millis(); + float now = (serverSendTime + serverRecvTime + deviceRecvTime - deviceSendTime) / 2; + // 过期时间 = 当前时间 + 1小时 + float expireTime = now + 1 * 60 * 60 * 1000; + String password = (String)mqttPwd + "&" + userId + "&" + String(expireTime, 0); + printMsg("密码(未加密):" + password); + return password; +} + +// HTTP获取时间 +String getTime() +{ + while (WiFi.status() == WL_CONNECTED) + { + HTTPClient http; + printMsg("获取时间..."); + if (http.begin(wifiClient, (ntpServer + (String)millis()).c_str())) + { + // 发送请求 + int httpCode = http.GET(); + if (httpCode > 0) + { + if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) + { + printMsg("获取时间成功,data:"); + Serial.print(http.getString()); + return http.getString(); + } + } + else + { + printMsg("获取时间失败,error:"); + Serial.printf(http.errorToString(httpCode).c_str()); + } + http.end(); + } + else + { + printMsg("连接Http失败"); + } + delay(500); + } +} + +//打印提示信息 +void printMsg(String msg) +{ + Serial.print("\r\n["); + Serial.print(millis()); + Serial.print("ms]"); + Serial.print(msg); +} + +// 控制指示灯闪烁 +void blink() +{ + printMsg("指示灯闪烁..."); + pinMode(15, OUTPUT); + for (int i = 0; i < 2; i++) + { + digitalWrite(15, HIGH); + delay(200); + digitalWrite(15, LOW); + delay(200); + } +} + +// 加密 (AES-CBC-128-pkcs5padding) +String encrypt(String plain_data, char *wumei_key, char *wumei_iv) +{ + int i; + // pkcs7padding填充 Block Size : 16 + int len = plain_data.length(); + int n_blocks = len / 16 + 1; + uint8_t n_padding = n_blocks * 16 - len; + uint8_t data[n_blocks * 16]; + memcpy(data, plain_data.c_str(), len); + for (i = len; i < n_blocks * 16; i++) + { + data[i] = n_padding; + } + uint8_t key[16], iv[16]; + memcpy(key, wumei_key, 16); + memcpy(iv, wumei_iv, 16); + // 加密 + br_aes_big_cbcenc_keys encCtx; + br_aes_big_cbcenc_init(&encCtx, key, 16); + br_aes_big_cbcenc_run(&encCtx, iv, data, n_blocks * 16); + // Base64编码 + len = n_blocks * 16; + char encoded_data[base64_enc_len(len)]; + base64_encode(encoded_data, (char *)data, len); + return String(encoded_data); +} + +// 解密 (AES-CBC-128-pkcs5padding) +String decrypt(String encoded_data_str, char *wumei_key, char *wumei_iv) +{ + int input_len = encoded_data_str.length(); + char *encoded_data = const_cast(encoded_data_str.c_str()); + int len = base64_dec_len(encoded_data, input_len); + uint8_t data[len]; + base64_decode((char *)data, encoded_data, input_len); + uint8_t key[16], iv[16]; + memcpy(key, wumei_key, 16); + memcpy(iv, wumei_iv, 16); + int n_blocks = len / 16; + br_aes_big_cbcdec_keys decCtx; + br_aes_big_cbcdec_init(&decCtx, key, 16); + br_aes_big_cbcdec_run(&decCtx, iv, data, n_blocks * 16); + // PKCS#7 Padding 填充 + uint8_t n_padding = data[n_blocks * 16 - 1]; + len = n_blocks * 16 - n_padding; + char plain_data[len + 1]; + memcpy(plain_data, data, len); + plain_data[len] = '\0'; + return String(plain_data); +} diff --git a/sdk/arduino/wumei/Helper.h b/sdk/arduino/wumei/Helper.h new file mode 100644 index 00000000..09e79351 --- /dev/null +++ b/sdk/arduino/wumei/Helper.h @@ -0,0 +1,79 @@ +/*********************************************************** + * author: kerwincui [物美智能 wumei-smart] + * create: 2022-02-20 + * email:164770707@qq.com + * source:https://github.com/kerwincui/wumei-smart + * board:esp8266 core for arduino v3.0.2 + ***********************************************************/ + +#ifndef _HELPER_H +#define _HELPER_H + +#include "Base64.h" +#include +#include +#include +#include // 版本2.8.0 +#include // 版本6.19.1 + +extern WiFiClient wifiClient; +extern PubSubClient mqttClient; + +extern String deviceNum ; // 设备编号(重要,同时是Mqtt的clientId) +extern String userId; // 用户ID +extern String productId; // 产品ID +extern float rssi; // 信号强度(信号极好4格[-55— 0],信号好3格[-70— -55],信号一般2格[-85— -70],信号差1格[-100— -85]) +extern String firmwareVersion; // 固件版本 +extern char *wifiSsid; // WIFI的SSID +extern char *wifiPwd; // WIFI的密码 +extern char *mqttHost; // Mqtt消息服务器地址 +extern int mqttPort; // Mqtt消息服务器端口 +extern char *mqttUserName; // Mqtt消息服务器账号 +extern char *mqttPwd; // Mqtt消息服务器密码 +extern char mqttSecret[17]; // Mqtt秘钥,16位 +extern char wumei_iv[17]; // AES加密偏移量,固定值16位 +extern String ntpServer; // NTP服务地址,用于获取当前时间 +extern int monitorCount; // 发布监测数据的最大次数 +extern long monitorInterval; // 发布监测数据的间隔,默认1000毫秒 + + +// 连接wifi +void connectWifi(); +// 连接mqtt +void connectMqtt(); +// Mqtt回调 +void callback(char *topic, byte *payload, unsigned int length); + +// 发布设备信息 +void publishInfo(); +// 发布时钟同步信息 +void publishNtp(); +// 发布事件 +void publishEvent(); +// 发布实时监测数据 +void publishMonitor(); +// 随机生成监测值 +String randomPropertyData(); +// 发布属性 +void publishProperty(String msg); +// 发布功能 +void publishFunction(String msg); +// 属性处理 +void processProperty(String payload); +// 功能处理 +void processFunction(String payload); + +// 生成密码 +String generationPwd(); +// 获取时间 +String getTime(); +// AES加密 +String encrypt(String plain_data,char *wumei_key,char *wumei_iv); +// AES解密 +String decrypt(String encoded_data_str,char *wumei_key,char *wumei_iv); +//打印提示信息 +void printMsg(String tips); +// 控制指示灯闪烁 +void blink(); + +#endif diff --git a/sdk/arduino/wumei/wumei.ino b/sdk/arduino/wumei/wumei.ino new file mode 100644 index 00000000..e204959e --- /dev/null +++ b/sdk/arduino/wumei/wumei.ino @@ -0,0 +1,85 @@ +/*********************************************************** + * author: kerwincui [物美智能 wumei-smart] + * create: 2022-02-20 + * email:164770707@qq.com + * source:https://github.com/kerwincui/wumei-smart + * board:esp8266 core for arduino v3.0.2 + ***********************************************************/ + +#include "Helper.h" + +long lastMqttConn; // 上次mqtt连接时间 +long lastPublishMonitor; // 上次发布监测数据时间 +long lastTimerMonitor; // 上次定时发布监测数据 + +/** + * 启动 + */ +void setup() +{ + //打开串行端口: + Serial.begin(115200); + printMsg("wumei smart device starting..."); + connectWifi(); + connectMqtt(); +} + +/** + * 循环执行 + */ +void loop() +{ + // Wifi掉线重连 + if (WiFi.status() != WL_CONNECTED) + { + connectWifi(); + } + + // 非阻塞Mqtt重连,间隔30秒 + if (WiFi.status() == WL_CONNECTED) + { + long now = millis(); + if (!mqttClient.connected()) + { + if (now - lastMqttConn > 30000) + { + lastMqttConn = now; + connectMqtt(); + } + } + else + { + mqttClient.loop(); + } + } + + // 非阻塞发布实时监测数据,间隔默认1秒 + if(WiFi.status() == WL_CONNECTED && monitorCount>0){ + long now = millis(); + if (now - lastPublishMonitor > monitorInterval) + { + lastPublishMonitor = now; + monitorCount--; + publishMonitor(); + } + } + + // 非阻塞定时上报,测试用,60秒发布一次 + if(WiFi.status() == WL_CONNECTED){ + long now = millis(); + if (now - lastTimerMonitor > 60000) + { + lastTimerMonitor = now; + printMsg("执行定时上报"); + // 发布事件 + publishEvent(); + // 发布时钟同步 + publishNtp(); + + // 发布属性(监测值) + String msg=randomPropertyData(); + publishProperty(msg); + } + } + +} diff --git a/springboot/.gitignore b/springboot/.gitignore new file mode 100644 index 00000000..2c0e049a --- /dev/null +++ b/springboot/.gitignore @@ -0,0 +1,44 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp + +!*/build/*.java +!*/build/*.html +!*/build/*.xml diff --git a/springboot/LICENSE b/springboot/LICENSE new file mode 100644 index 00000000..8564f294 --- /dev/null +++ b/springboot/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 RuoYi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/springboot/README.md b/springboot/README.md new file mode 100644 index 00000000..61938265 --- /dev/null +++ b/springboot/README.md @@ -0,0 +1,86 @@ +## 平台简介 + +若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 + +* 前端采用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 diff --git a/springboot/bin/clean.bat b/springboot/bin/clean.bat new file mode 100644 index 00000000..24c09741 --- /dev/null +++ b/springboot/bin/clean.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] target· +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean + +pause \ No newline at end of file diff --git a/springboot/bin/package.bat b/springboot/bin/package.bat new file mode 100644 index 00000000..c693ec06 --- /dev/null +++ b/springboot/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] Weḅwar/jarļ +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean package -Dmaven.test.skip=true + +pause \ No newline at end of file diff --git a/springboot/bin/run.bat b/springboot/bin/run.bat new file mode 100644 index 00000000..b0557be3 --- /dev/null +++ b/springboot/bin/run.bat @@ -0,0 +1,14 @@ +@echo off +echo. +echo [��Ϣ] ʹ��Jar��������Web���̡� +echo. + +cd %~dp0 +cd ../wumei-admin/target + +set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m + +java -jar %JAVA_OPTS% wumei-admin.jar + +cd bin +pause \ No newline at end of file diff --git a/springboot/doc/若依环境使用手册.docx b/springboot/doc/若依环境使用手册.docx new file mode 100644 index 00000000..9e4daef4 Binary files /dev/null and b/springboot/doc/若依环境使用手册.docx differ diff --git a/springboot/pom.xml b/springboot/pom.xml new file mode 100644 index 00000000..bcd64af3 --- /dev/null +++ b/springboot/pom.xml @@ -0,0 +1,272 @@ + + + 4.0.0 + + com.wumei + wumei + 3.8.0 + + wumei + http://www.wumei.live + 物美智能开源生活物联网平台 + + + 3.8.0 + UTF-8 + UTF-8 + 1.8 + 3.1.1 + 1.2.8 + 1.21 + 3.0.0 + 2.3.2 + 2.2.0 + 1.4.0 + 1.2.78 + 5.8.2 + 5.9.0 + 2.11.0 + 1.4 + 3.2.2 + 4.1.2 + 2.3 + 0.9.1 + + + + + + + + + org.springframework.boot + spring-boot-dependencies + 2.5.6 + pom + import + + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + + eu.bitwalker + UserAgentUtils + ${bitwalker.version} + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot.version} + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${pagehelper.boot.version} + + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + net.java.dev.jna + jna + ${jna.version} + + + + net.java.dev.jna + jna-platform + ${jna.version} + + + + + io.springfox + springfox-boot-starter + ${swagger.version} + + + io.swagger + swagger-models + + + + + + + commons-io + commons-io + ${commons.io.version} + + + + + commons-fileupload + commons-fileupload + ${commons.fileupload.version} + + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + + + commons-collections + commons-collections + ${commons.collections.version} + + + + + com.alibaba + fastjson + ${fastjson.version} + + + + + io.jsonwebtoken + jjwt + ${jwt.version} + + + + + com.github.penggle + kaptcha + ${kaptcha.version} + + + + + com.wumei + wumei-quartz + ${wumei.version} + + + + + com.wumei + wumei-generator + ${wumei.version} + + + + + com.wumei + wumei-framework + ${wumei.version} + + + + + com.wumei + wumei-system + ${wumei.version} + + + + + com.wumei + wumei-common + ${wumei.version} + + + + + com.wumei + wumei-iot + ${wumei.version} + + + + + + + wumei-admin + wumei-framework + wumei-system + wumei-quartz + wumei-generator + wumei-common + wumei-iot + + pom + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + ${java.home}/lib/rt.jar;${java.home}/lib/jce.jar + + + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + false + + + + + \ No newline at end of file diff --git a/springboot/ry.bat b/springboot/ry.bat new file mode 100644 index 00000000..d63f9b14 --- /dev/null +++ b/springboot/ry.bat @@ -0,0 +1,67 @@ +@echo off + +rem jarƽ��Ŀ¼ +set AppName=wumei-admin.jar + +rem JVM���� +set JVM_OPTS="-Dname=%AppName% -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC" + + +ECHO. + ECHO. [1] ����%AppName% + ECHO. [2] �ر�%AppName% + ECHO. [3] ����%AppName% + ECHO. [4] ����״̬ %AppName% + ECHO. [5] �� �� +ECHO. + +ECHO.������ѡ����Ŀ�����: +set /p ID= + IF "%id%"=="1" GOTO start + IF "%id%"=="2" GOTO stop + IF "%id%"=="3" GOTO restart + IF "%id%"=="4" GOTO status + IF "%id%"=="5" EXIT +PAUSE +:start + for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( + set pid=%%a + set image_name=%%b + ) + if defined pid ( + echo %%is running + PAUSE + ) + +start javaw %JAVA_OPTS% -jar %AppName% + +echo starting���� +echo Start %AppName% success... +goto:eof + +rem ����stopͨ��jps�������pid���������� +:stop + for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( + set pid=%%a + set image_name=%%b + ) + if not defined pid (echo process %AppName% does not exists) else ( + echo prepare to kill %image_name% + echo start kill %pid% ... + rem ���ݽ���ID��kill���� + taskkill /f /pid %pid% + ) +goto:eof +:restart + call :stop + call :start +goto:eof +:status + for /f "usebackq tokens=1-2" %%a in (`jps -l ^| findstr %AppName%`) do ( + set pid=%%a + set image_name=%%b + ) + if not defined pid (echo process %AppName% is dead ) else ( + echo %image_name% is running + ) +goto:eof diff --git a/springboot/ry.sh b/springboot/ry.sh new file mode 100644 index 00000000..9770af79 --- /dev/null +++ b/springboot/ry.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# ./ry.sh start 启动 stop 停止 restart 重启 status 状态 +AppName=wumei-admin.jar + +# JVM参数 +JVM_OPTS="-Dname=$AppName -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC" +APP_HOME=`pwd` +LOG_PATH=$APP_HOME/logs/$AppName.log + +if [ "$1" = "" ]; +then + echo -e "\033[0;31m 未输入操作名 \033[0m \033[0;34m {start|stop|restart|status} \033[0m" + exit 1 +fi + +if [ "$AppName" = "" ]; +then + echo -e "\033[0;31m 未输入应用名 \033[0m" + exit 1 +fi + +function start() +{ + PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` + + if [ x"$PID" != x"" ]; then + echo "$AppName is running..." + else + nohup java $JVM_OPTS -jar $AppName > /dev/null 2>&1 & + echo "Start $AppName success..." + fi +} + +function stop() +{ + echo "Stop $AppName" + + PID="" + query(){ + PID=`ps -ef |grep java|grep $AppName|grep -v grep|awk '{print $2}'` + } + + query + if [ x"$PID" != x"" ]; then + kill -TERM $PID + echo "$AppName (pid:$PID) exiting..." + while [ x"$PID" != x"" ] + do + sleep 1 + query + done + echo "$AppName exited." + else + echo "$AppName already stopped." + fi +} + +function restart() +{ + stop + sleep 2 + start +} + +function status() +{ + PID=`ps -ef |grep java|grep $AppName|grep -v grep|wc -l` + if [ $PID != 0 ];then + echo "$AppName is running..." + else + echo "$AppName is not running..." + fi +} + +case $1 in + start) + start;; + stop) + stop;; + restart) + restart;; + status) + status;; + *) + +esac diff --git a/springboot/sql/wumei.sql b/springboot/sql/wumei.sql new file mode 100644 index 00000000..a70ea190 --- /dev/null +++ b/springboot/sql/wumei.sql @@ -0,0 +1,2389 @@ +/* + Navicat Premium Data Transfer + + Source Server : wumei.live + Source Server Type : MySQL + Source Server Version : 50734 + Source Host : wumei.live:3306 + Source Schema : wumeismart1 + + Target Server Type : MySQL + Target Server Version : 50734 + File Encoding : 65001 + + Date: 16/03/2022 02:27:07 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- 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 20 10 ? * 1,2,3,4,5,6,7', '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.ruoyi.iot.util.quartz.QuartzDisallowConcurrentExecution', '0', '1', '0', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C7708000000100000000174000F5441534B5F50524F504552544945537372001E636F6D2E72756F79692E696F742E646F6D61696E2E4465766963654A6F6200000000000000010200114C0007616374696F6E737400124C6A6176612F6C616E672F537472696E673B4C0007616C65727449647400104C6A6176612F6C616E672F4C6F6E673B4C000A636F6E63757272656E7471007E00094C000E63726F6E45787072657373696F6E71007E00094C0008646576696365496471007E000A4C000A6465766963654E616D6571007E00094C00096973416476616E63657400134C6A6176612F6C616E672F496E74656765723B4C00086A6F6247726F757071007E00094C00056A6F62496471007E000A4C00076A6F624E616D6571007E00094C00076A6F625479706571007E000B4C000D6D697366697265506F6C69637971007E00094C000970726F64756374496471007E000A4C000B70726F647563744E616D6571007E00094C00077363656E65496471007E000A4C000C73657269616C4E756D62657271007E00094C000673746174757371007E000978720027636F6D2E72756F79692E636F6D6D6F6E2E636F72652E646F6D61696E2E42617365456E7469747900000000000000010200074C0008637265617465427971007E00094C000A63726561746554696D657400104C6A6176612F7574696C2F446174653B4C0006706172616D7371007E00034C000672656D61726B71007E00094C000B73656172636856616C756571007E00094C0008757064617465427971007E00094C000A75706461746554696D6571007E000D787074000561646D696E7372000E6A6176612E7574696C2E44617465686A81014B597419030000787077080000017F8E04515078737200176A6176612E7574696C2E4C696E6B6564486173684D617034C04E5C106CC0FB0200015A000B6163636573734F726465727871007E00053F400000000000007708000000100000000078007400007074000561646D696E707401C05B7B226964223A227265706F72745F6D6F6E69746F72222C226E616D65223A22E4B88AE68AA5E79B91E6B58BE695B0E68DAE222C2274797065223A322C2276616C7565223A2231222C22736F75726365223A322C226465766963654964223A332C22616C6572744E616D65223A22222C22616C6572744C6576656C223A312C226465766963654E616D65223A2245535038323636E5BC80E58F91E69DBF227D2C7B226964223A22737769746368222C226E616D65223A22E8AEBEE5A487E5BC80E585B3222C2274797065223A322C2276616C7565223A2231222C22736F75726365223A322C226465766963654964223A332C22616C6572744E616D65223A22222C22616C6572744C6576656C223A312C226465766963654E616D65223A2245535038323636E5BC80E58F91E69DBF227D2C7B226964223A2267656172222C226E616D65223A22E8BF90E8A18CE6A1A3E4BD8D222C2274797065223A322C2276616C7565223A2233222C22736F75726365223A322C226465766963654964223A332C22616C6572744E616D65223A22222C22616C6572744C6576656C223A312C226465766963654E616D65223A2245535038323636E5BC80E58F91E69DBF227D5D707400013174001930203230203130203F202A20312C322C332C342C352C362C377372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B0200007870000000000000000374001045535038323636E5BC80E58F91E69DBF737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C75657871007E001A0000000074000744454641554C547371007E00190000000000000001740018E5AE9AE697B6E4B88AE68AA5E79B91E6B58BE695B0E68DAE7371007E001D00000001740001327371007E0019000000000000000274001345535038323636E4BAA7E59381E7A4BAE4BE8B707400104436333239564C35343431394C315930740001307800); + +-- ---------------------------- +-- 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', 'DESKTOP-KKH3KAT1647364950518', 1647368822054, 15000); +INSERT INTO `QRTZ_SCHEDULER_STATE` VALUES ('RuoyiScheduler', 'hangzhou1647348778974', 1647368828933, 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, 1647397200000, -1, 5, 'WAITING', 'CRON', 1647368396000, 0, NULL, 1, ''); + +-- ---------------------------- +-- 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 = 28 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of gen_table +-- ---------------------------- +INSERT INTO `gen_table` VALUES (1, 'iot_category', '产品分类', '', '', 'Category', 'crud', 'com.ruoyi.iot', 'iot', 'category', '产品分类', 'kerwincui', '0', '/', '{\"treeCode\":\"category_id\",\"treeName\":\"category_name\",\"treeParentCode\":\"parent_id\",\"parentMenuId\":\"2000\"}', 'admin', '2021-12-15 22:56:19', '', '2021-12-20 14:35:01', NULL); +INSERT INTO `gen_table` VALUES (2, 'iot_device', '设备', NULL, NULL, 'Device', 'crud', 'com.ruoyi.iot', 'iot', 'device', '设备', 'kerwincui', '0', '/', '{\"parentMenuId\":\"2000\"}', 'admin', '2021-12-15 22:56:21', '', '2021-12-16 00:23:18', NULL); +INSERT INTO `gen_table` VALUES (5, 'iot_device_user', '设备用户', NULL, NULL, 'DeviceUser', 'crud', 'com.ruoyi.iot', 'iot', 'deviceuser', '设备用户', 'kerwincui', '0', '/', '{\"parentMenuId\":\"2000\"}', 'admin', '2021-12-15 22:56:29', '', '2021-12-16 01:16:47', NULL); +INSERT INTO `gen_table` VALUES (7, 'iot_firmware', '产品固件', NULL, NULL, 'Firmware', 'crud', 'com.ruoyi.iot', 'iot', 'firmware', '产品固件', 'kerwincui', '0', '/', '{\"parentMenuId\":2000}', 'admin', '2021-12-15 22:56:31', '', '2021-12-16 00:18:09', NULL); +INSERT INTO `gen_table` VALUES (9, 'iot_group', '设备分组', NULL, NULL, 'Group', 'crud', 'com.ruoyi.iot', 'iot', 'group', '设备分组', 'kerwincui', '0', '/', '{\"parentMenuId\":2000}', 'admin', '2021-12-15 22:56:34', '', '2021-12-16 00:31:15', NULL); +INSERT INTO `gen_table` VALUES (11, 'iot_product', '产品', NULL, NULL, 'Product', 'crud', 'com.ruoyi.iot', 'iot', 'product', '产品', 'kerwincui', '0', '/', '{\"parentMenuId\":2000}', 'admin', '2021-12-15 22:56:37', '', '2021-12-16 00:22:11', NULL); +INSERT INTO `gen_table` VALUES (13, 'iot_things_model', '物模型', NULL, NULL, 'ThingsModel', 'crud', 'com.ruoyi.iot', 'iot', 'model', '物模型', 'kerwincui', '0', '/', '{\"parentMenuId\":2000}', 'admin', '2021-12-15 22:56:40', '', '2021-12-16 00:29:39', NULL); +INSERT INTO `gen_table` VALUES (15, 'iot_things_model_template', '通用物模型', NULL, NULL, 'ThingsModelTemplate', 'crud', 'com.ruoyi.iot', 'iot', 'template', '通用物模型', 'kerwincui', '0', '/', '{\"parentMenuId\":2000}', 'admin', '2021-12-15 22:56:44', '', '2021-12-16 00:26:56', NULL); +INSERT INTO `gen_table` VALUES (20, 'iot_alert', '设备告警', NULL, NULL, 'Alert', 'crud', 'com.ruoyi.iot', 'iot', 'alert', '设备告警', 'kerwincui', '0', '/', '{}', 'admin', '2022-01-13 14:53:24', '', '2022-01-13 15:16:02', NULL); +INSERT INTO `gen_table` VALUES (21, 'iot_alert_log', '设备告警日志', NULL, NULL, 'AlertLog', 'crud', 'com.ruoyi.iot', 'iot', 'alertLog', '设备告警', 'kerwincui', '0', '/', '{\"parentMenuId\":\"2000\"}', 'admin', '2022-01-13 14:53:27', '', '2022-01-13 15:16:32', NULL); +INSERT INTO `gen_table` VALUES (22, 'iot_device_log', '设备日志', NULL, NULL, 'DeviceLog', 'crud', 'com.ruoyi.iot', 'iot', 'deviceLog', '设备日志', 'kerwincui', '0', '/', '{}', 'admin', '2022-01-13 14:53:30', '', '2022-01-13 15:17:15', NULL); +INSERT INTO `gen_table` VALUES (23, 'iot_scene', '场景联动', NULL, NULL, 'Scene', 'crud', 'com.ruoyi.iot', 'iot', 'scene', '场景联动', 'kerwincui', '0', '/', '{\"parentMenuId\":\"2000\"}', 'admin', '2022-01-13 14:53:33', '', '2022-01-13 15:17:37', NULL); +INSERT INTO `gen_table` VALUES (25, 'iot_device_job', '设备定时', NULL, NULL, 'DeviceJob', 'crud', 'com.ruoyi.iot', 'iot', 'job', '设备定时', 'kerwincui', '0', '/', NULL, 'admin', '2022-01-17 16:18:25', '', NULL, NULL); +INSERT INTO `gen_table` VALUES (26, 'iot_device_group', '设备分组', NULL, NULL, 'DeviceGroup', 'crud', 'com.ruoyi.iot', 'iot', 'group', '设备分组', 'kerwincui', '0', '/', NULL, 'admin', '2022-01-24 00:29:24', '', NULL, NULL); +INSERT INTO `gen_table` VALUES (27, 'oauth_client_details', '云云对接', NULL, NULL, 'OauthClientDetails', 'crud', 'com.ruoyi.iot', 'iot', 'clientDetails', '云云对接', 'kerwincui', '0', '/', '{\"parentMenuId\":\"2000\"}', 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11', NULL); + +-- ---------------------------- +-- 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 = 425 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '代码生成业务表字段' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of gen_table_column +-- ---------------------------- +INSERT INTO `gen_table_column` VALUES (1, '1', 'category_id', '产品分类ID', 'bigint(20)', 'Long', 'categoryId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2021-12-15 22:56:19', '', '2021-12-20 14:35:01'); +INSERT INTO `gen_table_column` VALUES (2, '1', 'category_name', '产品分类名称', 'varchar(64)', 'String', 'categoryName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 2, 'admin', '2021-12-15 22:56:19', '', '2021-12-20 14:35:02'); +INSERT INTO `gen_table_column` VALUES (3, '1', 'tenant_id', '租户ID', 'bigint(20)', 'Long', 'tenantId', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 3, 'admin', '2021-12-15 22:56:19', '', '2021-12-20 14:35:02'); +INSERT INTO `gen_table_column` VALUES (4, '1', 'tenant_name', '租户名称', 'varchar(30)', 'String', 'tenantName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 'admin', '2021-12-15 22:56:19', '', '2021-12-20 14:35:02'); +INSERT INTO `gen_table_column` VALUES (5, '1', 'is_sys', '是否系统通用(0-否,1-是)', 'tinyint(1)', 'Integer', 'isSys', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', 'iot_yes_no', 5, 'admin', '2021-12-15 22:56:19', '', '2021-12-20 14:35:02'); +INSERT INTO `gen_table_column` VALUES (6, '1', 'parent_id', '父级ID', 'bigint(20)', 'Long', 'parentId', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 6, 'admin', '2021-12-15 22:56:20', '', '2021-12-20 14:35:02'); +INSERT INTO `gen_table_column` VALUES (7, '1', 'order_num', '显示顺序', 'int(4)', 'Integer', 'orderNum', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 7, 'admin', '2021-12-15 22:56:20', '', '2021-12-20 14:35:02'); +INSERT INTO `gen_table_column` VALUES (8, '1', 'del_flag', '删除标志(0代表存在 2代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 8, 'admin', '2021-12-15 22:56:20', '', '2021-12-20 14:35:02'); +INSERT INTO `gen_table_column` VALUES (9, '1', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 9, 'admin', '2021-12-15 22:56:20', '', '2021-12-20 14:35:03'); +INSERT INTO `gen_table_column` VALUES (10, '1', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, '1', NULL, 'EQ', 'datetime', '', 10, 'admin', '2021-12-15 22:56:20', '', '2021-12-20 14:35:03'); +INSERT INTO `gen_table_column` VALUES (11, '1', 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 11, 'admin', '2021-12-15 22:56:20', '', '2021-12-20 14:35:03'); +INSERT INTO `gen_table_column` VALUES (12, '1', 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'datetime', '', 12, 'admin', '2021-12-15 22:56:21', '', '2021-12-20 14:35:03'); +INSERT INTO `gen_table_column` VALUES (13, '1', 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 13, 'admin', '2021-12-15 22:56:21', '', '2021-12-20 14:35:03'); +INSERT INTO `gen_table_column` VALUES (14, '2', 'device_id', '产品分类ID', 'bigint(20)', 'Long', 'deviceId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2021-12-15 22:56:21', '', '2021-12-16 00:23:18'); +INSERT INTO `gen_table_column` VALUES (15, '2', 'device_name', '产品分类名称', 'varchar(64)', 'String', 'deviceName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 2, 'admin', '2021-12-15 22:56:21', '', '2021-12-16 00:23:19'); +INSERT INTO `gen_table_column` VALUES (16, '2', 'product_id', '产品ID', 'bigint(20)', 'Long', 'productId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 'admin', '2021-12-15 22:56:21', '', '2021-12-16 00:23:19'); +INSERT INTO `gen_table_column` VALUES (17, '2', 'product_name', '产品名称', 'varchar(64)', 'String', 'productName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 'admin', '2021-12-15 22:56:22', '', '2021-12-16 00:23:19'); +INSERT INTO `gen_table_column` VALUES (18, '2', 'user_id', '用户ID', 'bigint(20)', 'Long', 'userId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 'admin', '2021-12-15 22:56:22', '', '2021-12-16 00:23:19'); +INSERT INTO `gen_table_column` VALUES (19, '2', 'user_name', '用户昵称', 'varchar(30)', 'String', 'userName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 6, 'admin', '2021-12-15 22:56:22', '', '2021-12-16 00:23:19'); +INSERT INTO `gen_table_column` VALUES (20, '2', 'tenant_id', '租户ID', 'bigint(20)', 'Long', 'tenantId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 'admin', '2021-12-15 22:56:22', '', '2021-12-16 00:23:19'); +INSERT INTO `gen_table_column` VALUES (21, '2', 'tenant_name', '租户名称', 'varchar(30)', 'String', 'tenantName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 8, 'admin', '2021-12-15 22:56:23', '', '2021-12-16 00:23:19'); +INSERT INTO `gen_table_column` VALUES (22, '2', 'serial_number', '设备编号', 'varchar(64)', 'String', 'serialNumber', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 9, 'admin', '2021-12-15 22:56:23', '', '2021-12-16 00:23:20'); +INSERT INTO `gen_table_column` VALUES (23, '2', 'firmware_version', '固件版本', 'float(11,2)', 'BigDecimal', 'firmwareVersion', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 10, 'admin', '2021-12-15 22:56:23', '', '2021-12-16 00:23:20'); +INSERT INTO `gen_table_column` VALUES (24, '2', 'status', '设备状态(1-未激活,2-禁用,3-在线,4-离线)', 'tinyint(1)', 'Integer', 'status', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', 'iot_yes_no', 11, 'admin', '2021-12-15 22:56:23', '', '2021-12-16 00:23:20'); +INSERT INTO `gen_table_column` VALUES (26, '2', 'rssi', 'wifi信号强度(\r\n信号极好4格[-55— 0],\r\n信号好3格[-70— -55],\r\n信号一般2格[-85— -70],\r\n信号差1格[-100— -85])', 'varchar(255)', 'Integer', 'rssi', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 13, 'admin', '2021-12-15 22:56:23', '', '2021-12-16 00:23:20'); +INSERT INTO `gen_table_column` VALUES (27, '2', 'network_address', '设备所在地址', 'varchar(255)', 'String', 'networkAddress', '0', '0', NULL, '1', '1', '1', '1', 'LIKE', 'input', '', 14, 'admin', '2021-12-15 22:56:23', '', '2021-12-16 00:23:20'); +INSERT INTO `gen_table_column` VALUES (28, '2', 'network_ip', '设备入网IP', 'varchar(32)', 'String', 'networkIp', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 15, 'admin', '2021-12-15 22:56:24', '', '2021-12-16 00:23:21'); +INSERT INTO `gen_table_column` VALUES (29, '2', 'longitude', '设备经度', 'double(11,0)', 'Long', 'longitude', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 16, 'admin', '2021-12-15 22:56:24', '', '2021-12-16 00:23:21'); +INSERT INTO `gen_table_column` VALUES (30, '2', 'latitude', '设备纬度', 'double(11,0)', 'Long', 'latitude', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 17, 'admin', '2021-12-15 22:56:24', '', '2021-12-16 00:23:21'); +INSERT INTO `gen_table_column` VALUES (31, '2', 'active_time', '激活时间', 'datetime', 'Date', 'activeTime', '0', '0', NULL, '1', '1', '1', '1', 'BETWEEN', 'datetime', '', 18, 'admin', '2021-12-15 22:56:24', '', '2021-12-16 00:23:21'); +INSERT INTO `gen_table_column` VALUES (32, '2', 'del_flag', '删除标志(0代表存在 2代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 19, 'admin', '2021-12-15 22:56:24', '', '2021-12-16 00:23:21'); +INSERT INTO `gen_table_column` VALUES (33, '2', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 20, 'admin', '2021-12-15 22:56:24', '', '2021-12-16 00:23:21'); +INSERT INTO `gen_table_column` VALUES (34, '2', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, '1', NULL, 'EQ', 'datetime', '', 21, 'admin', '2021-12-15 22:56:25', '', '2021-12-16 00:23:21'); +INSERT INTO `gen_table_column` VALUES (35, '2', 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 22, 'admin', '2021-12-15 22:56:25', '', '2021-12-16 00:23:22'); +INSERT INTO `gen_table_column` VALUES (36, '2', 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'datetime', '', 23, 'admin', '2021-12-15 22:56:25', '', '2021-12-16 00:23:22'); +INSERT INTO `gen_table_column` VALUES (37, '2', 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 24, 'admin', '2021-12-15 22:56:25', '', '2021-12-16 00:23:22'); +INSERT INTO `gen_table_column` VALUES (70, '5', 'device_id', '固件ID', 'bigint(20)', 'Long', 'deviceId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2021-12-15 22:56:29', '', '2021-12-16 01:16:47'); +INSERT INTO `gen_table_column` VALUES (71, '5', 'user_id', '用户ID', 'bigint(20)', 'Long', 'userId', '1', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 2, 'admin', '2021-12-15 22:56:29', '', '2021-12-16 01:16:47'); +INSERT INTO `gen_table_column` VALUES (73, '5', 'device_name', '设备名称', 'varchar(64)', 'String', 'deviceName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 3, 'admin', '2021-12-15 22:56:29', '', '2021-12-16 01:16:47'); +INSERT INTO `gen_table_column` VALUES (75, '5', 'user_name', '用户昵称', 'varchar(30)', 'String', 'userName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 'admin', '2021-12-15 22:56:29', '', '2021-12-16 01:16:47'); +INSERT INTO `gen_table_column` VALUES (77, '5', 'tenant_id', '租户ID', 'bigint(20)', 'Long', 'tenantId', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 5, 'admin', '2021-12-15 22:56:29', '', '2021-12-16 01:16:48'); +INSERT INTO `gen_table_column` VALUES (79, '5', 'tenant_name', '租户名称', 'varchar(30)', 'String', 'tenantName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 6, 'admin', '2021-12-15 22:56:30', '', '2021-12-16 01:16:48'); +INSERT INTO `gen_table_column` VALUES (81, '5', 'is_owner', '是否为设备所有者', 'tinyint(11)', 'Long', 'isOwner', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', 'iot_yes_no', 7, 'admin', '2021-12-15 22:56:30', '', '2021-12-16 01:16:48'); +INSERT INTO `gen_table_column` VALUES (85, '5', 'del_flag', '删除标志(0代表存在 2代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 9, 'admin', '2021-12-15 22:56:30', '', '2021-12-16 01:16:48'); +INSERT INTO `gen_table_column` VALUES (87, '5', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 10, 'admin', '2021-12-15 22:56:30', '', '2021-12-16 01:16:48'); +INSERT INTO `gen_table_column` VALUES (89, '5', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, '1', NULL, 'EQ', 'datetime', '', 11, 'admin', '2021-12-15 22:56:30', '', '2021-12-16 01:16:49'); +INSERT INTO `gen_table_column` VALUES (91, '5', 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 12, 'admin', '2021-12-15 22:56:31', '', '2021-12-16 01:16:49'); +INSERT INTO `gen_table_column` VALUES (93, '5', 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'datetime', '', 13, 'admin', '2021-12-15 22:56:31', '', '2021-12-16 01:16:49'); +INSERT INTO `gen_table_column` VALUES (95, '5', 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 14, 'admin', '2021-12-15 22:56:31', '', '2021-12-16 01:16:49'); +INSERT INTO `gen_table_column` VALUES (99, '7', 'firmware_id', '固件ID', 'bigint(20)', 'Long', 'firmwareId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2021-12-15 22:56:31', '', '2021-12-16 00:18:09'); +INSERT INTO `gen_table_column` VALUES (101, '7', 'firmware_name', '固件名称', 'varchar(64)', 'String', 'firmwareName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 2, 'admin', '2021-12-15 22:56:32', '', '2021-12-16 00:18:09'); +INSERT INTO `gen_table_column` VALUES (103, '7', 'product_id', '产品ID', 'bigint(20)', 'Long', 'productId', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 3, 'admin', '2021-12-15 22:56:32', '', '2021-12-16 00:18:09'); +INSERT INTO `gen_table_column` VALUES (105, '7', 'product_name', '产品名称', 'varchar(64)', 'String', 'productName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 'admin', '2021-12-15 22:56:32', '', '2021-12-16 00:18:09'); +INSERT INTO `gen_table_column` VALUES (107, '7', 'tenant_id', '租户ID', 'bigint(20)', 'Long', 'tenantId', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 5, 'admin', '2021-12-15 22:56:32', '', '2021-12-16 00:18:09'); +INSERT INTO `gen_table_column` VALUES (109, '7', 'tenant_name', '租户名称', 'varchar(30)', 'String', 'tenantName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 6, 'admin', '2021-12-15 22:56:32', '', '2021-12-16 00:18:09'); +INSERT INTO `gen_table_column` VALUES (111, '7', 'is_sys', '是否系统通用(0-否,1-是)', 'tinyint(1)', 'Integer', 'isSys', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', 'iot_yes_no', 7, 'admin', '2021-12-15 22:56:33', '', '2021-12-16 00:18:10'); +INSERT INTO `gen_table_column` VALUES (113, '7', 'version', '固件版本', 'float(11,2)', 'BigDecimal', 'version', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 8, 'admin', '2021-12-15 22:56:33', '', '2021-12-16 00:18:10'); +INSERT INTO `gen_table_column` VALUES (115, '7', 'file_path', '文件路径', 'varchar(255)', 'String', 'filePath', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 9, 'admin', '2021-12-15 22:56:33', '', '2021-12-16 00:18:10'); +INSERT INTO `gen_table_column` VALUES (117, '7', 'del_flag', '删除标志(0代表存在 2代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 10, 'admin', '2021-12-15 22:56:33', '', '2021-12-16 00:18:10'); +INSERT INTO `gen_table_column` VALUES (118, '7', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 11, 'admin', '2021-12-15 22:56:34', '', '2021-12-16 00:18:10'); +INSERT INTO `gen_table_column` VALUES (119, '7', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, '1', NULL, 'EQ', 'datetime', '', 12, 'admin', '2021-12-15 22:56:34', '', '2021-12-16 00:18:10'); +INSERT INTO `gen_table_column` VALUES (121, '7', 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 13, 'admin', '2021-12-15 22:56:34', '', '2021-12-16 00:18:10'); +INSERT INTO `gen_table_column` VALUES (123, '7', 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'datetime', '', 14, 'admin', '2021-12-15 22:56:34', '', '2021-12-16 00:18:10'); +INSERT INTO `gen_table_column` VALUES (125, '7', 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 15, 'admin', '2021-12-15 22:56:34', '', '2021-12-16 00:18:11'); +INSERT INTO `gen_table_column` VALUES (129, '9', 'group_id', '分组ID', 'bigint(20)', 'Long', 'groupId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2021-12-15 22:56:35', '', '2021-12-16 00:31:15'); +INSERT INTO `gen_table_column` VALUES (131, '9', 'group_name', '分组名称', 'varchar(64)', 'String', 'groupName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 2, 'admin', '2021-12-15 22:56:35', '', '2021-12-16 00:31:15'); +INSERT INTO `gen_table_column` VALUES (133, '9', 'group_order', '分组排序', 'tinyint(11)', 'Long', 'groupOrder', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 3, 'admin', '2021-12-15 22:56:35', '', '2021-12-16 00:31:16'); +INSERT INTO `gen_table_column` VALUES (135, '9', 'user_id', '用户ID', 'bigint(20)', 'Long', 'userId', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 4, 'admin', '2021-12-15 22:56:35', '', '2021-12-16 00:31:16'); +INSERT INTO `gen_table_column` VALUES (137, '9', 'user_name', '用户昵称', 'varchar(30)', 'String', 'userName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 5, 'admin', '2021-12-15 22:56:35', '', '2021-12-16 00:31:16'); +INSERT INTO `gen_table_column` VALUES (143, '9', 'del_flag', '删除标志(0代表存在 2代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 8, 'admin', '2021-12-15 22:56:36', '', '2021-12-16 00:31:17'); +INSERT INTO `gen_table_column` VALUES (145, '9', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 9, 'admin', '2021-12-15 22:56:36', '', '2021-12-16 00:31:17'); +INSERT INTO `gen_table_column` VALUES (147, '9', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, '1', NULL, 'EQ', 'datetime', '', 10, 'admin', '2021-12-15 22:56:36', '', '2021-12-16 00:31:17'); +INSERT INTO `gen_table_column` VALUES (149, '9', 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 11, 'admin', '2021-12-15 22:56:36', '', '2021-12-16 00:31:17'); +INSERT INTO `gen_table_column` VALUES (152, '9', 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'datetime', '', 12, 'admin', '2021-12-15 22:56:37', '', '2021-12-16 00:31:18'); +INSERT INTO `gen_table_column` VALUES (154, '9', 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 13, 'admin', '2021-12-15 22:56:37', '', '2021-12-16 00:31:18'); +INSERT INTO `gen_table_column` VALUES (155, '11', 'product_id', '产品ID', 'bigint(20)', 'Long', 'productId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2021-12-15 22:56:37', '', '2021-12-16 00:22:12'); +INSERT INTO `gen_table_column` VALUES (158, '11', 'product_name', '产品名称', 'varchar(64)', 'String', 'productName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 2, 'admin', '2021-12-15 22:56:37', '', '2021-12-16 00:22:12'); +INSERT INTO `gen_table_column` VALUES (160, '11', 'category_id', '产品分类ID', 'bigint(20)', 'Long', 'categoryId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 'admin', '2021-12-15 22:56:38', '', '2021-12-16 00:22:12'); +INSERT INTO `gen_table_column` VALUES (162, '11', 'category_name', '产品分类名称', 'varchar(64)', 'String', 'categoryName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 'admin', '2021-12-15 22:56:38', '', '2021-12-16 00:22:12'); +INSERT INTO `gen_table_column` VALUES (164, '11', 'tenant_id', '租户ID', 'bigint(20)', 'Long', 'tenantId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 'admin', '2021-12-15 22:56:38', '', '2021-12-16 00:22:12'); +INSERT INTO `gen_table_column` VALUES (166, '11', 'tenant_name', '租户名称', 'varchar(30)', 'String', 'tenantName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 6, 'admin', '2021-12-15 22:56:38', '', '2021-12-16 00:22:12'); +INSERT INTO `gen_table_column` VALUES (168, '11', 'is_sys', '是否系统通用(0-否,1-是)', 'tinyint(1)', 'Integer', 'isSys', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 'admin', '2021-12-15 22:56:38', '', '2021-12-16 00:22:12'); +INSERT INTO `gen_table_column` VALUES (169, '11', 'status', '状态(1-未发布,2-已发布,不能修改)', 'tinyint(1)', 'Integer', 'status', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'radio', 'iot_yes_no', 8, 'admin', '2021-12-15 22:56:38', '', '2021-12-16 00:22:13'); +INSERT INTO `gen_table_column` VALUES (173, '11', 'device_type', '设备类型(1-直连设备、2-网关子设备、3-网关设备)', 'tinyint(1)', 'Integer', 'deviceType', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'select', '', 10, 'admin', '2021-12-15 22:56:39', '', '2021-12-16 00:22:13'); +INSERT INTO `gen_table_column` VALUES (176, '11', 'network_method', '联网方式(1-wifi、2-蓝牙、3-wifi+蓝牙)', 'tinyint(1)', 'Integer', 'networkMethod', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', 'iot_yes_no', 11, 'admin', '2021-12-15 22:56:39', '', '2021-12-16 00:22:13'); +INSERT INTO `gen_table_column` VALUES (177, '11', 'vertificate_method', '认证方式(1-账号密码、2-证书、3-Http)', 'tinyint(1)', 'Integer', 'vertificateMethod', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 12, 'admin', '2021-12-15 22:56:39', '', '2021-12-16 00:22:13'); +INSERT INTO `gen_table_column` VALUES (179, '11', 'del_flag', '删除标志(0代表存在 2代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 13, 'admin', '2021-12-15 22:56:39', '', '2021-12-16 00:22:13'); +INSERT INTO `gen_table_column` VALUES (181, '11', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 14, 'admin', '2021-12-15 22:56:39', '', '2021-12-16 00:22:13'); +INSERT INTO `gen_table_column` VALUES (183, '11', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, '1', NULL, 'EQ', 'datetime', '', 15, 'admin', '2021-12-15 22:56:40', '', '2021-12-16 00:22:14'); +INSERT INTO `gen_table_column` VALUES (184, '11', 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 16, 'admin', '2021-12-15 22:56:40', '', '2021-12-16 00:22:14'); +INSERT INTO `gen_table_column` VALUES (186, '11', 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'datetime', '', 17, 'admin', '2021-12-15 22:56:40', '', '2021-12-16 00:22:14'); +INSERT INTO `gen_table_column` VALUES (188, '11', 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 18, 'admin', '2021-12-15 22:56:40', '', '2021-12-16 00:22:14'); +INSERT INTO `gen_table_column` VALUES (191, '13', 'model_id', '物模型ID', 'bigint(20)', 'Long', 'modelId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2021-12-15 22:56:40', '', '2021-12-16 00:29:39'); +INSERT INTO `gen_table_column` VALUES (193, '13', 'model_name', '物模型名称', 'varchar(64)', 'String', 'modelName', '0', '0', '1', '1', '1', '1', NULL, 'LIKE', 'input', '', 2, 'admin', '2021-12-15 22:56:41', '', '2021-12-16 00:29:39'); +INSERT INTO `gen_table_column` VALUES (195, '13', 'product_id', '产品ID', 'bigint(20)', 'Long', 'productId', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 3, 'admin', '2021-12-15 22:56:41', '', '2021-12-16 00:29:39'); +INSERT INTO `gen_table_column` VALUES (197, '13', 'product_name', '产品名称', 'varchar(64)', 'String', 'productName', '0', '0', '1', '1', '1', '1', NULL, 'LIKE', 'input', '', 4, 'admin', '2021-12-15 22:56:41', '', '2021-12-16 00:29:39'); +INSERT INTO `gen_table_column` VALUES (199, '13', 'tenant_id', '租户ID', 'bigint(20)', 'Long', 'tenantId', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 5, 'admin', '2021-12-15 22:56:41', '', '2021-12-16 00:29:40'); +INSERT INTO `gen_table_column` VALUES (201, '13', 'tenant_name', '租户名称', 'varchar(30)', 'String', 'tenantName', '0', '0', '1', '1', '1', '1', NULL, 'LIKE', 'input', '', 6, 'admin', '2021-12-15 22:56:41', '', '2021-12-16 00:29:40'); +INSERT INTO `gen_table_column` VALUES (203, '13', 'identifier', '标识符,产品下唯一', 'varchar(32)', 'String', 'identifier', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 7, 'admin', '2021-12-15 22:56:41', '', '2021-12-16 00:29:40'); +INSERT INTO `gen_table_column` VALUES (205, '13', 'type', '模型类别(1-属性,2-功能,3-事件)', 'tinyint(1)', 'Integer', 'type', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'select', 'iot_things_type', 8, 'admin', '2021-12-15 22:56:42', '', '2021-12-16 00:29:40'); +INSERT INTO `gen_table_column` VALUES (207, '13', 'datatype', '数据类型(integer、decimal、string、bool、array、enum)', 'varchar(64)', 'String', 'datatype', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'select', 'iot_data_type', 9, 'admin', '2021-12-15 22:56:42', '', '2021-12-16 00:29:40'); +INSERT INTO `gen_table_column` VALUES (209, '13', 'specs', '数据定义', 'json', 'String', 'specs', '0', '0', '1', '1', '1', '1', NULL, 'EQ', NULL, '', 10, 'admin', '2021-12-15 22:56:42', '', '2021-12-16 00:29:40'); +INSERT INTO `gen_table_column` VALUES (211, '13', 'is_Top', '是否首页显示(0-否,1-是)', 'tinyint(1)', 'Integer', 'isTop', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', 'iot_yes_no', 11, 'admin', '2021-12-15 22:56:42', '', '2021-12-16 00:29:41'); +INSERT INTO `gen_table_column` VALUES (213, '13', 'is_Monitor', '是否实时监测(0-否,1-是)', 'tinyint(1)', 'Integer', 'isMonitor', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', 'iot_yes_no', 12, 'admin', '2021-12-15 22:56:42', '', '2021-12-16 00:29:41'); +INSERT INTO `gen_table_column` VALUES (214, '13', 'del_flag', '删除标志(0代表存在 2代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 13, 'admin', '2021-12-15 22:56:42', '', '2021-12-16 00:29:41'); +INSERT INTO `gen_table_column` VALUES (215, '13', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 14, 'admin', '2021-12-15 22:56:43', '', '2021-12-16 00:29:41'); +INSERT INTO `gen_table_column` VALUES (217, '13', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, '1', NULL, 'EQ', 'datetime', '', 15, 'admin', '2021-12-15 22:56:43', '', '2021-12-16 00:29:41'); +INSERT INTO `gen_table_column` VALUES (220, '13', 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 16, 'admin', '2021-12-15 22:56:43', '', '2021-12-16 00:29:41'); +INSERT INTO `gen_table_column` VALUES (222, '13', 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'datetime', '', 17, 'admin', '2021-12-15 22:56:43', '', '2021-12-16 00:29:42'); +INSERT INTO `gen_table_column` VALUES (224, '13', 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 18, 'admin', '2021-12-15 22:56:43', '', '2021-12-16 00:29:42'); +INSERT INTO `gen_table_column` VALUES (227, '15', 'template_id', '物模型ID', 'bigint(20)', 'Long', 'templateId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2021-12-15 22:56:44', '', '2021-12-16 00:26:56'); +INSERT INTO `gen_table_column` VALUES (229, '15', 'template_name', '物模型名称', 'varchar(64)', 'String', 'templateName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 2, 'admin', '2021-12-15 22:56:44', '', '2021-12-16 00:26:57'); +INSERT INTO `gen_table_column` VALUES (235, '15', 'tenant_id', '租户ID', 'bigint(20)', 'Long', 'tenantId', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 5, 'admin', '2021-12-15 22:56:44', '', '2021-12-16 00:26:57'); +INSERT INTO `gen_table_column` VALUES (237, '15', 'tenant_name', '租户名称', 'varchar(30)', 'String', 'tenantName', '0', '0', '1', '1', '1', '1', NULL, 'LIKE', 'input', '', 6, 'admin', '2021-12-15 22:56:45', '', '2021-12-16 00:26:57'); +INSERT INTO `gen_table_column` VALUES (239, '15', 'identifier', '标识符,产品下唯一', 'varchar(32)', 'String', 'identifier', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 7, 'admin', '2021-12-15 22:56:45', '', '2021-12-16 00:26:58'); +INSERT INTO `gen_table_column` VALUES (240, '15', 'type', '模型类别(1-属性,2-功能,3-事件)', 'tinyint(1)', 'Integer', 'type', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', 'iot_things_type', 8, 'admin', '2021-12-15 22:56:45', '', '2021-12-16 00:26:58'); +INSERT INTO `gen_table_column` VALUES (241, '15', 'datatype', '数据类型(integer、decimal、string、bool、array、enum)', 'varchar(64)', 'String', 'datatype', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'select', 'iot_data_type', 9, 'admin', '2021-12-15 22:56:45', '', '2021-12-16 00:26:58'); +INSERT INTO `gen_table_column` VALUES (243, '15', 'specs', '数据定义', 'json', 'String', 'specs', '0', '0', '1', '1', '1', '1', '1', 'EQ', NULL, '', 10, 'admin', '2021-12-15 22:56:45', '', '2021-12-16 00:26:58'); +INSERT INTO `gen_table_column` VALUES (245, '15', 'is_sys', '是否系统通用(0-否,1-是)', 'tinyint(1)', 'Integer', 'isSys', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 11, 'admin', '2021-12-15 22:56:45', '', '2021-12-16 00:26:59'); +INSERT INTO `gen_table_column` VALUES (247, '15', 'is_Top', '是否首页显示(0-否,1-是)', 'tinyint(1)', 'Integer', 'isTop', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 12, 'admin', '2021-12-15 22:56:46', '', '2021-12-16 00:26:59'); +INSERT INTO `gen_table_column` VALUES (249, '15', 'is_Monitor', '是否实时监测(0-否,1-是)', 'tinyint(1)', 'Integer', 'isMonitor', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'input', '', 13, 'admin', '2021-12-15 22:56:46', '', '2021-12-16 00:26:59'); +INSERT INTO `gen_table_column` VALUES (251, '15', 'del_flag', '删除标志(0代表存在 2代表删除)', 'char(1)', 'String', 'delFlag', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 14, 'admin', '2021-12-15 22:56:46', '', '2021-12-16 00:26:59'); +INSERT INTO `gen_table_column` VALUES (253, '15', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 15, 'admin', '2021-12-15 22:56:46', '', '2021-12-16 00:26:59'); +INSERT INTO `gen_table_column` VALUES (255, '15', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, '1', NULL, 'EQ', 'datetime', '', 16, 'admin', '2021-12-15 22:56:46', '', '2021-12-16 00:26:59'); +INSERT INTO `gen_table_column` VALUES (257, '15', 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 17, 'admin', '2021-12-15 22:56:46', '', '2021-12-16 00:26:59'); +INSERT INTO `gen_table_column` VALUES (259, '15', 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'datetime', '', 18, 'admin', '2021-12-15 22:56:47', '', '2021-12-16 00:27:00'); +INSERT INTO `gen_table_column` VALUES (261, '15', 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 19, 'admin', '2021-12-15 22:56:47', '', '2021-12-16 00:27:00'); +INSERT INTO `gen_table_column` VALUES (309, '20', 'alert_id', '告警ID', 'bigint(20)', 'Long', 'alertId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2022-01-13 14:53:24', '', '2022-01-13 15:16:02'); +INSERT INTO `gen_table_column` VALUES (310, '20', 'alert_name', '告警名称', 'varchar(64)', 'String', 'alertName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 2, 'admin', '2022-01-13 14:53:24', '', '2022-01-13 15:16:02'); +INSERT INTO `gen_table_column` VALUES (311, '20', 'alert_level', '告警级别(1=提醒通知,2=轻微问题,3=严重警告)', 'tinyint(11)', 'Long', 'alertLevel', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', 'iot_alert_level', 3, 'admin', '2022-01-13 14:53:24', '', '2022-01-13 15:16:02'); +INSERT INTO `gen_table_column` VALUES (312, '20', 'product_id', '产品ID', 'bigint(20)', 'Long', 'productId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 4, 'admin', '2022-01-13 14:53:25', '', '2022-01-13 15:16:03'); +INSERT INTO `gen_table_column` VALUES (313, '20', 'product_name', '产品名称', 'varchar(64)', 'String', 'productName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 5, 'admin', '2022-01-13 14:53:25', '', '2022-01-13 15:16:03'); +INSERT INTO `gen_table_column` VALUES (314, '20', 'triggers', '触发器', 'json', 'String', 'triggers', '0', '0', '1', '1', '1', '1', NULL, 'EQ', NULL, '', 6, 'admin', '2022-01-13 14:53:25', '', '2022-01-13 15:16:03'); +INSERT INTO `gen_table_column` VALUES (315, '20', 'actions', '执行动作', 'json', 'String', 'actions', '0', '0', '1', '1', '1', '1', NULL, 'EQ', NULL, '', 7, 'admin', '2022-01-13 14:53:25', '', '2022-01-13 15:16:03'); +INSERT INTO `gen_table_column` VALUES (316, '20', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 8, 'admin', '2022-01-13 14:53:26', '', '2022-01-13 15:16:03'); +INSERT INTO `gen_table_column` VALUES (317, '20', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, '1', NULL, 'EQ', 'datetime', '', 9, 'admin', '2022-01-13 14:53:26', '', '2022-01-13 15:16:04'); +INSERT INTO `gen_table_column` VALUES (318, '20', 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 10, 'admin', '2022-01-13 14:53:26', '', '2022-01-13 15:16:04'); +INSERT INTO `gen_table_column` VALUES (319, '20', 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'datetime', '', 11, 'admin', '2022-01-13 14:53:26', '', '2022-01-13 15:16:04'); +INSERT INTO `gen_table_column` VALUES (320, '20', 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 12, 'admin', '2022-01-13 14:53:26', '', '2022-01-13 15:16:04'); +INSERT INTO `gen_table_column` VALUES (321, '21', 'alert_log__id', '告警ID', 'bigint(20)', 'Long', 'alertLogId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2022-01-13 14:53:27', '', '2022-01-13 15:16:32'); +INSERT INTO `gen_table_column` VALUES (323, '21', 'alert_level', '告警级别(1=提醒通知,2=轻微问题,3=严重警告,4=场景联动)', 'tinyint(11)', 'Long', 'alertLevel', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', 'iot_alert_level', 3, 'admin', '2022-01-13 14:53:27', '', '2022-01-13 15:16:33'); +INSERT INTO `gen_table_column` VALUES (324, '21', 'status', '处理状态(0=不需要处理,1=未处理,2=已处理)', 'tinyint(11)', 'Long', 'status', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', 'iot_process_status', 4, 'admin', '2022-01-13 14:53:28', '', '2022-01-13 15:16:33'); +INSERT INTO `gen_table_column` VALUES (325, '21', 'product_id', '产品ID', 'bigint(20)', 'Long', 'productId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 'admin', '2022-01-13 14:53:28', '', '2022-01-13 15:16:33'); +INSERT INTO `gen_table_column` VALUES (326, '21', 'product_name', '产品名称', 'varchar(64)', 'String', 'productName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 6, 'admin', '2022-01-13 14:53:28', '', '2022-01-13 15:16:33'); +INSERT INTO `gen_table_column` VALUES (327, '21', 'device_id', '设备ID', 'bigint(20)', 'Long', 'deviceId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 7, 'admin', '2022-01-13 14:53:28', '', '2022-01-13 15:16:33'); +INSERT INTO `gen_table_column` VALUES (328, '21', 'device_name', '设备名称', 'varchar(64)', 'String', 'deviceName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 8, 'admin', '2022-01-13 14:53:28', '', '2022-01-13 15:16:34'); +INSERT INTO `gen_table_column` VALUES (329, '21', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 9, 'admin', '2022-01-13 14:53:29', '', '2022-01-13 15:16:34'); +INSERT INTO `gen_table_column` VALUES (330, '21', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, '1', NULL, 'EQ', 'datetime', '', 10, 'admin', '2022-01-13 14:53:29', '', '2022-01-13 15:16:34'); +INSERT INTO `gen_table_column` VALUES (331, '21', 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 11, 'admin', '2022-01-13 14:53:29', '', '2022-01-13 15:16:34'); +INSERT INTO `gen_table_column` VALUES (332, '21', 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'datetime', '', 12, 'admin', '2022-01-13 14:53:29', '', '2022-01-13 15:16:34'); +INSERT INTO `gen_table_column` VALUES (333, '21', 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 13, 'admin', '2022-01-13 14:53:29', '', '2022-01-13 15:16:35'); +INSERT INTO `gen_table_column` VALUES (334, '22', 'log_id', '设备日志ID', 'bigint(20)', 'Long', 'logId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2022-01-13 14:53:30', '', '2022-01-13 15:17:15'); +INSERT INTO `gen_table_column` VALUES (335, '22', 'log_name', '日志名称', 'varchar(64)', 'String', 'logName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 2, 'admin', '2022-01-13 14:53:30', '', '2022-01-13 15:17:15'); +INSERT INTO `gen_table_column` VALUES (336, '22', 'log_type', '类型(1=属性上报,2=事件上报,3=调用功能,4=设备升级,5=设备上线,6=设备离线)', 'tinyint(1)', 'Integer', 'logType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', 'iot_device_log_type', 3, 'admin', '2022-01-13 14:53:30', '', '2022-01-13 15:17:15'); +INSERT INTO `gen_table_column` VALUES (337, '22', 'log_value', '日志值', 'varchar(64)', 'String', 'logValue', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 4, 'admin', '2022-01-13 14:53:31', '', '2022-01-13 15:17:16'); +INSERT INTO `gen_table_column` VALUES (338, '22', 'device_id', '设备ID', 'bigint(20)', 'Long', 'deviceId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 5, 'admin', '2022-01-13 14:53:31', '', '2022-01-13 15:17:16'); +INSERT INTO `gen_table_column` VALUES (339, '22', 'device_name', '设备名称', 'varchar(64)', 'String', 'deviceName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 6, 'admin', '2022-01-13 14:53:31', '', '2022-01-13 15:17:16'); +INSERT INTO `gen_table_column` VALUES (340, '22', 'identity', '标识符', 'varchar(64)', 'String', 'identity', '0', '0', NULL, '1', '1', '1', '1', 'LIKE', 'input', '', 7, 'admin', '2022-01-13 14:53:31', '', '2022-01-13 15:17:16'); +INSERT INTO `gen_table_column` VALUES (341, '22', 'datatype', '数据类型', 'varchar(32)', 'String', 'datatype', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'select', '', 8, 'admin', '2022-01-13 14:53:32', '', '2022-01-13 15:17:16'); +INSERT INTO `gen_table_column` VALUES (342, '22', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 9, 'admin', '2022-01-13 14:53:32', '', '2022-01-13 15:17:17'); +INSERT INTO `gen_table_column` VALUES (343, '22', 'is_monitor', '是否监测数据(1=是,0=否)', 'tinyint(1)', 'Integer', 'isMonitor', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 10, 'admin', '2022-01-13 14:53:32', '', '2022-01-13 15:17:17'); +INSERT INTO `gen_table_column` VALUES (344, '22', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, '1', NULL, 'EQ', 'datetime', '', 11, 'admin', '2022-01-13 14:53:32', '', '2022-01-13 15:17:17'); +INSERT INTO `gen_table_column` VALUES (345, '22', 'remark', '备注', 'varchar(200)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 12, 'admin', '2022-01-13 14:53:32', '', '2022-01-13 15:17:17'); +INSERT INTO `gen_table_column` VALUES (346, '23', 'scene_id', '场景ID', 'bigint(20)', 'Long', 'sceneId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2022-01-13 14:53:33', '', '2022-01-13 15:17:38'); +INSERT INTO `gen_table_column` VALUES (347, '23', 'scene_name', '场景名称', 'varchar(64)', 'String', 'sceneName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 2, 'admin', '2022-01-13 14:53:33', '', '2022-01-13 15:17:38'); +INSERT INTO `gen_table_column` VALUES (348, '23', 'user_id', '用户ID', 'bigint(20)', 'Long', 'userId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, 'admin', '2022-01-13 14:53:33', '', '2022-01-13 15:17:38'); +INSERT INTO `gen_table_column` VALUES (349, '23', 'user_name', '用户名称', 'varchar(64)', 'String', 'userName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 4, 'admin', '2022-01-13 14:53:34', '', '2022-01-13 15:17:38'); +INSERT INTO `gen_table_column` VALUES (350, '23', 'triggers', '触发器', 'json', 'String', 'triggers', '0', '0', '1', '1', '1', '1', NULL, 'EQ', NULL, '', 5, 'admin', '2022-01-13 14:53:34', '', '2022-01-13 15:17:38'); +INSERT INTO `gen_table_column` VALUES (351, '23', 'actions', '执行动作', 'json', 'String', 'actions', '0', '0', '1', '1', '1', '1', NULL, 'EQ', NULL, '', 6, 'admin', '2022-01-13 14:53:34', '', '2022-01-13 15:17:39'); +INSERT INTO `gen_table_column` VALUES (352, '23', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 7, 'admin', '2022-01-13 14:53:34', '', '2022-01-13 15:17:39'); +INSERT INTO `gen_table_column` VALUES (353, '23', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, '1', NULL, 'EQ', 'datetime', '', 8, 'admin', '2022-01-13 14:53:34', '', '2022-01-13 15:17:39'); +INSERT INTO `gen_table_column` VALUES (354, '23', 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 9, 'admin', '2022-01-13 14:53:35', '', '2022-01-13 15:17:39'); +INSERT INTO `gen_table_column` VALUES (355, '23', 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'datetime', '', 10, 'admin', '2022-01-13 14:53:35', '', '2022-01-13 15:17:39'); +INSERT INTO `gen_table_column` VALUES (356, '23', 'remark', '备注', 'varchar(500)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 11, 'admin', '2022-01-13 14:53:35', '', '2022-01-13 15:17:40'); +INSERT INTO `gen_table_column` VALUES (374, '25', 'job_id', '任务ID', 'bigint(20)', 'Long', 'jobId', '1', '1', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (375, '25', 'job_name', '任务名称', 'varchar(64)', 'String', 'jobName', '1', '0', NULL, '1', NULL, NULL, NULL, 'LIKE', 'input', '', 2, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (376, '25', 'job_group', '任务组名', 'varchar(64)', 'String', 'jobGroup', '1', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 3, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (378, '25', 'cron_expression', 'cron执行表达式', 'varchar(255)', 'String', 'cronExpression', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 5, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (379, '25', 'misfire_policy', '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', 'varchar(20)', 'String', 'misfirePolicy', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 6, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (380, '25', 'concurrent', '是否并发执行(0允许 1禁止)', 'char(1)', 'String', 'concurrent', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 7, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (381, '25', 'status', '状态(0正常 1暂停)', 'char(1)', 'String', 'status', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'radio', '', 8, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (382, '25', 'create_by', '创建者', 'varchar(64)', 'String', 'createBy', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 9, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (383, '25', 'create_time', '创建时间', 'datetime', 'Date', 'createTime', '0', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'datetime', '', 10, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (384, '25', 'update_by', '更新者', 'varchar(64)', 'String', 'updateBy', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 11, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (385, '25', 'update_time', '更新时间', 'datetime', 'Date', 'updateTime', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'datetime', '', 12, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (386, '25', 'remark', '备注信息', 'varchar(500)', 'String', 'remark', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 13, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (387, '25', 'device_id', '设备ID', 'bigint(11)', 'Long', 'deviceId', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 14, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (388, '25', 'device_name', '设备名称', 'varchar(64)', 'String', 'deviceName', '0', '0', NULL, '1', '1', '1', '1', 'LIKE', 'input', '', 15, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (389, '25', 'is_advance', '是否详细corn表达式(1=是,0=否)', 'tinyint(1)', 'Integer', 'isAdvance', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 16, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (390, '25', 'actions', '执行的动作集合', 'json', 'String', 'actions', '0', '0', NULL, '1', '1', '1', '1', 'EQ', NULL, '', 17, 'admin', '2022-01-17 16:18:25', '', NULL); +INSERT INTO `gen_table_column` VALUES (391, '26', 'device_id', '设备ID', 'bigint(20)', 'Long', 'deviceId', '1', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 1, 'admin', '2022-01-24 00:29:24', '', NULL); +INSERT INTO `gen_table_column` VALUES (392, '26', 'group_id', '分组ID', 'bigint(20)', 'Long', 'groupId', '1', '0', NULL, '1', NULL, NULL, NULL, 'EQ', 'input', '', 2, 'admin', '2022-01-24 00:29:24', '', NULL); +INSERT INTO `gen_table_column` VALUES (393, '25', 'is_repeat', '是否重复(0=否,1=是)', 'tinyint(11)', 'Long', 'isRepeat', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 17, '', '2022-01-24 00:30:59', '', NULL); +INSERT INTO `gen_table_column` VALUES (394, '25', 'job_type', '定时类型(1=设备定时,2=设备告警,3=场景联动)', 'tinyint(11)', 'Long', 'jobType', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'select', '', 18, '', '2022-01-24 00:30:59', '', NULL); +INSERT INTO `gen_table_column` VALUES (395, '25', 'product_id', '产品ID', 'bigint(20)', 'Long', 'productId', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 19, '', '2022-01-24 00:30:59', '', NULL); +INSERT INTO `gen_table_column` VALUES (396, '25', 'product_name', '产品名称', 'varchar(64)', 'String', 'productName', '0', '0', NULL, '1', '1', '1', '1', 'LIKE', 'input', '', 20, '', '2022-01-24 00:30:59', '', NULL); +INSERT INTO `gen_table_column` VALUES (397, '25', 'scene_id', '场景联动ID', 'bigint(20)', 'Long', 'sceneId', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 21, '', '2022-01-24 00:30:59', '', NULL); +INSERT INTO `gen_table_column` VALUES (398, '25', 'alert_id', '告警ID', 'bigint(20)', 'Long', 'alertId', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 22, '', '2022-01-24 00:31:00', '', NULL); +INSERT INTO `gen_table_column` VALUES (399, '2', 'is_shadow', '是否启用设备影子(0=禁用,1=启用)', 'tinyint(1)', 'Integer', 'isShadow', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 13, '', '2022-01-27 15:39:45', '', NULL); +INSERT INTO `gen_table_column` VALUES (400, '2', 'is_customer_location', '是否自定义位置(0=否,1=是)', 'tinyint(1)', 'Integer', 'isCustomerLocation', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 14, '', '2022-01-27 15:39:45', '', NULL); +INSERT INTO `gen_table_column` VALUES (401, '2', 'things_model_value', '物模型值', 'json', 'String', 'thingsModelValue', '0', '0', NULL, '1', '1', '1', '1', 'EQ', NULL, '', 15, '', '2022-01-27 15:39:45', '', NULL); +INSERT INTO `gen_table_column` VALUES (402, '2', 'img_url', '图片地址', 'varchar(255)', 'String', 'imgUrl', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 21, '', '2022-01-27 15:39:45', '', NULL); +INSERT INTO `gen_table_column` VALUES (403, '5', 'phonenumber', '手机号码', 'varchar(11)', 'String', 'phonenumber', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 6, '', '2022-01-27 15:39:52', '', NULL); +INSERT INTO `gen_table_column` VALUES (404, '11', 'mqtt_account', 'mqtt账号', 'varchar(64)', 'String', 'mqttAccount', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 8, '', '2022-01-27 15:40:00', '', NULL); +INSERT INTO `gen_table_column` VALUES (405, '11', 'mqtt_password', 'mqtt密码', 'varchar(64)', 'String', 'mqttPassword', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 9, '', '2022-01-27 15:40:00', '', NULL); +INSERT INTO `gen_table_column` VALUES (406, '11', 'things_models_json', '物模型JSON(属性、功能、事件)', 'json', 'String', 'thingsModelsJson', '0', '0', NULL, '1', '1', '1', '1', 'EQ', NULL, '', 11, '', '2022-01-27 15:40:00', '', NULL); +INSERT INTO `gen_table_column` VALUES (407, '11', 'img_url', '图片地址', 'varchar(255)', 'String', 'imgUrl', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 15, '', '2022-01-27 15:40:00', '', NULL); +INSERT INTO `gen_table_column` VALUES (408, '20', 'status', '告警状态(1-启动,2-停止)', 'tinyint(1)', 'Integer', 'status', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'radio', '', 8, '', '2022-01-27 15:40:08', '', NULL); +INSERT INTO `gen_table_column` VALUES (409, '21', 'alert_name', '告警名称', 'varchar(64)', 'String', 'alertName', '0', '0', '1', '1', '1', '1', '1', 'LIKE', 'input', '', 2, '', '2022-01-27 15:40:10', '', NULL); +INSERT INTO `gen_table_column` VALUES (410, '21', 'user_id', '用户ID', 'bigint(20)', 'Long', 'userId', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 9, '', '2022-01-27 15:40:10', '', NULL); +INSERT INTO `gen_table_column` VALUES (411, '21', 'user_name', '用户昵称', 'varchar(30)', 'String', 'userName', '0', '0', NULL, '1', '1', '1', '1', 'LIKE', 'input', '', 10, '', '2022-01-27 15:40:10', '', NULL); +INSERT INTO `gen_table_column` VALUES (412, '21', 'type', '类型(1=告警,2=场景联动)', 'tinyint(11)', 'Long', 'type', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'select', '', 16, '', '2022-01-27 15:40:10', '', NULL); +INSERT INTO `gen_table_column` VALUES (413, '27', 'client_id', '客户端ID', 'varchar(255)', 'String', 'clientId', '1', '0', NULL, '1', '1', '1', '1', 'EQ', 'input', '', 1, 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11'); +INSERT INTO `gen_table_column` VALUES (414, '27', 'resource_ids', '资源', 'varchar(255)', 'String', 'resourceIds', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 2, 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11'); +INSERT INTO `gen_table_column` VALUES (415, '27', 'client_secret', '客户端秘钥', 'varchar(255)', 'String', 'clientSecret', '0', '0', NULL, '1', '1', NULL, NULL, 'EQ', 'input', '', 3, 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11'); +INSERT INTO `gen_table_column` VALUES (416, '27', 'scope', '权限范围', 'varchar(255)', 'String', 'scope', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'select', '', 4, 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11'); +INSERT INTO `gen_table_column` VALUES (417, '27', 'authorized_grant_types', '授权模式', 'varchar(255)', 'String', 'authorizedGrantTypes', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'select', '', 5, 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11'); +INSERT INTO `gen_table_column` VALUES (418, '27', 'web_server_redirect_uri', '回调地址', 'varchar(255)', 'String', 'webServerRedirectUri', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 6, 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11'); +INSERT INTO `gen_table_column` VALUES (419, '27', 'authorities', '权限', 'varchar(255)', 'String', 'authorities', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 7, 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11'); +INSERT INTO `gen_table_column` VALUES (420, '27', 'access_token_validity', 'access token有效时间', 'int(11)', 'Long', 'accessTokenValidity', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 8, 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11'); +INSERT INTO `gen_table_column` VALUES (421, '27', 'refresh_token_validity', 'refresh token有效时间', 'int(11)', 'Long', 'refreshTokenValidity', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'input', '', 9, 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11'); +INSERT INTO `gen_table_column` VALUES (422, '27', 'additional_information', '预留的字段', 'text', 'String', 'additionalInformation', '0', '0', NULL, '1', '1', '1', NULL, 'EQ', 'textarea', '', 10, 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11'); +INSERT INTO `gen_table_column` VALUES (423, '27', 'autoapprove', '自动授权', 'varchar(255)', 'String', 'autoapprove', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'select', '', 11, 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11'); +INSERT INTO `gen_table_column` VALUES (424, '27', 'type', '平台', 'tinyint(1)', 'Integer', 'type', '0', '0', NULL, '1', '1', '1', '1', 'EQ', 'select', 'oauth_platform', 12, 'admin', '2022-02-07 20:24:16', '', '2022-02-07 20:38:11'); + +-- ---------------------------- +-- Table structure for iot_alert +-- ---------------------------- +DROP TABLE IF EXISTS `iot_alert`; +CREATE TABLE `iot_alert` ( + `alert_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '告警ID', + `alert_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '告警名称', + `alert_level` tinyint(11) NOT NULL COMMENT '告警级别(1=提醒通知,2=轻微问题,3=严重警告)', + `product_id` bigint(20) NOT NULL COMMENT '产品ID', + `product_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '产品名称', + `triggers` json NOT NULL COMMENT '触发器', + `actions` json NOT NULL COMMENT '执行动作', + `status` tinyint(1) NULL DEFAULT NULL COMMENT '告警状态(1-启动,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 (`alert_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备告警' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_alert +-- ---------------------------- + +-- ---------------------------- +-- Table structure for iot_alert_log +-- ---------------------------- +DROP TABLE IF EXISTS `iot_alert_log`; +CREATE TABLE `iot_alert_log` ( + `alert_log__id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '告警ID', + `alert_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '告警名称', + `alert_level` tinyint(11) NOT NULL COMMENT '告警级别(1=提醒通知,2=轻微问题,3=严重警告)', + `status` tinyint(11) NOT 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 '产品名称', + `device_id` bigint(20) NULL DEFAULT NULL COMMENT '设备ID', + `device_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '设备名称', + `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID', + `user_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 '创建时间', + `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 '备注', + `type` tinyint(11) NULL DEFAULT NULL COMMENT '类型(1=告警,2=场景联动)', + PRIMARY KEY (`alert_log__id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备告警日志' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_alert_log +-- ---------------------------- + +-- ---------------------------- +-- 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 +) 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', '', NULL, '例如:通断器、开关、插座、窗帘、灯'); +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', '', NULL, '例如:加湿器、风扇、扫地机器人'); +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 '设备编号', + `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=启用)', + `is_custom_location` tinyint(1) NULL DEFAULT NULL COMMENT '是否自定义位置(0=否,1=是)', + `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 '激活时间', + `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 '备注', + PRIMARY KEY (`device_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '设备' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_device +-- ---------------------------- +INSERT INTO `iot_device` VALUES (3, 'ESP8266开发板', 2, 'ESP8266产品示例', 1, 'admin', 1, 'admin', 'D6329VL54419L1Y0', 1.00, 4, -58, 1, 0, '[{\"id\": \"temperature\", \"value\": \"12.49\", \"shadow\": \"12.49\"}, {\"id\": \"humidity\", \"value\": \"32.49\", \"shadow\": \"32.49\"}, {\"id\": \"co2\", \"value\": \"753\", \"shadow\": \"753\"}, {\"id\": \"brightness\", \"value\": \"5674\", \"shadow\": \"5674\"}, {\"id\": \"switch\", \"value\": \"1\", \"shadow\": \"1\"}, {\"id\": \"gear\", \"value\": \"3\", \"shadow\": \"3\"}, {\"id\": \"light_color\", \"value\": \"255,100,120\", \"shadow\": \"255,100,120\"}, {\"id\": \"message\", \"value\": \"欢迎使用物美智能系统\", \"shadow\": \"欢迎使用物美智能系统\"}, {\"id\": \"report_monitor\", \"value\": \"1\", \"shadow\": \"1\"}]', '云南省昆明市 联通', '14.204.0.252', 102.839445, 24.886272, '2022-03-15 00:00:00', NULL, '0', '', '2022-03-15 22:23:46', '', '2022-03-16 02:16:14', 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(11) NOT 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) NOT 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', + PRIMARY KEY (`job_id`, `job_name`, `job_group`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备定时' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_device_job +-- ---------------------------- +INSERT INTO `iot_device_job` VALUES (1, '定时上报监测数据', 'DEFAULT', '0 20 10 ? * 1,2,3,4,5,6,7', '2', '1', '0', 'admin', '2022-03-15 22:38:42', 'admin', '2022-03-16 02:19:55', '', 3, 'D6329VL54419L1Y0', 'ESP8266开发板', 0, '[{\"id\": \"report_monitor\", \"name\": \"上报监测数据\", \"type\": 2, \"value\": \"1\", \"source\": 2, \"deviceId\": 3, \"alertName\": \"\", \"alertLevel\": 1, \"deviceName\": \"ESP8266开发板\"}, {\"id\": \"switch\", \"name\": \"设备开关\", \"type\": 2, \"value\": \"1\", \"source\": 2, \"deviceId\": 3, \"alertName\": \"\", \"alertLevel\": 1, \"deviceName\": \"ESP8266开发板\"}, {\"id\": \"gear\", \"name\": \"运行档位\", \"type\": 2, \"value\": \"3\", \"source\": 2, \"deviceId\": 3, \"alertName\": \"\", \"alertLevel\": 1, \"deviceName\": \"ESP8266开发板\"}]', 1, 2, 'ESP8266产品示例', NULL, NULL); + +-- ---------------------------- +-- 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 '标识符', + `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) NOT 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=否)', + `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 = 425 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '设备日志' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_device_log +-- ---------------------------- +INSERT INTO `iot_device_log` VALUES (88, 'co2', 1, '605', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:31:20', '10165'); +INSERT INTO `iot_device_log` VALUES (89, 'brightness', 1, '3792', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:31:21', '10165'); +INSERT INTO `iot_device_log` VALUES (90, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:31:22', NULL); +INSERT INTO `iot_device_log` VALUES (91, 'offline', 6, '0', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:31:42', '设备离线'); +INSERT INTO `iot_device_log` VALUES (92, 'online', 5, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:31:53', '设备上线'); +INSERT INTO `iot_device_log` VALUES (93, 'switch', 2, '0', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:32:31', NULL); +INSERT INTO `iot_device_log` VALUES (94, 'gear', 2, '3', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:32:36', NULL); +INSERT INTO `iot_device_log` VALUES (95, 'switch', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:32:38', NULL); +INSERT INTO `iot_device_log` VALUES (96, 'height_temperature', 3, '40', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:32:44', '温度过高警告'); +INSERT INTO `iot_device_log` VALUES (97, 'exception', 3, '异常消息,消息内容XXXXXXXX', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:32:44', '设备发生错误'); +INSERT INTO `iot_device_log` VALUES (98, 'temperature', 1, '20.08', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:32:45', '60011'); +INSERT INTO `iot_device_log` VALUES (99, 'humidity', 1, '55.56', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:32:45', '60011'); +INSERT INTO `iot_device_log` VALUES (100, 'co2', 1, '527', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:32:45', '60012'); +INSERT INTO `iot_device_log` VALUES (101, 'brightness', 1, '8693', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:32:46', '60012'); +INSERT INTO `iot_device_log` VALUES (102, 'temperature', 1, '25.07', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:32:56', '71197'); +INSERT INTO `iot_device_log` VALUES (103, 'humidity', 1, '53.46', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:32:56', '71198'); +INSERT INTO `iot_device_log` VALUES (104, 'co2', 1, '858', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:32:56', '71198'); +INSERT INTO `iot_device_log` VALUES (105, 'brightness', 1, '1384', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:32:56', '71198'); +INSERT INTO `iot_device_log` VALUES (106, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:32:57', NULL); +INSERT INTO `iot_device_log` VALUES (107, 'temperature', 1, '19.44', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:33:00', '76086'); +INSERT INTO `iot_device_log` VALUES (108, 'humidity', 1, '40.74', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:33:01', '76087'); +INSERT INTO `iot_device_log` VALUES (109, 'co2', 1, '884', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:33:01', '76087'); +INSERT INTO `iot_device_log` VALUES (110, 'brightness', 1, '1928', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:33:01', '76087'); +INSERT INTO `iot_device_log` VALUES (111, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:33:02', NULL); +INSERT INTO `iot_device_log` VALUES (112, 'height_temperature', 3, '40', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:33:44', '温度过高警告'); +INSERT INTO `iot_device_log` VALUES (113, 'exception', 3, '异常消息,消息内容XXXXXXXX', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:33:44', '设备发生错误'); +INSERT INTO `iot_device_log` VALUES (114, 'temperature', 1, '24.82', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:33:45', '120012'); +INSERT INTO `iot_device_log` VALUES (115, 'humidity', 1, '46.32', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:33:45', '120012'); +INSERT INTO `iot_device_log` VALUES (116, 'co2', 1, '510', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:33:45', '120013'); +INSERT INTO `iot_device_log` VALUES (117, 'brightness', 1, '8830', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:33:46', '120013'); +INSERT INTO `iot_device_log` VALUES (118, 'temperature', 1, '27.93', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:12', '147792'); +INSERT INTO `iot_device_log` VALUES (119, 'humidity', 1, '30.24', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:12', '147792'); +INSERT INTO `iot_device_log` VALUES (120, 'co2', 1, '932', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:13', '147792'); +INSERT INTO `iot_device_log` VALUES (121, 'brightness', 1, '7267', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:13', '147792'); +INSERT INTO `iot_device_log` VALUES (122, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:34:14', NULL); +INSERT INTO `iot_device_log` VALUES (123, 'temperature', 1, '12.51', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:22', '157691'); +INSERT INTO `iot_device_log` VALUES (124, 'humidity', 1, '35.35', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:22', '157691'); +INSERT INTO `iot_device_log` VALUES (125, 'co2', 1, '952', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:22', '157691'); +INSERT INTO `iot_device_log` VALUES (126, 'brightness', 1, '9204', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:23', '157691'); +INSERT INTO `iot_device_log` VALUES (127, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:34:24', NULL); +INSERT INTO `iot_device_log` VALUES (128, 'temperature', 1, '12.60', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:32', '167691'); +INSERT INTO `iot_device_log` VALUES (129, 'humidity', 1, '53.09', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:32', '167691'); +INSERT INTO `iot_device_log` VALUES (130, 'co2', 1, '569', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:33', '167691'); +INSERT INTO `iot_device_log` VALUES (131, 'brightness', 1, '9485', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:33', '167691'); +INSERT INTO `iot_device_log` VALUES (132, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:34:34', NULL); +INSERT INTO `iot_device_log` VALUES (133, 'temperature', 1, '19.49', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:42', '177590'); +INSERT INTO `iot_device_log` VALUES (134, 'humidity', 1, '57.37', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:42', '177590'); +INSERT INTO `iot_device_log` VALUES (135, 'co2', 1, '514', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:42', '177591'); +INSERT INTO `iot_device_log` VALUES (136, 'brightness', 1, '2660', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:43', '177591'); +INSERT INTO `iot_device_log` VALUES (137, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:34:43', NULL); +INSERT INTO `iot_device_log` VALUES (138, 'height_temperature', 3, '40', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:34:44', '温度过高警告'); +INSERT INTO `iot_device_log` VALUES (139, 'exception', 3, '异常消息,消息内容XXXXXXXX', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:34:44', '设备发生错误'); +INSERT INTO `iot_device_log` VALUES (140, 'temperature', 1, '24.67', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:45', '180013'); +INSERT INTO `iot_device_log` VALUES (141, 'humidity', 1, '55.76', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:45', '180013'); +INSERT INTO `iot_device_log` VALUES (142, 'co2', 1, '582', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:45', '180014'); +INSERT INTO `iot_device_log` VALUES (143, 'brightness', 1, '4288', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:46', '180014'); +INSERT INTO `iot_device_log` VALUES (144, 'temperature', 1, '22.47', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:52', '187756'); +INSERT INTO `iot_device_log` VALUES (145, 'humidity', 1, '42.34', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:52', '187756'); +INSERT INTO `iot_device_log` VALUES (146, 'co2', 1, '750', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:53', '187756'); +INSERT INTO `iot_device_log` VALUES (147, 'brightness', 1, '8468', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:34:53', '187756'); +INSERT INTO `iot_device_log` VALUES (148, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:34:54', NULL); +INSERT INTO `iot_device_log` VALUES (149, 'temperature', 1, '24.64', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:02', '197690'); +INSERT INTO `iot_device_log` VALUES (150, 'humidity', 1, '56.93', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:02', '197690'); +INSERT INTO `iot_device_log` VALUES (151, 'co2', 1, '841', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:02', '197690'); +INSERT INTO `iot_device_log` VALUES (152, 'brightness', 1, '7910', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:03', '197691'); +INSERT INTO `iot_device_log` VALUES (153, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:35:03', NULL); +INSERT INTO `iot_device_log` VALUES (154, 'temperature', 1, '10.00', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:12', '207704'); +INSERT INTO `iot_device_log` VALUES (155, 'humidity', 1, '46.95', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:12', '207704'); +INSERT INTO `iot_device_log` VALUES (156, 'co2', 1, '557', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:12', '207704'); +INSERT INTO `iot_device_log` VALUES (157, 'brightness', 1, '6761', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:13', '207704'); +INSERT INTO `iot_device_log` VALUES (158, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:35:14', NULL); +INSERT INTO `iot_device_log` VALUES (159, 'temperature', 1, '23.10', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:22', '217628'); +INSERT INTO `iot_device_log` VALUES (160, 'humidity', 1, '36.57', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:22', '217628'); +INSERT INTO `iot_device_log` VALUES (161, 'co2', 1, '531', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:22', '217628'); +INSERT INTO `iot_device_log` VALUES (162, 'brightness', 1, '3486', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:23', '217629'); +INSERT INTO `iot_device_log` VALUES (163, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:35:24', NULL); +INSERT INTO `iot_device_log` VALUES (164, 'temperature', 1, '23.82', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:32', '227731'); +INSERT INTO `iot_device_log` VALUES (165, 'humidity', 1, '36.52', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:32', '227731'); +INSERT INTO `iot_device_log` VALUES (166, 'co2', 1, '668', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:33', '227731'); +INSERT INTO `iot_device_log` VALUES (167, 'brightness', 1, '7590', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:33', '227732'); +INSERT INTO `iot_device_log` VALUES (168, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:35:34', NULL); +INSERT INTO `iot_device_log` VALUES (169, 'temperature', 1, '11.90', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:42', '237733'); +INSERT INTO `iot_device_log` VALUES (170, 'humidity', 1, '45.83', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:42', '237734'); +INSERT INTO `iot_device_log` VALUES (171, 'co2', 1, '995', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:42', '237734'); +INSERT INTO `iot_device_log` VALUES (172, 'brightness', 1, '3961', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:43', '237734'); +INSERT INTO `iot_device_log` VALUES (173, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:35:43', NULL); +INSERT INTO `iot_device_log` VALUES (174, 'height_temperature', 3, '40', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:35:44', '温度过高警告'); +INSERT INTO `iot_device_log` VALUES (175, 'exception', 3, '异常消息,消息内容XXXXXXXX', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:35:44', '设备发生错误'); +INSERT INTO `iot_device_log` VALUES (176, 'temperature', 1, '18.52', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:45', '240014'); +INSERT INTO `iot_device_log` VALUES (177, 'humidity', 1, '42.94', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:45', '240014'); +INSERT INTO `iot_device_log` VALUES (178, 'co2', 1, '505', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:45', '240015'); +INSERT INTO `iot_device_log` VALUES (179, 'brightness', 1, '5482', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:45', '240015'); +INSERT INTO `iot_device_log` VALUES (180, 'temperature', 1, '22.07', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:52', '247631'); +INSERT INTO `iot_device_log` VALUES (181, 'humidity', 1, '42.06', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:52', '247631'); +INSERT INTO `iot_device_log` VALUES (182, 'co2', 1, '529', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:52', '247632'); +INSERT INTO `iot_device_log` VALUES (183, 'brightness', 1, '9982', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:35:53', '247632'); +INSERT INTO `iot_device_log` VALUES (184, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:35:54', NULL); +INSERT INTO `iot_device_log` VALUES (185, 'temperature', 1, '24.86', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:02', '257635'); +INSERT INTO `iot_device_log` VALUES (186, 'humidity', 1, '54.21', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:02', '257635'); +INSERT INTO `iot_device_log` VALUES (187, 'co2', 1, '751', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:02', '257635'); +INSERT INTO `iot_device_log` VALUES (188, 'brightness', 1, '6826', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:03', '257635'); +INSERT INTO `iot_device_log` VALUES (189, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:36:04', NULL); +INSERT INTO `iot_device_log` VALUES (190, 'temperature', 1, '19.30', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:12', '267670'); +INSERT INTO `iot_device_log` VALUES (191, 'humidity', 1, '30.39', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:12', '267670'); +INSERT INTO `iot_device_log` VALUES (192, 'co2', 1, '508', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:12', '267670'); +INSERT INTO `iot_device_log` VALUES (193, 'brightness', 1, '4426', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:13', '267670'); +INSERT INTO `iot_device_log` VALUES (194, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:36:14', NULL); +INSERT INTO `iot_device_log` VALUES (195, 'temperature', 1, '13.60', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:20', '275893'); +INSERT INTO `iot_device_log` VALUES (196, 'humidity', 1, '43.72', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:20', '275893'); +INSERT INTO `iot_device_log` VALUES (197, 'co2', 1, '443', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:21', '275894'); +INSERT INTO `iot_device_log` VALUES (198, 'brightness', 1, '1748', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:21', '275894'); +INSERT INTO `iot_device_log` VALUES (199, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:36:22', NULL); +INSERT INTO `iot_device_log` VALUES (200, 'temperature', 1, '18.14', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:30', '285625'); +INSERT INTO `iot_device_log` VALUES (201, 'humidity', 1, '48.55', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:30', '285625'); +INSERT INTO `iot_device_log` VALUES (202, 'co2', 1, '576', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:30', '285625'); +INSERT INTO `iot_device_log` VALUES (203, 'brightness', 1, '9786', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:31', '285625'); +INSERT INTO `iot_device_log` VALUES (204, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:36:31', NULL); +INSERT INTO `iot_device_log` VALUES (205, 'temperature', 1, '12.10', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:40', '295577'); +INSERT INTO `iot_device_log` VALUES (206, 'humidity', 1, '36.49', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:40', '295577'); +INSERT INTO `iot_device_log` VALUES (207, 'co2', 1, '739', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:40', '295577'); +INSERT INTO `iot_device_log` VALUES (208, 'brightness', 1, '9250', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:41', '295577'); +INSERT INTO `iot_device_log` VALUES (209, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:36:42', NULL); +INSERT INTO `iot_device_log` VALUES (210, 'height_temperature', 3, '40', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:36:44', '温度过高警告'); +INSERT INTO `iot_device_log` VALUES (211, 'exception', 3, '异常消息,消息内容XXXXXXXX', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:36:44', '设备发生错误'); +INSERT INTO `iot_device_log` VALUES (212, 'temperature', 1, '25.72', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:45', '300015'); +INSERT INTO `iot_device_log` VALUES (213, 'humidity', 1, '57.96', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:45', '300015'); +INSERT INTO `iot_device_log` VALUES (214, 'co2', 1, '615', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:45', '300016'); +INSERT INTO `iot_device_log` VALUES (215, 'brightness', 1, '4748', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:46', '300016'); +INSERT INTO `iot_device_log` VALUES (216, 'temperature', 1, '18.65', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:50', '305589'); +INSERT INTO `iot_device_log` VALUES (217, 'humidity', 1, '57.35', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:50', '305589'); +INSERT INTO `iot_device_log` VALUES (218, 'co2', 1, '546', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:50', '305590'); +INSERT INTO `iot_device_log` VALUES (219, 'brightness', 1, '2502', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:36:51', '305590'); +INSERT INTO `iot_device_log` VALUES (220, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:36:52', NULL); +INSERT INTO `iot_device_log` VALUES (221, 'temperature', 1, '21.66', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:00', '315591'); +INSERT INTO `iot_device_log` VALUES (222, 'humidity', 1, '59.46', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:00', '315591'); +INSERT INTO `iot_device_log` VALUES (223, 'co2', 1, '945', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:00', '315592'); +INSERT INTO `iot_device_log` VALUES (224, 'brightness', 1, '8814', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:01', '315592'); +INSERT INTO `iot_device_log` VALUES (225, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:37:02', NULL); +INSERT INTO `iot_device_log` VALUES (226, 'temperature', 1, '23.58', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:10', '325594'); +INSERT INTO `iot_device_log` VALUES (227, 'humidity', 1, '50.04', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:10', '325594'); +INSERT INTO `iot_device_log` VALUES (228, 'co2', 1, '692', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:11', '325594'); +INSERT INTO `iot_device_log` VALUES (229, 'brightness', 1, '1334', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:11', '325595'); +INSERT INTO `iot_device_log` VALUES (230, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:37:12', NULL); +INSERT INTO `iot_device_log` VALUES (231, 'temperature', 1, '23.43', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:20', '335593'); +INSERT INTO `iot_device_log` VALUES (232, 'humidity', 1, '38.06', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:20', '335593'); +INSERT INTO `iot_device_log` VALUES (233, 'co2', 1, '459', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:20', '335593'); +INSERT INTO `iot_device_log` VALUES (234, 'brightness', 1, '3633', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:21', '335593'); +INSERT INTO `iot_device_log` VALUES (235, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:37:21', NULL); +INSERT INTO `iot_device_log` VALUES (236, 'temperature', 1, '19.52', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:30', '345596'); +INSERT INTO `iot_device_log` VALUES (237, 'humidity', 1, '50.40', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:30', '345596'); +INSERT INTO `iot_device_log` VALUES (238, 'co2', 1, '994', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:30', '345596'); +INSERT INTO `iot_device_log` VALUES (239, 'brightness', 1, '2477', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:31', '345596'); +INSERT INTO `iot_device_log` VALUES (240, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:37:31', NULL); +INSERT INTO `iot_device_log` VALUES (241, 'temperature', 1, '17.25', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:40', '355598'); +INSERT INTO `iot_device_log` VALUES (242, 'humidity', 1, '32.70', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:40', '355598'); +INSERT INTO `iot_device_log` VALUES (243, 'co2', 1, '650', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:41', '355598'); +INSERT INTO `iot_device_log` VALUES (244, 'brightness', 1, '7072', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:41', '355599'); +INSERT INTO `iot_device_log` VALUES (245, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:37:42', NULL); +INSERT INTO `iot_device_log` VALUES (246, 'height_temperature', 3, '40', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:37:44', '温度过高警告'); +INSERT INTO `iot_device_log` VALUES (247, 'exception', 3, '异常消息,消息内容XXXXXXXX', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:37:44', '设备发生错误'); +INSERT INTO `iot_device_log` VALUES (248, 'temperature', 1, '12.66', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:45', '360016'); +INSERT INTO `iot_device_log` VALUES (249, 'humidity', 1, '43.94', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:45', '360016'); +INSERT INTO `iot_device_log` VALUES (250, 'co2', 1, '731', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:45', '360017'); +INSERT INTO `iot_device_log` VALUES (251, 'brightness', 1, '4246', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:46', '360017'); +INSERT INTO `iot_device_log` VALUES (252, 'temperature', 1, '21.84', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:50', '365595'); +INSERT INTO `iot_device_log` VALUES (253, 'humidity', 1, '52.14', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:50', '365595'); +INSERT INTO `iot_device_log` VALUES (254, 'co2', 1, '714', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:50', '365596'); +INSERT INTO `iot_device_log` VALUES (255, 'brightness', 1, '5816', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:37:50', '365596'); +INSERT INTO `iot_device_log` VALUES (256, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:37:51', NULL); +INSERT INTO `iot_device_log` VALUES (257, 'temperature', 1, '28.82', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:00', '375598'); +INSERT INTO `iot_device_log` VALUES (258, 'humidity', 1, '54.74', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:00', '375599'); +INSERT INTO `iot_device_log` VALUES (259, 'co2', 1, '586', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:00', '375599'); +INSERT INTO `iot_device_log` VALUES (260, 'brightness', 1, '6792', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:01', '375599'); +INSERT INTO `iot_device_log` VALUES (261, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:38:02', NULL); +INSERT INTO `iot_device_log` VALUES (262, 'temperature', 1, '23.96', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:10', '385601'); +INSERT INTO `iot_device_log` VALUES (263, 'humidity', 1, '36.47', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:10', '385601'); +INSERT INTO `iot_device_log` VALUES (264, 'co2', 1, '888', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:11', '385601'); +INSERT INTO `iot_device_log` VALUES (265, 'brightness', 1, '4131', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:11', '385602'); +INSERT INTO `iot_device_log` VALUES (266, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:38:11', NULL); +INSERT INTO `iot_device_log` VALUES (267, 'temperature', 1, '20.13', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:20', '395599'); +INSERT INTO `iot_device_log` VALUES (268, 'humidity', 1, '41.65', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:20', '395599'); +INSERT INTO `iot_device_log` VALUES (269, 'co2', 1, '800', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:20', '395599'); +INSERT INTO `iot_device_log` VALUES (270, 'brightness', 1, '1396', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:21', '395599'); +INSERT INTO `iot_device_log` VALUES (271, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:38:21', NULL); +INSERT INTO `iot_device_log` VALUES (272, 'temperature', 1, '20.76', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:30', '405600'); +INSERT INTO `iot_device_log` VALUES (273, 'humidity', 1, '31.81', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:30', '405600'); +INSERT INTO `iot_device_log` VALUES (274, 'co2', 1, '775', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:30', '405601'); +INSERT INTO `iot_device_log` VALUES (275, 'brightness', 1, '2847', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:31', '405601'); +INSERT INTO `iot_device_log` VALUES (276, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:38:31', NULL); +INSERT INTO `iot_device_log` VALUES (277, 'temperature', 1, '14.27', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:40', '415603'); +INSERT INTO `iot_device_log` VALUES (278, 'humidity', 1, '51.81', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:41', '415603'); +INSERT INTO `iot_device_log` VALUES (279, 'co2', 1, '516', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:41', '415603'); +INSERT INTO `iot_device_log` VALUES (280, 'brightness', 1, '7762', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:41', '415603'); +INSERT INTO `iot_device_log` VALUES (281, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:38:42', NULL); +INSERT INTO `iot_device_log` VALUES (282, 'height_temperature', 3, '40', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:38:44', '温度过高警告'); +INSERT INTO `iot_device_log` VALUES (283, 'exception', 3, '异常消息,消息内容XXXXXXXX', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:38:44', '设备发生错误'); +INSERT INTO `iot_device_log` VALUES (284, 'temperature', 1, '23.05', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:45', '420017'); +INSERT INTO `iot_device_log` VALUES (285, 'humidity', 1, '38.79', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:45', '420017'); +INSERT INTO `iot_device_log` VALUES (286, 'co2', 1, '937', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:45', '420018'); +INSERT INTO `iot_device_log` VALUES (287, 'brightness', 1, '7721', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:46', '420018'); +INSERT INTO `iot_device_log` VALUES (288, 'temperature', 1, '29.11', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:50', '425601'); +INSERT INTO `iot_device_log` VALUES (289, 'humidity', 1, '54.40', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:50', '425601'); +INSERT INTO `iot_device_log` VALUES (290, 'co2', 1, '861', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:50', '425602'); +INSERT INTO `iot_device_log` VALUES (291, 'brightness', 1, '6319', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:38:51', '425602'); +INSERT INTO `iot_device_log` VALUES (292, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:38:52', NULL); +INSERT INTO `iot_device_log` VALUES (293, 'temperature', 1, '25.81', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:00', '435604'); +INSERT INTO `iot_device_log` VALUES (294, 'humidity', 1, '57.05', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:00', '435604'); +INSERT INTO `iot_device_log` VALUES (295, 'co2', 1, '852', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:01', '435604'); +INSERT INTO `iot_device_log` VALUES (296, 'brightness', 1, '9682', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:01', '435605'); +INSERT INTO `iot_device_log` VALUES (297, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:39:02', NULL); +INSERT INTO `iot_device_log` VALUES (298, 'temperature', 1, '23.86', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:10', '445606'); +INSERT INTO `iot_device_log` VALUES (299, 'humidity', 1, '33.96', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:10', '445606'); +INSERT INTO `iot_device_log` VALUES (300, 'co2', 1, '910', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:10', '445607'); +INSERT INTO `iot_device_log` VALUES (301, 'brightness', 1, '2303', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:11', '445607'); +INSERT INTO `iot_device_log` VALUES (302, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:39:11', NULL); +INSERT INTO `iot_device_log` VALUES (303, 'temperature', 1, '11.55', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:20', '455625'); +INSERT INTO `iot_device_log` VALUES (304, 'humidity', 1, '59.31', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:20', '455625'); +INSERT INTO `iot_device_log` VALUES (305, 'co2', 1, '442', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:21', '455625'); +INSERT INTO `iot_device_log` VALUES (306, 'brightness', 1, '7929', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:21', '455625'); +INSERT INTO `iot_device_log` VALUES (307, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:39:21', NULL); +INSERT INTO `iot_device_log` VALUES (308, 'temperature', 1, '23.35', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:30', '465627'); +INSERT INTO `iot_device_log` VALUES (309, 'humidity', 1, '33.69', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:30', '465628'); +INSERT INTO `iot_device_log` VALUES (310, 'co2', 1, '975', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:31', '465628'); +INSERT INTO `iot_device_log` VALUES (311, 'brightness', 1, '3315', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:39:31', '465628'); +INSERT INTO `iot_device_log` VALUES (312, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:39:32', NULL); +INSERT INTO `iot_device_log` VALUES (313, 'offline', 6, '0', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:39:53', '设备离线'); +INSERT INTO `iot_device_log` VALUES (314, 'online', 5, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:50:35', '设备上线'); +INSERT INTO `iot_device_log` VALUES (315, 'temperature', 1, '13.65', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:50:36', '5969'); +INSERT INTO `iot_device_log` VALUES (316, 'humidity', 1, '38.52', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:50:36', '5969'); +INSERT INTO `iot_device_log` VALUES (317, 'co2', 1, '457', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:50:36', '5969'); +INSERT INTO `iot_device_log` VALUES (318, 'brightness', 1, '6266', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:50:37', '5969'); +INSERT INTO `iot_device_log` VALUES (319, 'temperature', 1, '11.46', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:50:41', '10769'); +INSERT INTO `iot_device_log` VALUES (320, 'humidity', 1, '35.11', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:50:41', '10769'); +INSERT INTO `iot_device_log` VALUES (321, 'co2', 1, '849', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:50:41', '10769'); +INSERT INTO `iot_device_log` VALUES (322, 'brightness', 1, '4772', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:50:41', '10769'); +INSERT INTO `iot_device_log` VALUES (323, 'temperature', 1, '26.80', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:50:51', '20773'); +INSERT INTO `iot_device_log` VALUES (324, 'humidity', 1, '35.67', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:50:51', '20773'); +INSERT INTO `iot_device_log` VALUES (325, 'co2', 1, '667', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:50:51', '20773'); +INSERT INTO `iot_device_log` VALUES (326, 'brightness', 1, '9028', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:50:51', '20774'); +INSERT INTO `iot_device_log` VALUES (327, 'temperature', 1, '28.17', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:01', '30774'); +INSERT INTO `iot_device_log` VALUES (328, 'humidity', 1, '39.41', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:01', '30775'); +INSERT INTO `iot_device_log` VALUES (329, 'co2', 1, '611', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:01', '30775'); +INSERT INTO `iot_device_log` VALUES (330, 'brightness', 1, '5189', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:01', '30775'); +INSERT INTO `iot_device_log` VALUES (331, 'temperature', 1, '11.00', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:11', '40780'); +INSERT INTO `iot_device_log` VALUES (332, 'humidity', 1, '35.67', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:11', '40781'); +INSERT INTO `iot_device_log` VALUES (333, 'co2', 1, '991', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:11', '40781'); +INSERT INTO `iot_device_log` VALUES (334, 'brightness', 1, '7770', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:11', '40781'); +INSERT INTO `iot_device_log` VALUES (335, 'temperature', 1, '17.01', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:21', '50782'); +INSERT INTO `iot_device_log` VALUES (336, 'humidity', 1, '50.05', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:21', '50783'); +INSERT INTO `iot_device_log` VALUES (337, 'co2', 1, '661', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:21', '50783'); +INSERT INTO `iot_device_log` VALUES (338, 'brightness', 1, '5823', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:21', '50783'); +INSERT INTO `iot_device_log` VALUES (339, 'temperature', 1, '10.71', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:31', '60785'); +INSERT INTO `iot_device_log` VALUES (340, 'humidity', 1, '45.66', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:31', '60785'); +INSERT INTO `iot_device_log` VALUES (341, 'co2', 1, '557', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:31', '60785'); +INSERT INTO `iot_device_log` VALUES (342, 'brightness', 1, '6360', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:31', '60785'); +INSERT INTO `iot_device_log` VALUES (343, 'height_temperature', 3, '40', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:51:32', '温度过高警告'); +INSERT INTO `iot_device_log` VALUES (344, 'exception', 3, '异常消息,消息内容XXXXXXXX', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:51:32', '设备发生错误'); +INSERT INTO `iot_device_log` VALUES (345, 'temperature', 1, '29.35', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:33', '60846'); +INSERT INTO `iot_device_log` VALUES (346, 'humidity', 1, '43.64', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:33', '60846'); +INSERT INTO `iot_device_log` VALUES (347, 'co2', 1, '522', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:33', '60846'); +INSERT INTO `iot_device_log` VALUES (348, 'brightness', 1, '9869', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:33', '60846'); +INSERT INTO `iot_device_log` VALUES (349, 'temperature', 1, '20.19', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:41', '70849'); +INSERT INTO `iot_device_log` VALUES (350, 'humidity', 1, '35.57', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:41', '70849'); +INSERT INTO `iot_device_log` VALUES (351, 'co2', 1, '455', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:41', '70849'); +INSERT INTO `iot_device_log` VALUES (352, 'brightness', 1, '3056', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:41', '70849'); +INSERT INTO `iot_device_log` VALUES (353, 'temperature', 1, '28.34', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:51', '80850'); +INSERT INTO `iot_device_log` VALUES (354, 'humidity', 1, '51.12', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:51', '80851'); +INSERT INTO `iot_device_log` VALUES (355, 'co2', 1, '673', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:51', '80851'); +INSERT INTO `iot_device_log` VALUES (356, 'brightness', 1, '1764', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:51:52', '80851'); +INSERT INTO `iot_device_log` VALUES (357, 'temperature', 1, '10.02', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:52:01', '90819'); +INSERT INTO `iot_device_log` VALUES (358, 'humidity', 1, '45.27', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:52:01', '90819'); +INSERT INTO `iot_device_log` VALUES (359, 'co2', 1, '790', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:52:01', '90819'); +INSERT INTO `iot_device_log` VALUES (360, 'brightness', 1, '6068', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:52:02', '90819'); +INSERT INTO `iot_device_log` VALUES (361, 'temperature', 1, '17.72', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:52:11', '100824'); +INSERT INTO `iot_device_log` VALUES (362, 'humidity', 1, '30.18', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:52:11', '100824'); +INSERT INTO `iot_device_log` VALUES (363, 'co2', 1, '751', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:52:11', '100824'); +INSERT INTO `iot_device_log` VALUES (364, 'brightness', 1, '2366', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:52:11', '100824'); +INSERT INTO `iot_device_log` VALUES (365, 'temperature', 1, '17.16', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:52:21', '110785'); +INSERT INTO `iot_device_log` VALUES (366, 'humidity', 1, '51.98', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:52:21', '110785'); +INSERT INTO `iot_device_log` VALUES (367, 'co2', 1, '416', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:52:21', '110785'); +INSERT INTO `iot_device_log` VALUES (368, 'brightness', 1, '6061', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 01:52:21', '110785'); +INSERT INTO `iot_device_log` VALUES (369, 'offline', 6, '0', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 01:52:43', '设备离线'); +INSERT INTO `iot_device_log` VALUES (370, 'online', 5, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 02:04:14', '设备上线'); +INSERT INTO `iot_device_log` VALUES (371, 'temperature', 1, '22.74', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:04:15', '6670'); +INSERT INTO `iot_device_log` VALUES (372, 'humidity', 1, '48.61', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:04:15', '6670'); +INSERT INTO `iot_device_log` VALUES (373, 'co2', 1, '807', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:04:15', '6671'); +INSERT INTO `iot_device_log` VALUES (374, 'brightness', 1, '9803', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:04:15', '6671'); +INSERT INTO `iot_device_log` VALUES (375, 'temperature', 1, '26.77', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:04:46', '37913'); +INSERT INTO `iot_device_log` VALUES (376, 'humidity', 1, '34.18', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:04:46', '37913'); +INSERT INTO `iot_device_log` VALUES (377, 'co2', 1, '952', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:04:46', '37913'); +INSERT INTO `iot_device_log` VALUES (378, 'brightness', 1, '3045', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:04:47', '37913'); +INSERT INTO `iot_device_log` VALUES (379, 'temperature', 1, '18.15', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:04:53', '44979'); +INSERT INTO `iot_device_log` VALUES (380, 'humidity', 1, '43.26', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:04:53', '44979'); +INSERT INTO `iot_device_log` VALUES (381, 'co2', 1, '530', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:04:53', '44980'); +INSERT INTO `iot_device_log` VALUES (382, 'brightness', 1, '6280', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:04:54', '44980'); +INSERT INTO `iot_device_log` VALUES (383, 'temperature', 1, '27.69', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:03', '54810'); +INSERT INTO `iot_device_log` VALUES (384, 'humidity', 1, '46.42', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:03', '54810'); +INSERT INTO `iot_device_log` VALUES (385, 'co2', 1, '577', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:03', '54810'); +INSERT INTO `iot_device_log` VALUES (386, 'brightness', 1, '5387', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:03', '54810'); +INSERT INTO `iot_device_log` VALUES (387, 'height_temperature', 3, '40', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 02:05:08', '温度过高警告'); +INSERT INTO `iot_device_log` VALUES (388, 'exception', 3, '异常消息,消息内容XXXXXXXX', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 02:05:08', '设备发生错误'); +INSERT INTO `iot_device_log` VALUES (389, 'temperature', 1, '16.75', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:09', '60013'); +INSERT INTO `iot_device_log` VALUES (390, 'humidity', 1, '52.43', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:09', '60014'); +INSERT INTO `iot_device_log` VALUES (391, 'co2', 1, '728', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:09', '60014'); +INSERT INTO `iot_device_log` VALUES (392, 'brightness', 1, '5735', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:09', '60014'); +INSERT INTO `iot_device_log` VALUES (393, 'temperature', 1, '28.11', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:13', '64846'); +INSERT INTO `iot_device_log` VALUES (394, 'humidity', 1, '47.50', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:13', '64846'); +INSERT INTO `iot_device_log` VALUES (395, 'co2', 1, '518', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:13', '64846'); +INSERT INTO `iot_device_log` VALUES (396, 'brightness', 1, '3450', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:14', '64846'); +INSERT INTO `iot_device_log` VALUES (397, 'temperature', 1, '26.42', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:23', '74778'); +INSERT INTO `iot_device_log` VALUES (398, 'humidity', 1, '34.20', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:23', '74778'); +INSERT INTO `iot_device_log` VALUES (399, 'co2', 1, '681', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:23', '74778'); +INSERT INTO `iot_device_log` VALUES (400, 'brightness', 1, '3905', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:05:24', '74778'); +INSERT INTO `iot_device_log` VALUES (401, 'offline', 6, '0', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 02:05:44', '设备离线'); +INSERT INTO `iot_device_log` VALUES (402, 'online', 5, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 02:16:15', '设备上线'); +INSERT INTO `iot_device_log` VALUES (403, 'temperature', 1, '20.44', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:16:16', '5993'); +INSERT INTO `iot_device_log` VALUES (404, 'humidity', 1, '40.86', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:16:16', '5993'); +INSERT INTO `iot_device_log` VALUES (405, 'co2', 1, '530', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:16:16', '5993'); +INSERT INTO `iot_device_log` VALUES (406, 'brightness', 1, '2688', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 1, '', '2022-03-16 02:16:16', '5993'); +INSERT INTO `iot_device_log` VALUES (407, 'report_monitor', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 02:16:17', NULL); +INSERT INTO `iot_device_log` VALUES (408, 'switch', 2, '1', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 02:16:17', NULL); +INSERT INTO `iot_device_log` VALUES (409, 'gear', 2, '3', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 02:16:18', NULL); +INSERT INTO `iot_device_log` VALUES (424, 'offline', 6, '0', 3, 'ESP8266开发板', 'D6329VL54419L1Y0', 0, '', '2022-03-16 02:16:52', '设备离线'); + +-- ---------------------------- +-- Table structure for iot_device_user +-- ---------------------------- +DROP TABLE IF EXISTS `iot_device_user`; +CREATE TABLE `iot_device_user` ( + `device_id` bigint(20) NOT NULL AUTO_INCREMENT 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=是)', + `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 +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '设备用户' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_device_user +-- ---------------------------- + +-- ---------------------------- +-- Table structure for iot_firmware +-- ---------------------------- +DROP TABLE IF EXISTS `iot_firmware`; +CREATE TABLE `iot_firmware` ( + `firmware_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '固件ID', + `firmware_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 '租户名称', + `is_sys` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否系统通用(0-否,1-是)', + `version` float(11, 2) NOT NULL DEFAULT 0.10 COMMENT '固件版本', + `file_path` varchar(255) 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 (`firmware_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '产品固件' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_firmware +-- ---------------------------- + +-- ---------------------------- +-- 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 +) 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 (1, '客厅', 1, 1, 'admin', '0', '', '2021-12-29 13:13:26', '', '2021-12-29 13:14:11', '客厅设备'); +INSERT INTO `iot_group` VALUES (2, '卧室', 2, 1, 'admin', '0', '', '2021-12-29 13:12:42', '', '2021-12-29 13:13:40', '卧室设备'); +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 '产品名称', + `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-是)', + `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-蓝牙、3-wifi+蓝牙)', + `vertificate_method` tinyint(1) NULL DEFAULT 1 COMMENT '认证方式(1-账号密码、2-证书、3-Http)', + `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 '备注', + PRIMARY KEY (`product_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '产品' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_product +-- ---------------------------- +INSERT INTO `iot_product` VALUES (2, 'ESP8266产品示例', 1, '电工照明', 1, 'admin', 1, 'wumei-smart', 'P5FJKZJHIR82GNB2', 'K63C4EA3AI5TER97', 2, '{\"events\": [{\"id\": \"exception\", \"name\": \"设备发生异常\", \"datatype\": {\"type\": \"string\", \"maxLength\": 1024}}, {\"id\": \"height_temperature\", \"name\": \"环境温度过高\", \"datatype\": {\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}}], \"functions\": [{\"id\": \"switch\", \"name\": \"设备开关\", \"isTop\": 1, \"datatype\": {\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}}, {\"id\": \"gear\", \"name\": \"运行档位\", \"isTop\": 1, \"datatype\": {\"type\": \"enum\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}}, {\"id\": \"light_color\", \"name\": \"灯光颜色\", \"isTop\": 0, \"datatype\": {\"type\": \"array\", \"arrayType\": \"int\"}}, {\"id\": \"message\", \"name\": \"屏显消息\", \"isTop\": 0, \"datatype\": {\"type\": \"string\", \"maxLength\": 1024}}, {\"id\": \"report_monitor\", \"name\": \"上报监测数据\", \"isTop\": 0, \"datatype\": {\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}}], \"properties\": [{\"id\": \"temperature\", \"name\": \"空气温度\", \"isTop\": 1, \"datatype\": {\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}, \"isMonitor\": 1}, {\"id\": \"humidity\", \"name\": \"空气湿度\", \"isTop\": 1, \"datatype\": {\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"%\"}, \"isMonitor\": 1}, {\"id\": \"co2\", \"name\": \"二氧化碳\", \"isTop\": 0, \"datatype\": {\"max\": 6000, \"min\": 100, \"step\": 1, \"type\": \"integer\", \"unit\": \"ppm\"}, \"isMonitor\": 1}, {\"id\": \"brightness\", \"name\": \"室内亮度\", \"isTop\": 0, \"datatype\": {\"max\": 10000, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"cd/m2\"}, \"isMonitor\": 1}]}', 1, 1, 3, NULL, '0', '', '2022-03-15 22:18:37', '', NULL, '产品示例'); + +-- ---------------------------- +-- Table structure for iot_scene +-- ---------------------------- +DROP TABLE IF EXISTS `iot_scene`; +CREATE TABLE `iot_scene` ( + `scene_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '场景ID', + `scene_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '场景名称', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名称', + `triggers` json NOT NULL COMMENT '触发器', + `actions` json NOT NULL COMMENT '执行动作', + `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 (`scene_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '场景联动' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_scene +-- ---------------------------- + +-- ---------------------------- +-- 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 NOT NULL COMMENT '数据定义', + `is_Top` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否首页显示(0-否,1-是)', + `is_Monitor` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否实时监测(0-否,1-是)', + `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 (`model_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 22 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '物模型模板' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of iot_things_model +-- ---------------------------- +INSERT INTO `iot_things_model` VALUES (11, '空气温度', 2, 'ESP8266产品示例', 1, 'admin', 'temperature', 1, 'decimal', '{\"max\": 120, \"min\": -20, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}', 1, 1, '0', NULL, '2022-03-15 22:18:46', '', NULL, NULL); +INSERT INTO `iot_things_model` VALUES (12, '空气湿度', 2, 'ESP8266产品示例', 1, 'admin', 'humidity', 1, 'decimal', '{\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"%\"}', 1, 1, '0', NULL, '2022-03-15 22:18:46', '', NULL, NULL); +INSERT INTO `iot_things_model` VALUES (13, '二氧化碳', 2, 'ESP8266产品示例', 1, 'admin', 'co2', 1, 'integer', '{\"max\": 6000, \"min\": 100, \"step\": 1, \"type\": \"integer\", \"unit\": \"ppm\"}', 0, 1, '0', NULL, '2022-03-15 22:18:46', '', NULL, NULL); +INSERT INTO `iot_things_model` VALUES (14, '室内亮度', 2, 'ESP8266产品示例', 1, 'admin', 'brightness', 1, 'integer', '{\"max\": 10000, \"min\": 0, \"step\": 1, \"type\": \"integer\", \"unit\": \"cd/m2\"}', 0, 1, '0', NULL, '2022-03-15 22:18:46', '', NULL, NULL); +INSERT INTO `iot_things_model` VALUES (15, '设备开关', 2, 'ESP8266产品示例', 1, 'admin', 'switch', 2, 'bool', '{\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}', 1, 0, '0', NULL, '2022-03-15 22:18:46', '', NULL, NULL); +INSERT INTO `iot_things_model` VALUES (16, '运行档位', 2, 'ESP8266产品示例', 1, 'admin', 'gear', 2, 'enum', '{\"type\": \"enum\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}', 1, 0, '0', NULL, '2022-03-15 22:18:46', '', NULL, NULL); +INSERT INTO `iot_things_model` VALUES (17, '灯光颜色', 2, 'ESP8266产品示例', 1, 'admin', 'light_color', 2, 'array', '{\"type\": \"array\", \"arrayType\": \"int\"}', 0, 0, '0', NULL, '2022-03-15 22:18:46', '', NULL, NULL); +INSERT INTO `iot_things_model` VALUES (18, '屏显消息', 2, 'ESP8266产品示例', 1, 'admin', 'message', 2, 'string', '{\"type\": \"string\", \"maxLength\": 1024}', 0, 0, '0', NULL, '2022-03-15 22:18:46', '', NULL, NULL); +INSERT INTO `iot_things_model` VALUES (19, '上报监测数据', 2, 'ESP8266产品示例', 1, 'admin', 'report_monitor', 2, 'integer', '{\"max\": 10, \"min\": 1, \"step\": 1, \"type\": \"integer\", \"unit\": \"次数\"}', 0, 0, '0', NULL, '2022-03-15 22:18:46', '', NULL, NULL); +INSERT INTO `iot_things_model` VALUES (20, '环境温度过高', 2, 'ESP8266产品示例', 1, 'admin', 'height_temperature', 3, 'decimal', '{\"max\": 100, \"min\": 0, \"step\": 0.1, \"type\": \"decimal\", \"unit\": \"℃\"}', 0, 0, '0', NULL, '2022-03-15 22:18:46', '', NULL, NULL); +INSERT INTO `iot_things_model` VALUES (21, '设备发生异常', 2, 'ESP8266产品示例', 1, 'admin', 'exception', 3, 'string', '{\"type\": \"string\", \"maxLength\": 1024}', 0, 0, '0', NULL, '2022-03-15 22:18:52', '', 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 NOT NULL COMMENT '数据定义', + `is_sys` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否系统通用(0-否,1-是)', + `is_Top` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否首页显示(0-否,1-是)', + `is_Monitor` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否实时监测(0-否,1-是)', + `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 (`template_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 12 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, '0', '', '2022-03-09 17:41:49', '', '2022-03-09 20:58:06', 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, '0', '', '2022-03-09 17:41:49', '', '2022-03-11 00:11:08', 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, 0, 1, '0', '', '2022-03-09 17:41:49', '', '2022-03-11 00:10:09', 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, 0, 1, '0', '', '2022-03-09 17:41:49', '', '2022-03-11 00:10:17', NULL); +INSERT INTO `iot_things_model_template` VALUES (5, '设备开关', 1, 'admin', 'switch', 2, 'bool', '{\"type\": \"bool\", \"trueText\": \"打开\", \"falseText\": \"关闭\"}', 1, 1, 0, '0', '', '2022-03-09 17:41:49', '', '2022-03-09 17:41:49', NULL); +INSERT INTO `iot_things_model_template` VALUES (6, '运行档位', 1, 'admin', 'gear', 2, 'enum', '{\"type\": \"enum\", \"enumList\": [{\"text\": \"低速档位\", \"value\": \"0\"}, {\"text\": \"中速档位\", \"value\": \"1\"}, {\"text\": \"中高速档位\", \"value\": \"2\"}, {\"text\": \"高速档位\", \"value\": \"3\"}]}', 1, 1, 0, '0', '', '2022-03-09 17:41:49', '', '2022-03-09 17:41:49', NULL); +INSERT INTO `iot_things_model_template` VALUES (7, '灯光颜色', 1, 'admin', 'light_color', 2, 'array', '{\"type\": \"array\", \"arrayType\": \"int\"}', 1, 0, 0, '0', '', '2022-03-09 17:41:49', '', '2022-03-11 00:54:11', NULL); +INSERT INTO `iot_things_model_template` VALUES (8, '屏显消息', 1, 'admin', 'message', 2, 'string', '{\"type\": \"string\", \"maxLength\": 1024}', 1, 0, 0, '0', '', '2022-03-09 17:41:49', '', '2022-03-09 17:41:49', 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', '', '2022-03-09 17:41:49', '', '2022-03-09 17:41:49', 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, '0', '', '2022-03-09 17:41:49', '', '2022-03-11 14:48:21', NULL); +INSERT INTO `iot_things_model_template` VALUES (11, '设备发生异常', 1, 'admin', 'exception', 3, 'string', '{\"type\": \"string\", \"maxLength\": 1024}', 1, 0, 0, '0', '', '2022-03-09 17:41:49', '', '2022-03-09 17:41:49', NULL); + +-- ---------------------------- +-- 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 +-- ---------------------------- + +-- ---------------------------- +-- 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 +-- ---------------------------- + +-- ---------------------------- +-- 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 ('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 +-- ---------------------------- + +-- ---------------------------- +-- 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 +-- ---------------------------- + +-- ---------------------------- +-- 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 = 100 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 (4, '账号自助-验证码开关', 'sys.account.captchaOnOff', 'true', 'Y', 'admin', '2021-12-15 21:36:18', '', NULL, '是否开启验证码功能(true开启,false关闭)'); +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关闭)'); + +-- ---------------------------- +-- 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 = 200 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dept +-- ---------------------------- +INSERT INTO `sys_dept` VALUES (100, 0, '0', '物美智能', 0, '物美', '15888888888', '164770707@qq.com', '0', '0', 'admin', '2021-12-15 21:36:18', '', NULL); +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', '', NULL); +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 = 154 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, 3, '网关设备', '3', 'iot_device_type', NULL, 'default', 'N', '0', 'admin', '2021-12-19 15:04:28', 'admin', '2021-12-19 15:10:30', NULL); +INSERT INTO `sys_dict_data` VALUES (115, 2, '网关子设备', '2', 'iot_device_type', NULL, 'default', 'N', '0', 'admin', '2021-12-19 15:05:52', 'admin', '2021-12-19 15:10:23', 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', '2021-12-19 15:13:44', NULL); +INSERT INTO `sys_dict_data` VALUES (120, 2, '证书', '2', 'iot_vertificate_method', NULL, 'default', 'N', '0', 'admin', '2021-12-19 15:13:26', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (121, 3, 'Http', '3', 'iot_vertificate_method', NULL, 'default', 'N', '0', 'admin', '2021-12-19 15:13:37', '', NULL, 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); + +-- ---------------------------- +-- 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 = 115 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', '2021-12-19 20:56:55', '账号密码、证书、Http'); +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_platform', '0', 'admin', '2022-02-07 20:27:48', 'admin', '2022-02-07 20:28:26', '1=小度,2=天猫精灵,3=小爱,4=其他'); + +-- ---------------------------- +-- 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 = 100 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '定时任务调度表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_job +-- ---------------------------- +INSERT INTO `sys_job` VALUES (1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '0/10 * * * * ?', '3', '1', '1', 'admin', '2021-12-15 21:36:18', 'admin', '2022-01-29 21:48:13', ''); +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 (3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(\'ry\', true, 2000L, 316.50D, 100)', '0/20 * * * * ?', '3', '1', '1', 'admin', '2021-12-15 21:36:18', '', NULL, ''); + +-- ---------------------------- +-- 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 = 1997 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '定时任务调度日志表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_job_log +-- ---------------------------- +INSERT INTO `sys_job_log` VALUES (1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '系统默认(无参) 总共耗时:1毫秒', '0', '', '2022-03-11 17:22:20'); +INSERT INTO `sys_job_log` VALUES (1888, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:00:40'); +INSERT INTO `sys_job_log` VALUES (1889, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:00:50'); +INSERT INTO `sys_job_log` VALUES (1890, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:01:00'); +INSERT INTO `sys_job_log` VALUES (1891, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:01:10'); +INSERT INTO `sys_job_log` VALUES (1892, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:01:20'); +INSERT INTO `sys_job_log` VALUES (1893, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:01:30'); +INSERT INTO `sys_job_log` VALUES (1894, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:01:40'); +INSERT INTO `sys_job_log` VALUES (1895, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:01:50'); +INSERT INTO `sys_job_log` VALUES (1896, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:02:00'); +INSERT INTO `sys_job_log` VALUES (1897, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:02:10'); +INSERT INTO `sys_job_log` VALUES (1898, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:02:20'); +INSERT INTO `sys_job_log` VALUES (1899, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:02:30'); +INSERT INTO `sys_job_log` VALUES (1900, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:02:40'); +INSERT INTO `sys_job_log` VALUES (1901, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:02:50'); +INSERT INTO `sys_job_log` VALUES (1902, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:03:00'); +INSERT INTO `sys_job_log` VALUES (1903, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:03:10'); +INSERT INTO `sys_job_log` VALUES (1904, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:03:20'); +INSERT INTO `sys_job_log` VALUES (1905, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:109毫秒', '0', '', '2022-03-16 02:04:45'); +INSERT INTO `sys_job_log` VALUES (1906, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:107毫秒', '0', '', '2022-03-16 02:04:52'); +INSERT INTO `sys_job_log` VALUES (1907, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:101毫秒', '0', '', '2022-03-16 02:05:02'); +INSERT INTO `sys_job_log` VALUES (1908, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:96毫秒', '0', '', '2022-03-16 02:05:12'); +INSERT INTO `sys_job_log` VALUES (1909, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:87毫秒', '0', '', '2022-03-16 02:05:22'); +INSERT INTO `sys_job_log` VALUES (1910, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:112毫秒', '0', '', '2022-03-16 02:05:32'); +INSERT INTO `sys_job_log` VALUES (1911, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:107毫秒', '0', '', '2022-03-16 02:05:42'); +INSERT INTO `sys_job_log` VALUES (1912, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:05:50'); +INSERT INTO `sys_job_log` VALUES (1913, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:06:00'); +INSERT INTO `sys_job_log` VALUES (1914, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:06:10'); +INSERT INTO `sys_job_log` VALUES (1915, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:06:20'); +INSERT INTO `sys_job_log` VALUES (1916, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:06:30'); +INSERT INTO `sys_job_log` VALUES (1917, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:06:40'); +INSERT INTO `sys_job_log` VALUES (1918, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:10毫秒', '0', '', '2022-03-16 02:06:50'); +INSERT INTO `sys_job_log` VALUES (1919, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:07:00'); +INSERT INTO `sys_job_log` VALUES (1920, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:07:10'); +INSERT INTO `sys_job_log` VALUES (1921, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:07:20'); +INSERT INTO `sys_job_log` VALUES (1922, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:07:30'); +INSERT INTO `sys_job_log` VALUES (1923, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:07:40'); +INSERT INTO `sys_job_log` VALUES (1924, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:07:50'); +INSERT INTO `sys_job_log` VALUES (1925, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:08:00'); +INSERT INTO `sys_job_log` VALUES (1926, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:08:10'); +INSERT INTO `sys_job_log` VALUES (1927, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:08:20'); +INSERT INTO `sys_job_log` VALUES (1928, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:08:30'); +INSERT INTO `sys_job_log` VALUES (1929, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:08:40'); +INSERT INTO `sys_job_log` VALUES (1930, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:08:50'); +INSERT INTO `sys_job_log` VALUES (1931, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:09:00'); +INSERT INTO `sys_job_log` VALUES (1932, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:09:10'); +INSERT INTO `sys_job_log` VALUES (1933, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:09:20'); +INSERT INTO `sys_job_log` VALUES (1934, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:09:30'); +INSERT INTO `sys_job_log` VALUES (1935, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:09:40'); +INSERT INTO `sys_job_log` VALUES (1936, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:09:50'); +INSERT INTO `sys_job_log` VALUES (1937, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:10:00'); +INSERT INTO `sys_job_log` VALUES (1938, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:10:10'); +INSERT INTO `sys_job_log` VALUES (1939, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:10:20'); +INSERT INTO `sys_job_log` VALUES (1940, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:10:30'); +INSERT INTO `sys_job_log` VALUES (1941, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:10:40'); +INSERT INTO `sys_job_log` VALUES (1942, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:10:50'); +INSERT INTO `sys_job_log` VALUES (1943, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:11:00'); +INSERT INTO `sys_job_log` VALUES (1944, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:11:10'); +INSERT INTO `sys_job_log` VALUES (1945, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:11:20'); +INSERT INTO `sys_job_log` VALUES (1946, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:11:30'); +INSERT INTO `sys_job_log` VALUES (1947, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:11:40'); +INSERT INTO `sys_job_log` VALUES (1948, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:11:50'); +INSERT INTO `sys_job_log` VALUES (1949, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:12:00'); +INSERT INTO `sys_job_log` VALUES (1950, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:12:10'); +INSERT INTO `sys_job_log` VALUES (1951, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:12:20'); +INSERT INTO `sys_job_log` VALUES (1952, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:12:30'); +INSERT INTO `sys_job_log` VALUES (1953, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:12:40'); +INSERT INTO `sys_job_log` VALUES (1954, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:12:50'); +INSERT INTO `sys_job_log` VALUES (1955, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:13:00'); +INSERT INTO `sys_job_log` VALUES (1956, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:13:10'); +INSERT INTO `sys_job_log` VALUES (1957, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:13:20'); +INSERT INTO `sys_job_log` VALUES (1958, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:13:30'); +INSERT INTO `sys_job_log` VALUES (1959, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:13:40'); +INSERT INTO `sys_job_log` VALUES (1960, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:13:50'); +INSERT INTO `sys_job_log` VALUES (1961, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:14:00'); +INSERT INTO `sys_job_log` VALUES (1962, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:14:10'); +INSERT INTO `sys_job_log` VALUES (1963, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:14:20'); +INSERT INTO `sys_job_log` VALUES (1964, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:14:30'); +INSERT INTO `sys_job_log` VALUES (1965, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:14:40'); +INSERT INTO `sys_job_log` VALUES (1966, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:14:50'); +INSERT INTO `sys_job_log` VALUES (1967, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:15:00'); +INSERT INTO `sys_job_log` VALUES (1968, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:15:10'); +INSERT INTO `sys_job_log` VALUES (1969, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:15:20'); +INSERT INTO `sys_job_log` VALUES (1970, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:15:30'); +INSERT INTO `sys_job_log` VALUES (1971, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:15:40'); +INSERT INTO `sys_job_log` VALUES (1972, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:15:50'); +INSERT INTO `sys_job_log` VALUES (1973, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:16:00'); +INSERT INTO `sys_job_log` VALUES (1974, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:16:10'); +INSERT INTO `sys_job_log` VALUES (1975, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:16:20'); +INSERT INTO `sys_job_log` VALUES (1976, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:11毫秒', '0', '', '2022-03-16 02:16:30'); +INSERT INTO `sys_job_log` VALUES (1977, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:16:40'); +INSERT INTO `sys_job_log` VALUES (1978, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:16:50'); +INSERT INTO `sys_job_log` VALUES (1979, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:17:00'); +INSERT INTO `sys_job_log` VALUES (1980, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:8毫秒', '0', '', '2022-03-16 02:17:10'); +INSERT INTO `sys_job_log` VALUES (1981, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:17:20'); +INSERT INTO `sys_job_log` VALUES (1982, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:17:30'); +INSERT INTO `sys_job_log` VALUES (1983, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:17:40'); +INSERT INTO `sys_job_log` VALUES (1984, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:17:50'); +INSERT INTO `sys_job_log` VALUES (1985, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:18:00'); +INSERT INTO `sys_job_log` VALUES (1986, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:18:10'); +INSERT INTO `sys_job_log` VALUES (1987, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:18:20'); +INSERT INTO `sys_job_log` VALUES (1988, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:18:30'); +INSERT INTO `sys_job_log` VALUES (1989, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:18:40'); +INSERT INTO `sys_job_log` VALUES (1990, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:18:50'); +INSERT INTO `sys_job_log` VALUES (1991, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:19:00'); +INSERT INTO `sys_job_log` VALUES (1992, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:19:10'); +INSERT INTO `sys_job_log` VALUES (1993, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:19:20'); +INSERT INTO `sys_job_log` VALUES (1994, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:19:30'); +INSERT INTO `sys_job_log` VALUES (1995, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:6毫秒', '0', '', '2022-03-16 02:19:40'); +INSERT INTO `sys_job_log` VALUES (1996, '定时上报监测数据', 'DEFAULT', 'ESP8266开发板', '定时上报监测数据 总共耗时:7毫秒', '0', '', '2022-03-16 02:19:50'); + +-- ---------------------------- +-- 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 = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统访问记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_logininfor +-- ---------------------------- +INSERT INTO `sys_logininfor` VALUES (6, 'admin', '127.0.0.1', '内网IP', 'Chrome 9', 'Windows 10', '0', '退出成功', '2022-03-16 02:26:32'); +INSERT INTO `sys_logininfor` VALUES (7, 'admin', '127.0.0.1', '内网IP', 'Chrome 9', 'Windows 10', '0', '登录成功', '2022-03-16 02:26:44'); + +-- ---------------------------- +-- 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 = 2111 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 2, 'system', NULL, '', 1, 0, 'M', '0', '0', '', 'system', 'admin', '2021-12-15 21:36:18', 'admin', '2022-02-26 00:46:20', '系统管理目录'); +INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 3, 'monitor', NULL, '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', '2021-12-15 21:36:18', 'admin', '2022-02-26 00:43:05', '系统监控目录'); +INSERT INTO `sys_menu` VALUES (3, '系统工具', 0, 4, 'tool', NULL, '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', '2021-12-15 21:36:18', 'admin', '2022-02-26 00:42:59', '系统工具目录'); +INSERT INTO `sys_menu` VALUES (4, '物美智能', 0, 5, 'http://wumei.live', NULL, '', 0, 0, 'M', '0', '0', '', 'guide', 'admin', '2021-12-15 21:36:18', 'admin', '2022-02-26 00:42:48', '若依官网地址'); +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 (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 (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 (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 (2007, '设备管理', 2000, 6, '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 (2013, '产品固件', 2000, 4, 'firmware', 'iot/firmware/index', NULL, 1, 0, 'C', '0', '0', 'iot:firmware:list', 'firmware', 'admin', '2021-12-16 00:40:20', 'admin', '2021-12-26 23:56:42', '产品固件菜单'); +INSERT INTO `sys_menu` VALUES (2014, '产品固件查询', 2013, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:firmware:query', '#', 'admin', '2021-12-16 00:40:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2015, '产品固件新增', 2013, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:firmware:add', '#', 'admin', '2021-12-16 00:40:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2016, '产品固件修改', 2013, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:firmware:edit', '#', 'admin', '2021-12-16 00:40:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2017, '产品固件删除', 2013, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:firmware:remove', '#', 'admin', '2021-12-16 00:40:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2018, '产品固件导出', 2013, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:firmware:export', '#', 'admin', '2021-12-16 00:40:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2019, '设备分组', 2000, 5, '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 (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 (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 (2067, '设备告警', 2000, 7, 'alertLog', 'iot/alertLog/index', NULL, 1, 0, 'C', '1', '0', 'iot:alertLog:list', 'alert', 'admin', '2022-01-13 17:16:15', 'admin', '2022-03-01 17:48:57', '设备告警菜单'); +INSERT INTO `sys_menu` VALUES (2068, '设备告警查询', 2067, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:alertLog:query', '#', 'admin', '2022-01-13 17:16:15', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2069, '设备告警新增', 2067, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:alertLog:add', '#', 'admin', '2022-01-13 17:16:15', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2070, '设备告警修改', 2067, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:alertLog:edit', '#', 'admin', '2022-01-13 17:16:15', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2071, '设备告警删除', 2067, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:alertLog:remove', '#', 'admin', '2022-01-13 17:16:15', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2072, '设备告警导出', 2067, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:alertLog:export', '#', 'admin', '2022-01-13 17:16:15', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2085, '场景联动', 2000, 8, 'scene', 'iot/scene/index', NULL, 1, 0, 'C', '1', '0', 'iot:scene:list', 'scene', 'admin', '2022-01-13 17:16:45', 'admin', '2022-03-01 17:48:49', '场景联动菜单'); +INSERT INTO `sys_menu` VALUES (2086, '场景联动查询', 2085, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:scene:query', '#', 'admin', '2022-01-13 17:16:45', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2087, '场景联动新增', 2085, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:scene:add', '#', 'admin', '2022-01-13 17:16:45', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2088, '场景联动修改', 2085, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:scene:edit', '#', 'admin', '2022-01-13 17:16:45', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2089, '场景联动删除', 2085, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:scene:remove', '#', 'admin', '2022-01-13 17:16:45', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2090, '场景联动导出', 2085, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:scene:export', '#', 'admin', '2022-01-13 17:16:45', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2098, '云云对接', 2000, 9, 'clientDetails', 'iot/clientDetails/index', NULL, 1, 0, 'C', '1', '0', 'iot:clientDetails:list', 'cloud', 'admin', '2022-02-07 22:08:58', 'admin', '2022-03-01 17:48:37', '云云对接菜单'); +INSERT INTO `sys_menu` VALUES (2099, '云云对接查询', 2098, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:clientDetails:query', '#', 'admin', '2022-02-07 22:08:59', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2100, '云云对接新增', 2098, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:clientDetails:add', '#', 'admin', '2022-02-07 22:08:59', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2101, '云云对接修改', 2098, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:clientDetails:edit', '#', 'admin', '2022-02-07 22:08:59', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2102, '云云对接删除', 2098, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:clientDetails:remove', '#', 'admin', '2022-02-07 22:08:59', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2103, '云云对接导出', 2098, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:clientDetails:export', '#', 'admin', '2022-02-07 22:08:59', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2104, 'EMQ管理', 0, 1, 'emqx', NULL, NULL, 1, 0, 'M', '0', '0', '', 'mq', 'admin', '2022-02-26 00:42:12', 'admin', '2022-03-04 14:08:55', ''); +INSERT INTO `sys_menu` VALUES (2105, '客户端', 2104, 1, 'client', 'iot/emqx/client', NULL, 1, 0, 'C', '0', '0', 'monitor:online:list', 'client', 'admin', '2022-02-26 00:45:39', 'admin', '2022-03-04 14:09:23', ''); +INSERT INTO `sys_menu` VALUES (2106, '消息主题', 2104, 3, 'topic', 'iot/emqx/topic', NULL, 1, 0, 'C', '0', '0', 'monitor:online:list', 'topic', 'admin', '2022-02-27 16:31:17', 'admin', '2022-03-04 14:10:00', ''); +INSERT INTO `sys_menu` VALUES (2107, '消息订阅', 2104, 4, 'subscribe', 'iot/emqx/subscribe', NULL, 1, 0, 'C', '0', '0', 'monitor:online:list', 'subscribe', 'admin', '2022-02-27 16:32:21', 'admin', '2022-03-04 14:10:17', ''); +INSERT INTO `sys_menu` VALUES (2108, '插件管理', 2104, 5, 'plugin', 'iot/emqx/plugin', NULL, 1, 0, 'C', '0', '0', 'monitor:online:list', 'plugin', 'admin', '2022-02-27 19:10:40', 'admin', '2022-03-04 14:10:28', ''); +INSERT INTO `sys_menu` VALUES (2109, '监听器', 2104, 2, 'listener', 'iot/emqx/listener', NULL, 1, 0, 'C', '0', '0', 'monitor:online:list', 'listener', 'admin', '2022-02-27 19:52:08', 'admin', '2022-03-04 14:09:44', ''); +INSERT INTO `sys_menu` VALUES (2110, 'EMQ操作', 2104, 6, '', NULL, NULL, 1, 0, 'F', '0', '0', 'monitor:online:edit', '#', 'admin', '2022-03-09 16:58:19', '', NULL, ''); + +-- ---------------------------- +-- 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 = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知公告表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_notice +-- ---------------------------- +INSERT INTO `sys_notice` VALUES (1, '温馨提醒:2021-03-15 wumei-smart新版本发布了', '2', 0xE696B0E78988E69CACE58685E5AEB9, '0', 'admin', '2021-12-15 21:36:18', 'admin', '2022-02-06 17:34:05', '管理员'); +INSERT INTO `sys_notice` VALUES (2, '维护通知:2021-03151 wumei-smart系统凌晨2点-4点维护', '1', 0xE7BBB4E68AA4E58685E5AEB9, '0', 'admin', '2021-12-15 21:36:18', '', NULL, '管理员'); + +-- ---------------------------- +-- 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 '日志主键', + `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 = 50 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '操作日志记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_oper_log +-- ---------------------------- +INSERT INTO `sys_oper_log` VALUES (46, '定时任务', 2, 'com.ruoyi.iot.controller.DeviceJobController.edit()', 'PUT', 1, 'admin', NULL, '/iot/job', '127.0.0.1', '内网IP', '{\"isAdvance\":0,\"concurrent\":\"1\",\"remark\":\"\",\"deviceId\":3,\"deviceName\":\"ESP8266开发板\",\"productName\":\"ESP8266产品示例\",\"nextValidTime\":1647396000000,\"updateBy\":\"admin\",\"jobType\":1,\"jobName\":\"定时上报监测数据\",\"serialNumber\":\"D6329VL54419L1Y0\",\"productId\":2,\"jobGroup\":\"DEFAULT\",\"params\":{},\"cronExpression\":\"0 00 10 ? * 1,2,3,4,5,6,7\",\"jobId\":1,\"createBy\":\"admin\",\"createTime\":1647355122000,\"misfirePolicy\":\"2\",\"actions\":\"[{\\\"id\\\":\\\"report_monitor\\\",\\\"name\\\":\\\"上报监测数据\\\",\\\"type\\\":2,\\\"value\\\":\\\"1\\\",\\\"source\\\":2,\\\"deviceId\\\":3,\\\"alertName\\\":\\\"\\\",\\\"alertLevel\\\":1,\\\"deviceName\\\":\\\"ESP8266开发板\\\"},{\\\"id\\\":\\\"switch\\\",\\\"name\\\":\\\"设备开关\\\",\\\"value\\\":\\\"1\\\",\\\"type\\\":2,\\\"source\\\":2,\\\"deviceId\\\":3,\\\"deviceName\\\":\\\"ESP8266开发板\\\",\\\"alertName\\\":\\\"\\\",\\\"alertLevel\\\":1},{\\\"id\\\":\\\"gear\\\",\\\"name\\\":\\\"运行档位\\\",\\\"value\\\":\\\"3\\\",\\\"type\\\":2,\\\"source\\\":2,\\\"deviceId\\\":3,\\\"deviceName\\\":\\\"ESP8266开发板\\\",\\\"alertName\\\":\\\"\\\",\\\"alertLevel\\\":1}]\",\"status\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2022-03-16 02:03:32'); +INSERT INTO `sys_oper_log` VALUES (47, '定时任务', 2, 'com.ruoyi.iot.controller.DeviceJobController.edit()', 'PUT', 1, 'admin', NULL, '/iot/job', '127.0.0.1', '内网IP', '{\"isAdvance\":1,\"concurrent\":\"1\",\"remark\":\"\",\"deviceId\":3,\"deviceName\":\"ESP8266开发板\",\"productName\":\"ESP8266产品示例\",\"nextValidTime\":1647367490000,\"updateBy\":\"admin\",\"jobType\":1,\"jobName\":\"定时上报监测数据\",\"serialNumber\":\"D6329VL54419L1Y0\",\"productId\":2,\"jobGroup\":\"DEFAULT\",\"params\":{},\"cronExpression\":\"0/10 * * * * ?\",\"jobId\":1,\"createBy\":\"admin\",\"createTime\":1647355122000,\"misfirePolicy\":\"2\",\"actions\":\"[{\\\"id\\\":\\\"report_monitor\\\",\\\"name\\\":\\\"上报监测数据\\\",\\\"type\\\":2,\\\"value\\\":\\\"1\\\",\\\"source\\\":2,\\\"deviceId\\\":3,\\\"alertName\\\":\\\"\\\",\\\"alertLevel\\\":1,\\\"deviceName\\\":\\\"ESP8266开发板\\\"},{\\\"id\\\":\\\"switch\\\",\\\"name\\\":\\\"设备开关\\\",\\\"type\\\":2,\\\"value\\\":\\\"1\\\",\\\"source\\\":2,\\\"deviceId\\\":3,\\\"alertName\\\":\\\"\\\",\\\"alertLevel\\\":1,\\\"deviceName\\\":\\\"ESP8266开发板\\\"},{\\\"id\\\":\\\"gear\\\",\\\"name\\\":\\\"运行档位\\\",\\\"type\\\":2,\\\"value\\\":\\\"3\\\",\\\"source\\\":2,\\\"deviceId\\\":3,\\\"alertName\\\":\\\"\\\",\\\"alertLevel\\\":1,\\\"deviceName\\\":\\\"ESP8266开发板\\\"}]\",\"status\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2022-03-16 02:04:43'); +INSERT INTO `sys_oper_log` VALUES (48, '定时任务', 2, 'com.ruoyi.iot.controller.DeviceJobController.edit()', 'PUT', 1, 'admin', NULL, '/iot/job', '127.0.0.1', '内网IP', '{\"isAdvance\":0,\"concurrent\":\"1\",\"remark\":\"\",\"deviceId\":3,\"deviceName\":\"ESP8266开发板\",\"productName\":\"ESP8266产品示例\",\"nextValidTime\":1647397200000,\"updateBy\":\"admin\",\"jobType\":1,\"jobName\":\"定时上报监测数据\",\"serialNumber\":\"D6329VL54419L1Y0\",\"productId\":2,\"jobGroup\":\"DEFAULT\",\"params\":{},\"cronExpression\":\"0 20 10 ? * 1,2,3,4,5,6,7\",\"jobId\":1,\"createBy\":\"admin\",\"createTime\":1647355122000,\"misfirePolicy\":\"2\",\"actions\":\"[{\\\"id\\\":\\\"report_monitor\\\",\\\"name\\\":\\\"上报监测数据\\\",\\\"type\\\":2,\\\"value\\\":\\\"1\\\",\\\"source\\\":2,\\\"deviceId\\\":3,\\\"alertName\\\":\\\"\\\",\\\"alertLevel\\\":1,\\\"deviceName\\\":\\\"ESP8266开发板\\\"},{\\\"id\\\":\\\"switch\\\",\\\"name\\\":\\\"设备开关\\\",\\\"type\\\":2,\\\"value\\\":\\\"1\\\",\\\"source\\\":2,\\\"deviceId\\\":3,\\\"alertName\\\":\\\"\\\",\\\"alertLevel\\\":1,\\\"deviceName\\\":\\\"ESP8266开发板\\\"},{\\\"id\\\":\\\"gear\\\",\\\"name\\\":\\\"运行档位\\\",\\\"type\\\":2,\\\"value\\\":\\\"3\\\",\\\"source\\\":2,\\\"deviceId\\\":3,\\\"alertName\\\":\\\"\\\",\\\"alertLevel\\\":1,\\\"deviceName\\\":\\\"ESP8266开发板\\\"}]\",\"status\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2022-03-16 02:19:59'); +INSERT INTO `sys_oper_log` VALUES (49, '个人信息', 2, 'com.ruoyi.web.controller.system.SysProfileController.updatePwd()', 'PUT', 1, 'admin', NULL, '/system/user/profile/updatePwd', '127.0.0.1', '内网IP', 'cxw3513090 admin123', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2022-03-16 02:26:26'); + +-- ---------------------------- +-- 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 = 104 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', '2021-12-17 15:38:37', '不能删除,管理产品和设备的用户分配该权限'); +INSERT INTO `sys_role` VALUES (3, '普通用户', 'common', 3, '5', 1, 1, '0', '0', 'admin', '2021-12-15 21:36:18', 'admin', '2021-12-25 17:29:55', '普通角色,不能删除,设备的最终用户,只能管理设备'); +INSERT INTO `sys_role` VALUES (4, '游客', 'visitor', 4, '5', 1, 1, '0', '0', 'admin', '2021-12-16 16:44:30', 'admin', '2022-03-09 17:03:25', '只能查询系统数据'); + +-- ---------------------------- +-- 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, 2000); +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, 2031); +INSERT INTO `sys_role_menu` VALUES (2, 2032); +INSERT INTO `sys_role_menu` VALUES (2, 2033); +INSERT INTO `sys_role_menu` VALUES (2, 2034); +INSERT INTO `sys_role_menu` VALUES (2, 2035); +INSERT INTO `sys_role_menu` VALUES (2, 2036); +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, 2031); +INSERT INTO `sys_role_menu` VALUES (3, 2032); +INSERT INTO `sys_role_menu` VALUES (3, 2033); +INSERT INTO `sys_role_menu` VALUES (3, 2034); +INSERT INTO `sys_role_menu` VALUES (3, 2035); +INSERT INTO `sys_role_menu` VALUES (3, 2036); +INSERT INTO `sys_role_menu` VALUES (3, 2061); +INSERT INTO `sys_role_menu` VALUES (3, 2062); +INSERT INTO `sys_role_menu` VALUES (3, 2063); +INSERT INTO `sys_role_menu` VALUES (3, 2064); +INSERT INTO `sys_role_menu` VALUES (3, 2065); +INSERT INTO `sys_role_menu` VALUES (3, 2066); +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, 2004); +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, 2010); +INSERT INTO `sys_role_menu` VALUES (4, 2013); +INSERT INTO `sys_role_menu` VALUES (4, 2014); +INSERT INTO `sys_role_menu` VALUES (4, 2015); +INSERT INTO `sys_role_menu` VALUES (4, 2016); +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, 2022); +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, 2046); +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, 2052); +INSERT INTO `sys_role_menu` VALUES (4, 2067); +INSERT INTO `sys_role_menu` VALUES (4, 2068); +INSERT INTO `sys_role_menu` VALUES (4, 2069); +INSERT INTO `sys_role_menu` VALUES (4, 2070); +INSERT INTO `sys_role_menu` VALUES (4, 2085); +INSERT INTO `sys_role_menu` VALUES (4, 2086); +INSERT INTO `sys_role_menu` VALUES (4, 2087); +INSERT INTO `sys_role_menu` VALUES (4, 2088); +INSERT INTO `sys_role_menu` VALUES (4, 2098); +INSERT INTO `sys_role_menu` VALUES (4, 2099); +INSERT INTO `sys_role_menu` VALUES (4, 2100); +INSERT INTO `sys_role_menu` VALUES (4, 2101); +INSERT INTO `sys_role_menu` VALUES (4, 2104); +INSERT INTO `sys_role_menu` VALUES (4, 2105); +INSERT INTO `sys_role_menu` VALUES (4, 2106); +INSERT INTO `sys_role_menu` VALUES (4, 2107); +INSERT INTO `sys_role_menu` VALUES (4, 2108); +INSERT INTO `sys_role_menu` VALUES (4, 2109); +INSERT INTO `sys_role_menu` VALUES (100, 2000); +INSERT INTO `sys_role_menu` VALUES (100, 2001); +INSERT INTO `sys_role_menu` VALUES (100, 2002); +INSERT INTO `sys_role_menu` VALUES (100, 2003); +INSERT INTO `sys_role_menu` VALUES (100, 2004); +INSERT INTO `sys_role_menu` VALUES (100, 2005); +INSERT INTO `sys_role_menu` VALUES (100, 2006); +INSERT INTO `sys_role_menu` VALUES (100, 2007); +INSERT INTO `sys_role_menu` VALUES (100, 2008); +INSERT INTO `sys_role_menu` VALUES (100, 2009); +INSERT INTO `sys_role_menu` VALUES (100, 2010); +INSERT INTO `sys_role_menu` VALUES (100, 2011); +INSERT INTO `sys_role_menu` VALUES (100, 2012); +INSERT INTO `sys_role_menu` VALUES (100, 2013); +INSERT INTO `sys_role_menu` VALUES (100, 2014); +INSERT INTO `sys_role_menu` VALUES (100, 2015); +INSERT INTO `sys_role_menu` VALUES (100, 2016); +INSERT INTO `sys_role_menu` VALUES (100, 2017); +INSERT INTO `sys_role_menu` VALUES (100, 2018); +INSERT INTO `sys_role_menu` VALUES (100, 2019); +INSERT INTO `sys_role_menu` VALUES (100, 2020); +INSERT INTO `sys_role_menu` VALUES (100, 2021); +INSERT INTO `sys_role_menu` VALUES (100, 2022); +INSERT INTO `sys_role_menu` VALUES (100, 2023); +INSERT INTO `sys_role_menu` VALUES (100, 2024); +INSERT INTO `sys_role_menu` VALUES (100, 2031); +INSERT INTO `sys_role_menu` VALUES (100, 2032); +INSERT INTO `sys_role_menu` VALUES (100, 2033); +INSERT INTO `sys_role_menu` VALUES (100, 2034); +INSERT INTO `sys_role_menu` VALUES (100, 2035); +INSERT INTO `sys_role_menu` VALUES (100, 2036); +INSERT INTO `sys_role_menu` VALUES (100, 2037); +INSERT INTO `sys_role_menu` VALUES (100, 2038); +INSERT INTO `sys_role_menu` VALUES (100, 2039); +INSERT INTO `sys_role_menu` VALUES (100, 2040); +INSERT INTO `sys_role_menu` VALUES (100, 2041); +INSERT INTO `sys_role_menu` VALUES (100, 2042); +INSERT INTO `sys_role_menu` VALUES (100, 2043); +INSERT INTO `sys_role_menu` VALUES (100, 2044); +INSERT INTO `sys_role_menu` VALUES (100, 2045); +INSERT INTO `sys_role_menu` VALUES (100, 2046); +INSERT INTO `sys_role_menu` VALUES (100, 2047); +INSERT INTO `sys_role_menu` VALUES (100, 2048); +INSERT INTO `sys_role_menu` VALUES (100, 2049); +INSERT INTO `sys_role_menu` VALUES (100, 2050); +INSERT INTO `sys_role_menu` VALUES (100, 2051); +INSERT INTO `sys_role_menu` VALUES (100, 2052); +INSERT INTO `sys_role_menu` VALUES (100, 2053); +INSERT INTO `sys_role_menu` VALUES (100, 2054); +INSERT INTO `sys_role_menu` VALUES (100, 2061); +INSERT INTO `sys_role_menu` VALUES (100, 2062); +INSERT INTO `sys_role_menu` VALUES (100, 2063); +INSERT INTO `sys_role_menu` VALUES (100, 2064); +INSERT INTO `sys_role_menu` VALUES (100, 2065); +INSERT INTO `sys_role_menu` VALUES (100, 2066); +INSERT INTO `sys_role_menu` VALUES (101, 1); +INSERT INTO `sys_role_menu` VALUES (101, 2); +INSERT INTO `sys_role_menu` VALUES (101, 100); +INSERT INTO `sys_role_menu` VALUES (101, 101); +INSERT INTO `sys_role_menu` VALUES (101, 102); +INSERT INTO `sys_role_menu` VALUES (101, 103); +INSERT INTO `sys_role_menu` VALUES (101, 104); +INSERT INTO `sys_role_menu` VALUES (101, 107); +INSERT INTO `sys_role_menu` VALUES (101, 109); +INSERT INTO `sys_role_menu` VALUES (101, 110); +INSERT INTO `sys_role_menu` VALUES (101, 1001); +INSERT INTO `sys_role_menu` VALUES (101, 1008); +INSERT INTO `sys_role_menu` VALUES (101, 1013); +INSERT INTO `sys_role_menu` VALUES (101, 1017); +INSERT INTO `sys_role_menu` VALUES (101, 1021); +INSERT INTO `sys_role_menu` VALUES (101, 1036); +INSERT INTO `sys_role_menu` VALUES (101, 1046); +INSERT INTO `sys_role_menu` VALUES (101, 1049); +INSERT INTO `sys_role_menu` VALUES (101, 2000); +INSERT INTO `sys_role_menu` VALUES (101, 2001); +INSERT INTO `sys_role_menu` VALUES (101, 2002); +INSERT INTO `sys_role_menu` VALUES (101, 2007); +INSERT INTO `sys_role_menu` VALUES (101, 2008); +INSERT INTO `sys_role_menu` VALUES (101, 2013); +INSERT INTO `sys_role_menu` VALUES (101, 2014); +INSERT INTO `sys_role_menu` VALUES (101, 2019); +INSERT INTO `sys_role_menu` VALUES (101, 2020); +INSERT INTO `sys_role_menu` VALUES (101, 2031); +INSERT INTO `sys_role_menu` VALUES (101, 2032); +INSERT INTO `sys_role_menu` VALUES (101, 2037); +INSERT INTO `sys_role_menu` VALUES (101, 2038); +INSERT INTO `sys_role_menu` VALUES (101, 2043); +INSERT INTO `sys_role_menu` VALUES (101, 2044); +INSERT INTO `sys_role_menu` VALUES (101, 2049); +INSERT INTO `sys_role_menu` VALUES (101, 2050); +INSERT INTO `sys_role_menu` VALUES (101, 2061); +INSERT INTO `sys_role_menu` VALUES (101, 2062); + +-- ---------------------------- +-- 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 = 119 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$0Duw0QB6s7YnQEaNSdSVWeXHMmSa090pG15ZXpf.CQEzEhgxyr7IO', '0', '0', '127.0.0.1', '2022-03-16 02:26:44', 'admin', '2021-12-15 21:36:18', '', '2022-03-16 02:26:44', '管理员'); +INSERT INTO `sys_user` VALUES (2, 100, 'wumei-tenant', '物美智能租户', '00', '15666666@qq.com', '15666666666', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '27.23.210.211', '2022-01-15 21:00:33', 'admin', '2021-12-15 21:36:18', 'admin', '2022-03-09 17:04:33', '租户'); +INSERT INTO `sys_user` VALUES (118, 100, 'wumei', '游客账号', '00', '', '', '0', '', '$2a$10$kKeZptrTnSlm0fencX4U2eq.QiaukDs.DckiUsMCwVTxh0IS2LRQ.', '0', '0', '127.0.0.1', '2022-03-15 23:25:38', 'admin', '2022-03-09 16:49:19', 'admin', '2022-03-15 23:25:38', 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); + +-- ---------------------------- +-- 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 (118, 4); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/springboot/wumei-admin/pom.xml b/springboot/wumei-admin/pom.xml new file mode 100644 index 00000000..69a93079 --- /dev/null +++ b/springboot/wumei-admin/pom.xml @@ -0,0 +1,102 @@ + + + + wumei + com.wumei + 3.8.0 + + 4.0.0 + war + wumei-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.wumei + wumei-framework + + + + + com.wumei + wumei-quartz + + + + + com.wumei + wumei-generator + + + + + com.wumei + wumei-iot + + + + + + + + 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} + + + \ No newline at end of file diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/WumeiSmartApplication.java b/springboot/wumei-admin/src/main/java/com/ruoyi/WumeiSmartApplication.java new file mode 100644 index 00000000..362ddb85 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/WumeiSmartApplication.java @@ -0,0 +1,30 @@ +package com.ruoyi; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.core.annotation.Order; + +/** + * 启动程序 + * + * @author ruoyi + */ +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) +public class WumeiSmartApplication +{ + public static void main(String[] args) + { + // System.setProperty("spring.devtools.restart.enabled", "false"); + SpringApplication.run(WumeiSmartApplication.class, args); + System.out.println(" _ _ \n" + + " (_) | | \n" + + "__ ___ _ _ __ ___ ___ _ ___ _ __ ___ __ _ _ __| |_ \n" + + "\\ \\ /\\ / / | | | '_ ` _ \\ / _ \\ | / __| '_ ` _ \\ / _` | '__| __| \n" + + " \\ V V /| |_| | | | | | | __/ | \\__ \\ | | | | | (_| | | | |_ \n" + + " \\_/\\_/ \\__,_|_| |_| |_|\\___|_| |___/_| |_| |_|\\__,_|_| \\__| \n"); + System.out.println( + " ----------开源生活物联网平台-----------\n" + + " =========wumei-smart启动成功=========\n"); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/WumeiSmartServletInitializer.java b/springboot/wumei-admin/src/main/java/com/ruoyi/WumeiSmartServletInitializer.java new file mode 100644 index 00000000..7412edb1 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/WumeiSmartServletInitializer.java @@ -0,0 +1,18 @@ +package com.ruoyi; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * web容器中进行部署 + * + * @author ruoyi + */ +public class WumeiSmartServletInitializer extends SpringBootServletInitializer +{ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) + { + return application.sources(WumeiSmartApplication.class); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java new file mode 100644 index 00000000..6bd8ddd6 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java @@ -0,0 +1,93 @@ +package com.ruoyi.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 com.ruoyi.common.config.RuoYiConfig; +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.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.sign.Base64; +import com.ruoyi.common.utils.uuid.IdUtils; +import com.ruoyi.system.service.ISysConfigService; + +/** + * 验证码操作处理 + * + * @author ruoyi + */ +@RestController +public class CaptchaController +{ + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysConfigService configService; + /** + * 生成验证码 + */ + @GetMapping("/captchaImage") + public AjaxResult getCode(HttpServletResponse response) throws IOException + { + AjaxResult ajax = AjaxResult.success(); + boolean captchaOnOff = configService.selectCaptchaOnOff(); + ajax.put("captchaOnOff", captchaOnOff); + if (!captchaOnOff) + { + return ajax; + } + + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(); + String verifyKey = Constants.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/wumei-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java new file mode 100644 index 00000000..e0c728fa --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java @@ -0,0 +1,118 @@ +package com.ruoyi.web.controller.common; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +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.RestController; +import org.springframework.web.multipart.MultipartFile; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.framework.config.ServerConfig; + +/** + * 通用请求处理 + * + * @author ruoyi + */ +@RestController +public class CommonController +{ + private static final Logger log = LoggerFactory.getLogger(CommonController.class); + + @Autowired + private ServerConfig serverConfig; + + /** + * 通用下载请求 + * + * @param fileName 文件名称 + * @param delete 是否删除 + */ + @GetMapping("common/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); + } + } + + /** + * 通用上传请求 + */ + @PostMapping("/common/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("fileName", fileName); + ajax.put("url", url); + return ajax; + } + catch (Exception e) + { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 本地资源通用下载 + */ + @GetMapping("/common/download/resource") + 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/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java new file mode 100644 index 00000000..7b97de24 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/CacheController.java @@ -0,0 +1,53 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.StringUtils; + +/** + * 缓存监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/cache") +public class CacheController +{ + @Autowired + private RedisTemplate redisTemplate; + + @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); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java new file mode 100644 index 00000000..cc805ad8 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/ServerController.java @@ -0,0 +1,27 @@ +package com.ruoyi.web.controller.monitor; + +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.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.framework.web.domain.Server; + +/** + * 服务器监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/server") +public class ServerController +{ + @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/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java new file mode 100644 index 00000000..41dcf981 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/SysLogininforController.java @@ -0,0 +1,69 @@ +package com.ruoyi.web.controller.monitor; + +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.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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.SysLogininfor; +import com.ruoyi.system.service.ISysLogininforService; + +/** + * 系统访问记录 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/logininfor") +public class SysLogininforController extends BaseController +{ + @Autowired + private ISysLogininforService logininforService; + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')") + @GetMapping("/list") + public TableDataInfo list(SysLogininfor logininfor) + { + startPage(); + List list = logininforService.selectLogininforList(logininfor); + return getDataTable(list); + } + + @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, "登录日志"); + } + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{infoIds}") + public AjaxResult remove(@PathVariable Long[] infoIds) + { + return toAjax(logininforService.deleteLogininforByIds(infoIds)); + } + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + logininforService.cleanLogininfor(); + return AjaxResult.success(); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java new file mode 100644 index 00000000..b6ba56be --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/SysOperlogController.java @@ -0,0 +1,69 @@ +package com.ruoyi.web.controller.monitor; + +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.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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.SysOperLog; +import com.ruoyi.system.service.ISysOperLogService; + +/** + * 操作日志记录 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/operlog") +public class SysOperlogController extends BaseController +{ + @Autowired + private ISysOperLogService operLogService; + + @PreAuthorize("@ss.hasPermi('monitor:operlog:list')") + @GetMapping("/list") + public TableDataInfo list(SysOperLog operLog) + { + startPage(); + List list = operLogService.selectOperLogList(operLog); + return getDataTable(list); + } + + @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, "操作日志"); + } + + @Log(title = "操作日志", businessType = BusinessType.DELETE) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/{operIds}") + public AjaxResult remove(@PathVariable Long[] operIds) + { + return toAjax(operLogService.deleteOperLogByIds(operIds)); + } + + @Log(title = "操作日志", businessType = BusinessType.CLEAN) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/clean") + public AjaxResult clean() + { + operLogService.cleanOperLog(); + return AjaxResult.success(); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java new file mode 100644 index 00000000..9b157ea7 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/monitor/SysUserOnlineController.java @@ -0,0 +1,92 @@ +package com.ruoyi.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.domain.SysUserOnline; +import com.ruoyi.system.service.ISysUserOnlineService; + +/** + * 在线用户监控 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/monitor/online") +public class SysUserOnlineController extends BaseController +{ + @Autowired + private ISysUserOnlineService userOnlineService; + + @Autowired + private RedisCache redisCache; + + @PreAuthorize("@ss.hasPermi('monitor:online:list')") + @GetMapping("/list") + public TableDataInfo list(String ipaddr, String userName) + { + Collection keys = redisCache.keys(Constants.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); + } + + /** + * 强退用户 + */ + @PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')") + @Log(title = "在线用户", businessType = BusinessType.FORCE) + @DeleteMapping("/{tokenId}") + public AjaxResult forceLogout(@PathVariable String tokenId) + { + redisCache.deleteObject(Constants.LOGIN_TOKEN_KEY + tokenId); + return AjaxResult.success(); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java new file mode 100644 index 00000000..b6f0518c --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysConfigController.java @@ -0,0 +1,134 @@ +package com.ruoyi.web.controller.system; + +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.SysConfig; +import com.ruoyi.system.service.ISysConfigService; + +/** + * 参数配置 信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/config") +public class SysConfigController extends BaseController +{ + @Autowired + private ISysConfigService configService; + + /** + * 获取参数配置列表 + */ + @PreAuthorize("@ss.hasPermi('system:config:list')") + @GetMapping("/list") + public TableDataInfo list(SysConfig config) + { + startPage(); + List list = configService.selectConfigList(config); + return getDataTable(list); + } + + @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, "参数数据"); + } + + /** + * 根据参数编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:config:query')") + @GetMapping(value = "/{configId}") + public AjaxResult getInfo(@PathVariable Long configId) + { + return AjaxResult.success(configService.selectConfigById(configId)); + } + + /** + * 根据参数键名查询参数值 + */ + @GetMapping(value = "/configKey/{configKey}") + public AjaxResult getConfigKey(@PathVariable String configKey) + { + return AjaxResult.success(configService.selectConfigByKey(configKey)); + } + + /** + * 新增参数配置 + */ + @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 AjaxResult.error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setCreateBy(getUsername()); + return toAjax(configService.insertConfig(config)); + } + + /** + * 修改参数配置 + */ + @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 AjaxResult.error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setUpdateBy(getUsername()); + return toAjax(configService.updateConfig(config)); + } + + /** + * 删除参数配置 + */ + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{configIds}") + public AjaxResult remove(@PathVariable Long[] configIds) + { + configService.deleteConfigByIds(configIds); + return success(); + } + + /** + * 刷新参数缓存 + */ + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + configService.resetConfigCache(); + return AjaxResult.success(); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java new file mode 100644 index 00000000..a6030ea3 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysDeptController.java @@ -0,0 +1,163 @@ +package com.ruoyi.web.controller.system; + +import java.util.Iterator; +import java.util.List; +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysDeptService; + +/** + * 部门信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/dept") +public class SysDeptController extends BaseController +{ + @Autowired + private ISysDeptService deptService; + + /** + * 获取部门列表 + */ + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list") + public AjaxResult list(SysDept dept) + { + List depts = deptService.selectDeptList(dept); + return AjaxResult.success(depts); + } + + /** + * 查询部门列表(排除节点) + */ + @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()); + Iterator it = depts.iterator(); + while (it.hasNext()) + { + SysDept d = (SysDept) it.next(); + if (d.getDeptId().intValue() == deptId + || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + "")) + { + it.remove(); + } + } + return AjaxResult.success(depts); + } + + /** + * 根据部门编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:dept:query')") + @GetMapping(value = "/{deptId}") + public AjaxResult getInfo(@PathVariable Long deptId) + { + deptService.checkDeptDataScope(deptId); + return AjaxResult.success(deptService.selectDeptById(deptId)); + } + + /** + * 获取部门下拉树列表 + */ + @GetMapping("/treeselect") + public AjaxResult treeselect(SysDept dept) + { + List depts = deptService.selectDeptList(dept); + return AjaxResult.success(deptService.buildDeptTreeSelect(depts)); + } + + /** + * 加载对应角色部门列表树 + */ + @GetMapping(value = "/roleDeptTreeselect/{roleId}") + public AjaxResult roleDeptTreeselect(@PathVariable("roleId") Long roleId) + { + List depts = deptService.selectDeptList(new SysDept()); + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId)); + ajax.put("depts", deptService.buildDeptTreeSelect(depts)); + return ajax; + } + + /** + * 新增部门 + */ + @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 AjaxResult.error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + dept.setCreateBy(getUsername()); + return toAjax(deptService.insertDept(dept)); + } + + /** + * 修改部门 + */ + @PreAuthorize("@ss.hasPermi('system:dept:edit')") + @Log(title = "部门管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDept dept) + { + if (UserConstants.NOT_UNIQUE.equals(deptService.checkDeptNameUnique(dept))) + { + return AjaxResult.error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + else if (dept.getParentId().equals(dept.getDeptId())) + { + return AjaxResult.error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己"); + } + else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) + && deptService.selectNormalChildrenDeptById(dept.getDeptId()) > 0) + { + return AjaxResult.error("该部门包含未停用的子部门!"); + } + dept.setUpdateBy(getUsername()); + return toAjax(deptService.updateDept(dept)); + } + + /** + * 删除部门 + */ + @PreAuthorize("@ss.hasPermi('system:dept:remove')") + @Log(title = "部门管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{deptId}") + public AjaxResult remove(@PathVariable Long deptId) + { + if (deptService.hasChildByDeptId(deptId)) + { + return AjaxResult.error("存在下级部门,不允许删除"); + } + if (deptService.checkDeptExistUser(deptId)) + { + return AjaxResult.error("部门存在用户,不允许删除"); + } + return toAjax(deptService.deleteDeptById(deptId)); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java new file mode 100644 index 00000000..b53fe478 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysDictDataController.java @@ -0,0 +1,121 @@ +package com.ruoyi.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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.service.ISysDictDataService; +import com.ruoyi.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 AjaxResult.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 AjaxResult.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/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java new file mode 100644 index 00000000..f8b7dbc0 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysDictTypeController.java @@ -0,0 +1,132 @@ +package com.ruoyi.web.controller.system; + +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysDictType; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.service.ISysDictTypeService; + +/** + * 数据字典信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/dict/type") +public class SysDictTypeController extends BaseController +{ + @Autowired + private ISysDictTypeService dictTypeService; + + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictType dictType) + { + startPage(); + List list = dictTypeService.selectDictTypeList(dictType); + return getDataTable(list); + } + + @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, "字典类型"); + } + + /** + * 查询字典类型详细 + */ + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictId}") + public AjaxResult getInfo(@PathVariable Long dictId) + { + return AjaxResult.success(dictTypeService.selectDictTypeById(dictId)); + } + + /** + * 新增字典类型 + */ + @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 AjaxResult.error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setCreateBy(getUsername()); + return toAjax(dictTypeService.insertDictType(dict)); + } + + /** + * 修改字典类型 + */ + @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 AjaxResult.error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setUpdateBy(getUsername()); + return toAjax(dictTypeService.updateDictType(dict)); + } + + /** + * 删除字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictIds}") + public AjaxResult remove(@PathVariable Long[] dictIds) + { + dictTypeService.deleteDictTypeByIds(dictIds); + return success(); + } + + /** + * 刷新字典缓存 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + dictTypeService.resetDictCache(); + return AjaxResult.success(); + } + + /** + * 获取字典选择框列表 + */ + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List dictTypes = dictTypeService.selectDictTypeAll(); + return AjaxResult.success(dictTypes); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java new file mode 100644 index 00000000..13007eb1 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysIndexController.java @@ -0,0 +1,29 @@ +package com.ruoyi.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.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.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/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java new file mode 100644 index 00000000..d959a17e --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -0,0 +1,86 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import java.util.Set; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginBody; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.framework.web.service.SysLoginService; +import com.ruoyi.framework.web.service.SysPermissionService; +import com.ruoyi.system.service.ISysMenuService; + +/** + * 登录验证 + * + * @author ruoyi + */ +@RestController +public class SysLoginController +{ + @Autowired + private SysLoginService loginService; + + @Autowired + private ISysMenuService menuService; + + @Autowired + private SysPermissionService permissionService; + + /** + * 登录方法 + * + * @param loginBody 登录信息 + * @return 结果 + */ + @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 用户信息 + */ + @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); + return ajax; + } + + /** + * 获取路由信息 + * + * @return 路由信息 + */ + @GetMapping("getRouters") + public AjaxResult getRouters() + { + Long userId = SecurityUtils.getUserId(); + List menus = menuService.selectMenuTreeByUserId(userId); + return AjaxResult.success(menuService.buildMenus(menus)); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java new file mode 100644 index 00000000..4a9f73f8 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java @@ -0,0 +1,142 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysMenu; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.system.service.ISysMenuService; + +/** + * 菜单信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/menu") +public class SysMenuController extends BaseController +{ + @Autowired + private ISysMenuService menuService; + + /** + * 获取菜单列表 + */ + @PreAuthorize("@ss.hasPermi('system:menu:list')") + @GetMapping("/list") + public AjaxResult list(SysMenu menu) + { + List menus = menuService.selectMenuList(menu, getUserId()); + return AjaxResult.success(menus); + } + + /** + * 根据菜单编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:menu:query')") + @GetMapping(value = "/{menuId}") + public AjaxResult getInfo(@PathVariable Long menuId) + { + return AjaxResult.success(menuService.selectMenuById(menuId)); + } + + /** + * 获取菜单下拉树列表 + */ + @GetMapping("/treeselect") + public AjaxResult treeselect(SysMenu menu) + { + List menus = menuService.selectMenuList(menu, getUserId()); + return AjaxResult.success(menuService.buildMenuTreeSelect(menus)); + } + + /** + * 加载对应角色菜单列表树 + */ + @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; + } + + /** + * 新增菜单 + */ + @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 AjaxResult.error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return AjaxResult.error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + menu.setCreateBy(getUsername()); + return toAjax(menuService.insertMenu(menu)); + } + + /** + * 修改菜单 + */ + @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 AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + else if (menu.getMenuId().equals(menu.getParentId())) + { + return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); + } + menu.setUpdateBy(getUsername()); + return toAjax(menuService.updateMenu(menu)); + } + + /** + * 删除菜单 + */ + @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 AjaxResult.error("存在子菜单,不允许删除"); + } + if (menuService.checkMenuExistRole(menuId)) + { + return AjaxResult.error("菜单已分配,不允许删除"); + } + return toAjax(menuService.deleteMenuById(menuId)); + } +} \ No newline at end of file diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java new file mode 100644 index 00000000..4da9f04b --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysNoticeController.java @@ -0,0 +1,91 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.system.domain.SysNotice; +import com.ruoyi.system.service.ISysNoticeService; + +/** + * 公告 信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/notice") +public class SysNoticeController extends BaseController +{ + @Autowired + private ISysNoticeService noticeService; + + /** + * 获取通知公告列表 + */ + @PreAuthorize("@ss.hasPermi('system:notice:list')") + @GetMapping("/list") + public TableDataInfo list(SysNotice notice) + { + startPage(); + List list = noticeService.selectNoticeList(notice); + return getDataTable(list); + } + + /** + * 根据通知公告编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:notice:query')") + @GetMapping(value = "/{noticeId}") + public AjaxResult getInfo(@PathVariable Long noticeId) + { + return AjaxResult.success(noticeService.selectNoticeById(noticeId)); + } + + /** + * 新增通知公告 + */ + @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)); + } + + /** + * 修改通知公告 + */ + @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)); + } + + /** + * 删除通知公告 + */ + @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/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java new file mode 100644 index 00000000..f73cea4d --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysPostController.java @@ -0,0 +1,130 @@ +package com.ruoyi.web.controller.system; + +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.SysPost; +import com.ruoyi.system.service.ISysPostService; + +/** + * 岗位信息操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/post") +public class SysPostController extends BaseController +{ + @Autowired + private ISysPostService postService; + + /** + * 获取岗位列表 + */ + @PreAuthorize("@ss.hasPermi('system:post:list')") + @GetMapping("/list") + public TableDataInfo list(SysPost post) + { + startPage(); + List list = postService.selectPostList(post); + return getDataTable(list); + } + + @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, "岗位数据"); + } + + /** + * 根据岗位编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:post:query')") + @GetMapping(value = "/{postId}") + public AjaxResult getInfo(@PathVariable Long postId) + { + return AjaxResult.success(postService.selectPostById(postId)); + } + + /** + * 新增岗位 + */ + @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 AjaxResult.error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (UserConstants.NOT_UNIQUE.equals(postService.checkPostCodeUnique(post))) + { + return AjaxResult.error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setCreateBy(getUsername()); + return toAjax(postService.insertPost(post)); + } + + /** + * 修改岗位 + */ + @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 AjaxResult.error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (UserConstants.NOT_UNIQUE.equals(postService.checkPostCodeUnique(post))) + { + return AjaxResult.error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setUpdateBy(getUsername()); + return toAjax(postService.updatePost(post)); + } + + /** + * 删除岗位 + */ + @PreAuthorize("@ss.hasPermi('system:post:remove')") + @Log(title = "岗位管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{postIds}") + public AjaxResult remove(@PathVariable Long[] postIds) + { + return toAjax(postService.deletePostByIds(postIds)); + } + + /** + * 获取岗位选择框列表 + */ + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List posts = postService.selectPostAll(); + return AjaxResult.success(posts); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java new file mode 100644 index 00000000..ba38bd0c --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java @@ -0,0 +1,142 @@ +package com.ruoyi.web.controller.system; + +import java.io.IOException; +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 com.ruoyi.common.annotation.Log; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 个人信息 业务处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/user/profile") +public class SysProfileController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private TokenService tokenService; + + /** + * 个人信息 + */ + @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())); + return ajax; + } + + /** + * 修改用户 + */ + @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 AjaxResult.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) + { + return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUserId(sysUser.getUserId()); + user.setPassword(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 AjaxResult.success(); + } + return AjaxResult.error("修改个人信息异常,请联系管理员"); + } + + /** + * 重置密码 + */ + @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 AjaxResult.error("修改密码失败,旧密码错误"); + } + if (SecurityUtils.matchesPassword(newPassword, password)) + { + return AjaxResult.error("新密码不能与旧密码相同"); + } + if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0) + { + // 更新缓存用户密码 + loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword)); + tokenService.setLoginUser(loginUser); + return AjaxResult.success(); + } + return AjaxResult.error("修改密码异常,请联系管理员"); + } + + /** + * 头像上传 + */ + @Log(title = "用户头像", businessType = BusinessType.UPDATE) + @PostMapping("/avatar") + public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws IOException + { + if (!file.isEmpty()) + { + LoginUser loginUser = getLoginUser(); + String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file); + if (userService.updateUserAvatar(loginUser.getUsername(), avatar)) + { + AjaxResult ajax = AjaxResult.success(); + ajax.put("imgUrl", avatar); + // 更新缓存用户头像 + loginUser.getUser().setAvatar(avatar); + tokenService.setLoginUser(loginUser); + return ajax; + } + } + return AjaxResult.error("上传图片异常,请联系管理员"); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java new file mode 100644 index 00000000..fe192492 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java @@ -0,0 +1,38 @@ +package com.ruoyi.web.controller.system; + +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.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.RegisterBody; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.web.service.SysRegisterService; +import com.ruoyi.system.service.ISysConfigService; + +/** + * 注册验证 + * + * @author ruoyi + */ +@RestController +public class SysRegisterController extends BaseController +{ + @Autowired + private SysRegisterService registerService; + + @Autowired + private ISysConfigService configService; + + @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/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java new file mode 100644 index 00000000..a5948deb --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysRoleController.java @@ -0,0 +1,241 @@ +package com.ruoyi.web.controller.system; + +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.framework.web.service.SysPermissionService; +import com.ruoyi.framework.web.service.TokenService; +import com.ruoyi.system.domain.SysUserRole; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 角色信息 + * + * @author ruoyi + */ +@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; + + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/list") + public TableDataInfo list(SysRole role) + { + startPage(); + List list = roleService.selectRoleList(role); + return getDataTable(list); + } + + @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, "角色数据"); + } + + /** + * 根据角色编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/{roleId}") + public AjaxResult getInfo(@PathVariable Long roleId) + { + roleService.checkRoleDataScope(roleId); + return AjaxResult.success(roleService.selectRoleById(roleId)); + } + + /** + * 新增角色 + */ + @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 AjaxResult.error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } + else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) + { + return AjaxResult.error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setCreateBy(getUsername()); + return toAjax(roleService.insertRole(role)); + + } + + /** + * 修改保存角色 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleNameUnique(role))) + { + return AjaxResult.error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } + else if (UserConstants.NOT_UNIQUE.equals(roleService.checkRoleKeyUnique(role))) + { + return AjaxResult.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 AjaxResult.success(); + } + return AjaxResult.error("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); + } + + /** + * 修改保存数据权限 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/dataScope") + public AjaxResult dataScope(@RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + return toAjax(roleService.authDataScope(role)); + } + + /** + * 状态修改 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + role.setUpdateBy(getUsername()); + return toAjax(roleService.updateRoleStatus(role)); + } + + /** + * 删除角色 + */ + @PreAuthorize("@ss.hasPermi('system:role:remove')") + @Log(title = "角色管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{roleIds}") + public AjaxResult remove(@PathVariable Long[] roleIds) + { + return toAjax(roleService.deleteRoleByIds(roleIds)); + } + + /** + * 获取角色选择框列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + return AjaxResult.success(roleService.selectRoleAll()); + } + + /** + * 查询已分配用户角色列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/allocatedList") + public TableDataInfo allocatedList(SysUser user) + { + startPage(); + List list = userService.selectAllocatedList(user); + return getDataTable(list); + } + + /** + * 查询未分配用户角色列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/unallocatedList") + public TableDataInfo unallocatedList(SysUser user) + { + startPage(); + List list = userService.selectUnallocatedList(user); + return getDataTable(list); + } + + /** + * 取消授权用户 + */ + @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)); + } + + /** + * 批量取消授权用户 + */ + @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)); + } + + /** + * 批量选择用户授权 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/selectAll") + public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds) + { + return toAjax(roleService.insertAuthUsers(roleId, userIds)); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java new file mode 100644 index 00000000..688e57a7 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java @@ -0,0 +1,232 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletResponse; +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.service.ISysPostService; +import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 用户信息 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/system/user") +public class SysUserController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysPostService postService; + + /** + * 获取用户列表 + */ + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/list") + public TableDataInfo list(SysUser user) + { + startPage(); + List list = userService.selectUserList(user); + return getDataTable(list); + } + + @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, "用户数据"); + } + + @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 AjaxResult.success(message); + } + + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) + { + ExcelUtil util = new ExcelUtil(SysUser.class); + util.importTemplateExcel(response, "用户数据"); + } + + /** + * 根据用户编号获取详细信息 + */ + @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)) + { + ajax.put(AjaxResult.DATA_TAG, userService.selectUserById(userId)); + ajax.put("postIds", postService.selectPostListByUserId(userId)); + ajax.put("roleIds", roleService.selectRoleListByUserId(userId)); + } + return ajax; + } + + /** + * 新增用户 + */ + @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.getUserName()))) + { + return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) + { + return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) + { + return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setCreateBy(getUsername()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + return toAjax(userService.insertUser(user)); + } + + /** + * 修改用户 + */ + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysUser user) + { + userService.checkUserAllowed(user); + if (StringUtils.isNotEmpty(user.getPhonenumber()) + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) + { + return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) + { + return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUser(user)); + } + + /** + * 删除用户 + */ + @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)); + } + + /** + * 重置密码 + */ + @PreAuthorize("@ss.hasPermi('system:user:resetPwd')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/resetPwd") + public AjaxResult resetPwd(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + user.setUpdateBy(getUsername()); + return toAjax(userService.resetPwd(user)); + } + + /** + * 状态修改 + */ + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUserStatus(user)); + } + + /** + * 根据用户编号获取授权角色 + */ + @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; + } + + /** + * 用户授权角色 + */ + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.GRANT) + @PutMapping("/authRole") + public AjaxResult insertAuthRole(Long userId, Long[] roleIds) + { + userService.insertUserAuth(userId, roleIds); + return success(); + } +} diff --git a/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java new file mode 100644 index 00000000..f66ca24e --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/tool/SwaggerController.java @@ -0,0 +1,24 @@ +package com.ruoyi.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.ruoyi.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/wumei-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java new file mode 100644 index 00000000..3361ef90 --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/controller/tool/TestController.java @@ -0,0 +1,181 @@ +package com.ruoyi.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.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.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; + +/** + * swagger 用户测试方法 + * + * @author ruoyi + */ +@Api("用户信息管理") +@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 AjaxResult userList() + { + List userList = new ArrayList(users.values()); + return AjaxResult.success(userList); + } + + @ApiOperation("获取用户详细") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class) + @GetMapping("/{userId}") + public AjaxResult getUser(@PathVariable Integer userId) + { + if (!users.isEmpty() && users.containsKey(userId)) + { + return AjaxResult.success(users.get(userId)); + } + else + { + return error("用户不存在"); + } + } + + @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 AjaxResult save(UserEntity user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) + { + return error("用户ID不能为空"); + } + return AjaxResult.success(users.put(user.getUserId(), user)); + } + + @ApiOperation("更新用户") + @PutMapping("/update") + public AjaxResult update(@RequestBody UserEntity user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) + { + return error("用户ID不能为空"); + } + if (users.isEmpty() || !users.containsKey(user.getUserId())) + { + return error("用户不存在"); + } + users.remove(user.getUserId()); + return AjaxResult.success(users.put(user.getUserId(), user)); + } + + @ApiOperation("删除用户信息") + @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class) + @DeleteMapping("/{userId}") + public AjaxResult delete(@PathVariable Integer userId) + { + if (!users.isEmpty() && users.containsKey(userId)) + { + users.remove(userId); + return success(); + } + else + { + return error("用户不存在"); + } + } +} + +@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/wumei-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java b/springboot/wumei-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java new file mode 100644 index 00000000..8b6bcb8c --- /dev/null +++ b/springboot/wumei-admin/src/main/java/com/ruoyi/web/core/config/SwaggerConfig.java @@ -0,0 +1,125 @@ +package com.ruoyi.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.ruoyi.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.ruoyi.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("标题:wumei-smart接口文档") + // 描述 + .description("描述:开源生活物联网平台") + // 作者信息 + .contact(new Contact(ruoyiConfig.getName(), null, null)) + // 版本 + .version("版本号:" + ruoyiConfig.getVersion()) + .build(); + } +} diff --git a/springboot/wumei-admin/src/main/resources/META-INF/spring-devtools.properties b/springboot/wumei-admin/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 00000000..2b23f85a --- /dev/null +++ b/springboot/wumei-admin/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1 @@ +restart.include.json=/com.alibaba.fastjson.*.jar \ No newline at end of file diff --git a/springboot/wumei-admin/src/main/resources/application-druid.yml b/springboot/wumei-admin/src/main/resources/application-druid.yml new file mode 100644 index 00000000..424bb77b --- /dev/null +++ b/springboot/wumei-admin/src/main/resources/application-druid.yml @@ -0,0 +1,57 @@ +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://localhost/wumeismart?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: admin + # 从库数据源 + 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: admin + login-password: admin123 + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true diff --git a/springboot/wumei-admin/src/main/resources/application.yml b/springboot/wumei-admin/src/main/resources/application.yml new file mode 100644 index 00000000..57a075be --- /dev/null +++ b/springboot/wumei-admin/src/main/resources/application.yml @@ -0,0 +1,129 @@ +# 项目相关配置 +ruoyi: + # 名称 + name: ruoyi + # 版本 + version: 3.8.0 + # 版权年份 + copyrightYear: 2021 + # 实例演示开关 + demoEnabled: true + # 文件路径 示例( Windows配置D:/wumei/uploadPath,Linux配置 /home/ruoyi/uploadPath) + profile: D:\project\github\wumei-smart\springboot\uploadpath + # 获取ip地址开关 + addressEnabled: true + # 验证码类型 math 数组计算 char 字符验证 + captchaType: math + +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 8080 + servlet: + # 应用的访问路径 + context-path: / + tomcat: + # tomcat的URI编码 + uri-encoding: UTF-8 + # tomcat最大线程数,默认为200 + max-threads: 800 + # Tomcat启动初始化的线程数,默认值25 + min-spare-threads: 30 + +# 日志配置 +logging: + level: + com.ruoyi: debug + org.springframework: warn + +# Spring配置 +spring: + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + profiles: + active: druid + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 10MB + # 设置总上传的文件大小 + max-request-size: 20MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + # redis 配置 + redis: + # 地址 + host: localhost + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 1 + # 密码 + password: admin + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + # mqtt 配置 + mqtt: + username: wumei-smart # 账号 + password: wumei-smart # 密码 + host-url: tcp://localhost:1883 # mqtt连接tcp地址 + client-id: ${random.value} # 客户端Id,不能相同,采用随机数 ${random.value} + default-topic: test # 默认主题 + timeout: 60000 # 超时时间 + keepalive: 30 # 保持连接 + +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklwumeismartrstuvwxyz + # 令牌有效期(默认30分钟)1440为一天 + expireTime: 1440 + +# MyBatis配置 +mybatis: + # 搜索指定包别名 + typeAliasesPackage: com.ruoyi.**.domain + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 加载全局的配置文件 + configLocation: classpath:mybatis/mybatis-config.xml + +# PageHelper分页插件 +pagehelper: + helperDialect: mysql + supportMethodsArguments: true + params: count=countSql + +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: /dev-api + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* diff --git a/springboot/wumei-admin/src/main/resources/banner.txt b/springboot/wumei-admin/src/main/resources/banner.txt new file mode 100644 index 00000000..fee1ce3b --- /dev/null +++ b/springboot/wumei-admin/src/main/resources/banner.txt @@ -0,0 +1,2 @@ +Application Version: ${ruoyi.version} +Spring Boot Version: ${spring-boot.version} \ No newline at end of file diff --git a/springboot/wumei-admin/src/main/resources/i18n/messages.properties b/springboot/wumei-admin/src/main/resources/i18n/messages.properties new file mode 100644 index 00000000..71cf52d9 --- /dev/null +++ b/springboot/wumei-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}次,帐户锁定10分钟 +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/wumei-admin/src/main/resources/logback.xml b/springboot/wumei-admin/src/main/resources/logback.xml new file mode 100644 index 00000000..37e14f6f --- /dev/null +++ b/springboot/wumei-admin/src/main/resources/logback.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/sys-info.log + + + + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/sys-error.log + + + + ${log.path}/sys-error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log.path}/sys-user.log + + + ${log.path}/sys-user.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/springboot/wumei-admin/src/main/resources/mybatis/mybatis-config.xml b/springboot/wumei-admin/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 00000000..ac47c038 --- /dev/null +++ b/springboot/wumei-admin/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/springboot/wumei-common/pom.xml b/springboot/wumei-common/pom.xml new file mode 100644 index 00000000..f1dcae65 --- /dev/null +++ b/springboot/wumei-common/pom.xml @@ -0,0 +1,130 @@ + + + + wumei + com.wumei + 3.8.0 + + 4.0.0 + + wumei-common + + + common通用工具 + + + + + + + org.springframework + spring-context-support + + + + + org.springframework + spring-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.apache.commons + commons-lang3 + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.alibaba + fastjson + + + + + 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 + + + + + \ No newline at end of file diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/DataScope.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/DataScope.java new file mode 100644 index 00000000..176878e9 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/DataScope.java @@ -0,0 +1,28 @@ +package com.ruoyi.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 ""; +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/DataSource.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/DataSource.java new file mode 100644 index 00000000..79cd191f --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/DataSource.java @@ -0,0 +1,28 @@ +package com.ruoyi.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.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/annotation/Excel.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/Excel.java new file mode 100644 index 00000000..3754e0e0 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/Excel.java @@ -0,0 +1,176 @@ +package com.ruoyi.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 com.ruoyi.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; + + /** + * 导出类型(0数字 1字符串) + */ + public ColumnType cellType() default ColumnType.STRING; + + /** + * 导出时在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 {}; + + /** + * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. + */ + public boolean isExport() default true; + + /** + * 另一个类中的属性名称,支持多级获取,以小数点隔开 + */ + public String targetAttr() default ""; + + /** + * 是否自动统计数据,在最后追加一行统计数据总和 + */ + public boolean isStatistics() default false; + + /** + * 导出字段对齐方式(0:默认;1:靠左;2:居中;3:靠右) + */ + public Align align() default Align.AUTO; + + /** + * 自定义数据处理器 + */ + public Class handler() default ExcelHandlerAdapter.class; + + /** + * 自定义数据处理器参数 + */ + public String[] args() default {}; + + public enum Align + { + AUTO(0), LEFT(1), CENTER(2), RIGHT(3); + private final int value; + + Align(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } + + /** + * 字段类型(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/wumei-common/src/main/java/com/ruoyi/common/annotation/Excels.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/Excels.java new file mode 100644 index 00000000..1f1cc81b --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/Excels.java @@ -0,0 +1,18 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/annotation/Log.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/Log.java new file mode 100644 index 00000000..ca02c6c4 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/Log.java @@ -0,0 +1,46 @@ +package com.ruoyi.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.ruoyi.common.enums.BusinessType; +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java new file mode 100644 index 00000000..69461ead --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/RateLimiter.java @@ -0,0 +1,40 @@ +package com.ruoyi.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.ruoyi.common.constant.Constants; +import com.ruoyi.common.enums.LimitType; + +/** + * 限流注解 + * + * @author ruoyi + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter +{ + /** + * 限流key + */ + public String key() default Constants.RATE_LIMIT_KEY; + + /** + * 限流时间,单位秒 + */ + public int time() default 60; + + /** + * 限流次数 + */ + public int count() default 100; + + /** + * 限流类型 + */ + public LimitType limitType() default LimitType.DEFAULT; +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java new file mode 100644 index 00000000..b7697481 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/annotation/RepeatSubmit.java @@ -0,0 +1,31 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java new file mode 100644 index 00000000..00f70f64 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java @@ -0,0 +1,135 @@ +package com.ruoyi.common.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 读取项目相关配置 + * + * @author ruoyi + */ +@Component +@ConfigurationProperties(prefix = "ruoyi") +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/wumei-common/src/main/java/com/ruoyi/common/constant/Constants.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/Constants.java new file mode 100644 index 00000000..33b745b0 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/Constants.java @@ -0,0 +1,157 @@ +package com.ruoyi.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"; + + /** + * 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"; + + /** + * 验证码 redis key + */ + public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; + + /** + * 登录用户 redis key + */ + public static final String LOGIN_TOKEN_KEY = "login_tokens:"; + + /** + * 防重提交 redis key + */ + public static final String REPEAT_SUBMIT_KEY = "repeat_submit:"; + + /** + * 限流 redis key + */ + public static final String RATE_LIMIT_KEY = "rate_limit:"; + + /** + * 验证码有效期(分钟) + */ + 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"; + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config:"; + + /** + * 字典管理 cache key + */ + public static final String SYS_DICT_KEY = "sys_dict:"; + + /** + * 资源映射路径 前缀 + */ + public static final String RESOURCE_PREFIX = "/profile"; + + /** + * RMI 远程方法调用 + */ + public static final String LOOKUP_RMI = "rmi://"; + + /** + * LDAP 远程方法调用 + */ + public static final String LOOKUP_LDAP = "ldap://"; + + /** + * 定时任务违规的字符 + */ + public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", + "org.springframework.jndi" }; +} \ No newline at end of file diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/GenConstants.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/GenConstants.java new file mode 100644 index 00000000..1fa328e4 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/GenConstants.java @@ -0,0 +1,114 @@ +package com.ruoyi.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 REQUIRE = "1"; +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java new file mode 100644 index 00000000..d60afee2 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java @@ -0,0 +1,89 @@ +package com.ruoyi.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 ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java new file mode 100644 index 00000000..62ad8154 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/ScheduleConstants.java @@ -0,0 +1,50 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/constant/UserConstants.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/UserConstants.java new file mode 100644 index 00000000..4ed60095 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/constant/UserConstants.java @@ -0,0 +1,78 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java new file mode 100644 index 00000000..9b3d9c71 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/controller/BaseController.java @@ -0,0 +1,185 @@ +package com.ruoyi.common.core.controller; + +import java.beans.PropertyEditorSupport; +import java.util.Date; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.page.PageDomain; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.page.TableSupport; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.sql.SqlUtil; + +/** + * web层通用数据处理 + * + * @author ruoyi + */ +public class BaseController +{ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * 将前台传递过来的日期格式的字符串,自动转化为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() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + Integer pageNum = pageDomain.getPageNum(); + Integer pageSize = pageDomain.getPageSize(); + if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize)) + { + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + Boolean reasonable = pageDomain.getReasonable(); + PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable); + } + } + + /** + * 设置请求排序数据 + */ + protected void startOrderBy() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) + { + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + PageHelper.orderBy(orderBy); + } + } + + /** + * 响应请求分页数据 + */ + @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 error(String message) + { + return AjaxResult.error(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); + } + + /** + * 获取用户缓存信息 + */ + public LoginUser getLoginUser() + { + return SecurityUtils.getLoginUser(); + } + + /** + * 获取登录用户id + */ + public Long getUserId() + { + return getLoginUser().getUserId(); + } + + /** + * 获取登录部门id + */ + public Long getDeptId() + { + return getLoginUser().getDeptId(); + } + + /** + * 获取登录用户名 + */ + public String getUsername() + { + return getLoginUser().getUsername(); + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java new file mode 100644 index 00000000..4950bd00 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/AjaxResult.java @@ -0,0 +1,162 @@ +package com.ruoyi.common.core.domain; + +import java.util.HashMap; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.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); + } + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static AjaxResult success() + { + return AjaxResult.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static AjaxResult success(Object data) + { + return AjaxResult.success("操作成功", data); + } + + /** + * 返回成功消息 + * + * @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); + } + + /** + * 返回错误消息 + * + * @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/wumei-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java new file mode 100644 index 00000000..f7d5bf46 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/BaseEntity.java @@ -0,0 +1,114 @@ +package com.ruoyi.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; + +/** + * Entity基类 + * + * @author ruoyi + */ +public class BaseEntity implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 搜索值 */ + private String searchValue; + + /** 创建者 */ + private String createBy; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** 更新者 */ + private String updateBy; + + /** 更新时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** 备注 */ + private String remark; + + /** 请求参数 */ + 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/wumei-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java new file mode 100644 index 00000000..a180a18c --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/TreeEntity.java @@ -0,0 +1,79 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java new file mode 100644 index 00000000..bd835db9 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/TreeSelect.java @@ -0,0 +1,77 @@ +package com.ruoyi.common.core.domain; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.ruoyi.common.core.domain.entity.SysDept; +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java new file mode 100644 index 00000000..c9238e23 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDept.java @@ -0,0 +1,202 @@ +package com.ruoyi.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.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 部门表 sys_dept + * + * @author ruoyi + */ +public class SysDept extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 部门ID */ + private Long deptId; + + /** 父部门ID */ + private Long parentId; + + /** 祖级列表 */ + private String ancestors; + + /** 部门名称 */ + private String deptName; + + /** 显示顺序 */ + private String orderNum; + + /** 负责人 */ + private String leader; + + /** 联系电话 */ + private String phone; + + /** 邮箱 */ + private String email; + + /** 部门状态:0正常,1停用 */ + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 父部门名称 */ + private String parentName; + + /** 子部门 */ + 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; + } + + @NotBlank(message = "显示顺序不能为空") + public String getOrderNum() + { + return orderNum; + } + + public void setOrderNum(String 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/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java new file mode 100644 index 00000000..3f152b34 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictData.java @@ -0,0 +1,176 @@ +package com.ruoyi.common.core.domain.entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 字典数据表 sys_dict_data + * + * @author ruoyi + */ +public class SysDictData extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典编码 */ + @Excel(name = "字典编码", cellType = ColumnType.NUMERIC) + private Long dictCode; + + /** 字典排序 */ + @Excel(name = "字典排序", cellType = ColumnType.NUMERIC) + private Long dictSort; + + /** 字典标签 */ + @Excel(name = "字典标签") + private String dictLabel; + + /** 字典键值 */ + @Excel(name = "字典键值") + private String dictValue; + + /** 字典类型 */ + @Excel(name = "字典类型") + private String dictType; + + /** 样式属性(其他样式扩展) */ + private String cssClass; + + /** 表格字典样式 */ + private String listClass; + + /** 是否默认(Y是 N否) */ + @Excel(name = "是否默认", readConverterExp = "Y=是,N=否") + private String isDefault; + + /** 状态(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) ? true : false; + } + + 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/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java new file mode 100644 index 00000000..d2c42498 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysDictType.java @@ -0,0 +1,94 @@ +package com.ruoyi.common.core.domain.entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 字典类型表 sys_dict_type + * + * @author ruoyi + */ +public class SysDictType extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典主键 */ + @Excel(name = "字典主键", cellType = ColumnType.NUMERIC) + private Long dictId; + + /** 字典名称 */ + @Excel(name = "字典名称") + private String dictName; + + /** 字典类型 */ + @Excel(name = "字典类型") + private String dictType; + + /** 状态(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个字符") + 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/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java new file mode 100644 index 00000000..5e79fc9c --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java @@ -0,0 +1,258 @@ +package com.ruoyi.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 菜单权限表 sys_menu + * + * @author ruoyi + */ +public class SysMenu extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 菜单ID */ + private Long menuId; + + /** 菜单名称 */ + private String menuName; + + /** 父菜单名称 */ + private String parentName; + + /** 父菜单ID */ + private Long parentId; + + /** 显示顺序 */ + private String orderNum; + + /** 路由地址 */ + private String path; + + /** 组件路径 */ + private String component; + + /** 路由参数 */ + private String query; + + /** 是否为外链(0是 1否) */ + private String isFrame; + + /** 是否缓存(0缓存 1不缓存) */ + private String isCache; + + /** 类型(M目录 C菜单 F按钮) */ + private String menuType; + + /** 显示状态(0显示 1隐藏) */ + private String visible; + + /** 菜单状态(0显示 1隐藏) */ + private String status; + + /** 权限字符串 */ + private String perms; + + /** 菜单图标 */ + private String icon; + + /** 子菜单 */ + 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; + } + + @NotBlank(message = "显示顺序不能为空") + public String getOrderNum() + { + return orderNum; + } + + public void setOrderNum(String 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/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java new file mode 100644 index 00000000..36629eb9 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysRole.java @@ -0,0 +1,226 @@ +package com.ruoyi.common.core.domain.entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 角色表 sys_role + * + * @author ruoyi + */ +public class SysRole extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 角色ID */ + @Excel(name = "角色序号", cellType = ColumnType.NUMERIC) + private Long roleId; + + /** 角色名称 */ + @Excel(name = "角色名称") + private String roleName; + + /** 角色权限 */ + @Excel(name = "角色权限") + private String roleKey; + + /** 角色排序 */ + @Excel(name = "角色排序") + private String roleSort; + + /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ + @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") + private String dataScope; + + /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */ + private boolean menuCheckStrictly; + + /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */ + private boolean deptCheckStrictly; + + /** 角色状态(0正常 1停用) */ + @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 用户是否存在此角色标识 默认不存在 */ + private boolean flag = false; + + /** 菜单组 */ + private Long[] menuIds; + + /** 部门组(数据权限) */ + private Long[] deptIds; + + 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; + } + + @NotBlank(message = "显示顺序不能为空") + public String getRoleSort() + { + return roleSort; + } + + public void setRoleSort(String 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; + } + + @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/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java new file mode 100644 index 00000000..f6968c05 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java @@ -0,0 +1,341 @@ +package com.ruoyi.common.core.domain.entity; + +import java.util.Date; +import java.util.List; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.annotation.Excel.Type; +import com.ruoyi.common.annotation.Excels; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 用户对象 sys_user + * + * @author ruoyi + */ +public class SysUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 用户ID */ + @Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号") + private Long userId; + + /** 部门ID */ + @Excel(name = "部门编号", type = Type.IMPORT) + private Long deptId; + + /** 用户账号 */ + @Excel(name = "登录名称") + private String userName; + + /** 用户昵称 */ + @Excel(name = "用户名称") + private String nickName; + + /** 用户邮箱 */ + @Excel(name = "用户邮箱") + private String email; + + /** 手机号码 */ + @Excel(name = "手机号码") + private String phonenumber; + + /** 用户性别 */ + @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") + private String sex; + + /** 用户头像 */ + private String avatar; + + /** 密码 */ + private String password; + + /** 盐加密 */ + private String salt; + + /** 帐号状态(0正常 1停用) */ + @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 最后登录IP */ + @Excel(name = "最后登录IP", type = Type.EXPORT) + private String loginIp; + + /** 最后登录时间 */ + @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) + private Date loginDate; + + /** 部门对象 */ + @Excels({ + @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT), + @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT) + }) + private SysDept dept; + + /** 角色对象 */ + private List roles; + + /** 角色组 */ + private Long[] roleIds; + + /** 岗位组 */ + private Long[] postIds; + + /** 角色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; + } + + @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") + public String getNickName() + { + return nickName; + } + + public void setNickName(String nickName) + { + this.nickName = nickName; + } + + @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; + } + + @JsonIgnore + @JsonProperty + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getSalt() + { + return salt; + } + + public void setSalt(String salt) + { + this.salt = salt; + } + + 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("salt", getSalt()) + .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/wumei-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java new file mode 100644 index 00000000..03948b43 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java @@ -0,0 +1,69 @@ +package com.ruoyi.common.core.domain.model; + +/** + * 用户登录对象 + * + * @author ruoyi + */ +public class LoginBody +{ + /** + * 用户名 + */ + private String username; + + /** + * 用户密码 + */ + private String password; + + /** + * 验证码 + */ + private String code; + + /** + * 唯一标识 + */ + private String uuid = ""; + + 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/wumei-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java new file mode 100644 index 00000000..db4d2a5a --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUser.java @@ -0,0 +1,266 @@ +package com.ruoyi.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.fastjson.annotation.JSONField; +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java new file mode 100644 index 00000000..868a1fc5 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/domain/model/RegisterBody.java @@ -0,0 +1,11 @@ +package com.ruoyi.common.core.domain.model; + +/** + * 用户注册对象 + * + * @author ruoyi + */ +public class RegisterBody extends LoginBody +{ + +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java new file mode 100644 index 00000000..8966cb4c --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/page/PageDomain.java @@ -0,0 +1,101 @@ +package com.ruoyi.common.core.page; + +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java new file mode 100644 index 00000000..847685ba --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/page/TableDataInfo.java @@ -0,0 +1,85 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java new file mode 100644 index 00000000..5f301686 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/page/TableSupport.java @@ -0,0 +1,55 @@ +package com.ruoyi.common.core.page; + +import com.ruoyi.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(ServletUtils.getParameterToInt(PAGE_NUM)); + pageDomain.setPageSize(ServletUtils.getParameterToInt(PAGE_SIZE)); + 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/wumei-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java new file mode 100644 index 00000000..658926e7 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java @@ -0,0 +1,246 @@ +package com.ruoyi.common.core.redis; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +/** + * spring redis 工具类 + * + * @author ruoyi + **/ +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@Component +public class RedisCache +{ + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,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 缓存键值 + * @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 long deleteObject(final Collection collection) + { + return redisTemplate.delete(collection); + } + + /** + * 缓存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 + * @param mapkey + */ + public void delCacheMapValue(final String key, final String hkey) + { + HashOperations hashOperations = redisTemplate.opsForHash(); + hashOperations.delete(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); + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) + { + return redisTemplate.keys(pattern); + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java new file mode 100644 index 00000000..84124aac --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/text/CharsetKit.java @@ -0,0 +1,86 @@ +package com.ruoyi.common.core.text; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/core/text/Convert.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/text/Convert.java new file mode 100644 index 00000000..1fb74619 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/text/Convert.java @@ -0,0 +1,1005 @@ +package com.ruoyi.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.ruoyi.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": + return true; + case "false": + return false; + case "yes": + return true; + case "ok": + return true; + case "no": + return false; + case "1": + return true; + 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 new BigDecimal((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/wumei-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java new file mode 100644 index 00000000..c78ac776 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/core/text/StrFormatter.java @@ -0,0 +1,92 @@ +package com.ruoyi.common.core.text; + +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java new file mode 100644 index 00000000..10b7306f --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/BusinessStatus.java @@ -0,0 +1,20 @@ +package com.ruoyi.common.enums; + +/** + * 操作状态 + * + * @author ruoyi + * + */ +public enum BusinessStatus +{ + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/BusinessType.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/BusinessType.java new file mode 100644 index 00000000..2e17c4a5 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/BusinessType.java @@ -0,0 +1,59 @@ +package com.ruoyi.common.enums; + +/** + * 业务操作类型 + * + * @author ruoyi + */ +public enum BusinessType +{ + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空数据 + */ + CLEAN, +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java new file mode 100644 index 00000000..0d945be5 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/DataSourceType.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.enums; + +/** + * 数据源 + * + * @author ruoyi + */ +public enum DataSourceType +{ + /** + * 主库 + */ + MASTER, + + /** + * 从库 + */ + SLAVE +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java new file mode 100644 index 00000000..be6f7392 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/HttpMethod.java @@ -0,0 +1,36 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/enums/LimitType.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/LimitType.java new file mode 100644 index 00000000..c609fd8a --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/LimitType.java @@ -0,0 +1,20 @@ +package com.ruoyi.common.enums; + +/** + * 限流类型 + * + * @author ruoyi + */ + +public enum LimitType +{ + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/OperatorType.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/OperatorType.java new file mode 100644 index 00000000..bdd143c1 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/OperatorType.java @@ -0,0 +1,24 @@ +package com.ruoyi.common.enums; + +/** + * 操作人类别 + * + * @author ruoyi + */ +public enum OperatorType +{ + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/UserStatus.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/UserStatus.java new file mode 100644 index 00000000..d7ff44a9 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/enums/UserStatus.java @@ -0,0 +1,30 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java new file mode 100644 index 00000000..f6ad2ab4 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/DemoModeException.java @@ -0,0 +1,15 @@ +package com.ruoyi.common.exception; + +/** + * 演示模式异常 + * + * @author ruoyi + */ +public class DemoModeException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public DemoModeException() + { + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/GlobalException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/GlobalException.java new file mode 100644 index 00000000..211441bf --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/GlobalException.java @@ -0,0 +1,58 @@ +package com.ruoyi.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; + } + + 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/wumei-common/src/main/java/com/ruoyi/common/exception/ServiceException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/ServiceException.java new file mode 100644 index 00000000..6297f87f --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/ServiceException.java @@ -0,0 +1,73 @@ +package com.ruoyi.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 String getDetailMessage() + { + return detailMessage; + } + + 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; + } +} \ No newline at end of file diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/UtilException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/UtilException.java new file mode 100644 index 00000000..980fa465 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/UtilException.java @@ -0,0 +1,26 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java new file mode 100644 index 00000000..b55d72e1 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/base/BaseException.java @@ -0,0 +1,97 @@ +package com.ruoyi.common.exception.base; + +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/exception/file/FileException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/file/FileException.java new file mode 100644 index 00000000..871f09b5 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/file/FileException.java @@ -0,0 +1,19 @@ +package com.ruoyi.common.exception.file; + +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 00000000..70e0ec9b --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,16 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 00000000..ec6ab054 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,16 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java new file mode 100644 index 00000000..f1c8e835 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/file/InvalidExtensionException.java @@ -0,0 +1,81 @@ +package com.ruoyi.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 : [" + filename + "], extension : [" + extension + "], allowed 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/wumei-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java new file mode 100644 index 00000000..a567b408 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/job/TaskException.java @@ -0,0 +1,34 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java new file mode 100644 index 00000000..389dbc75 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/user/CaptchaException.java @@ -0,0 +1,16 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java new file mode 100644 index 00000000..85f94861 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/user/CaptchaExpireException.java @@ -0,0 +1,16 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/exception/user/UserException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/user/UserException.java new file mode 100644 index 00000000..c292d70e --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/user/UserException.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.exception.user; + +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java new file mode 100644 index 00000000..a7f3e5ff --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/exception/user/UserPasswordNotMatchException.java @@ -0,0 +1,16 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java new file mode 100644 index 00000000..a1bcfe2a --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/filter/RepeatableFilter.java @@ -0,0 +1,52 @@ +package com.ruoyi.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.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 00000000..614c24ce --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,75 @@ +package com.ruoyi.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.ruoyi.common.utils.http.HttpHelper; + +/** + * 构建可重复读取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("UTF-8"); + response.setCharacterEncoding("UTF-8"); + + body = HttpHelper.getBodyString(request).getBytes("UTF-8"); + } + + @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/wumei-common/src/main/java/com/ruoyi/common/filter/XssFilter.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/filter/XssFilter.java new file mode 100644 index 00000000..99323edf --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/filter/XssFilter.java @@ -0,0 +1,74 @@ +package com.ruoyi.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.ruoyi.common.utils.StringUtils; + +/** + * 防止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 || method.matches("GET") || method.matches("DELETE")) + { + return true; + } + return StringUtils.matches(url, excludes); + } + + @Override + public void destroy() + { + + } +} \ No newline at end of file diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 00000000..b1eeb65c --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,111 @@ +package com.ruoyi.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.ruoyi.common.utils.StringUtils; +import com.ruoyi.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[] escapseValues = new String[length]; + for (int i = 0; i < length; i++) + { + // 防xss攻击和过滤前后空格 + escapseValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapseValues; + } + 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/wumei-common/src/main/java/com/ruoyi/common/utils/Arith.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/Arith.java new file mode 100644 index 00000000..b6326c2b --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/Arith.java @@ -0,0 +1,114 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/DateUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/DateUtils.java new file mode 100644 index 00000000..536cb3cf --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/DateUtils.java @@ -0,0 +1,155 @@ +package com.ruoyi.common.utils; + +import java.lang.management.ManagementFactory; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +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"; + + 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"); + } + + /** + * 日期型字符串转化为日期 格式 + */ + 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 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 + "分钟"; + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/DictUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/DictUtils.java new file mode 100644 index 00000000..fd9946d6 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/DictUtils.java @@ -0,0 +1,183 @@ +package com.ruoyi.common.utils; + +import java.util.Collection; +import java.util.List; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.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) + { + Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key)); + if (StringUtils.isNotNull(cacheObj)) + { + List dictDatas = StringUtils.cast(cacheObj); + return dictDatas; + } + 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.containsAny(separator, dictValue) && StringUtils.isNotEmpty(datas)) + { + for (SysDictData dict : datas) + { + for (String value : dictValue.split(separator)) + { + if (value.equals(dict.getDictValue())) + { + propertyString.append(dict.getDictLabel() + 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() + 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(Constants.SYS_DICT_KEY + "*"); + SpringUtils.getBean(RedisCache.class).deleteObject(keys); + } + + /** + * 设置cache key + * + * @param configKey 参数键 + * @return 缓存键key + */ + public static String getCacheKey(String configKey) + { + return Constants.SYS_DICT_KEY + configKey; + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java new file mode 100644 index 00000000..ae81a697 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/ExceptionUtil.java @@ -0,0 +1,40 @@ +package com.ruoyi.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)); + String str = sw.toString(); + return str; + } + + 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/wumei-common/src/main/java/com/ruoyi/common/utils/LogUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/LogUtils.java new file mode 100644 index 00000000..0de30c6b --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/LogUtils.java @@ -0,0 +1,18 @@ +package com.ruoyi.common.utils; + +/** + * 处理并记录日志文件 + * + * @author ruoyi + */ +public class LogUtils +{ + public static String getBlock(Object msg) + { + if (msg == null) + { + msg = ""; + } + return "[" + msg.toString() + "]"; + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java new file mode 100644 index 00000000..7dac75a3 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/MessageUtils.java @@ -0,0 +1,26 @@ +package com.ruoyi.common.utils; + +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java new file mode 100644 index 00000000..a6f3d538 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java @@ -0,0 +1,120 @@ +package com.ruoyi.common.utils; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java new file mode 100644 index 00000000..03576c51 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/ServletUtils.java @@ -0,0 +1,152 @@ +package com.ruoyi.common.utils; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import com.ruoyi.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); + } + + /** + * 获取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 待渲染的字符串 + * @return null + */ + public static String 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(); + } + return null; + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + public static boolean isAjaxRequest(HttpServletRequest request) + { + String accept = request.getHeader("accept"); + if (accept != null && accept.indexOf("application/json") != -1) + { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1) + { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) + { + return true; + } + + String ajax = request.getParameter("__ajax"); + if (StringUtils.inStringIgnoreCase(ajax, "json", "xml")) + { + return true; + } + return false; + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/StringUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/StringUtils.java new file mode 100644 index 00000000..4ca2bfcf --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/StringUtils.java @@ -0,0 +1,534 @@ +package com.ruoyi.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 org.springframework.util.AntPathMatcher; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.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; + } + + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + * + * @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; + } +} \ No newline at end of file diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/Threads.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/Threads.java new file mode 100644 index 00000000..71fe6d52 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/Threads.java @@ -0,0 +1,99 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/VerifyCodeUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/VerifyCodeUtils.java new file mode 100644 index 00000000..5fdf3a9d --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/VerifyCodeUtils.java @@ -0,0 +1,228 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java new file mode 100644 index 00000000..4463662d --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/bean/BeanUtils.java @@ -0,0 +1,110 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java new file mode 100644 index 00000000..68130b9c --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/FileTypeUtils.java @@ -0,0 +1,76 @@ +package com.ruoyi.common.utils.file; + +import java.io.File; +import org.apache.commons.lang3.StringUtils; + +/** + * 文件类型工具类 + * + * @author ruoyi + */ +public class FileTypeUtils +{ + /** + * 获取文件类型 + *

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

+ * 例如: ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java new file mode 100644 index 00000000..eb12806e --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/FileUploadUtils.java @@ -0,0 +1,235 @@ +package com.ruoyi.common.utils.file; + +import java.io.File; +import java.io.IOException; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException; +import com.ruoyi.common.exception.file.FileSizeLimitExceededException; +import com.ruoyi.common.exception.file.InvalidExtensionException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.uuid.IdUtils; + +/** + * 文件上传工具类 + * + * @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 = 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); + + File desc = getAbsoluteFile(baseDir, fileName); + file.transferTo(desc); + String pathFileName = getPathFileName(baseDir, fileName); + return pathFileName; + } + + /** + * 编码文件名 + */ + public static final String extractFilename(MultipartFile file) + { + String fileName = file.getOriginalFilename(); + String extension = getExtension(file); + fileName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; + return fileName; + } + + 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); + String pathFileName = Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; + return pathFileName; + } + + /** + * 文件大小校验 + * + * @param file 上传的文件 + * @return + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws InvalidExtensionException + */ + public static final void assertAllowed(MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, InvalidExtensionException + { + long size = file.getSize(); + if (DEFAULT_MAX_SIZE != -1 && 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(file.getContentType()); + } + return extension; + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java new file mode 100644 index 00000000..7a244598 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/FileUtils.java @@ -0,0 +1,259 @@ +package com.ruoyi.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 org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.uuid.IdUtils; + +/** + * 文件处理工具类 + * + * @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()) + { + file.delete(); + flag = true; + } + 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 真实文件名 + * @return + */ + 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-Allow-Origin", "*"); + 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; + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java new file mode 100644 index 00000000..0b7b5aaa --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/ImageUtils.java @@ -0,0 +1,101 @@ +package com.ruoyi.common.utils.file; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +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.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.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 key 地址 + * @return 字节数据 + */ + public static byte[] readFile(String url) + { + InputStream in = null; + ByteArrayOutputStream baos = 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); + IOUtils.closeQuietly(baos); + } + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java new file mode 100644 index 00000000..f968f1a1 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java @@ -0,0 +1,59 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java new file mode 100644 index 00000000..f52e83e5 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/html/EscapeUtil.java @@ -0,0 +1,167 @@ +package com.ruoyi.common.utils.html; + +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java new file mode 100644 index 00000000..db069bc8 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/html/HTMLFilter.java @@ -0,0 +1,570 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java new file mode 100644 index 00000000..dac60ad0 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/http/HttpHelper.java @@ -0,0 +1,55 @@ +package com.ruoyi.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +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, Charset.forName("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/wumei-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java new file mode 100644 index 00000000..e319d370 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java @@ -0,0 +1,274 @@ +package com.ruoyi.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.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.ruoyi.common.constant.Constants; +import com.ruoyi.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 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param) + { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try + { + String urlNameString = url; + log.info("sendPost - {}", urlNameString); + URL realUrl = new URL(urlNameString); + 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(), "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("ISO-8859-1"), "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; + } + } +} \ No newline at end of file diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java new file mode 100644 index 00000000..01c24fc7 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/ip/AddressUtils.java @@ -0,0 +1,56 @@ +package com.ruoyi.common.utils.ip; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.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) + { + String address = UNKNOWN; + // 内网不查询 + 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 = JSONObject.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 address; + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java new file mode 100644 index 00000000..1b3f42ba --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/ip/IpUtils.java @@ -0,0 +1,195 @@ +package com.ruoyi.common.utils.ip; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import javax.servlet.http.HttpServletRequest; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.html.EscapeUtil; + +/** + * 获取IP方法 + * + * @author ruoyi + */ +public class IpUtils +{ + 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" : EscapeUtil.clean(ip); + } + + public static boolean internalIp(String ip) + { + byte[] addr = textToNumericFormatV4(ip); + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + 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; + } + + public static String getHostIp() + { + try + { + return InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException e) + { + } + return "127.0.0.1"; + } + + public static String getHostName() + { + try + { + return InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException e) + { + } + return "未知"; + } +} \ No newline at end of file diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java new file mode 100644 index 00000000..5ea74c11 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java @@ -0,0 +1,19 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java new file mode 100644 index 00000000..4557ca0b --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java @@ -0,0 +1,1391 @@ +package com.ruoyi.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.math.BigDecimal; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +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.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.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.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.annotation.Excel.Type; +import com.ruoyi.common.annotation.Excels; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.exception.UtilException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileTypeUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.common.utils.file.ImageUtils; +import com.ruoyi.common.utils.reflect.ReflectUtils; + +/** + * Excel相关处理 + * + * @author ruoyi + */ +public class ExcelUtil +{ + private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); + + /** + * 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 Map statistics = new HashMap(); + + /** + * 数字格式 + */ + private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00"); + + /** + * 实体对象 + */ + public Class clazz; + + public ExcelUtil(Class clazz) + { + this.clazz = clazz; + } + + 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(); + } + + /** + * 创建excel第一行标题 + */ + public void createTitle() + { + if (StringUtils.isNotEmpty(title)) + { + 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(), + this.fields.size() - 1)); + } + } + + /** + * 对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 = DateUtils.parseDateToStr(dateFormat, (Date) 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) + { + 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 结果 + * @throws IOException + */ + 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 结果 + * @throws IOException + */ + 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) + { + Excel excel = (Excel) os[1]; + this.createCell(excel, row, column++); + } + if (Type.EXPORT.equals(type)) + { + fillExcelData(index, row); + addStatisticsRow(); + } + } + } + + /** + * 填充excel数据 + * + * @param index 序号 + * @param row 单元格行 + */ + public void fillExcelData(int index, Row row) + { + int startNo = index * sheetSize; + int endNo = Math.min(startNo + sheetSize, list.size()); + for (int i = startNo; i < endNo; i++) + { + row = sheet.createRow(i + 1 + rownum - startNo); + // 得到导出对象. + T vo = (T) list.get(i); + int column = 0; + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + 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.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBold(true); + headerFont.setColor(IndexedColors.WHITE.getIndex()); + style.setFont(headerFont); + styles.put("header", 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); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.LEFT); + styles.put("data1", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.CENTER); + styles.put("data2", style); + + style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.RIGHT); + styles.put("data3", style); + + return styles; + } + + /** + * 创建单元格 + */ + public Cell createCell(Excel attr, Row row, int column) + { + // 创建列 + Cell cell = row.createCell(column); + // 写入列信息 + cell.setCellValue(attr.name()); + setDataValidation(attr, row, column); + cell.setCellStyle(styles.get("header")); + return cell; + } + + /** + * 设置单元格信息 + * + * @param value 单元格值 + * @param attr 注解相关 + * @param cell 单元格信息 + */ + public void setCellVo(Object value, Excel attr, Cell cell) + { + if (ColumnType.STRING == attr.cellType()) + { + cell.setCellValue(StringUtils.isNull(value) ? attr.defaultValue() : value + 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())) + { + // 这里默认设了2-101列提示. + setXSSFPrompt(sheet, "", attr.prompt(), 1, 100, column, column); + } + // 如果设置了combo属性则本列只能选择不能输入 + if (attr.combo().length > 0) + { + // 这里默认设了2-101列只能选择不能输入. + setXSSFValidation(sheet, attr.combo(), 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); + int align = attr.align().value(); + cell.setCellStyle(styles.get("data" + (align >= 1 && align <= 3 ? align : ""))); + + // 用于读取对象中的属性 + 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(DateUtils.parseDateToStr(dateFormat, (Date) 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())).toString()); + } + 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 promptTitle 提示标题 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setXSSFPrompt(Sheet sheet, String promptTitle, String promptContent, int firstRow, int endRow, + int firstCol, int endCol) + { + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint = helper.createCustomConstraint("DD1"); + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + DataValidation dataValidation = helper.createValidation(constraint, regions); + dataValidation.createPromptBox(promptTitle, promptContent); + dataValidation.setShowPromptBox(true); + sheet.addValidationData(dataValidation); + } + + /** + * 设置某些列的值只能输入预制的数据,显示下拉框. + * + * @param sheet 要设置的sheet. + * @param textlist 下拉框显示的内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + * @return 设置好的sheet. + */ + public void setXSSFValidation(Sheet sheet, String[] textlist, int firstRow, int endRow, int firstCol, int endCol) + { + DataValidationHelper helper = sheet.getDataValidationHelper(); + // 加载下拉列表内容 + DataValidationConstraint constraint = helper.createExplicitListConstraint(textlist); + // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + // 数据有效性对象 + DataValidation dataValidation = helper.createValidation(constraint, regions); + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + + sheet.addValidationData(dataValidation); + } + + /** + * 解析导出值 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(separator, propertyValue)) + { + 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(separator, propertyValue)) + { + 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.indexOf(".") > -1) + { + 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 (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 (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 = maxHeight > excel.height() ? 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; + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java new file mode 100644 index 00000000..b19953e0 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/reflect/ReflectUtils.java @@ -0,0 +1,410 @@ +package com.ruoyi.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.ruoyi.common.core.text.Convert; +import com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java new file mode 100644 index 00000000..ca1cd924 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/sign/Base64.java @@ -0,0 +1,291 @@ +package com.ruoyi.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/wumei-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java new file mode 100644 index 00000000..de77ee82 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/sign/Md5Utils.java @@ -0,0 +1,66 @@ +package com.ruoyi.common.utils.sign; + +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("UTF-8"), "UTF-8"); + } + catch (Exception e) + { + log.error("not supported charset...{}", e); + return s; + } + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java new file mode 100644 index 00000000..9993b98f --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/spring/SpringUtils.java @@ -0,0 +1,146 @@ +package com.ruoyi.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.ruoyi.common.utils.StringUtils; + +/** + * 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; + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java new file mode 100644 index 00000000..a1989463 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/sql/SqlUtil.java @@ -0,0 +1,37 @@ +package com.ruoyi.common.utils.sql; + +import com.ruoyi.common.exception.UtilException; +import com.ruoyi.common.utils.StringUtils; + +/** + * sql操作工具类 + * + * @author ruoyi + */ +public class SqlUtil +{ + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + 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); + } +} diff --git a/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java new file mode 100644 index 00000000..2c844271 --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/uuid/IdUtils.java @@ -0,0 +1,49 @@ +package com.ruoyi.common.utils.uuid; + +/** + * ID生成器工具类 + * + * @author ruoyi + */ +public class IdUtils +{ + /** + * 获取随机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/wumei-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java new file mode 100644 index 00000000..dfda46cf --- /dev/null +++ b/springboot/wumei-common/src/main/java/com/ruoyi/common/utils/uuid/UUID.java @@ -0,0 +1,484 @@ +package com.ruoyi.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.ruoyi.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/wumei-framework/pom.xml b/springboot/wumei-framework/pom.xml new file mode 100644 index 00000000..cb502414 --- /dev/null +++ b/springboot/wumei-framework/pom.xml @@ -0,0 +1,64 @@ + + + + wumei + com.wumei + 3.8.0 + + 4.0.0 + + wumei-framework + + + framework框架核心 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.alibaba + druid-spring-boot-starter + + + + + com.github.penggle + kaptcha + + + javax.servlet-api + javax.servlet + + + + + + + com.github.oshi + oshi-core + + + + + com.wumei + wumei-system + + + + + \ No newline at end of file diff --git a/springboot/wumei-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java new file mode 100644 index 00000000..48f8d728 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/aspectj/DataScopeAspect.java @@ -0,0 +1,149 @@ +package com.ruoyi.framework.aspectj; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; +import com.ruoyi.common.annotation.DataScope; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.SecurityUtils; + +/** + * 数据过滤处理 + * + * @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()) + { + dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), + controllerDataScope.userAlias()); + } + } + } + + /** + * 数据范围过滤 + * + * @param joinPoint 切点 + * @param user 用户 + * @param userAlias 别名 + */ + public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias) + { + StringBuilder sqlString = new StringBuilder(); + + for (SysRole role : user.getRoles()) + { + String dataScope = role.getDataScope(); + if (DATA_SCOPE_ALL.equals(dataScope)) + { + sqlString = new StringBuilder(); + break; + } + else if (DATA_SCOPE_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(" OR 1=0 "); + } + } + } + + 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/wumei-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java new file mode 100644 index 00000000..8c2c9f43 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/aspectj/DataSourceAspect.java @@ -0,0 +1,72 @@ +package com.ruoyi.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.ruoyi.common.annotation.DataSource; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder; + +/** + * 多数据源处理 + * + * @author ruoyi + */ +@Aspect +@Order(1) +@Component +public class DataSourceAspect +{ + protected Logger logger = LoggerFactory.getLogger(getClass()); + + @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)" + + "|| @within(com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java new file mode 100644 index 00000000..9bf5b8c5 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/aspectj/LogAspect.java @@ -0,0 +1,218 @@ +package com.ruoyi.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 org.springframework.web.servlet.HandlerMapping; +import com.alibaba.fastjson.JSON; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.BusinessStatus; +import com.ruoyi.common.enums.HttpMethod; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.system.domain.SysOperLog; + +/** + * 操作日志记录处理 + * + * @author ruoyi + */ +@Aspect +@Component +public class LogAspect +{ + private static final Logger log = LoggerFactory.getLogger(LogAspect.class); + + /** + * 处理完请求后执行 + * + * @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(ServletUtils.getRequest().getRequestURI()); + 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("==前置通知异常=="); + 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 = (Map) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 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 + { + Object jsonObj = JSON.toJSON(o); + params += jsonObj.toString() + " "; + } + catch (Exception e) + { + } + } + } + } + return params.trim(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @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/wumei-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java new file mode 100644 index 00000000..0d1b62f1 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java @@ -0,0 +1,91 @@ +package com.ruoyi.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.ruoyi.common.annotation.RateLimiter; +import com.ruoyi.common.enums.LimitType; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.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 + { + String key = rateLimiter.key(); + 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(), key); + } + 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/wumei-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java new file mode 100644 index 00000000..1d4dc1f7 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java @@ -0,0 +1,30 @@ +package com.ruoyi.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.ruoyi.**.mapper") +public class ApplicationConfig +{ + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() + { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } +} diff --git a/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java new file mode 100644 index 00000000..43e78aeb --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/CaptchaConfig.java @@ -0,0 +1,83 @@ +package com.ruoyi.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.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java new file mode 100644 index 00000000..f6abac13 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/DruidConfig.java @@ -0,0 +1,126 @@ +package com.ruoyi.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.ruoyi.common.enums.DataSourceType; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.framework.config.properties.DruidProperties; +import com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 00000000..59812ea5 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,71 @@ +package com.ruoyi.framework.config; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.TypeFactory; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import com.alibaba.fastjson.parser.ParserConfig; +import org.springframework.util.Assert; +import java.nio.charset.Charset; + +/** + * Redis使用FastJson序列化 + * + * @author ruoyi + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer +{ + @SuppressWarnings("unused") + private ObjectMapper objectMapper = new ObjectMapper(); + + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + private Class clazz; + + static + { + ParserConfig.getGlobalInstance().setAutoTypeSupport(true); + } + + 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, SerializerFeature.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); + } + + public void setObjectMapper(ObjectMapper objectMapper) + { + Assert.notNull(objectMapper, "'objectMapper' must not be null"); + this.objectMapper = objectMapper; + } + + protected JavaType getJavaType(Class clazz) + { + return TypeFactory.defaultInstance().constructType(clazz); + } +} diff --git a/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java new file mode 100644 index 00000000..bb14c04d --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java @@ -0,0 +1,58 @@ +package com.ruoyi.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.ruoyi.common.filter.RepeatableFilter; +import com.ruoyi.common.filter.XssFilter; +import com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java new file mode 100644 index 00000000..3e745800 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/KaptchaTextCreator.java @@ -0,0 +1,75 @@ +package com.ruoyi.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 = (int) Math.round(Math.random() * 2); + 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 (randomoperands == 2) + { + 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]); + } + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + suChinese.append("=?@" + result); + return suChinese.toString(); + } +} \ No newline at end of file diff --git a/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java new file mode 100644 index 00000000..057c9419 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/MyBatisConfig.java @@ -0,0 +1,132 @@ +package com.ruoyi.framework.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import javax.sql.DataSource; +import org.apache.ibatis.io.VFS; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.util.ClassUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * 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()]); + } + + @Bean + 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(); + } +} \ No newline at end of file diff --git a/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java new file mode 100644 index 00000000..833f219c --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java @@ -0,0 +1,79 @@ +package com.ruoyi.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; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; + +/** + * 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); + + ObjectMapper mapper = new ObjectMapper(); + mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + serializer.setObjectMapper(mapper); + + // 使用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/wumei-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java new file mode 100644 index 00000000..50ad496c --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java @@ -0,0 +1,70 @@ +package com.ruoyi.framework.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.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/"); + } + + /** + * 自定义拦截规则 + */ + @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/wumei-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java new file mode 100644 index 00000000..ada966ca --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -0,0 +1,160 @@ +package com.ruoyi.framework.config; + +import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter; +import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl; +import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; +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.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +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; + +/** + * spring security配置 + * + * @author ruoyi + */ +@EnableWebSecurity +@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; + + /** + * 解决 无法直接注入 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 + { + httpSecurity + // CSRF禁用,因为不使用session + .csrf().disable() + // 认证失败处理类 TODO 启用自定义认证会导致oauth授权地址不能访问 + //.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/mqtt/auth","/iot/tool/mqtt/webhook").anonymous() + .antMatchers( + HttpMethod.GET, + "/", + "/*.html", + "/**/*.html", + "/**/*.css", + "/**/*.js", + "/profile/**" + ).permitAll() + .antMatchers("/swagger-ui.html").anonymous() + .antMatchers("/swagger-resources/**").anonymous() + .antMatchers("/webjars/**").anonymous() + .antMatchers("/*/api-docs").anonymous() + .antMatchers("/druid/**").anonymous() + /************ oauth ************************/ + // 静态资源文件 + .antMatchers("/oauth/logout/**","/oauth/css/**","/oauth/fonts/**").permitAll() + // 智能音箱控制器由资源服务器管理 + .antMatchers("/oauth/speaker/**").permitAll() + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage("/oauth/login") + .permitAll() + .and() + .logout().logoutUrl("/oauth/logout") + .permitAll() + /************* oauth **************************/ + .and() + .headers().frameOptions().disable(); + 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/wumei-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java new file mode 100644 index 00000000..b5b7de31 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/ServerConfig.java @@ -0,0 +1,32 @@ +package com.ruoyi.framework.config; + +import javax.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java new file mode 100644 index 00000000..0d67c224 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/ThreadPoolConfig.java @@ -0,0 +1,62 @@ +package com.ruoyi.framework.config; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +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 com.ruoyi.common.utils.Threads; + +/** + * 线程池配置 + * + * @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()) + { + @Override + protected void afterExecute(Runnable r, Throwable t) + { + super.afterExecute(r, t); + Threads.printException(r, t); + } + }; + } +} diff --git a/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java new file mode 100644 index 00000000..84f7e009 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/config/properties/DruidProperties.java @@ -0,0 +1,77 @@ +package com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java new file mode 100644 index 00000000..e70b8cfa --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java @@ -0,0 +1,26 @@ +package com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java new file mode 100644 index 00000000..3572db91 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/datasource/DynamicDataSourceContextHolder.java @@ -0,0 +1,45 @@ +package com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java new file mode 100644 index 00000000..75757489 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java @@ -0,0 +1,55 @@ +package com.ruoyi.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.fastjson.JSONObject; +import com.ruoyi.common.annotation.RepeatSubmit; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.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, JSONObject.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/wumei-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java new file mode 100644 index 00000000..0138f140 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java @@ -0,0 +1,114 @@ +package com.ruoyi.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.fastjson.JSONObject; +import com.ruoyi.common.annotation.RepeatSubmit; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.filter.RepeatedlyRequestWrapper; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.http.HttpHelper; +import com.ruoyi.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 = JSONObject.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 = request.getHeader(header); + if (StringUtils.isEmpty(submitKey)) + { + submitKey = url; + } + + // 唯一标识(指定key + 消息头) + String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + 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/wumei-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java new file mode 100644 index 00000000..7387a02c --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/manager/AsyncManager.java @@ -0,0 +1,55 @@ +package com.ruoyi.framework.manager; + +import java.util.TimerTask; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import com.ruoyi.common.utils.Threads; +import com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java new file mode 100644 index 00000000..e36ca3c5 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/manager/ShutdownManager.java @@ -0,0 +1,39 @@ +package com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java new file mode 100644 index 00000000..23d02308 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/manager/factory/AsyncFactory.java @@ -0,0 +1,102 @@ +package com.ruoyi.framework.manager.factory; + +import java.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.LogUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.AddressUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.system.domain.SysLogininfor; +import com.ruoyi.system.domain.SysOperLog; +import com.ruoyi.system.service.ISysLogininforService; +import com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 00000000..3eb24954 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,44 @@ +package com.ruoyi.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.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java new file mode 100644 index 00000000..c22dd324 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java @@ -0,0 +1,34 @@ +package com.ruoyi.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.fastjson.JSON; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.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))); + } +} diff --git a/springboot/wumei-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java new file mode 100644 index 00000000..e5fc11d0 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -0,0 +1,53 @@ +package com.ruoyi.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.fastjson.JSON; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.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.error(HttpStatus.SUCCESS, "退出成功"))); + } +} diff --git a/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java new file mode 100644 index 00000000..63b03da7 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/Server.java @@ -0,0 +1,240 @@ +package com.ruoyi.framework.web.domain; + +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import com.ruoyi.common.utils.Arith; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.framework.web.domain.server.Cpu; +import com.ruoyi.framework.web.domain.server.Jvm; +import com.ruoyi.framework.web.domain.server.Mem; +import com.ruoyi.framework.web.domain.server.Sys; +import com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java new file mode 100644 index 00000000..a13a66cf --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/Cpu.java @@ -0,0 +1,101 @@ +package com.ruoyi.framework.web.domain.server; + +import com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java new file mode 100644 index 00000000..485d201d --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/Jvm.java @@ -0,0 +1,122 @@ +package com.ruoyi.framework.web.domain.server; + +import java.lang.management.ManagementFactory; +import com.ruoyi.common.utils.Arith; +import com.ruoyi.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()); + } +} diff --git a/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java new file mode 100644 index 00000000..13eec521 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/Mem.java @@ -0,0 +1,61 @@ +package com.ruoyi.framework.web.domain.server; + +import com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java new file mode 100644 index 00000000..45d64d9c --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/Sys.java @@ -0,0 +1,84 @@ +package com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java new file mode 100644 index 00000000..1320cde6 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/domain/server/SysFile.java @@ -0,0 +1,114 @@ +package com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..51dd8c54 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java @@ -0,0 +1,114 @@ +package com.ruoyi.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.ruoyi.common.constant.HttpStatus; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.exception.DemoModeException; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.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/wumei-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java new file mode 100644 index 00000000..68b1d696 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/PermissionService.java @@ -0,0 +1,165 @@ +package com.ruoyi.framework.web.service; + +import java.util.Set; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; + +/** + * 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; + } + 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; + } + 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/wumei-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java new file mode 100644 index 00000000..6131f72e --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java @@ -0,0 +1,133 @@ +package com.ruoyi.framework.web.service; + +import javax.annotation.Resource; +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.stereotype.Component; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.exception.user.CaptchaException; +import com.ruoyi.common.exception.user.CaptchaExpireException; +import com.ruoyi.common.exception.user.UserPasswordNotMatchException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysUserService; + +/** + * 登录校验方法 + * + * @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; + + /** + * 登录验证 + * + * @param username 用户名 + * @param password 密码 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public String login(String username, String password, String code, String uuid) + { + boolean captchaOnOff = configService.selectCaptchaOnOff(); + // 验证码开关 + if (captchaOnOff) + { + validateCaptcha(username, code, uuid); + } + // 用户验证 + 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 code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + String verifyKey = Constants.CAPTCHA_CODE_KEY + 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/wumei-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java new file mode 100644 index 00000000..feb80387 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java @@ -0,0 +1,66 @@ +package com.ruoyi.framework.web.service; + +import java.util.HashSet; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.system.service.ISysMenuService; +import com.ruoyi.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 + { + perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); + } + return perms; + } +} diff --git a/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java new file mode 100644 index 00000000..9d346d43 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/SysRegisterService.java @@ -0,0 +1,115 @@ +package com.ruoyi.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.RegisterBody; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.exception.user.CaptchaException; +import com.ruoyi.common.exception.user.CaptchaExpireException; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.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(); + + boolean captchaOnOff = configService.selectCaptchaOnOff(); + // 验证码开关 + if (captchaOnOff) + { + 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(username))) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + } + else + { + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + sysUser.setNickName(username); + sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword())); + 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 = Constants.CAPTCHA_CODE_KEY + 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/wumei-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java new file mode 100644 index 00000000..bf28e0f5 --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java @@ -0,0 +1,225 @@ +package com.ruoyi.framework.web.service; + +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.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.ip.AddressUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.common.utils.uuid.IdUtils; +import eu.bitwalker.useragentutils.UserAgent; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +/** + * 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; + } + + /** + * 设置用户身份信息 + */ + 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); + } + + /** + * 设置用户代理信息 + * + * @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 Constants.LOGIN_TOKEN_KEY + uuid; + } +} diff --git a/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java new file mode 100644 index 00000000..4b808f9c --- /dev/null +++ b/springboot/wumei-framework/src/main/java/com/ruoyi/framework/web/service/UserDetailsServiceImpl.java @@ -0,0 +1,60 @@ +package com.ruoyi.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.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.enums.UserStatus; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.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 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 + " 已停用"); + } + UserDetails details=createLoginUser(user); + return createLoginUser(user); + } + + public UserDetails createLoginUser(SysUser user) + { + return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + } +} diff --git a/springboot/wumei-generator/pom.xml b/springboot/wumei-generator/pom.xml new file mode 100644 index 00000000..43c0aae8 --- /dev/null +++ b/springboot/wumei-generator/pom.xml @@ -0,0 +1,40 @@ + + + + wumei + com.wumei + 3.8.0 + + 4.0.0 + + wumei-generator + + + generator代码生成 + + + + + + + org.apache.velocity + velocity-engine-core + + + + + commons-collections + commons-collections + + + + + com.wumei + wumei-common + + + + + \ No newline at end of file diff --git a/springboot/wumei-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java new file mode 100644 index 00000000..cc4cd14c --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java @@ -0,0 +1,73 @@ +package com.ruoyi.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/wumei-generator/src/main/java/com/ruoyi/generator/controller/GenController.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/controller/GenController.java new file mode 100644 index 00000000..6dfe5e31 --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/controller/GenController.java @@ -0,0 +1,214 @@ +package com.ruoyi.generator.controller; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletResponse; +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.generator.domain.GenTableColumn; +import com.ruoyi.generator.service.IGenTableColumnService; +import com.ruoyi.generator.service.IGenTableService; + +/** + * 代码生成 操作处理 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/tool/gen") +public class GenController extends BaseController +{ + @Autowired + private IGenTableService genTableService; + + @Autowired + private IGenTableColumnService genTableColumnService; + + /** + * 查询代码生成列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/list") + public TableDataInfo genList(GenTable genTable) + { + startPage(); + List list = genTableService.selectGenTableList(genTable); + return getDataTable(list); + } + + /** + * 修改代码生成业务 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:query')") + @GetMapping(value = "/{talbleId}") + public AjaxResult getInfo(@PathVariable Long talbleId) + { + GenTable table = genTableService.selectGenTableById(talbleId); + List tables = genTableService.selectGenTableAll(); + List list = genTableColumnService.selectGenTableColumnListByTableId(talbleId); + Map map = new HashMap(); + map.put("info", table); + map.put("rows", list); + map.put("tables", tables); + return AjaxResult.success(map); + } + + /** + * 查询数据库列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/db/list") + public TableDataInfo dataList(GenTable genTable) + { + startPage(); + List list = genTableService.selectDbTableList(genTable); + return getDataTable(list); + } + + /** + * 查询数据表字段列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping(value = "/column/{talbleId}") + public TableDataInfo columnList(Long tableId) + { + TableDataInfo dataInfo = new TableDataInfo(); + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + dataInfo.setRows(list); + dataInfo.setTotal(list.size()); + return dataInfo; + } + + /** + * 导入表结构(保存) + */ + @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 AjaxResult.success(); + } + + /** + * 修改保存代码生成业务 + */ + @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 AjaxResult.success(); + } + + /** + * 删除代码生成 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:remove')") + @Log(title = "代码生成", businessType = BusinessType.DELETE) + @DeleteMapping("/{tableIds}") + public AjaxResult remove(@PathVariable Long[] tableIds) + { + genTableService.deleteGenTableByIds(tableIds); + return AjaxResult.success(); + } + + /** + * 预览代码 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:preview')") + @GetMapping("/preview/{tableId}") + public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException + { + Map dataMap = genTableService.previewCode(tableId); + return AjaxResult.success(dataMap); + } + + /** + * 生成代码(下载方式) + */ + @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); + } + + /** + * 生成代码(自定义路径) + */ + @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 AjaxResult.success(); + } + + /** + * 同步数据库 + */ + @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 AjaxResult.success(); + } + + /** + * 批量生成代码 + */ + @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=\"ruoyi.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/wumei-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java new file mode 100644 index 00000000..269779cf --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java @@ -0,0 +1,372 @@ +package com.ruoyi.generator.domain; + +import java.util.List; +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import org.apache.commons.lang3.ArrayUtils; +import com.ruoyi.common.constant.GenConstants; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.StringUtils; + +/** + * 业务表 gen_table + * + * @author ruoyi + */ +public class GenTable extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 编号 */ + private Long tableId; + + /** 表名称 */ + @NotBlank(message = "表名称不能为空") + private String tableName; + + /** 表描述 */ + @NotBlank(message = "表描述不能为空") + private String tableComment; + + /** 关联父表的表名 */ + private String subTableName; + + /** 本表关联父表的外键名 */ + private String subTableFkName; + + /** 实体类名称(首字母大写) */ + @NotBlank(message = "实体类名称不能为空") + private String className; + + /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */ + private String tplCategory; + + /** 生成包路径 */ + @NotBlank(message = "生成包路径不能为空") + private String packageName; + + /** 生成模块名 */ + @NotBlank(message = "生成模块名不能为空") + private String moduleName; + + /** 生成业务名 */ + @NotBlank(message = "生成业务名不能为空") + private String businessName; + + /** 生成功能名 */ + @NotBlank(message = "生成功能名不能为空") + private String functionName; + + /** 生成作者 */ + @NotBlank(message = "作者不能为空") + private String functionAuthor; + + /** 生成代码方式(0zip压缩包 1自定义路径) */ + private String genType; + + /** 生成路径(不填默认项目路径) */ + private String genPath; + + /** 主键信息 */ + private GenTableColumn pkColumn; + + /** 子表信息 */ + private GenTable subTable; + + /** 表列信息 */ + @Valid + private List columns; + + /** 其它生成选项 */ + private String options; + + /** 树编码字段 */ + private String treeCode; + + /** 树父编码字段 */ + private String treeParentCode; + + /** 树名称字段 */ + private String treeName; + + /** 上级菜单ID字段 */ + private String parentMenuId; + + /** 上级菜单名称字段 */ + 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/wumei-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java new file mode 100644 index 00000000..d1733b64 --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java @@ -0,0 +1,373 @@ +package com.ruoyi.generator.domain; + +import javax.validation.constraints.NotBlank; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.StringUtils; + +/** + * 代码生成业务字段表 gen_table_column + * + * @author ruoyi + */ +public class GenTableColumn extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 编号 */ + private Long columnId; + + /** 归属表编号 */ + private Long tableId; + + /** 列名称 */ + private String columnName; + + /** 列描述 */ + private String columnComment; + + /** 列类型 */ + private String columnType; + + /** JAVA类型 */ + private String javaType; + + /** JAVA字段名 */ + @NotBlank(message = "Java属性不能为空") + private String javaField; + + /** 是否主键(1是) */ + private String isPk; + + /** 是否自增(1是) */ + private String isIncrement; + + /** 是否必填(1是) */ + private String isRequired; + + /** 是否为插入字段(1是) */ + private String isInsert; + + /** 是否编辑字段(1是) */ + private String isEdit; + + /** 是否列表字段(1是) */ + private String isList; + + /** 是否查询字段(1是) */ + private String isQuery; + + /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */ + private String queryType; + + /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) */ + private String htmlType; + + /** 字典类型 */ + private String dictType; + + /** 排序 */ + 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/wumei-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java new file mode 100644 index 00000000..951e1667 --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java @@ -0,0 +1,60 @@ +package com.ruoyi.generator.mapper; + +import java.util.List; +import com.ruoyi.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/wumei-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java new file mode 100644 index 00000000..9b330df8 --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java @@ -0,0 +1,83 @@ +package com.ruoyi.generator.mapper; + +import java.util.List; +import com.ruoyi.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/wumei-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java new file mode 100644 index 00000000..0679689d --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java @@ -0,0 +1,68 @@ +package com.ruoyi.generator.service; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.generator.domain.GenTableColumn; +import com.ruoyi.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/wumei-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java new file mode 100644 index 00000000..499da03e --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java @@ -0,0 +1,500 @@ +package com.ruoyi.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.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.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.GenConstants; +import com.ruoyi.common.core.text.CharsetKit; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.generator.domain.GenTableColumn; +import com.ruoyi.generator.mapper.GenTableColumnMapper; +import com.ruoyi.generator.mapper.GenTableMapper; +import com.ruoyi.generator.util.GenUtils; +import com.ruoyi.generator.util.VelocityInitializer; +import com.ruoyi.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(); + List tableColumnNames = tableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList()); + + 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 -> { + if (!tableColumnNames.contains(column.getColumnName())) + { + GenUtils.initColumnField(column, table); + 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 = JSONObject.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 = JSONObject.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/wumei-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java new file mode 100644 index 00000000..3037f707 --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java @@ -0,0 +1,44 @@ +package com.ruoyi.generator.service; + +import java.util.List; +import com.ruoyi.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/wumei-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java new file mode 100644 index 00000000..9d53f95f --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java @@ -0,0 +1,121 @@ +package com.ruoyi.generator.service; + +import java.util.List; +import java.util.Map; +import com.ruoyi.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/wumei-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java new file mode 100644 index 00000000..06c68214 --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java @@ -0,0 +1,258 @@ +package com.ruoyi.generator.util; + +import java.util.Arrays; +import org.apache.commons.lang3.RegExUtils; +import com.ruoyi.common.constant.GenConstants; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.generator.config.GenConfig; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.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); + + 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(); + String moduleName = StringUtils.substring(packageName, lastIndex + 1, nameLength); + return moduleName; + } + + /** + * 获取业务名 + * + * @param tableName 表名 + * @return 业务名 + */ + public static String getBusinessName(String tableName) + { + int lastIndex = tableName.lastIndexOf("_"); + int nameLength = tableName.length(); + String businessName = StringUtils.substring(tableName, lastIndex + 1, nameLength); + return businessName; + } + + /** + * 表名转换成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/wumei-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java new file mode 100644 index 00000000..9f694038 --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java @@ -0,0 +1,34 @@ +package com.ruoyi.generator.util; + +import java.util.Properties; +import org.apache.velocity.app.Velocity; +import com.ruoyi.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/wumei-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java new file mode 100644 index 00000000..5a580d1b --- /dev/null +++ b/springboot/wumei-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java @@ -0,0 +1,385 @@ +package com.ruoyi.generator.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import org.apache.velocity.VelocityContext; +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.constant.GenConstants; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.generator.domain.GenTable; +import com.ruoyi.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 = JSONObject.parseObject(options); + String parentMenuId = getParentMenuId(paramsObj); + context.put("parentMenuId", parentMenuId); + } + + public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSONObject.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("."); + String basePackage = StringUtils.substring(packageName, 0, lastIndex); + return basePackage; + } + + /** + * 根据列类型获取导入包 + * + * @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(); + List dicts = new ArrayList(); + 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() + "'"); + } + } + return StringUtils.join(dicts, ", "); + } + + /** + * 获取权限前缀 + * + * @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 = JSONObject.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/wumei-generator/src/main/resources/generator.yml b/springboot/wumei-generator/src/main/resources/generator.yml new file mode 100644 index 00000000..3f2ae2e1 --- /dev/null +++ b/springboot/wumei-generator/src/main/resources/generator.yml @@ -0,0 +1,10 @@ +# 代码生成 +gen: + # 作者 + author: kerwincui + # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool + packageName: com.ruoyi.iot + # 自动去除表前缀,默认是false + autoRemovePre: true + # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) + tablePrefix: iot_ \ No newline at end of file diff --git a/springboot/wumei-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/springboot/wumei-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml new file mode 100644 index 00000000..5fa790f5 --- /dev/null +++ b/springboot/wumei-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/wumei-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/springboot/wumei-generator/src/main/resources/mapper/generator/GenTableMapper.xml new file mode 100644 index 00000000..b605e906 --- /dev/null +++ b/springboot/wumei-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/wumei-generator/src/main/resources/vm/java/controller.java.vm b/springboot/wumei-generator/src/main/resources/vm/java/controller.java.vm new file mode 100644 index 00000000..ab19cf51 --- /dev/null +++ b/springboot/wumei-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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.ruoyi.common.utils.poi.ExcelUtil; +#if($table.crud || $table.sub) +import com.ruoyi.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 AjaxResult.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 AjaxResult.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/wumei-generator/src/main/resources/vm/java/domain.java.vm b/springboot/wumei-generator/src/main/resources/vm/java/domain.java.vm new file mode 100644 index 00000000..bd51c177 --- /dev/null +++ b/springboot/wumei-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.ruoyi.common.annotation.Excel; +#if($table.crud || $table.sub) +import com.ruoyi.common.core.domain.BaseEntity; +#elseif($table.tree) +import com.ruoyi.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/wumei-iot/pom.xml b/springboot/wumei-iot/pom.xml new file mode 100644 index 00000000..3c67c32b --- /dev/null +++ b/springboot/wumei-iot/pom.xml @@ -0,0 +1,80 @@ + + + + wumei + com.wumei + 3.8.0 + + 4.0.0 + + wumei-iot + + + 物美智能-开源生活物联网平台 + + + + + + + com.wumei + wumei-common + + + io.swagger + swagger-annotations + 1.6.2 + compile + + + org.apache.velocity + velocity-engine-core + + + com.wumei + wumei-framework + + + com.wumei + wumei-quartz + + + + 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 + + + com.wumei + wumei-quartz + 3.8.0 + + + + + + \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/AlertController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/AlertController.java new file mode 100644 index 00000000..49ea9e9b --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/AlertController.java @@ -0,0 +1,104 @@ +package com.ruoyi.iot.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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.iot.domain.Alert; +import com.ruoyi.iot.service.IAlertService; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; + +/** + * 设备告警Controller + * + * @author kerwincui + * @date 2022-01-13 + */ +@RestController +@RequestMapping("/iot/alert") +public class AlertController extends BaseController +{ + @Autowired + private IAlertService alertService; + + /** + * 查询设备告警列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/list") + public TableDataInfo list(Alert alert) + { + startPage(); + List list = alertService.selectAlertList(alert); + return getDataTable(list); + } + + /** + * 导出设备告警列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:export')") + @Log(title = "设备告警", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, Alert alert) + { + List list = alertService.selectAlertList(alert); + ExcelUtil util = new ExcelUtil(Alert.class); + util.exportExcel(response, list, "设备告警数据"); + } + + /** + * 获取设备告警详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/{alertId}") + public AjaxResult getInfo(@PathVariable("alertId") Long alertId) + { + return AjaxResult.success(alertService.selectAlertByAlertId(alertId)); + } + + /** + * 新增设备告警 + */ + @PreAuthorize("@ss.hasPermi('iot:device:add')") + @Log(title = "设备告警", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody Alert alert) + { + return toAjax(alertService.insertAlert(alert)); + } + + /** + * 修改设备告警 + */ + @PreAuthorize("@ss.hasPermi('iot:device:edit')") + @Log(title = "设备告警", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody Alert alert) + { + return toAjax(alertService.updateAlert(alert)); + } + + /** + * 删除设备告警 + */ + @PreAuthorize("@ss.hasPermi('iot:device:remove')") + @Log(title = "设备告警", businessType = BusinessType.DELETE) + @DeleteMapping("/{alertIds}") + public AjaxResult remove(@PathVariable Long[] alertIds) + { + return toAjax(alertService.deleteAlertByAlertIds(alertIds)); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/AlertLogController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/AlertLogController.java new file mode 100644 index 00000000..e2e28189 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/AlertLogController.java @@ -0,0 +1,104 @@ +package com.ruoyi.iot.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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.iot.domain.AlertLog; +import com.ruoyi.iot.service.IAlertLogService; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; + +/** + * 设备告警Controller + * + * @author kerwincui + * @date 2022-01-13 + */ +@RestController +@RequestMapping("/iot/alertLog") +public class AlertLogController extends BaseController +{ + @Autowired + private IAlertLogService alertLogService; + + /** + * 查询设备告警列表 + */ + @PreAuthorize("@ss.hasPermi('iot:alertLog:list')") + @GetMapping("/list") + public TableDataInfo list(AlertLog alertLog) + { + startPage(); + List list = alertLogService.selectAlertLogList(alertLog); + return getDataTable(list); + } + + /** + * 导出设备告警列表 + */ + @PreAuthorize("@ss.hasPermi('iot:alertLog:export')") + @Log(title = "设备告警", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, AlertLog alertLog) + { + List list = alertLogService.selectAlertLogList(alertLog); + ExcelUtil util = new ExcelUtil(AlertLog.class); + util.exportExcel(response, list, "设备告警数据"); + } + + /** + * 获取设备告警详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:alertLog:query')") + @GetMapping(value = "/{alertLogId}") + public AjaxResult getInfo(@PathVariable("alertLogId") Long alertLogId) + { + return AjaxResult.success(alertLogService.selectAlertLogByAlertLogId(alertLogId)); + } + + /** + * 新增设备告警 + */ + @PreAuthorize("@ss.hasPermi('iot:alertLog:add')") + @Log(title = "设备告警", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody AlertLog alertLog) + { + return toAjax(alertLogService.insertAlertLog(alertLog)); + } + + /** + * 修改设备告警 + */ + @PreAuthorize("@ss.hasPermi('iot:alertLog:edit')") + @Log(title = "设备告警", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody AlertLog alertLog) + { + return toAjax(alertLogService.updateAlertLog(alertLog)); + } + + /** + * 删除设备告警 + */ + @PreAuthorize("@ss.hasPermi('iot:alertLog:remove')") + @Log(title = "设备告警", businessType = BusinessType.DELETE) + @DeleteMapping("/{alertLogIds}") + public AjaxResult remove(@PathVariable Long[] alertLogIds) + { + return toAjax(alertLogService.deleteAlertLogByAlertLogIds(alertLogIds)); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/AuthResourceController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/AuthResourceController.java new file mode 100644 index 00000000..c54c9cdf --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/AuthResourceController.java @@ -0,0 +1,37 @@ +package com.ruoyi.iot.controller; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.iot.domain.Alert; +import com.ruoyi.iot.service.IAlertService; +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 2022-01-13 + */ +@RestController +@RequestMapping("/oauth/resource") +public class AuthResourceController extends BaseController +{ + /** + * 查询设备告警列表 + */ + @GetMapping("/product") + public String findAll() { + return "查询产品列表成功!"; + } + + +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/CategoryController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/CategoryController.java new file mode 100644 index 00000000..3e4a1896 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/CategoryController.java @@ -0,0 +1,127 @@ +package com.ruoyi.iot.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.iot.domain.Category; +import com.ruoyi.iot.service.ICategoryService; +import com.ruoyi.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(); + List list = categoryService.selectCategoryList(category); + return getDataTable(list); + } + + /** + * 查询产品简短分类列表 + */ + @PreAuthorize("@ss.hasPermi('iot:category:list')") + @GetMapping("/shortlist") + @ApiOperation("分类简短列表") + public AjaxResult shortlist() + { + List list = categoryService.selectCategoryShortList(); + return AjaxResult.success(list); + } + + /** + * 导出产品分类列表 + */ + @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/wumei-iot/src/main/java/com/ruoyi/iot/controller/DeviceController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/DeviceController.java new file mode 100644 index 00000000..89b918b0 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/DeviceController.java @@ -0,0 +1,152 @@ +package com.ruoyi.iot.controller; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.iot.domain.Device; +import com.ruoyi.iot.model.DeviceAllShortOutput; +import com.ruoyi.iot.model.DeviceShortOutput; +import com.ruoyi.iot.service.IDeviceService; +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; + + /** + * 查询设备列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/list") + @ApiOperation("设备分页列表") + public TableDataInfo list(Device device) + { + startPage(); + List list = deviceService.selectDeviceList(device); + return getDataTable(list); + } + + /** + * 查询设备简短列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/shortList") + @ApiOperation("设备分页简短列表") + public TableDataInfo shortList(Device device) + { + startPage(); + List list = deviceService.selectDeviceShortList(device); + return getDataTable(list); + } + + /** + * 查询所有设备简短列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/all") + @ApiOperation("查询所有设备简短列表") + public TableDataInfo allShortList() + { + List list = deviceService.selectAllDeviceShortList(); + return getDataTable(list); + } + + /** + * 导出设备列表 + */ + @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 = "/runningStatus/{deviceId}") + @ApiOperation("获取设备详情和运行状态") + public AjaxResult getRunningStatusInfo(@PathVariable("deviceId") Long deviceId) + { + return AjaxResult.success(deviceService.selectDeviceRunningStatusByDeviceId(deviceId)); + } + + /** + * 新增设备 + */ + @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:edit')") + @Log(title = "设备", businessType = BusinessType.UPDATE) + @PutMapping + @ApiOperation("修改设备") + public AjaxResult edit(@RequestBody Device device) + { + return toAjax(deviceService.updateDevice(device)); + } + + /** + * 删除设备 + */ + @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.deleteDeviceByDeviceIds(deviceIds)); + } + + @PreAuthorize("@ss.hasPermi('iot:device:edit')") + @GetMapping("/generator") + @ApiOperation("生成设备编号") + public AjaxResult generatorDeviceNum(){ + return AjaxResult.success("操作成功",deviceService.generationDeviceNum()); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/DeviceJobController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/DeviceJobController.java new file mode 100644 index 00000000..060ed4f1 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/DeviceJobController.java @@ -0,0 +1,136 @@ +package com.ruoyi.iot.controller; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.exception.job.TaskException; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.iot.domain.DeviceJob; +import com.ruoyi.iot.service.IDeviceJobService; +import com.ruoyi.quartz.util.CronUtils; +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 + */ +@RestController +@RequestMapping("/iot/job") +public class DeviceJobController extends BaseController +{ + @Autowired + private IDeviceJobService jobService; + + /** + * 查询定时任务列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/list") + public TableDataInfo list(DeviceJob deviceJob) + { + startPage(); + List list = jobService.selectJobList(deviceJob); + return getDataTable(list); + } + + /** + * 导出定时任务列表 + */ + @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, "定时任务"); + } + + /** + * 获取定时任务详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/{jobId}") + public AjaxResult getInfo(@PathVariable("jobId") Long jobId) + { + return AjaxResult.success(jobService.selectJobById(jobId)); + } + + /** + * 新增定时任务 + */ + @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)); + } + + /** + * 修改定时任务 + */ + @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)); + } + + /** + * 定时任务状态修改 + */ + @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)); + } + + /** + * 定时任务立即执行一次 + */ + @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(); + } + + /** + * 删除定时任务 + */ + @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/wumei-iot/src/main/java/com/ruoyi/iot/controller/DeviceLogController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/DeviceLogController.java new file mode 100644 index 00000000..85a03ce4 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/DeviceLogController.java @@ -0,0 +1,110 @@ +package com.ruoyi.iot.controller; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.iot.domain.DeviceLog; +import com.ruoyi.iot.model.MonitorModel; +import com.ruoyi.iot.service.IDeviceLogService; +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 2022-01-13 + */ +@RestController +@RequestMapping("/iot/deviceLog") +public class DeviceLogController extends BaseController +{ + @Autowired + private IDeviceLogService deviceLogService; + + /** + * 查询设备日志列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/list") + public TableDataInfo list(DeviceLog deviceLog) + { + startPage(); + List list = deviceLogService.selectDeviceLogList(deviceLog); + return getDataTable(list); + } + + /** + * 查询设备的监测数据 + */ + @PreAuthorize("@ss.hasPermi('iot:device:list')") + @GetMapping("/monitor") + public TableDataInfo monitorList(DeviceLog deviceLog) + { + List list = deviceLogService.selectMonitorList(deviceLog); + return getDataTable(list); + } + + /** + * 导出设备日志列表 + */ + @PreAuthorize("@ss.hasPermi('iot:device:export')") + @Log(title = "设备日志", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, DeviceLog deviceLog) + { + List list = deviceLogService.selectDeviceLogList(deviceLog); + ExcelUtil util = new ExcelUtil(DeviceLog.class); + util.exportExcel(response, list, "设备日志数据"); + } + + /** + * 获取设备日志详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/{logId}") + public AjaxResult getInfo(@PathVariable("logId") Long logId) + { + return AjaxResult.success(deviceLogService.selectDeviceLogByLogId(logId)); + } + + /** + * 新增设备日志 + */ + @PreAuthorize("@ss.hasPermi('iot:device:add')") + @Log(title = "设备日志", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody DeviceLog deviceLog) + { + return toAjax(deviceLogService.insertDeviceLog(deviceLog)); + } + + /** + * 修改设备日志 + */ + @PreAuthorize("@ss.hasPermi('iot:device:edit')") + @Log(title = "设备日志", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody DeviceLog deviceLog) + { + return toAjax(deviceLogService.updateDeviceLog(deviceLog)); + } + + /** + * 删除设备日志 + */ + @PreAuthorize("@ss.hasPermi('iot:device:remove')") + @Log(title = "设备日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{logIds}") + public AjaxResult remove(@PathVariable Long[] logIds) + { + return toAjax(deviceLogService.deleteDeviceLogByLogIds(logIds)); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/DeviceUserController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/DeviceUserController.java new file mode 100644 index 00000000..54c574fd --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/DeviceUserController.java @@ -0,0 +1,107 @@ +package com.ruoyi.iot.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.iot.domain.Firmware; +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.iot.domain.DeviceUser; +import com.ruoyi.iot.service.IDeviceUserService; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.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); + } + + /** + * 获取设备用户详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:device:query')") + @GetMapping(value = "/{deviceId}") + @ApiOperation("获取设备用户详情") + public AjaxResult getInfo(@PathVariable("deviceId") Long deviceId) + { + return AjaxResult.success(deviceUserService.selectDeviceUserByDeviceId(deviceId)); + } + + /** + * 新增设备用户 + */ + @PreAuthorize("@ss.hasPermi('iot:device:add')") + @Log(title = "设备用户", businessType = BusinessType.INSERT) + @PostMapping + @ApiOperation("添加设备用户") + public AjaxResult add(@RequestBody DeviceUser deviceUser) + { + return toAjax(deviceUserService.insertDeviceUser(deviceUser)); + } + + /** + * 修改设备用户 + */ + @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("/{deviceIds}") + public AjaxResult remove(@PathVariable Long[] deviceIds) + { + int count=deviceUserService.deleteDeviceUserByDeviceIds(deviceIds); + if(count==0){ + return AjaxResult.error("设备所有者不能删除"); + }else{ + return AjaxResult.success(); + } + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/FirmwareController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/FirmwareController.java new file mode 100644 index 00000000..110ebd23 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/FirmwareController.java @@ -0,0 +1,126 @@ +package com.ruoyi.iot.controller; + +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.iot.domain.Firmware; +import com.ruoyi.iot.service.IFirmwareService; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; +import org.springframework.web.multipart.MultipartFile; + +/** + * 产品固件Controller + * + * @author kerwincui + * @date 2021-12-16 + */ +@Api(tags = "产品固件") +@RestController +@RequestMapping("/iot/firmware") +public class FirmwareController extends BaseController +{ + private static final Logger log = LoggerFactory.getLogger(FirmwareController.class); + + @Autowired + private IFirmwareService firmwareService; + + /** + * 查询产品固件列表 + */ + @PreAuthorize("@ss.hasPermi('iot:firmware:list')") + @GetMapping("/list") + @ApiOperation("产品固件分页列表") + public TableDataInfo list(Firmware firmware) + { + startPage(); + List list = firmwareService.selectFirmwareList(firmware); + return getDataTable(list); + } + + /** + * 导出产品固件列表 + */ + @PreAuthorize("@ss.hasPermi('iot:firmware:export')") + @Log(title = "产品固件", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ApiOperation("导出固件") + public void export(HttpServletResponse response, Firmware firmware) + { + List list = firmwareService.selectFirmwareList(firmware); + ExcelUtil util = new ExcelUtil(Firmware.class); + util.exportExcel(response, list, "产品固件数据"); + } + + /** + * 获取产品固件详细信息 + */ + @ApiOperation("获取固件详情") + @PreAuthorize("@ss.hasPermi('iot:firmware:query')") + @GetMapping(value = "/{firmwareId}") + public AjaxResult getInfo(@PathVariable("firmwareId") Long firmwareId) + { + return AjaxResult.success(firmwareService.selectFirmwareByFirmwareId(firmwareId)); + } + + /** + * 新增产品固件 + */ + @ApiOperation("添加产品固件") + @PreAuthorize("@ss.hasPermi('iot:firmware:add')") + @Log(title = "产品固件", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody Firmware firmware) + { + return toAjax(firmwareService.insertFirmware(firmware)); + } + + /** + * 修改产品固件 + */ + @ApiOperation("修改产品固件") + @PreAuthorize("@ss.hasPermi('iot:firmware:edit')") + @Log(title = "产品固件", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody Firmware firmware) + { + return toAjax(firmwareService.updateFirmware(firmware)); + } + + /** + * 删除产品固件 + */ + @ApiOperation("批量删除产品固件") + @PreAuthorize("@ss.hasPermi('iot:firmware:remove')") + @Log(title = "产品固件", businessType = BusinessType.DELETE) + @DeleteMapping("/{firmwareIds}") + public AjaxResult remove(@PathVariable Long[] firmwareIds) + { + return toAjax(firmwareService.deleteFirmwareByFirmwareIds(firmwareIds)); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/GroupController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/GroupController.java new file mode 100644 index 00000000..335c3a6f --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/GroupController.java @@ -0,0 +1,140 @@ +package com.ruoyi.iot.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.iot.domain.DeviceGroup; +import com.ruoyi.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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.iot.domain.Group; +import com.ruoyi.iot.service.IGroupService; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.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(); + List list = groupService.selectGroupList(group); + return getDataTable(list); + } + + /** + * 导出设备分组列表 + */ + @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/wumei-iot/src/main/java/com/ruoyi/iot/controller/OauthClientDetailsController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/OauthClientDetailsController.java new file mode 100644 index 00000000..23984ea3 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/OauthClientDetailsController.java @@ -0,0 +1,104 @@ +package com.ruoyi.iot.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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.iot.domain.OauthClientDetails; +import com.ruoyi.iot.service.IOauthClientDetailsService; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; + +/** + * 云云对接Controller + * + * @author kerwincui + * @date 2022-02-07 + */ +@RestController +@RequestMapping("/iot/clientDetails") +public class OauthClientDetailsController extends BaseController +{ + @Autowired + private IOauthClientDetailsService oauthClientDetailsService; + + /** + * 查询云云对接列表 + */ + @PreAuthorize("@ss.hasPermi('iot:clientDetails:list')") + @GetMapping("/list") + public TableDataInfo list(OauthClientDetails oauthClientDetails) + { + startPage(); + List list = oauthClientDetailsService.selectOauthClientDetailsList(oauthClientDetails); + return getDataTable(list); + } + + /** + * 导出云云对接列表 + */ + @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, "云云对接数据"); + } + + /** + * 获取云云对接详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:clientDetails:query')") + @GetMapping(value = "/{clientId}") + public AjaxResult getInfo(@PathVariable("clientId") String clientId) + { + return AjaxResult.success(oauthClientDetailsService.selectOauthClientDetailsByClientId(clientId)); + } + + /** + * 新增云云对接 + */ + @PreAuthorize("@ss.hasPermi('iot:clientDetails:add')") + @Log(title = "云云对接", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody OauthClientDetails oauthClientDetails) + { + return toAjax(oauthClientDetailsService.insertOauthClientDetails(oauthClientDetails)); + } + + /** + * 修改云云对接 + */ + @PreAuthorize("@ss.hasPermi('iot:clientDetails:edit')") + @Log(title = "云云对接", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody OauthClientDetails oauthClientDetails) + { + return toAjax(oauthClientDetailsService.updateOauthClientDetails(oauthClientDetails)); + } + + /** + * 删除云云对接 + */ + @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/wumei-iot/src/main/java/com/ruoyi/iot/controller/ProductController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/ProductController.java new file mode 100644 index 00000000..c803b87a --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/ProductController.java @@ -0,0 +1,133 @@ +package com.ruoyi.iot.controller; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.iot.domain.Product; +import com.ruoyi.iot.model.ChangeProductStatusModel; +import com.ruoyi.iot.model.IdAndName; +import com.ruoyi.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; + + /** + * 查询产品列表 + */ + @PreAuthorize("@ss.hasPermi('iot:product:list')") + @GetMapping("/list") + @ApiOperation("产品分页列表") + public TableDataInfo list(Product product) + { + startPage(); + List list = productService.selectProductList(product); + return getDataTable(list); + } + + /** + * 查询产品简短列表 + */ + @PreAuthorize("@ss.hasPermi('iot:product:list')") + @GetMapping("/shortList") + @ApiOperation("产品简短列表") + public AjaxResult shortList() + { + 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:edit')") + @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); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/SceneController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/SceneController.java new file mode 100644 index 00000000..944e033f --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/SceneController.java @@ -0,0 +1,104 @@ +package com.ruoyi.iot.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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.iot.domain.Scene; +import com.ruoyi.iot.service.ISceneService; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; + +/** + * 场景联动Controller + * + * @author kerwincui + * @date 2022-01-13 + */ +@RestController +@RequestMapping("/iot/scene") +public class SceneController extends BaseController +{ + @Autowired + private ISceneService sceneService; + + /** + * 查询场景联动列表 + */ + @PreAuthorize("@ss.hasPermi('iot:scene:list')") + @GetMapping("/list") + public TableDataInfo list(Scene scene) + { + startPage(); + List list = sceneService.selectSceneList(scene); + return getDataTable(list); + } + + /** + * 导出场景联动列表 + */ + @PreAuthorize("@ss.hasPermi('iot:scene:export')") + @Log(title = "场景联动", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, Scene scene) + { + List list = sceneService.selectSceneList(scene); + ExcelUtil util = new ExcelUtil(Scene.class); + util.exportExcel(response, list, "场景联动数据"); + } + + /** + * 获取场景联动详细信息 + */ + @PreAuthorize("@ss.hasPermi('iot:scene:query')") + @GetMapping(value = "/{sceneId}") + public AjaxResult getInfo(@PathVariable("sceneId") Long sceneId) + { + return AjaxResult.success(sceneService.selectSceneBySceneId(sceneId)); + } + + /** + * 新增场景联动 + */ + @PreAuthorize("@ss.hasPermi('iot:scene:add')") + @Log(title = "场景联动", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody Scene scene) + { + return toAjax(sceneService.insertScene(scene)); + } + + /** + * 修改场景联动 + */ + @PreAuthorize("@ss.hasPermi('iot:scene:edit')") + @Log(title = "场景联动", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody Scene scene) + { + return toAjax(sceneService.updateScene(scene)); + } + + /** + * 删除场景联动 + */ + @PreAuthorize("@ss.hasPermi('iot:scene:remove')") + @Log(title = "场景联动", businessType = BusinessType.DELETE) + @DeleteMapping("/{sceneIds}") + public AjaxResult remove(@PathVariable Long[] sceneIds) + { + return toAjax(sceneService.deleteSceneBySceneIds(sceneIds)); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/ThingsModelController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/ThingsModelController.java new file mode 100644 index 00000000..e1fe3117 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/ThingsModelController.java @@ -0,0 +1,138 @@ +package com.ruoyi.iot.controller; + +import java.util.List; +import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.iot.model.ImportThingsModelInput; +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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.iot.domain.ThingsModel; +import com.ruoyi.iot.service.IThingsModelService; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; + +/** + * 物模型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: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)); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/ThingsModelTemplateController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/ThingsModelTemplateController.java new file mode 100644 index 00000000..e7941fb0 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/ThingsModelTemplateController.java @@ -0,0 +1,114 @@ +package com.ruoyi.iot.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.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.iot.domain.ThingsModelTemplate; +import com.ruoyi.iot.service.IThingsModelTemplateService; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; + +/** + * 通用物模型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(); + List list = thingsModelTemplateService.selectThingsModelTemplateList(thingsModelTemplate); + return getDataTable(list); + } + + /** + * 导出通用物模型列表 + */ + @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)); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/ToolController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/ToolController.java new file mode 100644 index 00000000..592ceedb --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/controller/ToolController.java @@ -0,0 +1,391 @@ +package com.ruoyi.iot.controller; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.file.FileUploadUtils; +import com.ruoyi.common.utils.file.FileUtils; +import com.ruoyi.iot.domain.Device; +import com.ruoyi.iot.model.AuthenticateInputModel; +import com.ruoyi.iot.model.DeviceAuthenticateModel; +import com.ruoyi.iot.model.MqttClientConnectModel; +import com.ruoyi.iot.model.RegisterUserInput; +import com.ruoyi.iot.model.ThingsModels.IdentityAndName; +import com.ruoyi.iot.model.ThingsModels.ThingsModelValueItem; +import com.ruoyi.iot.model.ThingsModels.ThingsModelShadow; +import com.ruoyi.iot.mqtt.EmqxService; +import com.ruoyi.iot.mqtt.MqttConfig; +import com.ruoyi.iot.service.IDeviceService; +import com.ruoyi.iot.service.IToolService; +import com.ruoyi.iot.service.impl.ThingsModelServiceImpl; +import com.ruoyi.iot.util.AESUtils; +import com.ruoyi.iot.util.VelocityInitializer; +import com.ruoyi.iot.util.VelocityUtils; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +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.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.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import static com.ruoyi.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 ThingsModelServiceImpl thingsModelService; + + @Autowired + private EmqxService emqxService; + + @Autowired + private MqttConfig mqttConfig; + + @Autowired + private IToolService toolService; + + // 令牌秘钥 + @Value("${token.secret}") + private String secret; + + @Autowired + private RedisCache redisCache; + + /** + * 用户注册 + */ + @ApiOperation("用户注册") + @PostMapping("/register") + public AjaxResult register(@RequestBody RegisterUserInput user) { + String msg = toolService.register(user); + return StringUtils.isEmpty(msg) ? success() : error(msg); + } + + @ApiOperation("mqtt认证") + @PostMapping("/mqtt/auth") + public ResponseEntity mqttAuth(@RequestParam String clientid, @RequestParam String username, @RequestParam String password) throws Exception { + try { + if (clientid.startsWith("server")) { + // 服务端配置账号认证 + if (mqttConfig.getusername().equals(username) && mqttConfig.getpassword().equals(password)) { + System.out.println("-----------认证成功,clientId:" + clientid + "---------------"); + return ResponseEntity.ok().body("ok"); + } + } 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(); + System.out.println("-----------认证成功,clientId:" + clientid + "---------------"); + return ResponseEntity.ok().body("ok"); + } catch (Exception ex) { + return returnUnauthorized(clientid, username, password, ex.getMessage()); + } + } else { + // 设备端 + String[] clientInfo = clientid.split("&"); + if (clientInfo.length != 2) { + // 设备未加密认证 + if (mqttConfig.getusername().equals(username) && mqttConfig.getpassword().equals(password)) { + System.out.println("-----------认证成功,clientId:" + clientid + "---------------"); + return ResponseEntity.ok().body("ok"); + } + return returnUnauthorized(clientid, username, password, "认证信息有误"); + } + // 设备加密认证 + String deviceNum = clientInfo[0]; + Long productId = Long.valueOf(clientInfo[1]); + AuthenticateInputModel authenticateInputModel = new AuthenticateInputModel(deviceNum, productId); + DeviceAuthenticateModel model = deviceService.selectDeviceAuthenticate(authenticateInputModel); + if (model == null) { + return returnUnauthorized(clientid, username, password, "认证信息有误"); + } + // 密码解密,密码加密格式 password & productId & userId & expireTime + String decryptPassword = AESUtils.decrypt(password, model.getMqttSecret()); + if (decryptPassword == null || decryptPassword == "") { + return returnUnauthorized(clientid, username, password, "认证信息有误"); + } + String[] infos = decryptPassword.split("&"); + if (infos.length != 3) { + return returnUnauthorized(clientid, username, password, "认证信息有误"); + } + String mqttPassword = infos[0]; + Long userId = Long.valueOf(infos[1]); + Long expireTime = Long.valueOf(infos[2]); + // 账号密码验证,产品必须为发布状态:1-未发布,2-已发布 + if (mqttPassword.equals(model.getMqttPassword()) + && username.equals(model.getMqttAccount()) + && expireTime > System.currentTimeMillis() + && model.getProductStatus() == 2) { + + // 设备状态验证 (1-未激活,2-禁用,3-在线,4-离线) + if (model.getDeviceId() != null && model.getDeviceId() != 0 && model.getStatus() != 2) { + System.out.println("-----------认证成功,clientId:" + clientid + "---------------"); + return ResponseEntity.ok().body("ok"); + } else { + // 自动添加设备 + int result = deviceService.insertDeviceAuto(deviceNum, userId, productId); + if (result == 1) { + System.out.println("-----------认证成功,clientId:" + clientid + "---------------"); + return ResponseEntity.ok().body("ok"); + } + } + } + } + } catch (Exception ex) { + // ex.printStackTrace(); + return returnUnauthorized(clientid, username, password, ex.getMessage()); + } + return returnUnauthorized(clientid, username, password, "认证信息有误"); + } + + /** + * 返回认证信息 + */ + private ResponseEntity returnUnauthorized(String clientid, String username, String password, String message) { + System.out.println("认证失败:" + message + + "\nclientid:" + clientid + + "\nusername:" + username + + "\npassword:" + password); + log.error("认证失败:" + message + + "\nclientid:" + clientid + + "\nusername:" + username + + "\npassword:" + password); + return ResponseEntity.status(401).body("Unauthorized"); + } + + + @ApiOperation("mqtt钩子处理") + @PostMapping("/mqtt/webhook") + public AjaxResult 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 AjaxResult.success(); + } + String[] clientInfo = model.getClientid().split("&"); + String deviceNum = clientInfo[0]; + Device device = deviceService.selectShortDeviceBySerialNumber(deviceNum); + // 设备状态(1-未激活,2-禁用,3-在线,4-离线) + if (model.getAction().equals("client_disconnected")) { + device.setStatus(4); + deviceService.updateDeviceStatusAndLocation(device, ""); + // 发布设备状态 + emqxService.publishStatus(device.getProductId(), device.getSerialNumber(), 4, device.getIsShadow()); + // 清空保留消息,上线后发布新的属性功能保留消息 + emqxService.publishProperty(device.getProductId(), device.getSerialNumber(), null); + emqxService.publishFunction(device.getProductId(), device.getSerialNumber(), null); + } else if (model.getAction().equals("client_connected")) { + device.setStatus(3); + deviceService.updateDeviceStatusAndLocation(device, model.getIpaddress()); + // 发布设备状态 + emqxService.publishStatus(device.getProductId(), device.getSerialNumber(), 3, device.getIsShadow()); + // 影子模式,发布属性和功能 + if (device.getIsShadow() == 1) { + ThingsModelShadow shadow = deviceService.getDeviceShadowThingsModel(device); + if (shadow.getProperties().size() > 0) { + emqxService.publishProperty(device.getProductId(), device.getSerialNumber(), shadow.getProperties()); + } + if (shadow.getFunctions().size() > 0) { + emqxService.publishFunction(device.getProductId(), device.getSerialNumber(), shadow.getFunctions()); + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + log.error("发生错误:" + ex.getMessage()); + } + return AjaxResult.success(); + } + + + @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=\"ruoyi.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("渲染模板失败"); + } + } + } + +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Alert.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Alert.java new file mode 100644 index 00000000..f0c12d91 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Alert.java @@ -0,0 +1,137 @@ +package com.ruoyi.iot.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 设备告警对象 iot_alert + * + * @author kerwincui + * @date 2022-01-13 + */ +public class Alert extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 告警ID */ + private Long alertId; + + /** 告警名称 */ + @Excel(name = "告警名称") + private String alertName; + + /** 告警级别(1=提醒通知,2=轻微问题,3=严重警告) */ + @Excel(name = "告警级别", readConverterExp = "1==提醒通知,2=轻微问题,3=严重警告") + private Long alertLevel; + + /** 产品ID */ + @Excel(name = "产品ID") + private Long productId; + + /** 产品名称 */ + @Excel(name = "产品名称") + private String productName; + + /** 触发器 */ + @Excel(name = "触发器") + private String triggers; + + /** 执行动作 */ + @Excel(name = "执行动作") + private String actions; + + /** 告警状态 (1-启动,2-停止)**/ + private Integer status; + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public void setAlertId(Long alertId) + { + this.alertId = alertId; + } + + public Long getAlertId() + { + return alertId; + } + public void setAlertName(String alertName) + { + this.alertName = alertName; + } + + public String getAlertName() + { + return alertName; + } + public void setAlertLevel(Long alertLevel) + { + this.alertLevel = alertLevel; + } + + public Long getAlertLevel() + { + return alertLevel; + } + 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 setTriggers(String triggers) + { + this.triggers = triggers; + } + + public String getTriggers() + { + return triggers; + } + public void setActions(String actions) + { + this.actions = actions; + } + + public String getActions() + { + return actions; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("alertId", getAlertId()) + .append("alertName", getAlertName()) + .append("alertLevel", getAlertLevel()) + .append("productId", getProductId()) + .append("productName", getProductName()) + .append("triggers", getTriggers()) + .append("actions", getActions()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/AlertLog.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/AlertLog.java new file mode 100644 index 00000000..4efd4c1c --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/AlertLog.java @@ -0,0 +1,140 @@ +package com.ruoyi.iot.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 设备告警对象 iot_alert_log + * + * @author kerwincui + * @date 2022-01-13 + */ +public class AlertLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 告警ID */ + private Long alertLogId; + + /** 告警名称 */ + @Excel(name = "告警名称") + private String alertName; + + /** 告警级别(1=提醒通知,2=轻微问题,3=严重警告,4=场景联动) */ + @Excel(name = "告警级别", readConverterExp = "1==提醒通知,2=轻微问题,3=严重警告,4=场景联动") + private Long alertLevel; + + /** 处理状态(0=不需要处理,1=未处理,2=已处理) */ + @Excel(name = "处理状态(0=不需要处理,1=未处理,2=已处理)") + private Long status; + + /** 产品ID */ + @Excel(name = "产品ID") + private Long productId; + + /** 产品名称 */ + @Excel(name = "产品名称") + private String productName; + + /** 设备ID */ + @Excel(name = "设备ID") + private Long deviceId; + + /** 设备名称 */ + @Excel(name = "设备名称") + private String deviceName; + + public void setAlertLogId(Long alertLogId) + { + this.alertLogId = alertLogId; + } + + public Long getAlertLogId() + { + return alertLogId; + } + public void setAlertName(String alertLogName) + { + this.alertName = alertLogName; + } + + public String getAlertName() + { + return alertName; + } + public void setAlertLevel(Long alertLevel) + { + this.alertLevel = alertLevel; + } + + public Long getAlertLevel() + { + return alertLevel; + } + public void setStatus(Long status) + { + this.status = status; + } + + public Long getStatus() + { + return status; + } + 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 setDeviceId(Long deviceId) + { + this.deviceId = deviceId; + } + + public Long getDeviceId() + { + return deviceId; + } + public void setDeviceName(String deviceName) + { + this.deviceName = deviceName; + } + + public String getDeviceName() + { + return deviceName; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("alertLogId", getAlertLogId()) + .append("alertName", getAlertName()) + .append("alertLevel", getAlertLevel()) + .append("status", getStatus()) + .append("productId", getProductId()) + .append("productName", getProductName()) + .append("deviceId", getDeviceId()) + .append("deviceName", getDeviceName()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Category.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Category.java new file mode 100644 index 00000000..c5c9de6c --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Category.java @@ -0,0 +1,124 @@ +package com.ruoyi.iot.domain; + +import com.ruoyi.common.core.domain.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; + +/** + * 产品分类对象 iot_category + * + * @author kerwincui + * @date 2021-12-16 + */ +public class Category extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 产品分类ID */ + private Long categoryId; + + /** 产品分类名称 */ + @Excel(name = "产品分类名称") + private String categoryName; + + /** 租户ID */ + @Excel(name = "租户ID") + private Long tenantId; + + /** 租户名称 */ + @Excel(name = "租户名称") + private String tenantName; + + /** 是否系统通用(0-否,1-是) */ + @Excel(name = "是否系统通用", readConverterExp = "0=-否,1-是") + private Integer isSys; + + /** 显示顺序 */ + @Excel(name = "显示顺序") + private Integer orderNum; + + /** 删除标志(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/wumei-iot/src/main/java/com/ruoyi/iot/domain/Device.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Device.java new file mode 100644 index 00000000..6804a8f7 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Device.java @@ -0,0 +1,331 @@ +package com.ruoyi.iot.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 设备对象 iot_device + * + * @author kerwincui + * @date 2021-12-16 + */ +public class Device extends BaseEntity +{ + 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; + + /** 用户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; + + /** wifi信号强度(信号极好4格[-55— 0],信号好3格[-70— -55],信号一般2格[-85— -70],信号差1格[-100— -85]) */ + @Excel(name = "wifi信号强度") + private Integer rssi; + + /** 设备影子 */ + private Integer isShadow; + + /** 设备所在地址 */ + @Excel(name = "设备所在地址") + private String networkAddress; + + /** 设备入网IP */ + @Excel(name = "设备入网IP") + private String networkIp; + + /** 设备经度 */ + @Excel(name = "设备经度") + private BigDecimal longitude; + + /** 设备纬度 */ + @Excel(name = "设备纬度") + private BigDecimal latitude; + + /** 激活时间 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "激活时间", width = 30, dateFormat = "yyyy-MM-dd") + private Date activeTime; + + @Excel(name = "物模型") + private String thingsModelValue; + + /** 图片地址 */ + private String imgUrl; + + /** 是否自定义位置 **/ + private Integer isCustomLocation; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + public Integer getIsCustomLocation() { + return isCustomLocation; + } + + public void setIsCustomLocation(Integer isCustomLocation) { + this.isCustomLocation = isCustomLocation; + } + + 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 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/wumei-iot/src/main/java/com/ruoyi/iot/domain/DeviceGroup.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/DeviceGroup.java new file mode 100644 index 00000000..7eb49c26 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/DeviceGroup.java @@ -0,0 +1,41 @@ +package com.ruoyi.iot.domain; + +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * 设备分组对象 iot_device_group + * + * @author kerwincui + * @date 2021-12-16 + */ +public class DeviceGroup +{ + private static final long serialVersionUID = 1L; + + /** 分组ID */ + private Long groupId; + + /** 设备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/wumei-iot/src/main/java/com/ruoyi/iot/domain/DeviceJob.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/DeviceJob.java new file mode 100644 index 00000000..fad8af4a --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/DeviceJob.java @@ -0,0 +1,276 @@ +package com.ruoyi.iot.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.annotation.Excel.ColumnType; +import com.ruoyi.common.constant.ScheduleConstants; +import com.ruoyi.common.core.domain.BaseEntity; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.quartz.util.CronUtils; +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 + */ +public class DeviceJob extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 任务ID */ + @Excel(name = "任务序号", cellType = ColumnType.NUMERIC) + private Long jobId; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String jobName; + + /** 设备编号 */ + @Excel(name = "设备编号") + private String serialNumber; + + /** 设备id */ + private Long deviceId; + + /** 设备名称 */ + private String deviceName; + + /** 执行动作 */ + private String actions; + + /** 任务组名 */ + @Excel(name = "任务组名") + private String jobGroup; + + /** cron执行表达式 */ + @Excel(name = "执行表达式 ") + private String cronExpression; + + /** cron计划策略 */ + @Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行") + private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT; + + /** 是否并发执行(0允许 1禁止) */ + @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止") + private String concurrent; + + /** 任务状态(0正常 1暂停) */ + @Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停") + private String status; + + /** 是否详细corn表达式 */ + private Integer isAdvance; + + /** 定时类型(1=设备定时,2=设备告警,3=场景联动) */ + @Excel(name = "定时类型", readConverterExp = "1==设备定时,2=设备告警,3=场景联动") + private Integer jobType; + + /** 产品ID */ + @Excel(name = "产品ID") + private Long productId; + + /** 产品名称 */ + @Excel(name = "产品名称") + private String productName; + + /** 场景联动ID */ + @Excel(name = "场景联动ID") + private Long sceneId; + + /** 告警ID */ + @Excel(name = "告警ID") + private Long alertId; + + 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/wumei-iot/src/main/java/com/ruoyi/iot/domain/DeviceLog.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/DeviceLog.java new file mode 100644 index 00000000..b2909ef8 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/DeviceLog.java @@ -0,0 +1,137 @@ +package com.ruoyi.iot.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 设备日志对象 iot_device_log + * + * @author kerwincui + * @date 2022-01-13 + */ +public class DeviceLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 设备日志ID */ + private Long logId; + + /** 类型(1=属性上报,2=事件上报,3=调用功能,4=设备升级,5=设备上线,6=设备离线) */ + @Excel(name = "类型", readConverterExp = "1==属性上报,2=事件上报,3=调用功能,4=设备升级,5=设备上线,6=设备离线") + private Integer logType; + + /** 日志值 */ + @Excel(name = "日志值") + private String logValue; + + /** 设备ID */ + @Excel(name = "设备ID") + private Long deviceId; + + /** 设备名称 */ + @Excel(name = "设备名称") + private String deviceName; + + /** 设备编号 */ + @Excel(name = "设备编号") + private String serialNumber; + + /** 标识符 */ + @Excel(name = "标识符") + private String identity; + + /** 是否监测数据(1=是,0=否) */ + @Excel(name = "是否监测数据", readConverterExp = "1==是,0=否") + private Integer isMonitor; + + 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(); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/DeviceUser.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/DeviceUser.java new file mode 100644 index 00000000..daf4987e --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/DeviceUser.java @@ -0,0 +1,143 @@ +package com.ruoyi.iot.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 设备用户对象 iot_device_user + * + * @author kerwincui + * @date 2021-12-16 + */ +public class DeviceUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 固件ID */ + private Long deviceId; + + /** 用户ID */ + private Long userId; + + /** 设备名称 */ + @Excel(name = "设备名称") + private String deviceName; + + /** 用户昵称 */ + @Excel(name = "用户昵称") + private String userName; + + /** 是否为设备所有者 */ + @Excel(name = "是否为设备所有者") + private Integer isOwner; + + /** 租户ID */ + private Long tenantId; + + /** 租户名称 */ + private String tenantName; + + /** 手机号码 */ + private String phonenumber; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + 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/wumei-iot/src/main/java/com/ruoyi/iot/domain/Firmware.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Firmware.java new file mode 100644 index 00000000..919d93ed --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Firmware.java @@ -0,0 +1,168 @@ +package com.ruoyi.iot.domain; + +import java.math.BigDecimal; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 产品固件对象 iot_firmware + * + * @author kerwincui + * @date 2021-12-16 + */ +public class Firmware extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 固件ID */ + private Long firmwareId; + + /** 固件名称 */ + @Excel(name = "固件名称") + private String firmwareName; + + /** 产品ID */ + @Excel(name = "产品ID") + private Long productId; + + /** 产品名称 */ + @Excel(name = "产品名称") + private String productName; + + /** 租户ID */ + @Excel(name = "租户ID") + private Long tenantId; + + /** 租户名称 */ + @Excel(name = "租户名称") + private String tenantName; + + /** 是否系统通用(0-否,1-是) */ + @Excel(name = "是否系统通用", readConverterExp = "0=-否,1-是") + private Integer isSys; + + /** 固件版本 */ + @Excel(name = "固件版本") + private BigDecimal version; + + /** 文件路径 */ + @Excel(name = "文件路径") + private String filePath; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + public void setFirmwareId(Long firmwareId) + { + this.firmwareId = firmwareId; + } + + public Long getFirmwareId() + { + return firmwareId; + } + public void setFirmwareName(String firmwareName) + { + this.firmwareName = firmwareName; + } + + public String getFirmwareName() + { + return firmwareName; + } + 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 setIsSys(Integer isSys) + { + this.isSys = isSys; + } + + public Integer getIsSys() + { + return isSys; + } + public void setVersion(BigDecimal version) + { + this.version = version; + } + + public BigDecimal getVersion() + { + return version; + } + public void setFilePath(String filePath) + { + this.filePath = filePath; + } + + public String getFilePath() + { + return filePath; + } + 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("firmwareId", getFirmwareId()) + .append("firmwareName", getFirmwareName()) + .append("productId", getProductId()) + .append("productName", getProductName()) + .append("tenantId", getTenantId()) + .append("tenantName", getTenantName()) + .append("isSys", getIsSys()) + .append("version", getVersion()) + .append("filePath", getFilePath()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Group.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Group.java new file mode 100644 index 00000000..31045953 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Group.java @@ -0,0 +1,111 @@ +package com.ruoyi.iot.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 设备分组对象 iot_group + * + * @author kerwincui + * @date 2021-12-16 + */ +public class Group extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 分组ID */ + private Long groupId; + + /** 分组名称 */ + @Excel(name = "分组名称") + private String groupName; + + /** 分组排序 */ + @Excel(name = "分组排序") + private Long groupOrder; + + /** 用户ID */ + @Excel(name = "用户ID") + private Long userId; + + /** 用户昵称 */ + @Excel(name = "用户昵称") + private String userName; + + /** 删除标志(0代表存在 2代表删除) */ + 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/wumei-iot/src/main/java/com/ruoyi/iot/domain/OauthClientDetails.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/OauthClientDetails.java new file mode 100644 index 00000000..f0abe17e --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/OauthClientDetails.java @@ -0,0 +1,191 @@ +package com.ruoyi.iot.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 云云对接对象 oauth_client_details + * + * @author kerwincui + * @date 2022-02-07 + */ +public class OauthClientDetails extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 客户端ID */ + @Excel(name = "客户端ID") + private String clientId; + + /** 资源 */ + @Excel(name = "资源") + private String resourceIds; + + /** 客户端秘钥 */ + private String clientSecret; + + /** 权限范围 */ + @Excel(name = "权限范围") + private String scope; + + /** 授权模式 */ + @Excel(name = "授权模式") + private String authorizedGrantTypes; + + /** 回调地址 */ + @Excel(name = "回调地址") + private String webServerRedirectUri; + + /** 权限 */ + @Excel(name = "权限") + private String authorities; + + /** access token有效时间 */ + @Excel(name = "access token有效时间") + private Long accessTokenValidity; + + /** refresh token有效时间 */ + @Excel(name = "refresh token有效时间") + private Long refreshTokenValidity; + + /** 预留的字段 */ + @Excel(name = "预留的字段") + private String additionalInformation; + + /** 自动授权 */ + @Excel(name = "自动授权") + private String autoapprove; + + /** 平台 */ + @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/wumei-iot/src/main/java/com/ruoyi/iot/domain/Product.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Product.java new file mode 100644 index 00000000..4f8ad3bf --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Product.java @@ -0,0 +1,253 @@ +package com.ruoyi.iot.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 产品对象 iot_product + * + * @author kerwincui + * @date 2021-12-16 + */ +public class Product extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 产品ID */ + private Long productId; + + /** 产品名称 */ + @Excel(name = "产品名称") + private String productName; + + /** 产品分类ID */ + @Excel(name = "产品分类ID") + private Long categoryId; + + /** 产品分类名称 */ + @Excel(name = "产品分类名称") + private String categoryName; + + /** 租户ID */ + @Excel(name = "租户ID") + private Long tenantId; + + /** 租户名称 */ + @Excel(name = "租户名称") + private String tenantName; + + /** 是否系统通用(0-否,1-是) */ + @Excel(name = "是否系统通用", readConverterExp = "0=-否,1-是") + private Integer isSys; + + /** mqtt账号 */ + private String mqttAccount; + + /** mqtt密码 */ + private String mqttPassword; + + /** 产品秘钥 */ + private String mqttSecret; + + public String getMqttSecret() { + return mqttSecret; + } + + public void setMqttSecret(String mqttSecret) { + this.mqttSecret = mqttSecret; + } + + /** 状态(1-未发布,2-已发布,不能修改) */ + @Excel(name = "状态", readConverterExp = "1=-未发布,2-已发布,不能修改") + private Integer status; + + /** 设备类型(1-直连设备、2-网关子设备、3-网关设备) */ + @Excel(name = "设备类型", readConverterExp = "1=-直连设备、2-网关子设备、3-网关设备") + private Integer deviceType; + + /** 联网方式(1-wifi、2-蓝牙、3-wifi+蓝牙) */ + @Excel(name = "联网方式", readConverterExp = "1=-wifi、2-蓝牙、3-wifi+蓝牙") + private Integer networkMethod; + + /** 认证方式(1-账号密码、2-证书、3-Http) */ + @Excel(name = "认证方式", readConverterExp = "1=-账号密码、2-证书、3-Http") + private Integer vertificateMethod; + + /** 图片地址 */ + private String imgUrl; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 物模型Json **/ + private String thingsModelsJson; + + 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 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("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/wumei-iot/src/main/java/com/ruoyi/iot/domain/Scene.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Scene.java new file mode 100644 index 00000000..9ef55d18 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/Scene.java @@ -0,0 +1,112 @@ +package com.ruoyi.iot.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 场景联动对象 iot_scene + * + * @author kerwincui + * @date 2022-01-13 + */ +public class Scene extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 场景ID */ + private Long sceneId; + + /** 场景名称 */ + @Excel(name = "场景名称") + private String sceneName; + + /** 用户ID */ + @Excel(name = "用户ID") + private Long userId; + + /** 用户名称 */ + @Excel(name = "用户名称") + private String userName; + + /** 触发器 */ + @Excel(name = "触发器") + private String triggers; + + /** 执行动作 */ + @Excel(name = "执行动作") + private String actions; + + public void setSceneId(Long sceneId) + { + this.sceneId = sceneId; + } + + public Long getSceneId() + { + return sceneId; + } + public void setSceneName(String sceneName) + { + this.sceneName = sceneName; + } + + public String getSceneName() + { + return sceneName; + } + 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 setTriggers(String triggers) + { + this.triggers = triggers; + } + + public String getTriggers() + { + return triggers; + } + public void setActions(String actions) + { + this.actions = actions; + } + + public String getActions() + { + return actions; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("sceneId", getSceneId()) + .append("sceneName", getSceneName()) + .append("userId", getUserId()) + .append("userName", getUserName()) + .append("triggers", getTriggers()) + .append("actions", getActions()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/ThingsModel.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/ThingsModel.java new file mode 100644 index 00000000..0c02c8bb --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/ThingsModel.java @@ -0,0 +1,196 @@ +package com.ruoyi.iot.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 物模型对象 iot_things_model + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ThingsModel extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 物模型ID */ + private Long modelId; + + /** 物模型名称 */ + @Excel(name = "物模型名称") + private String modelName; + + /** 产品ID */ + @Excel(name = "产品ID") + private Long productId; + + /** 产品名称 */ + @Excel(name = "产品名称") + private String productName; + + /** 租户ID */ + @Excel(name = "租户ID") + private Long tenantId; + + /** 租户名称 */ + @Excel(name = "租户名称") + private String tenantName; + + /** 标识符,产品下唯一 */ + @Excel(name = "标识符,产品下唯一") + private String identifier; + + /** 模型类别(1-属性,2-功能,3-事件) */ + @Excel(name = "模型类别", readConverterExp = "1=-属性,2-功能,3-事件,4-属性和功能") + private Integer type; + + /** 数据类型(integer、decimal、string、bool、array、enum) */ + @Excel(name = "数据类型", readConverterExp = "i=nteger、decimal、string、bool、array、enum") + private String datatype; + + /** 数据定义 */ + @Excel(name = "数据定义") + private String specs; + + /** 是否首页显示(0-否,1-是) */ + @Excel(name = "是否首页显示", readConverterExp = "0=-否,1-是") + private Integer isTop; + + /** 是否实时监测(0-否,1-是) */ + @Excel(name = "是否实时监测", readConverterExp = "0=-否,1-是") + private Integer isMonitor; + + 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 void setSpecs(String specs) + { + this.specs = specs; + } + + public String getSpecs() + { + return specs; + } + public void setIsTop(Integer isTop) + { + this.isTop = isTop; + } + + public Integer getIsTop() + { + return isTop; + } + 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("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("specs", getSpecs()) + .append("isTop", getIsTop()) + .append("isMonitor", getIsMonitor()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/ThingsModelTemplate.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/ThingsModelTemplate.java new file mode 100644 index 00000000..49ab9521 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/domain/ThingsModelTemplate.java @@ -0,0 +1,195 @@ +package com.ruoyi.iot.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 通用物模型对象 iot_things_model_template + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ThingsModelTemplate extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 物模型ID */ + private Long templateId; + + /** 物模型名称 */ + @Excel(name = "物模型名称") + private String templateName; + + /** 租户ID */ + @Excel(name = "租户ID") + private Long tenantId; + + /** 租户名称 */ + @Excel(name = "租户名称") + private String tenantName; + + /** 标识符,产品下唯一 */ + @Excel(name = "标识符") + private String identifier; + + /** 模型类别(1-属性,2-功能,3-事件) */ + @Excel(name = "模型类别", dictType = "iot_things_type") + private Integer type; + + /** 数据类型(integer、decimal、string、bool、array、enum) */ + @Excel(name = "数据类型") + private String datatype; + + /** 数据定义 */ + @Excel(name = "数据定义") + private String specs; + + /** 是否系统通用(0-否,1-是) */ + @Excel(name = "是否系统通用", dictType = "iot_yes_no") + private Integer isSys; + + /** 是否首页显示(0-否,1-是) */ + @Excel(name = "是否首页显示", dictType = "iot_yes_no") + private Integer isTop; + + /** 是否实时监测(0-否,1-是) */ + @Excel(name = "是否实时监测", dictType = "iot_yes_no") + private Integer isMonitor; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + 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 setIsTop(Integer isTop) + { + this.isTop = isTop; + } + + public Integer getIsTop() + { + return isTop; + } + 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; + } + + @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("isTop", getIsTop()) + .append("isMonitor", getIsMonitor()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/AlertLogMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/AlertLogMapper.java new file mode 100644 index 00000000..72ceaf00 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/AlertLogMapper.java @@ -0,0 +1,64 @@ +package com.ruoyi.iot.mapper; + +import com.ruoyi.iot.domain.AlertLog; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 设备告警Mapper接口 + * + * @author kerwincui + * @date 2022-01-13 + */ +@Repository +public interface AlertLogMapper +{ + /** + * 查询设备告警 + * + * @param alertLogId 设备告警主键 + * @return 设备告警 + */ + public AlertLog selectAlertLogByAlertLogId(Long alertLogId); + + /** + * 查询设备告警列表 + * + * @param alertLog 设备告警 + * @return 设备告警集合 + */ + public List selectAlertLogList(AlertLog alertLog); + + /** + * 新增设备告警 + * + * @param alertLog 设备告警 + * @return 结果 + */ + public int insertAlertLog(AlertLog alertLog); + + /** + * 修改设备告警 + * + * @param alertLog 设备告警 + * @return 结果 + */ + public int updateAlertLog(AlertLog alertLog); + + /** + * 删除设备告警 + * + * @param alertLogId 设备告警主键 + * @return 结果 + */ + public int deleteAlertLogByAlertLogId(Long alertLogId); + + /** + * 批量删除设备告警 + * + * @param alertLogIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteAlertLogByAlertLogIds(Long[] alertLogIds); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/AlertMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/AlertMapper.java new file mode 100644 index 00000000..a4f48c7f --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/AlertMapper.java @@ -0,0 +1,63 @@ +package com.ruoyi.iot.mapper; + +import java.util.List; +import com.ruoyi.iot.domain.Alert; +import org.springframework.stereotype.Repository; + +/** + * 设备告警Mapper接口 + * + * @author kerwincui + * @date 2022-01-13 + */ +@Repository +public interface AlertMapper +{ + /** + * 查询设备告警 + * + * @param alertId 设备告警主键 + * @return 设备告警 + */ + public Alert selectAlertByAlertId(Long alertId); + + /** + * 查询设备告警列表 + * + * @param alert 设备告警 + * @return 设备告警集合 + */ + public List selectAlertList(Alert alert); + + /** + * 新增设备告警 + * + * @param alert 设备告警 + * @return 结果 + */ + public int insertAlert(Alert alert); + + /** + * 修改设备告警 + * + * @param alert 设备告警 + * @return 结果 + */ + public int updateAlert(Alert alert); + + /** + * 删除设备告警 + * + * @param alertId 设备告警主键 + * @return 结果 + */ + public int deleteAlertByAlertId(Long alertId); + + /** + * 批量删除设备告警 + * + * @param alertIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteAlertByAlertIds(Long[] alertIds); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/CategoryMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/CategoryMapper.java new file mode 100644 index 00000000..3e536013 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/CategoryMapper.java @@ -0,0 +1,80 @@ +package com.ruoyi.iot.mapper; + +import com.ruoyi.iot.domain.Category; +import com.ruoyi.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(); + + /** + * 新增产品分类 + * + * @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/wumei-iot/src/main/java/com/ruoyi/iot/mapper/DeviceJobMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/DeviceJobMapper.java new file mode 100644 index 00000000..3e9da07a --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/DeviceJobMapper.java @@ -0,0 +1,86 @@ +package com.ruoyi.iot.mapper; + +import com.ruoyi.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); + + /** + * 查询所有调度任务 + * + * @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); + + /** + * 修改调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int updateJob(DeviceJob job); + + /** + * 新增调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int insertJob(DeviceJob job); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/DeviceLogMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/DeviceLogMapper.java new file mode 100644 index 00000000..619d0d57 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/DeviceLogMapper.java @@ -0,0 +1,81 @@ +package com.ruoyi.iot.mapper; + +import com.ruoyi.iot.domain.DeviceLog; +import com.ruoyi.iot.model.MonitorModel; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 设备日志Mapper接口 + * + * @author kerwincui + * @date 2022-01-13 + */ +@Repository +public interface DeviceLogMapper +{ + /** + * 查询设备日志 + * + * @param logId 设备日志主键 + * @return 设备日志 + */ + public DeviceLog selectDeviceLogByLogId(Long logId); + + /** + * 查询设备日志列表 + * + * @param deviceLog 设备日志 + * @return 设备日志集合 + */ + public List selectDeviceLogList(DeviceLog deviceLog); + + /** + * 查询设备监测数据 + * + * @param deviceLog 设备日志 + * @return 设备日志集合 + */ + public List selectMonitorList(DeviceLog deviceLog); + + /** + * 新增设备日志 + * + * @param deviceLog 设备日志 + * @return 结果 + */ + public int insertDeviceLog(DeviceLog deviceLog); + + /** + * 修改设备日志 + * + * @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 deviceIds 需要删除的数据设备Ids + * @return 结果 + */ + public int deleteDeviceLogByDeviceIds(Long[] deviceIds); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/DeviceMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/DeviceMapper.java new file mode 100644 index 00000000..7d0d9241 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/DeviceMapper.java @@ -0,0 +1,164 @@ +package com.ruoyi.iot.mapper; + +import com.ruoyi.iot.domain.Device; +import com.ruoyi.iot.model.AuthenticateInputModel; +import com.ruoyi.iot.model.DeviceAllShortOutput; +import com.ruoyi.iot.model.DeviceAuthenticateModel; +import com.ruoyi.iot.model.DeviceShortOutput; +import com.ruoyi.iot.model.ThingsModels.ThingsModelValuesInput; +import com.ruoyi.iot.model.ThingsModels.ThingsModelValuesOutput; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 设备Mapper接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Repository +public interface DeviceMapper +{ + /** + * 查询设备 + * + * @param deviceId 设备主键 + * @return 设备 + */ + public Device selectDeviceByDeviceId(Long deviceId); + + /** + * 根据设备编号查询设备 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + public Device selectDeviceBySerialNumber(String serialNumber); + + /** + * 根据设备编号查询简介设备 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + public Device selectShortDeviceBySerialNumber(String serialNumber); + + /** + * 根据设备编号查询设备认证信息 + * + * @param model 设备编号和产品ID + * @return 设备 + */ + public DeviceAuthenticateModel selectDeviceAuthenticate(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 selectDeviceShortList(Device device); + + /** + * 查询所有设备简短列表 + * + * @return 设备集合 + */ + public List selectAllDeviceShortList(); + + /** + * 新增设备 + * + * @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 updateDeviceBySerialNumber(Device device); + + /** + * 删除设备 + * + * @param deviceId 设备主键 + * @return 结果 + */ + public int deleteDeviceByDeviceId(Long deviceId); + + /** + * 批量删除设备 + * + * @param deviceIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteDeviceByDeviceIds(Long[] deviceIds); + + /** + * 查询设备序列号的数量 + * @param deviceNum + * @return + */ + public int getDeviceNumCount(String deviceNum); + + /** + * 根据设备IDS删除设备分组 + * @param deviceIds + * @return + */ + public int deleteDeviceGroupByDeviceIds(Long[] deviceIds); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/DeviceUserMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/DeviceUserMapper.java new file mode 100644 index 00000000..7260a576 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/DeviceUserMapper.java @@ -0,0 +1,63 @@ +package com.ruoyi.iot.mapper; + +import java.util.List; +import com.ruoyi.iot.domain.DeviceUser; +import org.springframework.stereotype.Repository; + +/** + * 设备用户Mapper接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Repository +public interface DeviceUserMapper +{ + /** + * 查询设备用户 + * + * @param deviceId 设备用户主键 + * @return 设备用户 + */ + public DeviceUser selectDeviceUserByDeviceId(Long deviceId); + + /** + * 查询设备用户列表 + * + * @param deviceUser 设备用户 + * @return 设备用户集合 + */ + public List selectDeviceUserList(DeviceUser deviceUser); + + /** + * 新增设备用户 + * + * @param deviceUser 设备用户 + * @return 结果 + */ + public int insertDeviceUser(DeviceUser deviceUser); + + /** + * 修改设备用户 + * + * @param deviceUser 设备用户 + * @return 结果 + */ + public int updateDeviceUser(DeviceUser deviceUser); + + /** + * 删除设备用户 + * + * @param deviceId 设备用户主键 + * @return 结果 + */ + public int deleteDeviceUserByDeviceId(Long deviceId); + + /** + * 批量删除设备用户 + * + * @param deviceIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteDeviceUserByDeviceIds(Long[] deviceIds); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/FirmwareMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/FirmwareMapper.java new file mode 100644 index 00000000..3635160d --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/FirmwareMapper.java @@ -0,0 +1,63 @@ +package com.ruoyi.iot.mapper; + +import java.util.List; +import com.ruoyi.iot.domain.Firmware; +import org.springframework.stereotype.Repository; + +/** + * 产品固件Mapper接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Repository +public interface FirmwareMapper +{ + /** + * 查询产品固件 + * + * @param firmwareId 产品固件主键 + * @return 产品固件 + */ + public Firmware selectFirmwareByFirmwareId(Long firmwareId); + + /** + * 查询产品固件列表 + * + * @param firmware 产品固件 + * @return 产品固件集合 + */ + public List selectFirmwareList(Firmware firmware); + + /** + * 新增产品固件 + * + * @param firmware 产品固件 + * @return 结果 + */ + public int insertFirmware(Firmware firmware); + + /** + * 修改产品固件 + * + * @param firmware 产品固件 + * @return 结果 + */ + public int updateFirmware(Firmware firmware); + + /** + * 删除产品固件 + * + * @param firmwareId 产品固件主键 + * @return 结果 + */ + public int deleteFirmwareByFirmwareId(Long firmwareId); + + /** + * 批量删除产品固件 + * + * @param firmwareIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteFirmwareByFirmwareIds(Long[] firmwareIds); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/GroupMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/GroupMapper.java new file mode 100644 index 00000000..230a7fa2 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/GroupMapper.java @@ -0,0 +1,86 @@ +package com.ruoyi.iot.mapper; + +import java.util.List; +import com.ruoyi.iot.domain.Group; +import com.ruoyi.iot.model.DeviceGroupInput; +import com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/mapper/OauthClientDetailsMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/OauthClientDetailsMapper.java new file mode 100644 index 00000000..e6f14266 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/OauthClientDetailsMapper.java @@ -0,0 +1,63 @@ +package com.ruoyi.iot.mapper; + +import java.util.List; +import com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/mapper/ProductMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/ProductMapper.java new file mode 100644 index 00000000..dae480a1 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/ProductMapper.java @@ -0,0 +1,119 @@ +package com.ruoyi.iot.mapper; + +import com.ruoyi.iot.domain.Product; +import com.ruoyi.iot.model.ChangeProductStatusModel; +import com.ruoyi.iot.model.IdAndName; +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 userId 用户ID + * @return 产品集合 + */ + public List selectProductShortList(Long userId); + + /** + * 新增产品 + * + * @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 firmwareCountInProducts(Long[] productIds); + + /** + * 产品下的设备数量 + * @param productIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deviceCountInProducts(Long[] productIds); + + /** + * 产品下的物模型数量 + * @param productId 需要删除的数据主键集合 + * @return 结果 + */ + public int thingsCountInProduct(Long productId); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/SceneMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/SceneMapper.java new file mode 100644 index 00000000..f7ba778c --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/SceneMapper.java @@ -0,0 +1,63 @@ +package com.ruoyi.iot.mapper; + +import java.util.List; +import com.ruoyi.iot.domain.Scene; +import org.springframework.stereotype.Repository; + +/** + * 场景联动Mapper接口 + * + * @author kerwincui + * @date 2022-01-13 + */ +@Repository +public interface SceneMapper +{ + /** + * 查询场景联动 + * + * @param sceneId 场景联动主键 + * @return 场景联动 + */ + public Scene selectSceneBySceneId(Long sceneId); + + /** + * 查询场景联动列表 + * + * @param scene 场景联动 + * @return 场景联动集合 + */ + public List selectSceneList(Scene scene); + + /** + * 新增场景联动 + * + * @param scene 场景联动 + * @return 结果 + */ + public int insertScene(Scene scene); + + /** + * 修改场景联动 + * + * @param scene 场景联动 + * @return 结果 + */ + public int updateScene(Scene scene); + + /** + * 删除场景联动 + * + * @param sceneId 场景联动主键 + * @return 结果 + */ + public int deleteSceneBySceneId(Long sceneId); + + /** + * 批量删除场景联动 + * + * @param sceneIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSceneBySceneIds(Long[] sceneIds); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/ThingsModelMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/ThingsModelMapper.java new file mode 100644 index 00000000..ca8ebb48 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/ThingsModelMapper.java @@ -0,0 +1,78 @@ +package com.ruoyi.iot.mapper; + +import com.ruoyi.iot.domain.ThingsModel; +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 thingsModel 物模型 + * @return 物模型集合 + */ + public List selectThingsModelList(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); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/ThingsModelTemplateMapper.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/ThingsModelTemplateMapper.java new file mode 100644 index 00000000..bbfa24fb --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mapper/ThingsModelTemplateMapper.java @@ -0,0 +1,71 @@ +package com.ruoyi.iot.mapper; + +import com.ruoyi.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); + + /** + * 删除通用物模型 + * + * @param templateId 通用物模型主键 + * @return 结果 + */ + public int deleteThingsModelTemplateByTemplateId(Long templateId); + + /** + * 批量删除通用物模型 + * + * @param templateIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteThingsModelTemplateByTemplateIds(Long[] templateIds); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/Action.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/Action.java new file mode 100644 index 00000000..436d8fc5 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/Action.java @@ -0,0 +1,108 @@ +package com.ruoyi.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; + + /** 源:1=设备,2=定时,3=告警输出 */ + private int source; + + /** 设备ID */ + private Long deviceId; + + /** 设备名称 */ + private String deviceName; + + /** 告警名称 */ + private String alertName; + + /** 告警级别 */ + private int alertLevel; + + 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 int getSource() { + return source; + } + + public void setSource(int source) { + this.source = source; + } + + 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 getAlertName() { + return alertName; + } + + public void setAlertName(String alertName) { + this.alertName = alertName; + } + + public int getAlertLevel() { + return alertLevel; + } + + public void setAlertLevel(int alertLevel) { + this.alertLevel = alertLevel; + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/AuthenticateInputModel.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/AuthenticateInputModel.java new file mode 100644 index 00000000..66c43211 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/AuthenticateInputModel.java @@ -0,0 +1,36 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/ChangeProductStatusModel.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ChangeProductStatusModel.java new file mode 100644 index 00000000..39ece707 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ChangeProductStatusModel.java @@ -0,0 +1,30 @@ +package com.ruoyi.iot.model; + +/** + * id和name + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ChangeProductStatusModel +{ + private Long productId; + + private Integer status; + + 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/wumei-iot/src/main/java/com/ruoyi/iot/model/DeviceAllShortOutput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/DeviceAllShortOutput.java new file mode 100644 index 00000000..22224811 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/DeviceAllShortOutput.java @@ -0,0 +1,188 @@ +package com.ruoyi.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; + + /** 用户昵称 */ + 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 isCustomLocation; + + /** 设备地址 */ + private String networkAddress; + + /** 经度 */ + private BigDecimal longitude; + + /** 纬度 */ + private BigDecimal latitude; + + 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 getIsCustomLocation() { + return isCustomLocation; + } + + public void setIsCustomLocation(Integer isCustomLocation) { + this.isCustomLocation = isCustomLocation; + } + + 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 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/wumei-iot/src/main/java/com/ruoyi/iot/model/DeviceAuthenticateModel.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/DeviceAuthenticateModel.java new file mode 100644 index 00000000..188ef6f3 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/DeviceAuthenticateModel.java @@ -0,0 +1,114 @@ +package com.ruoyi.iot.model; + + +public class DeviceAuthenticateModel { + /** 产品分类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; + + /** 设备编号 */ + private String serialNumber; + + /** mqtt账号 */ + private String mqttAccount; + + /** mqtt密码 */ + private String mqttPassword; + + /** 产品秘钥 */ + private String mqttSecret; + + 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/wumei-iot/src/main/java/com/ruoyi/iot/model/DeviceGroupInput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/DeviceGroupInput.java new file mode 100644 index 00000000..61f135d8 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/DeviceGroupInput.java @@ -0,0 +1,36 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/DeviceShortOutput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/DeviceShortOutput.java new file mode 100644 index 00000000..dd0d8a2d --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/DeviceShortOutput.java @@ -0,0 +1,311 @@ +package com.ruoyi.iot.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.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; + + /** 用户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; + + /** 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; + + /** 是否自定义位置 **/ + private Integer isCustomLocation; + + /** 图片地址 */ + private String imgUrl; + + private List stringList; + private List integerList; + private List decimalList; + private List enumList; + private List arrayList; + private List boolList; + private List readOnlyList; + + public Integer getIsCustomLocation() { + return isCustomLocation; + } + + public void setIsCustomLocation(Integer isCustomLocation) { + this.isCustomLocation = isCustomLocation; + } + + 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 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 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/wumei-iot/src/main/java/com/ruoyi/iot/model/IdAndName.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/IdAndName.java new file mode 100644 index 00000000..4e2a39ea --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/IdAndName.java @@ -0,0 +1,44 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/IdOutput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/IdOutput.java new file mode 100644 index 00000000..e5d96c97 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/IdOutput.java @@ -0,0 +1,24 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/ImportThingsModelInput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ImportThingsModelInput.java new file mode 100644 index 00000000..e81e3075 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ImportThingsModelInput.java @@ -0,0 +1,47 @@ +package com.ruoyi.iot.model; + +import com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/MonitorModel.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/MonitorModel.java new file mode 100644 index 00000000..1cb2f1e8 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/MonitorModel.java @@ -0,0 +1,34 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/MqttAuthModel.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/MqttAuthModel.java new file mode 100644 index 00000000..9597fa89 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/MqttAuthModel.java @@ -0,0 +1,43 @@ +package com.ruoyi.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 MqttAuthModel +{ + 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/wumei-iot/src/main/java/com/ruoyi/iot/model/MqttClientConnectModel.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/MqttClientConnectModel.java new file mode 100644 index 00000000..c9192b5b --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/MqttClientConnectModel.java @@ -0,0 +1,98 @@ +package com.ruoyi.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 Integer 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 Integer getConnected_at() { + return connected_at; + } + + public void setConnected_at(Integer connected_at) { + this.connected_at = connected_at; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/NtpModel.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/NtpModel.java new file mode 100644 index 00000000..6e132203 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/NtpModel.java @@ -0,0 +1,40 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/RegisterUserInput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/RegisterUserInput.java new file mode 100644 index 00000000..5119a79a --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/RegisterUserInput.java @@ -0,0 +1,73 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/ArrayModelOutput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/ArrayModelOutput.java new file mode 100644 index 00000000..5c910634 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/ArrayModelOutput.java @@ -0,0 +1,14 @@ +package com.ruoyi.iot.model.ThingsModelItem; + +public class ArrayModelOutput extends ThingsModelItemBase +{ + private String arrayType; + + public String getArrayType() { + return arrayType; + } + + public void setArrayType(String arrayType) { + this.arrayType = arrayType; + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/BoolModelOutput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/BoolModelOutput.java new file mode 100644 index 00000000..7f362bc2 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/BoolModelOutput.java @@ -0,0 +1,23 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/DecimalModelOutput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/DecimalModelOutput.java new file mode 100644 index 00000000..d8ff3145 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/DecimalModelOutput.java @@ -0,0 +1,43 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/EnumItemOutput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/EnumItemOutput.java new file mode 100644 index 00000000..7a9b25c4 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/EnumItemOutput.java @@ -0,0 +1,23 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/EnumModelOutput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/EnumModelOutput.java new file mode 100644 index 00000000..56e04c37 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/EnumModelOutput.java @@ -0,0 +1,16 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/IntegerModelOutput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/IntegerModelOutput.java new file mode 100644 index 00000000..4d6a30f1 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/IntegerModelOutput.java @@ -0,0 +1,43 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/ReadOnlyModelOutput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/ReadOnlyModelOutput.java new file mode 100644 index 00000000..3da683d4 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/ReadOnlyModelOutput.java @@ -0,0 +1,89 @@ +package com.ruoyi.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; + private String arrayType; + private String falseText; + private String trueText; + private int maxLength; + private List enumList; + + public List getEnumList() { + return enumList; + } + + public void setEnumList(List enumList) { + this.enumList = enumList; + } + + public int getMaxLength() { + return maxLength; + } + + public void setMaxLength(int maxLength) { + this.maxLength = maxLength; + } + + 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 String getArrayType() { + return arrayType; + } + + public void setArrayType(String arrayType) { + this.arrayType = arrayType; + } + + 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/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/StringModelOutput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/StringModelOutput.java new file mode 100644 index 00000000..2429a7b0 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/StringModelOutput.java @@ -0,0 +1,14 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/ThingsModelItemBase.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/ThingsModelItemBase.java new file mode 100644 index 00000000..6efcf555 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModelItem/ThingsModelItemBase.java @@ -0,0 +1,78 @@ +package com.ruoyi.iot.model.ThingsModelItem; + +import com.ruoyi.common.annotation.Excel; + +public class ThingsModelItemBase +{ + /** 物模型唯一标识符 */ + private String id; + /** 物模型名称 */ + private String name; + /** 物模型值 */ + private String value; + /** 是否首页显示(0-否,1-是) */ + private Integer isTop; + /** 是否实时监测(0-否,1-是) */ + private Integer isMonitor; + /** 数据类型 */ + private String type; + /** 影子值 */ + private String shadow; + + public String getShadow() { + return shadow; + } + + public void setShadow(String shadow) { + this.shadow = shadow; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + 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 getIsTop() { + return isTop; + } + + public void setIsTop(Integer isTop) { + this.isTop = isTop; + } + + public Integer getIsMonitor() { + return isMonitor; + } + + public void setIsMonitor(Integer isMonitor) { + this.isMonitor = isMonitor; + } + +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/EventDto.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/EventDto.java new file mode 100644 index 00000000..a345bf00 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/EventDto.java @@ -0,0 +1,45 @@ +package com.ruoyi.iot.model.ThingsModels; + +import com.alibaba.fastjson.JSONObject; + +import java.util.List; + +/** + * 产品分类的Id和名称输出 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class EventDto +{ + /** 物模型唯一标识符 */ + private String id; + /** 物模型名称 */ + private String name; + /** 数据定义 */ + private JSONObject 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 JSONObject getDatatype() { + return datatype; + } + + public void setDatatype(JSONObject datatype) { + this.datatype = datatype; + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/FunctionDto.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/FunctionDto.java new file mode 100644 index 00000000..def54f02 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/FunctionDto.java @@ -0,0 +1,55 @@ +package com.ruoyi.iot.model.ThingsModels; + +import com.alibaba.fastjson.JSONObject; + +import java.util.List; + +/** + * 产品分类的Id和名称输出 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class FunctionDto +{ + /** 物模型唯一标识符 */ + private String id; + /** 物模型名称 */ + private String name; + /** 是否首页显示(0-否,1-是) */ + private Integer isTop; + /** 数据定义 */ + private JSONObject 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 Integer getIsTop() { + return isTop; + } + + public void setIsTop(Integer isTop) { + this.isTop = isTop; + } + + public JSONObject getDatatype() { + return datatype; + } + + public void setDatatype(JSONObject datatype) { + this.datatype = datatype; + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/IdentityAndName.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/IdentityAndName.java new file mode 100644 index 00000000..5cd7e64a --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/IdentityAndName.java @@ -0,0 +1,44 @@ +package com.ruoyi.iot.model.ThingsModels; + +import org.apache.commons.math3.analysis.function.Identity; + +/** + * 物模型值的项 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class IdentityAndName +{ + public IdentityAndName(){ + + } + + public IdentityAndName(String id,String value){ + this.id=id; + this.value=value; + } + + /** 物模型唯一标识符 */ + private String id; + + /** 物模型值 */ + private String value; + + 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; + } + +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/PropertyDto.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/PropertyDto.java new file mode 100644 index 00000000..fb61538f --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/PropertyDto.java @@ -0,0 +1,65 @@ +package com.ruoyi.iot.model.ThingsModels; + +import com.alibaba.fastjson.JSONObject; + +import java.util.List; + +/** + * 产品分类的Id和名称输出 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class PropertyDto +{ + /** 物模型唯一标识符 */ + private String id; + /** 物模型名称 */ + private String name; + /** 是否首页显示(0-否,1-是) */ + private Integer isTop; + /** 是否实时监测(0-否,1-是) */ + private Integer isMonitor; + /** 数据定义 */ + private JSONObject 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 Integer getIsTop() { + return isTop; + } + + public void setIsTop(Integer isTop) { + this.isTop = isTop; + } + + public Integer getIsMonitor() { + return isMonitor; + } + + public void setIsMonitor(Integer isMonitor) { + this.isMonitor = isMonitor; + } + + public JSONObject getDatatype() { + return datatype; + } + + public void setDatatype(JSONObject datatype) { + this.datatype = datatype; + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelShadow.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelShadow.java new file mode 100644 index 00000000..6b58d8bc --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelShadow.java @@ -0,0 +1,45 @@ +package com.ruoyi.iot.model.ThingsModels; + +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/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValueItem.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValueItem.java new file mode 100644 index 00000000..89c95185 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValueItem.java @@ -0,0 +1,44 @@ +package com.ruoyi.iot.model.ThingsModels; + +/** + * 物模型值的项 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ThingsModelValueItem +{ + /** 物模型唯一标识符 */ + private String id; + + /** 物模型值 */ + private String value; + + /** 影子值 **/ + private String shadow; + + 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; + } + +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValueItemDto.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValueItemDto.java new file mode 100644 index 00000000..0ea0d0c9 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValueItemDto.java @@ -0,0 +1,196 @@ +package com.ruoyi.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 isTop; + + /** 是否实时监测(0-否,1-是) */ + private Integer isMonitor; + + private DataType dataType; + + public Integer getIsTop() { + return isTop; + } + + public void setIsTop(Integer isTop) { + this.isTop = isTop; + } + + 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/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValueRemarkItem.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValueRemarkItem.java new file mode 100644 index 00000000..a633c7a2 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValueRemarkItem.java @@ -0,0 +1,46 @@ +package com.ruoyi.iot.model.ThingsModels; + +import com.alibaba.fastjson.JSONObject; + +/** + * 物模型值的项 + * + * @author kerwincui + * @date 2021-12-16 + */ +public class ThingsModelValueRemarkItem +{ + /** 物模型唯一标识符 */ + private String id; + + /** 物模型值 */ + private String value; + + /** 备注 **/ + private String remark; + + 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; + } + +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValuesInput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValuesInput.java new file mode 100644 index 00000000..3011f470 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValuesInput.java @@ -0,0 +1,66 @@ +package com.ruoyi.iot.model.ThingsModels; + +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 thingsModelValueRemarkItem; + + 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 thingsModelValueRemarkItem; + } + + public void setThingsModelValueRemarkItem(List thingsModelValueRemarkItem) { + this.thingsModelValueRemarkItem = thingsModelValueRemarkItem; + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValuesOutput.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValuesOutput.java new file mode 100644 index 00000000..474ad0b4 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelValuesOutput.java @@ -0,0 +1,93 @@ +package com.ruoyi.iot.model.ThingsModels; + +/** + * 设备输入物模型值参数 + * + * @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; + + /** 设备物模型值 **/ + private String thingsModelValue; + + 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/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelsDto.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelsDto.java new file mode 100644 index 00000000..5236ab19 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/model/ThingsModels/ThingsModelsDto.java @@ -0,0 +1,50 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxCallback.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxCallback.java new file mode 100644 index 00000000..6d9abae5 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxCallback.java @@ -0,0 +1,54 @@ +package com.ruoyi.iot.mqtt; + +import com.ruoyi.framework.web.domain.server.Sys; +import org.eclipse.paho.client.mqttv3.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @Classname MqttCallback + * @Description 消费监听类 + */ +@Component +public class EmqxCallback implements MqttCallback { + private static final Logger logger = LoggerFactory.getLogger(EmqxCallback.class); + + @Autowired + private MqttConfig mqttConfig; + + @Autowired + private EmqxService emqxService; + + @Override + public void connectionLost(Throwable throwable) { + try { + // 重连mqtt + while(true) { + logger.info("mqtt连接断开,重新连接中..."); + EmqxClient.client.reconnect(); + Thread.sleep(10000); + if(EmqxClient.client.isConnected()){ + logger.info("mqtt已经重新连接"); + break; + } + } + } catch (MqttException | InterruptedException e) { + // e.printStackTrace(); + logger.error("发生错误:"+e.getMessage()); + } + } + + + + @Override + public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception { + emqxService.subscribeCallback(topic,mqttMessage); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + // 消息到达 MQTT 代理时触发的事件 + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxClient.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxClient.java new file mode 100644 index 00000000..56a4afb5 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxClient.java @@ -0,0 +1,115 @@ +package com.ruoyi.iot.mqtt; + +import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @Classname EmqxClient + * @Description mqtt推送客户端 + */ +@Component +public class EmqxClient { + private static final Logger logger = LoggerFactory.getLogger(EmqxClient.class); + + @Autowired + private EmqxCallback emqxCallback; + + @Autowired + private EmqxService emqxService; + + public static MqttClient client; + + /** + * 客户端连接 + * + * @param host ip+端口 + * @param clientID 客户端Id + * @param username 用户名 + * @param password 密码 + * @param timeout 超时时间 + * @param keepalive 保留数 + */ + public void connect(String host, String clientID, String username, String password, int timeout, int keepalive) { + MqttClient client; + try { + client = new MqttClient(host, clientID, new MemoryPersistence()); + MqttConnectOptions options = new MqttConnectOptions(); + options.setCleanSession(true); + options.setUserName(username); + options.setPassword(password.toCharArray()); + options.setConnectionTimeout(timeout); + options.setKeepAliveInterval(keepalive); + EmqxClient.client=client; + client.setCallback(emqxCallback); + clientConnect(options,client); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 10秒重连一次 + * @param options + * @param client + * @throws InterruptedException + */ + public void clientConnect(MqttConnectOptions options, MqttClient client) throws InterruptedException { + try { + client.connect(options); + logger.info("mqtt连接成功"); + // 订阅主题 + emqxService.subscribe(client); + } catch (Exception e) { + logger.error("mqtt连接失败,"+e.getMessage()); + //发生错误后重新连接 + Thread.sleep(10000); + clientConnect(options,client); + } + } + + /** + * 发布 + * @param qos 连接方式 + * @param retained 是否保留 + * @param topic 主题 + * @param pushMessage 消息体 + */ + public void publish(int qos, boolean retained, String topic, String pushMessage) { + logger.info("发布主题" + topic); + MqttMessage message = new MqttMessage(); + message.setQos(qos); + message.setRetained(retained); + message.setPayload(pushMessage.getBytes()); + MqttTopic mTopic = EmqxClient.client.getTopic(topic); + if (null == mTopic) { + logger.error("topic not exist"); + } + MqttDeliveryToken token; + try { + token = mTopic.publish(message); + token.waitForCompletion(); + } catch (MqttPersistenceException e) { + e.printStackTrace(); + } catch (MqttException e) { + e.printStackTrace(); + } + } + + /** + * 订阅某个主题 + * @param topic 主题 + * @param qos 连接方式 + */ + public void subscribe(String topic, int qos) { + logger.info("订阅主题" + topic); + try { + EmqxClient.client.subscribe(topic, qos); + } catch (MqttException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxService.java new file mode 100644 index 00000000..9c2b5ba0 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxService.java @@ -0,0 +1,211 @@ +package com.ruoyi.iot.mqtt; + +import com.alibaba.fastjson.JSON; +import com.ruoyi.iot.domain.Device; +import com.ruoyi.iot.domain.DeviceLog; +import com.ruoyi.iot.model.NtpModel; +import com.ruoyi.iot.model.ThingsModels.IdentityAndName; +import com.ruoyi.iot.model.ThingsModels.ThingsModelValueItem; +import com.ruoyi.iot.model.ThingsModels.ThingsModelValueRemarkItem; +import com.ruoyi.iot.model.ThingsModels.ThingsModelValuesInput; +import com.ruoyi.iot.service.IDeviceLogService; +import com.ruoyi.iot.service.IDeviceService; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class EmqxService { + private static final Logger logger = LoggerFactory.getLogger(EmqxService.class); + + @Autowired + private EmqxClient emqxClient; + + @Autowired + private IDeviceService deviceService; + + @Autowired + private IDeviceLogService deviceLogService; + + /** 订阅的主题 */ + private static final String prefix="/+/+/"; + String sInfoTopic = prefix + "info/post"; + String sNtpTopic = prefix + "ntp/post"; + String sPropertyTopic = prefix + "property/post"; + String sFunctionTopic = prefix + "function/post"; + String sEventTopic = prefix + "event/post"; + String sShadowPropertyTopic = prefix + "property-offline/post"; + String sShadowFunctionTopic = prefix + "function-offline/post"; + + /** 发布的主题 */ + String pStatusTopic = "/status/post"; + String pNtpTopic = "/ntp/get"; + String pPropertyTopic = "/property/get"; + String pFunctionTopic = "/function/get"; + + public void subscribe(MqttClient client) throws MqttException { + // 订阅设备信息 + client.subscribe(sInfoTopic, 1); + // 订阅时钟同步 + client.subscribe(sNtpTopic, 1); + // 订阅设备属性 + client.subscribe(sPropertyTopic,1); + // 订阅设备功能 + client.subscribe(sFunctionTopic,1); + // 订阅设备事件 + client.subscribe(sEventTopic,1); + // 订阅属性(影子模式) + client.subscribe(sShadowPropertyTopic,1); + // 订阅功能(影子模式) + client.subscribe(sShadowFunctionTopic,1); + logger.info("mqtt订阅了设备信息和物模型主题"); + } + + public void subscribeCallback(String topic, MqttMessage mqttMessage){ + // subscribe后得到的消息会执行到这里面 + String message=new String(mqttMessage.getPayload()); + logger.info("接收消息主题 : " + topic); + logger.info("接收消息Qos : " + mqttMessage.getQos()); + logger.info("接收消息内容 : " + message); + + String[] topicItem=topic.substring(1).split("/"); + Long productId= Long.valueOf(topicItem[0]); + String deviceNum=topicItem[1]; + String name=topicItem[2]; + switch (name){ + case "info": + reportDevice(productId,deviceNum,message); + break; + case "ntp": + publishNtp(productId,deviceNum,message); + break; + case "property": + reportProperty(productId,deviceNum,message,false); + break; + case "function": + reportFunction(productId,deviceNum,message,false); + break; + case "event": + reportEvent(productId,deviceNum,message); + break; + case "property-offline": + reportProperty(productId,deviceNum,message,true); + break; + case "function-offline": + reportFunction(productId,deviceNum,message,true); + break; + } + } + + /** + * 上报设备信息 + */ + private void reportDevice(Long productId,String deviceNum,String message){ + Device device=JSON.parseObject(message,Device.class); + device.setProductId(productId); + device.setSerialNumber(deviceNum); + deviceService.reportDevice(device); + } + + /** + * 上报属性 + * @param message + */ + private void reportProperty(Long productId,String deviceNum,String message,boolean isShadow){ + List thingsModelValueRemarkItems=JSON.parseArray(message, ThingsModelValueRemarkItem.class); + ThingsModelValuesInput input=new ThingsModelValuesInput(); + input.setProductId(productId); + input.setDeviceNumber(deviceNum); + input.setThingsModelValueRemarkItem(thingsModelValueRemarkItems); + deviceService.reportDeviceThingsModelValue(input,1,isShadow); + } + + /** + * 上报功能 + * @param message + */ + private void reportFunction(Long productId,String deviceNum,String message,boolean isShadow){ + List thingsModelValueRemarkItems=JSON.parseArray(message, ThingsModelValueRemarkItem.class); + ThingsModelValuesInput input=new ThingsModelValuesInput(); + input.setProductId(productId); + input.setDeviceNumber(deviceNum); + input.setThingsModelValueRemarkItem(thingsModelValueRemarkItems); + deviceService.reportDeviceThingsModelValue(input,2,isShadow); + } + + /** + * 上报事件 + * @param message + */ + private void reportEvent(Long productId,String deviceNum,String message){ + List thingsModelValueRemarkItems=JSON.parseArray(message, ThingsModelValueRemarkItem.class); + Device device =deviceService.selectDeviceBySerialNumber(deviceNum); + for(int i=0;i thingsList){ + if(thingsList==null){ + emqxClient.publish(1,true,"/"+productId+"/"+deviceNum+pPropertyTopic, ""); + }else{ + emqxClient.publish(1,true,"/"+productId+"/"+deviceNum+pPropertyTopic, JSON.toJSONString(thingsList)); + } + } + + /** + * 4.发布功能 + */ + public void publishFunction(Long productId,String deviceNum,List thingsList){ + if(thingsList==null){ + emqxClient.publish(1,true,"/"+productId+"/"+deviceNum+pFunctionTopic, ""); + }else{ + emqxClient.publish(1,true,"/"+productId+"/"+deviceNum+pFunctionTopic, JSON.toJSONString(thingsList)); + } + + } + + + + +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxStart.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxStart.java new file mode 100644 index 00000000..b9a30af9 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/EmqxStart.java @@ -0,0 +1,23 @@ +package com.ruoyi.iot.mqtt; + +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; + +/** + *项目启动执行 + */ +@Component +@Order(value = 1) //执行顺序控制 +public class EmqxStart implements ApplicationRunner { + + @Autowired + private MqttConfig mqttConfig; + + @Override + public void run(ApplicationArguments applicationArguments){ + mqttConfig.EmqxClientStart(); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/MqttConfig.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/MqttConfig.java new file mode 100644 index 00000000..cb614bd3 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/mqtt/MqttConfig.java @@ -0,0 +1,101 @@ +package com.ruoyi.iot.mqtt; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +/** + * @Classname MqttConfig + * @Description mqtt配置信息 + * @author kerwincui + */ +@Component +@ConfigurationProperties("spring.mqtt") +public class MqttConfig { + + private static final Logger logger = LoggerFactory.getLogger(MqttConfig.class); + + @Autowired + private EmqxClient emqxClient; + + /** + * 用户名 + */ + private String username; + /** + * 密码 + */ + private String password; + /** + * 连接地址 + */ + private String hostUrl; + /** + * 客户Id + */ + private String clientId; + /** + * 默认连接话题 + */ + private String defaultTopic; + /** + * 超时时间 + */ + private int timeout; + /** + * 保持连接数 + */ + private int keepalive; + + + 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 gethostUrl() + { + return hostUrl; + } + public void sethostUrl(String hostUrl) {this.hostUrl = hostUrl;} + + public String getclientId() + { + return "server-"+clientId; + } + public void setclientId(String clientId) {this.clientId = clientId;} + + public String getdefaultTopic() + { + return defaultTopic; + } + public void setdefaultTopic(String defaultTopic) {this.defaultTopic = defaultTopic;} + + public int gettimeout() + { + return timeout; + } + public void settimeout(int timeout) {this.timeout = timeout;} + + public int getkeepalive() + { + return keepalive; + } + public void setkeepalive(int keepalive) {this.keepalive = keepalive;} + + // @Bean + public void EmqxClientStart() { + logger.info("mqtt启动中..."); + emqxClient.connect(gethostUrl(), getclientId(), getusername(), getpassword(), gettimeout(), getkeepalive()); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/AuthorizationServerConfig.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/AuthorizationServerConfig.java new file mode 100644 index 00000000..f07302b8 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/AuthorizationServerConfig.java @@ -0,0 +1,119 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/oauth/ResourceServerConfig.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/ResourceServerConfig.java new file mode 100644 index 00000000..a97973c5 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/ResourceServerConfig.java @@ -0,0 +1,50 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/oauth/SpeakerApprovalHandler.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/SpeakerApprovalHandler.java new file mode 100644 index 00000000..f6260371 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/SpeakerApprovalHandler.java @@ -0,0 +1,83 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/oauth/api/ConfirmAccessController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/api/ConfirmAccessController.java new file mode 100644 index 00000000..afeb7386 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/api/ConfirmAccessController.java @@ -0,0 +1,49 @@ +package com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/oauth/api/LoginController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/api/LoginController.java new file mode 100644 index 00000000..aa1913eb --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/api/LoginController.java @@ -0,0 +1,40 @@ +package com.ruoyi.iot.oauth.api; + +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.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class LoginController { + @Autowired + private TokenStore tokenStore; + + @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/wumei-iot/src/main/java/com/ruoyi/iot/oauth/api/SpeakerController.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/api/SpeakerController.java new file mode 100644 index 00000000..9c41e9b7 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/oauth/api/SpeakerController.java @@ -0,0 +1,33 @@ +package com.ruoyi.iot.oauth.api; + +import com.alibaba.fastjson.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/wumei-iot/src/main/java/com/ruoyi/iot/service/IAlertLogService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IAlertLogService.java new file mode 100644 index 00000000..984be858 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IAlertLogService.java @@ -0,0 +1,62 @@ +package com.ruoyi.iot.service; + +import com.ruoyi.iot.domain.AlertLog; + +import java.util.List; + +/** + * 设备告警Service接口 + * + * @author kerwincui + * @date 2022-01-13 + */ +public interface IAlertLogService +{ + /** + * 查询设备告警 + * + * @param alertLogId 设备告警主键 + * @return 设备告警 + */ + public AlertLog selectAlertLogByAlertLogId(Long alertLogId); + + /** + * 查询设备告警列表 + * + * @param alertLog 设备告警 + * @return 设备告警集合 + */ + public List selectAlertLogList(AlertLog alertLog); + + /** + * 新增设备告警 + * + * @param alertLog 设备告警 + * @return 结果 + */ + public int insertAlertLog(AlertLog alertLog); + + /** + * 修改设备告警 + * + * @param alertLog 设备告警 + * @return 结果 + */ + public int updateAlertLog(AlertLog alertLog); + + /** + * 批量删除设备告警 + * + * @param alertLogIds 需要删除的设备告警主键集合 + * @return 结果 + */ + public int deleteAlertLogByAlertLogIds(Long[] alertLogIds); + + /** + * 删除设备告警信息 + * + * @param alertLogId 设备告警主键 + * @return 结果 + */ + public int deleteAlertLogByAlertLogId(Long alertLogId); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IAlertService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IAlertService.java new file mode 100644 index 00000000..f43ef244 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IAlertService.java @@ -0,0 +1,61 @@ +package com.ruoyi.iot.service; + +import java.util.List; +import com.ruoyi.iot.domain.Alert; + +/** + * 设备告警Service接口 + * + * @author kerwincui + * @date 2022-01-13 + */ +public interface IAlertService +{ + /** + * 查询设备告警 + * + * @param alertId 设备告警主键 + * @return 设备告警 + */ + public Alert selectAlertByAlertId(Long alertId); + + /** + * 查询设备告警列表 + * + * @param alert 设备告警 + * @return 设备告警集合 + */ + public List selectAlertList(Alert alert); + + /** + * 新增设备告警 + * + * @param alert 设备告警 + * @return 结果 + */ + public int insertAlert(Alert alert); + + /** + * 修改设备告警 + * + * @param alert 设备告警 + * @return 结果 + */ + public int updateAlert(Alert alert); + + /** + * 批量删除设备告警 + * + * @param alertIds 需要删除的设备告警主键集合 + * @return 结果 + */ + public int deleteAlertByAlertIds(Long[] alertIds); + + /** + * 删除设备告警信息 + * + * @param alertId 设备告警主键 + * @return 结果 + */ + public int deleteAlertByAlertId(Long alertId); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/ICategoryService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/ICategoryService.java new file mode 100644 index 00000000..45833cde --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/ICategoryService.java @@ -0,0 +1,71 @@ +package com.ruoyi.iot.service; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.iot.domain.Category; +import com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/service/IDeviceJobService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IDeviceJobService.java new file mode 100644 index 00000000..0dfc3b55 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IDeviceJobService.java @@ -0,0 +1,111 @@ +package com.ruoyi.iot.service; + +import com.ruoyi.common.exception.job.TaskException; +import com.ruoyi.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; + + /** + * 任务调度状态修改 + * + * @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/wumei-iot/src/main/java/com/ruoyi/iot/service/IDeviceLogService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IDeviceLogService.java new file mode 100644 index 00000000..f4a57b95 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IDeviceLogService.java @@ -0,0 +1,79 @@ +package com.ruoyi.iot.service; + +import com.ruoyi.iot.domain.DeviceLog; +import com.ruoyi.iot.model.MonitorModel; + +import java.util.List; + +/** + * 设备日志Service接口 + * + * @author kerwincui + * @date 2022-01-13 + */ +public interface IDeviceLogService +{ + /** + * 查询设备日志 + * + * @param logId 设备日志主键 + * @return 设备日志 + */ + public DeviceLog selectDeviceLogByLogId(Long logId); + + /** + * 查询设备日志列表 + * + * @param deviceLog 设备日志 + * @return 设备日志集合 + */ + public List selectDeviceLogList(DeviceLog deviceLog); + + /** + * 查询设备监测数据 + * + * @param deviceLog 设备日志 + * @return 设备日志集合 + */ + public List selectMonitorList(DeviceLog deviceLog); + + /** + * 新增设备日志 + * + * @param deviceLog 设备日志 + * @return 结果 + */ + public int insertDeviceLog(DeviceLog deviceLog); + + /** + * 修改设备日志 + * + * @param deviceLog 设备日志 + * @return 结果 + */ + public int updateDeviceLog(DeviceLog deviceLog); + + /** + * 批量删除设备日志 + * + * @param logIds 需要删除的设备日志主键集合 + * @return 结果 + */ + public int deleteDeviceLogByLogIds(Long[] logIds); + + /** + * 删除设备日志信息 + * + * @param logId 设备日志主键 + * @return 结果 + */ + public int deleteDeviceLogByLogId(Long logId); + + /** + * 根据设备IDs批量删除设备日志 + * + * @param deviceIds 需要删除的设备日志IDs + * @return 结果 + */ + public int deleteDeviceLogByDeviceIds(Long[] deviceIds); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IDeviceService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IDeviceService.java new file mode 100644 index 00000000..266cd8fe --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IDeviceService.java @@ -0,0 +1,154 @@ +package com.ruoyi.iot.service; + +import com.ruoyi.iot.domain.Device; +import com.ruoyi.iot.model.*; +import com.ruoyi.iot.model.ThingsModels.ThingsModelShadow; +import com.ruoyi.iot.model.ThingsModels.ThingsModelValuesInput; +import org.quartz.SchedulerException; + +import java.util.List; + +/** + * 设备Service接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface IDeviceService +{ + /** + * 查询设备 + * + * @param deviceId 设备主键 + * @return 设备 + */ + public Device selectDeviceByDeviceId(Long deviceId); + + /** + * 根据设备编号查询设备 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + public Device selectDeviceBySerialNumber(String serialNumber); + + /** + * 根据设备编号查询简介设备 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + public Device selectShortDeviceBySerialNumber(String serialNumber); + + /** + * 根据设备编号查询设备认证信息 + * + * @param model 设备编号和产品ID + * @return 设备 + */ + public DeviceAuthenticateModel selectDeviceAuthenticate(AuthenticateInputModel model); + + /** + * 查询设备和运行状态 + * + * @param deviceId 设备主键 + * @return 设备 + */ + public DeviceShortOutput selectDeviceRunningStatusByDeviceId(Long deviceId); + + /** + * 上报设备的物模型 + * @param input + * @return + */ + public int reportDeviceThingsModelValue(ThingsModelValuesInput input,int type,boolean isShadow); + + /** + * 查询设备列表 + * + * @param device 设备 + * @return 设备集合 + */ + public List selectDeviceList(Device device); + + /** + * 查询所有设备简短列表 + * + * @return 设备 + */ + public List selectAllDeviceShortList(); + + /** + * 查询设备简短列表 + * + * @param device 设备 + * @return 设备集合 + */ + public List selectDeviceShortList(Device device); + + /** + * 新增设备 + * + * @param device 设备 + * @return 结果 + */ + public Device insertDevice(Device device); + + /** + * 设备认证后自动添加设备 + * + * @return 结果 + */ + public int insertDeviceAuto(String serialNumber,Long userId,Long productId); + + /** + * 获取设备设置的影子 + * @param device + * @return + */ + public ThingsModelShadow getDeviceShadowThingsModel(Device device); + + /** + * 修改设备 + * + * @param device 设备 + * @return 结果 + */ + public int updateDevice(Device device); + + /** + * 更新设备状态 + * @param device 设备 + * @return 结果 + */ + public int updateDeviceStatusAndLocation(Device device,String ipAddress); + + /** + * 上报设备信息 + * @param device 设备 + * @return 结果 + */ + public int reportDevice(Device device); + + /** + * 批量删除设备 + * + * @param deviceIds 需要删除的设备主键集合 + * @return 结果 + */ + public int deleteDeviceByDeviceIds(Long[] deviceIds) throws SchedulerException; + + /** + * 删除设备信息 + * + * @param deviceId 设备主键 + * @return 结果 + */ + public int deleteDeviceByDeviceId(Long deviceId); + + /** + * 生成设备唯一编号 + * @return 结果 + */ + public String generationDeviceNum(); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IDeviceUserService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IDeviceUserService.java new file mode 100644 index 00000000..6a92a3fa --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IDeviceUserService.java @@ -0,0 +1,61 @@ +package com.ruoyi.iot.service; + +import java.util.List; +import com.ruoyi.iot.domain.DeviceUser; + +/** + * 设备用户Service接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface IDeviceUserService +{ + /** + * 查询设备用户 + * + * @param deviceId 设备用户主键 + * @return 设备用户 + */ + public DeviceUser selectDeviceUserByDeviceId(Long deviceId); + + /** + * 查询设备用户列表 + * + * @param deviceUser 设备用户 + * @return 设备用户集合 + */ + public List selectDeviceUserList(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); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IFirmwareService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IFirmwareService.java new file mode 100644 index 00000000..5b5ccd2d --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IFirmwareService.java @@ -0,0 +1,61 @@ +package com.ruoyi.iot.service; + +import java.util.List; +import com.ruoyi.iot.domain.Firmware; + +/** + * 产品固件Service接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface IFirmwareService +{ + /** + * 查询产品固件 + * + * @param firmwareId 产品固件主键 + * @return 产品固件 + */ + public Firmware selectFirmwareByFirmwareId(Long firmwareId); + + /** + * 查询产品固件列表 + * + * @param firmware 产品固件 + * @return 产品固件集合 + */ + public List selectFirmwareList(Firmware firmware); + + /** + * 新增产品固件 + * + * @param firmware 产品固件 + * @return 结果 + */ + public int insertFirmware(Firmware firmware); + + /** + * 修改产品固件 + * + * @param firmware 产品固件 + * @return 结果 + */ + public int updateFirmware(Firmware firmware); + + /** + * 批量删除产品固件 + * + * @param firmwareIds 需要删除的产品固件主键集合 + * @return 结果 + */ + public int deleteFirmwareByFirmwareIds(Long[] firmwareIds); + + /** + * 删除产品固件信息 + * + * @param firmwareId 产品固件主键 + * @return 结果 + */ + public int deleteFirmwareByFirmwareId(Long firmwareId); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IGroupService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IGroupService.java new file mode 100644 index 00000000..3c140fa3 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IGroupService.java @@ -0,0 +1,77 @@ +package com.ruoyi.iot.service; + +import com.ruoyi.iot.domain.Group; +import com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/service/IOauthClientDetailsService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IOauthClientDetailsService.java new file mode 100644 index 00000000..f5eabab8 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IOauthClientDetailsService.java @@ -0,0 +1,61 @@ +package com.ruoyi.iot.service; + +import java.util.List; +import com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/service/IProductService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IProductService.java new file mode 100644 index 00000000..92931e51 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IProductService.java @@ -0,0 +1,80 @@ +package com.ruoyi.iot.service; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.iot.domain.Product; +import com.ruoyi.iot.model.ChangeProductStatusModel; +import com.ruoyi.iot.model.IdAndName; + +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); + + /** + * 更新产品状态,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); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/ISceneService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/ISceneService.java new file mode 100644 index 00000000..d27cd23a --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/ISceneService.java @@ -0,0 +1,61 @@ +package com.ruoyi.iot.service; + +import java.util.List; +import com.ruoyi.iot.domain.Scene; + +/** + * 场景联动Service接口 + * + * @author kerwincui + * @date 2022-01-13 + */ +public interface ISceneService +{ + /** + * 查询场景联动 + * + * @param sceneId 场景联动主键 + * @return 场景联动 + */ + public Scene selectSceneBySceneId(Long sceneId); + + /** + * 查询场景联动列表 + * + * @param scene 场景联动 + * @return 场景联动集合 + */ + public List selectSceneList(Scene scene); + + /** + * 新增场景联动 + * + * @param scene 场景联动 + * @return 结果 + */ + public int insertScene(Scene scene); + + /** + * 修改场景联动 + * + * @param scene 场景联动 + * @return 结果 + */ + public int updateScene(Scene scene); + + /** + * 批量删除场景联动 + * + * @param sceneIds 需要删除的场景联动主键集合 + * @return 结果 + */ + public int deleteSceneBySceneIds(Long[] sceneIds); + + /** + * 删除场景联动信息 + * + * @param sceneId 场景联动主键 + * @return 结果 + */ + public int deleteSceneBySceneId(Long sceneId); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IThingsModelService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IThingsModelService.java new file mode 100644 index 00000000..11554b3a --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IThingsModelService.java @@ -0,0 +1,76 @@ +package com.ruoyi.iot.service; + +import java.util.List; +import com.ruoyi.iot.domain.ThingsModel; +import com.ruoyi.iot.model.ImportThingsModelInput; + +/** + * 物模型Service接口 + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface IThingsModelService +{ + /** + * 查询物模型 + * + * @param modelId 物模型主键 + * @return 物模型 + */ + public ThingsModel selectThingsModelByModelId(Long modelId); + + /** + * 查询物模型列表 + * + * @param thingsModel 物模型 + * @return 物模型集合 + */ + public List selectThingsModelList(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); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IThingsModelTemplateService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IThingsModelTemplateService.java new file mode 100644 index 00000000..e4bb54e2 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IThingsModelTemplateService.java @@ -0,0 +1,61 @@ +package com.ruoyi.iot.service; + +import java.util.List; +import com.ruoyi.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); + + /** + * 批量删除通用物模型 + * + * @param templateIds 需要删除的通用物模型主键集合 + * @return 结果 + */ + public int deleteThingsModelTemplateByTemplateIds(Long[] templateIds); + + /** + * 删除通用物模型信息 + * + * @param templateId 通用物模型主键 + * @return 结果 + */ + public int deleteThingsModelTemplateByTemplateId(Long templateId); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IToolService.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IToolService.java new file mode 100644 index 00000000..cf9f946c --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/IToolService.java @@ -0,0 +1,21 @@ +package com.ruoyi.iot.service; + +import com.ruoyi.iot.model.RegisterUserInput; + +/** + * + * @author kerwincui + * @date 2021-12-16 + */ +public interface IToolService +{ + /** + * 注册 + */ + public String register(RegisterUserInput registerBody); + + /** + * 生成随机数字和字母 + */ + public String getStringRandom(int length); +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/AlertLogServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/AlertLogServiceImpl.java new file mode 100644 index 00000000..5fe6d135 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/AlertLogServiceImpl.java @@ -0,0 +1,96 @@ +package com.ruoyi.iot.service.impl; + +import java.util.List; +import com.ruoyi.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.iot.mapper.AlertLogMapper; +import com.ruoyi.iot.domain.AlertLog; +import com.ruoyi.iot.service.IAlertLogService; + +/** + * 设备告警Service业务层处理 + * + * @author kerwincui + * @date 2022-01-13 + */ +@Service +public class AlertLogServiceImpl implements IAlertLogService +{ + @Autowired + private AlertLogMapper alertLogMapper; + + /** + * 查询设备告警 + * + * @param alertLogId 设备告警主键 + * @return 设备告警 + */ + @Override + public AlertLog selectAlertLogByAlertLogId(Long alertLogId) + { + return alertLogMapper.selectAlertLogByAlertLogId(alertLogId); + } + + /** + * 查询设备告警列表 + * + * @param alertLog 设备告警 + * @return 设备告警 + */ + @Override + public List selectAlertLogList(AlertLog alertLog) + { + return alertLogMapper.selectAlertLogList(alertLog); + } + + /** + * 新增设备告警 + * + * @param alertLog 设备告警 + * @return 结果 + */ + @Override + public int insertAlertLog(AlertLog alertLog) + { + alertLog.setCreateTime(DateUtils.getNowDate()); + return alertLogMapper.insertAlertLog(alertLog); + } + + /** + * 修改设备告警 + * + * @param alertLog 设备告警 + * @return 结果 + */ + @Override + public int updateAlertLog(AlertLog alertLog) + { + alertLog.setUpdateTime(DateUtils.getNowDate()); + return alertLogMapper.updateAlertLog(alertLog); + } + + /** + * 批量删除设备告警 + * + * @param alertLogIds 需要删除的设备告警主键 + * @return 结果 + */ + @Override + public int deleteAlertLogByAlertLogIds(Long[] alertLogIds) + { + return alertLogMapper.deleteAlertLogByAlertLogIds(alertLogIds); + } + + /** + * 删除设备告警信息 + * + * @param alertLogId 设备告警主键 + * @return 结果 + */ + @Override + public int deleteAlertLogByAlertLogId(Long alertLogId) + { + return alertLogMapper.deleteAlertLogByAlertLogId(alertLogId); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/AlertServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/AlertServiceImpl.java new file mode 100644 index 00000000..a5211ae9 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/AlertServiceImpl.java @@ -0,0 +1,96 @@ +package com.ruoyi.iot.service.impl; + +import java.util.List; +import com.ruoyi.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.iot.mapper.AlertMapper; +import com.ruoyi.iot.domain.Alert; +import com.ruoyi.iot.service.IAlertService; + +/** + * 设备告警Service业务层处理 + * + * @author kerwincui + * @date 2022-01-13 + */ +@Service +public class AlertServiceImpl implements IAlertService +{ + @Autowired + private AlertMapper alertMapper; + + /** + * 查询设备告警 + * + * @param alertId 设备告警主键 + * @return 设备告警 + */ + @Override + public Alert selectAlertByAlertId(Long alertId) + { + return alertMapper.selectAlertByAlertId(alertId); + } + + /** + * 查询设备告警列表 + * + * @param alert 设备告警 + * @return 设备告警 + */ + @Override + public List selectAlertList(Alert alert) + { + return alertMapper.selectAlertList(alert); + } + + /** + * 新增设备告警 + * + * @param alert 设备告警 + * @return 结果 + */ + @Override + public int insertAlert(Alert alert) + { + alert.setCreateTime(DateUtils.getNowDate()); + return alertMapper.insertAlert(alert); + } + + /** + * 修改设备告警 + * + * @param alert 设备告警 + * @return 结果 + */ + @Override + public int updateAlert(Alert alert) + { + alert.setUpdateTime(DateUtils.getNowDate()); + return alertMapper.updateAlert(alert); + } + + /** + * 批量删除设备告警 + * + * @param alertIds 需要删除的设备告警主键 + * @return 结果 + */ + @Override + public int deleteAlertByAlertIds(Long[] alertIds) + { + return alertMapper.deleteAlertByAlertIds(alertIds); + } + + /** + * 删除设备告警信息 + * + * @param alertId 设备告警主键 + * @return 结果 + */ + @Override + public int deleteAlertByAlertId(Long alertId) + { + return alertMapper.deleteAlertByAlertId(alertId); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/CategoryServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/CategoryServiceImpl.java new file mode 100644 index 00000000..6f09b18c --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/CategoryServiceImpl.java @@ -0,0 +1,130 @@ +package com.ruoyi.iot.service.impl; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.iot.domain.Category; +import com.ruoyi.iot.mapper.CategoryMapper; +import com.ruoyi.iot.model.IdAndName; +import com.ruoyi.iot.service.ICategoryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static com.ruoyi.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) + { + return categoryMapper.selectCategoryList(category); + } + + /** + * 查询产品分简短类列表 + * + * @return 产品分类 + */ + @Override + public List selectCategoryShortList() + { + return categoryMapper.selectCategoryShortList(); + } + + /** + * 新增产品分类 + * + * @param category 产品分类 + * @return 结果 + */ + @Override + public int insertCategory(Category category) + { + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + if(roles==null || roles.size()==0) {return 0;} + // 系统管理员 + if(roles.stream().anyMatch(a->a.getRoleKey().equals("admin"))){ + category.setIsSys(1); + } + category.setTenantId(user.getUserId()); + category.setTenantName(user.getUserName()); + category.setCreateTime(DateUtils.getNowDate()); + return categoryMapper.insertCategory(category); + } + + /** + * 修改产品分类 + * + * @param category 产品分类 + * @return 结果 + */ + @Override + public int updateCategory(Category category) + { + category.setUpdateTime(DateUtils.getNowDate()); + return categoryMapper.updateCategory(category); + } + + /** + * 批量删除产品分类 + * + * @param categoryIds 需要删除的产品分类主键 + * @return 结果 + */ + @Override + public AjaxResult deleteCategoryByCategoryIds(Long[] categoryIds) + { + int productCount=categoryMapper.productCountInCategorys(categoryIds); + if(productCount>0){ + 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/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceJobServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceJobServiceImpl.java new file mode 100644 index 00000000..1d714cc7 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceJobServiceImpl.java @@ -0,0 +1,277 @@ +package com.ruoyi.iot.service.impl; + +import com.ruoyi.common.constant.ScheduleConstants; +import com.ruoyi.common.exception.job.TaskException; +import com.ruoyi.iot.domain.DeviceJob; +import com.ruoyi.iot.mapper.DeviceJobMapper; +import com.ruoyi.iot.service.IDeviceJobService; +import com.ruoyi.iot.util.quartz.CronUtils; +import com.ruoyi.iot.util.quartz.ScheduleUtils; +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 javax.annotation.PostConstruct; +import java.util.List; + +/** + * 定时任务调度信息 服务层 + * + * @author kerwincui + */ +@Service +public class DeviceJobServiceImpl implements IDeviceJobService +{ + @Autowired + private Scheduler scheduler; + + @Autowired + private DeviceJobMapper jobMapper; + + /** + * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据) + */ + @PostConstruct + public void init() throws SchedulerException, TaskException + { + scheduler.clear(); + List jobList = jobMapper.selectJobAll(); + for (DeviceJob deviceJob : jobList) + { + ScheduleUtils.createScheduleJob(scheduler, deviceJob); + } + } + + /** + * 获取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())); + } + + } + + /** + * 任务调度状态修改 + * + * @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/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceLogServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceLogServiceImpl.java new file mode 100644 index 00000000..c0fbd020 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceLogServiceImpl.java @@ -0,0 +1,121 @@ +package com.ruoyi.iot.service.impl; + +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.iot.domain.DeviceLog; +import com.ruoyi.iot.mapper.DeviceLogMapper; +import com.ruoyi.iot.model.MonitorModel; +import com.ruoyi.iot.service.IDeviceLogService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 设备日志Service业务层处理 + * + * @author kerwincui + * @date 2022-01-13 + */ +@Service +public class DeviceLogServiceImpl implements IDeviceLogService +{ + @Autowired + private DeviceLogMapper deviceLogMapper; + + /** + * 查询设备日志 + * + * @param logId 设备日志主键 + * @return 设备日志 + */ + @Override + public DeviceLog selectDeviceLogByLogId(Long logId) + { + return deviceLogMapper.selectDeviceLogByLogId(logId); + } + + /** + * 查询设备日志列表 + * + * @param deviceLog 设备日志 + * @return 设备日志 + */ + @Override + public List selectDeviceLogList(DeviceLog deviceLog) + { + return deviceLogMapper.selectDeviceLogList(deviceLog); + } + + /** + * 查询设备监测数据 + * + * @param deviceLog 设备日志 + * @return 设备日志 + */ + @Override + public List selectMonitorList(DeviceLog deviceLog) + { + return deviceLogMapper.selectMonitorList(deviceLog); + } + + /** + * 新增设备日志 + * + * @param deviceLog 设备日志 + * @return 结果 + */ + @Override + public int insertDeviceLog(DeviceLog deviceLog) + { + deviceLog.setCreateTime(DateUtils.getNowDate()); + return deviceLogMapper.insertDeviceLog(deviceLog); + } + + /** + * 修改设备日志 + * + * @param deviceLog 设备日志 + * @return 结果 + */ + @Override + public int updateDeviceLog(DeviceLog deviceLog) + { + return deviceLogMapper.updateDeviceLog(deviceLog); + } + + /** + * 批量删除设备日志 + * + * @param logIds 需要删除的设备日志主键 + * @return 结果 + */ + @Override + public int deleteDeviceLogByLogIds(Long[] logIds) + { + return deviceLogMapper.deleteDeviceLogByLogIds(logIds); + } + + /** + * 根据设备Ids批量删除设备日志 + * + * @param deviceIds 需要删除数据的设备Ids + * @return 结果 + */ + @Override + public int deleteDeviceLogByDeviceIds(Long[] deviceIds) + { + return deviceLogMapper.deleteDeviceLogByDeviceIds(deviceIds); + } + + /** + * 删除设备日志信息 + * + * @param logId 设备日志主键 + * @return 结果 + */ + @Override + public int deleteDeviceLogByLogId(Long logId) + { + return deviceLogMapper.deleteDeviceLogByLogId(logId); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceServiceImpl.java new file mode 100644 index 00000000..d0cad52b --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceServiceImpl.java @@ -0,0 +1,674 @@ +package com.ruoyi.iot.service.impl; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.http.HttpUtils; +import com.ruoyi.common.utils.ip.IpUtils; +import com.ruoyi.iot.domain.Device; +import com.ruoyi.iot.domain.DeviceLog; +import com.ruoyi.iot.domain.Product; +import com.ruoyi.iot.mapper.DeviceLogMapper; +import com.ruoyi.iot.mapper.DeviceMapper; +import com.ruoyi.iot.mapper.DeviceUserMapper; +import com.ruoyi.iot.model.*; +import com.ruoyi.iot.model.ThingsModelItem.*; +import com.ruoyi.iot.model.ThingsModels.*; +import com.ruoyi.iot.service.IDeviceLogService; +import com.ruoyi.iot.service.IDeviceService; +import com.ruoyi.iot.service.IProductService; +import com.ruoyi.iot.service.IToolService; +import com.ruoyi.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.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import static com.ruoyi.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 DeviceJobServiceImpl deviceJobService; + + @Autowired + private DeviceLogMapper deviceLogMapper; + + @Autowired + private IToolService toolService; + + @Autowired + private IProductService productService; + + @Autowired + private ISysUserService userService; + + @Autowired + private IDeviceLogService deviceLogService; + + /** + * 查询设备 + * + * @param deviceId 设备主键 + * @return 设备 + */ + @Override + public Device selectDeviceByDeviceId(Long deviceId) { + return deviceMapper.selectDeviceByDeviceId(deviceId); + } + + /** + * 根据设备编号查询设备 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + @Override + public Device selectDeviceBySerialNumber(String serialNumber) { + return deviceMapper.selectDeviceBySerialNumber(serialNumber); + } + + /** + * 根据设备编号查询简介设备 + * + * @param serialNumber 设备主键 + * @return 设备 + */ + @Override + public Device selectShortDeviceBySerialNumber(String serialNumber) { + return deviceMapper.selectShortDeviceBySerialNumber(serialNumber); + } + + /** + * 根据设备编号查询设备认证信息 + * + * @param model 设备编号和产品ID + * @return 设备 + */ + @Override + public DeviceAuthenticateModel selectDeviceAuthenticate(AuthenticateInputModel model) { + return deviceMapper.selectDeviceAuthenticate(model); + } + + /** + * 查询设备 + * + * @param deviceId 设备主键 + * @return 设备 + */ + @Override + public DeviceShortOutput selectDeviceRunningStatusByDeviceId(Long deviceId) { + DeviceShortOutput device = deviceMapper.selectDeviceRunningStatusByDeviceId(deviceId); + JSONObject thingsModelObject = JSONObject.parseObject(thingsModelService.getCacheThingsModelByProductId(device.getProductId())); + JSONArray properties = thingsModelObject.getJSONArray("properties"); + JSONArray functions = thingsModelObject.getJSONArray("functions"); + // 物模型转换为对象中的不同类别集合 + convertJsonToCategoryList(properties, device, false, false); + convertJsonToCategoryList(functions, device, false, false); + device.setThingsModelValue(""); + return device; + } + + + /** + * 更新设备的物模型 + * + * @param input 设备ID和物模型值 + * @param type 1=属性 2=功能 + * @return 设备 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int reportDeviceThingsModelValue(ThingsModelValuesInput input, int type,boolean isShadow) { + // 查询物模型 + String thingsModels = thingsModelService.getCacheThingsModelByProductId(input.getProductId()); + JSONObject thingsModelObject = JSONObject.parseObject(thingsModels); + List valueList =null; + if(type==1){ + JSONArray properties = thingsModelObject.getJSONArray("properties"); + valueList = properties.toJavaList(ThingsModelValueItemDto.class); + }else if(type==2){ + JSONArray functions = thingsModelObject.getJSONArray("functions"); + valueList = functions.toJavaList(ThingsModelValueItemDto.class); + } + + // 查询物模型值 + ThingsModelValuesOutput deviceThings = deviceMapper.selectDeviceThingsModelValueBySerialNumber(input.getDeviceNumber()); + List thingsModelValues = JSONObject.parseArray(deviceThings.getThingsModelValue(), ThingsModelValueItem.class); + + for(int i=0;i selectDeviceList(Device device) { + return deviceMapper.selectDeviceList(device); + } + + /** + * 查询所有设备简短列表 + * + * @return 设备 + */ + @Override + public List selectAllDeviceShortList() { + // TODO redis缓存 + return deviceMapper.selectAllDeviceShortList(); + } + + /** + * 查询设备简短列表 + * + * @param device 设备 + * @return 设备 + */ + @Override + public List selectDeviceShortList(Device device) { + // TODO 关联设备用户表 + List deviceList = deviceMapper.selectDeviceShortList(device); + for (int i = 0; i < deviceList.size(); i++) { + JSONObject thingsModelObject = JSONObject.parseObject(thingsModelService.getCacheThingsModelByProductId(deviceList.get(i).getProductId())); + JSONArray properties = thingsModelObject.getJSONArray("properties"); + JSONArray functions = thingsModelObject.getJSONArray("functions"); + // 物模型转换为对象中的不同类别集合 + convertJsonToCategoryList(properties, deviceList.get(i), true, true); + convertJsonToCategoryList(functions, deviceList.get(i), true, false); + deviceList.get(i).setThingsModelValue(""); + } + return deviceList; + } + + /** + * Json物模型集合转换为对象中的分类集合 + * + * @param jsonArray 物模型集合 + * @param isOnlyTop 是否只显示置顶数据 + * @param isOnlyRead 是否设置为只读 + * @param device 设备 + */ + @Async + public void convertJsonToCategoryList(JSONArray jsonArray, DeviceShortOutput device, boolean isOnlyTop, boolean isOnlyRead) { + // 获取物模型值 + JSONArray thingsValueArray = JSONObject.parseArray(device.getThingsModelValue()); + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject thingsJson = jsonArray.getJSONObject(i); + JSONObject datatypeJson = thingsJson.getJSONObject("datatype"); + ThingsModelItemBase thingsModel = new ThingsModelItemBase(); + thingsModel.setIsTop(thingsJson.getInteger("isTop")); + // 只显示isTop数据 + if (thingsModel.getIsTop() == 0 && isOnlyTop == true) { + continue; + } + + thingsModel.setId(thingsJson.getString("id")); + thingsModel.setName(thingsJson.getString("name")); + thingsModel.setIsMonitor(thingsJson.getInteger("isMonitor") == null ? 0 : thingsJson.getInteger("isMonitor")); + thingsModel.setType(datatypeJson.getString("type")); + thingsModel.setValue(""); + // 获取value + for (int j = 0; j < thingsValueArray.size(); j++) { + if (thingsValueArray.getJSONObject(j).getString("id").equals(thingsModel.getId())) { + thingsModel.setValue(thingsValueArray.getJSONObject(j).getString("value")); + thingsModel.setShadow(thingsValueArray.getJSONObject(j).getString("shadow")); + break; + } + } + // 根据分类不同,存储到不同集合 + if (datatypeJson.getString("type").equals("decimal")) { + DecimalModelOutput model = new DecimalModelOutput(); + BeanUtils.copyProperties(thingsModel, model); + model.setMax(datatypeJson.getBigDecimal("max")); + model.setMin(datatypeJson.getBigDecimal("min")); + model.setStep(datatypeJson.getBigDecimal("step")); + model.setUnit(datatypeJson.getString("unit")); + if (model.getIsMonitor() == 1 || isOnlyRead == true) { + ReadOnlyModelOutput readonlyModel = new ReadOnlyModelOutput(); + BeanUtils.copyProperties(model, readonlyModel); + device.getReadOnlyList().add(readonlyModel); + } else { + device.getDecimalList().add(model); + } + } else if (datatypeJson.getString("type").equals("integer")) { + IntegerModelOutput model = new IntegerModelOutput(); + BeanUtils.copyProperties(thingsModel, model); + model.setMax(datatypeJson.getBigDecimal("max")); + model.setMin(datatypeJson.getBigDecimal("min")); + model.setStep(datatypeJson.getBigDecimal("step")); + model.setUnit(datatypeJson.getString("unit")); + if (model.getIsMonitor() == 1 || isOnlyRead == true) { + ReadOnlyModelOutput readonlyModel = new ReadOnlyModelOutput(); + BeanUtils.copyProperties(model, readonlyModel); + device.getReadOnlyList().add(readonlyModel); + } else { + device.getIntegerList().add(model); + } + } else if (datatypeJson.getString("type").equals("bool")) { + BoolModelOutput model = new BoolModelOutput(); + BeanUtils.copyProperties(thingsModel, model); + model.setFalseText(datatypeJson.getString("falseText")); + model.setTrueText(datatypeJson.getString("trueText")); + if (model.getIsMonitor() == 1 || isOnlyRead == true) { + ReadOnlyModelOutput readonlyModel = new ReadOnlyModelOutput(); + BeanUtils.copyProperties(model, readonlyModel); + device.getReadOnlyList().add(readonlyModel); + } else { + device.getBoolList().add(model); + } + } else if (datatypeJson.getString("type").equals("string")) { + StringModelOutput model = new StringModelOutput(); + BeanUtils.copyProperties(thingsModel, model); + model.setMaxLength(datatypeJson.getInteger("maxLength")); + if (model.getIsMonitor() == 1 || isOnlyRead == true) { + ReadOnlyModelOutput readonlyModel = new ReadOnlyModelOutput(); + BeanUtils.copyProperties(model, readonlyModel); + device.getReadOnlyList().add(readonlyModel); + } else { + device.getStringList().add(model); + } + } else if (datatypeJson.getString("type").equals("array")) { + ArrayModelOutput model = new ArrayModelOutput(); + BeanUtils.copyProperties(thingsModel, model); + model.setArrayType(datatypeJson.getString("arrayType")); + if (model.getIsMonitor() == 1 || isOnlyRead == true) { + ReadOnlyModelOutput readonlyModel = new ReadOnlyModelOutput(); + BeanUtils.copyProperties(model, readonlyModel); + device.getReadOnlyList().add(readonlyModel); + } else { + device.getArrayList().add(model); + } + } else if (datatypeJson.getString("type").equals("enum")) { + EnumModelOutput model = new EnumModelOutput(); + BeanUtils.copyProperties(thingsModel, model); + List enumItemList = JSONObject.parseArray(datatypeJson.getString("enumList"), EnumItemOutput.class); + model.setEnumList(enumItemList); + if (model.getIsMonitor() == 1 || isOnlyRead == true) { + ReadOnlyModelOutput readonlyModel = new ReadOnlyModelOutput(); + BeanUtils.copyProperties(model, readonlyModel); + device.getReadOnlyList().add(readonlyModel); + } else { + device.getEnumList().add(model); + } + } + } + // 排序 + device.setReadOnlyList(device.getReadOnlyList().stream().sorted(Comparator.comparing(ThingsModelItemBase::getIsMonitor).reversed()).collect(Collectors.toList())); + } + + /** + * 新增设备 + * + * @param device 设备 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Device insertDevice(Device device) { + SysUser sysUser = getLoginUser().getUser(); + //添加设备 + device.setCreateTime(DateUtils.getNowDate()); + device.setThingsModelValue(JSONObject.toJSONString(getThingsModelDefaultValue(device.getProductId()))); + device.setUserId(sysUser.getUserId()); + device.setUserName(sysUser.getUserName()); + device.setTenantId(sysUser.getUserId()); + device.setTenantName(sysUser.getUserName()); + device.setRssi(0); + 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(device.getDeviceId()); +// deviceUser.setTenantName(device.getTenantName()); +// deviceUser.setIsOwner(1); +// deviceUserMapper.insertDeviceUser(deviceUser); + return device; + } + + /** + * 设备认证后自动添加设备 + * + * @return 结果 + */ + @Override + public int insertDeviceAuto(String serialNumber,Long userId,Long productId) { + Device device = new Device(); + int random = (int) (Math.random() * (9000)) + 1000; + device.setDeviceName("设备" + random); + device.setSerialNumber(serialNumber); + SysUser user=userService.selectUserById(userId); + device.setUserId(userId); + device.setUserName(user.getUserName()); + Product product=productService.selectProductByProductId(productId); + device.setProductId(productId); + device.setProductName(product.getProductName()); + device.setTenantId(userId); + device.setTenantName(user.getUserName()); + device.setFirmwareVersion(BigDecimal.valueOf(1.0)); + device.setStatus(3); + device.setActiveTime(DateUtils.getNowDate()); + device.setIsShadow(0); + device.setRssi(0); + device.setIsCustomLocation(0); + device.setCreateTime(DateUtils.getNowDate()); + device.setThingsModelValue(JSONObject.toJSONString(getThingsModelDefaultValue(device.getProductId()))); + return deviceMapper.insertDevice(device); + } + + /** + * 获取物模型值 + * @param productId + * @return + */ + private List getThingsModelDefaultValue(Long productId) { + // 获取物模型,设置默认值 + String thingsModels = thingsModelService.getCacheThingsModelByProductId(productId); + JSONObject thingsModelObject = JSONObject.parseObject(thingsModels); + JSONArray properties = thingsModelObject.getJSONArray("properties"); + JSONArray functions = thingsModelObject.getJSONArray("functions"); + List valueList = properties.toJavaList(ThingsModelValueItem.class); + valueList.addAll(functions.toJavaList(ThingsModelValueItem.class)); + valueList.forEach(x -> { + x.setValue(""); + x.setShadow(""); + }); + return valueList; + } + + /** + * 获取设备设置的影子 + * @param device + * @return + */ + @Override + public ThingsModelShadow getDeviceShadowThingsModel(Device device) { + // 物模型 + String thingsModels = thingsModelService.getCacheThingsModelByProductId(device.getProductId()); + JSONObject thingsModelObject = JSONObject.parseObject(thingsModels); + JSONArray properties = thingsModelObject.getJSONArray("properties"); + JSONArray functions = thingsModelObject.getJSONArray("functions"); + + // 物模型值 + List thingsModelValueItems = JSONObject.parseArray(device.getThingsModelValue(), ThingsModelValueItem.class); + + // 查询出设置的影子值 + List shadowList = new ArrayList<>(); + for (int i = 0; i < thingsModelValueItems.size(); i++) { + if (!thingsModelValueItems.get(i).getValue().equals(thingsModelValueItems.get(i).getShadow())) { + shadowList.add(thingsModelValueItems.get(i)); + System.out.println("添加影子:"+thingsModelValueItems.get(i).getId()); + } + } + ThingsModelShadow shadow=new ThingsModelShadow(); + for (int i = 0; i < shadowList.size(); i++) { + boolean isGetValue = false; + for (int j = 0; j < properties.size(); j++) { + if (properties.getJSONObject(j).getInteger("isMonitor")==0 && properties.getJSONObject(j).getString("id").equals(shadowList.get(i).getId())) { + IdentityAndName item = new IdentityAndName(shadowList.get(i).getId(), shadowList.get(i).getShadow()); + shadow.getProperties().add(item); + System.out.println("添加影子属性:"+item.getId()); + isGetValue = true; + break; + } + } + if (!isGetValue) { + for (int k = 0; k < functions.size(); k++) { + if (functions.getJSONObject(k).getString("id").equals(shadowList.get(i).getId())) { + IdentityAndName item = new IdentityAndName(shadowList.get(i).getId(), shadowList.get(i).getShadow()); + shadow.getFunctions().add(item); + System.out.println("添加影子功能:"+item.getId()); + break; + } + } + } + } + return shadow; + } + + + /** + * 修改设备 + * @param device 设备 + * @return 结果 + */ + @Override + public int updateDevice(Device device) { + device.setUpdateTime(DateUtils.getNowDate()); + // 未激活状态,可以修改产品以及物模型值 + if (device.getStatus() == 1) { + device.setThingsModelValue(JSONObject.toJSONString(getThingsModelDefaultValue(device.getProductId()))); + } else { + device.setProductId(null); + device.setProductName(null); + } + return deviceMapper.updateDevice(device); + } + + /** + * 生成设备唯一编号 + * @return 结果 + */ + @Override + public String generationDeviceNum() { + String number= "D"+toolService.getStringRandom(15); + int count= deviceMapper.getDeviceNumCount(number); + if(count==0) { + return number; + }else{ + generationDeviceNum(); + } + 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()); + } + if (device.getIsCustomLocation() == 0) { + device.setNetworkIp(ipAddress); + setLocation(ipAddress, device); + } + } + int result=deviceMapper.updateDeviceStatus(device); + + // 添加到设备日志 + DeviceLog deviceLog = new DeviceLog(); + deviceLog.setDeviceId(device.getDeviceId()); + deviceLog.setDeviceName(device.getDeviceName()); + deviceLog.setSerialNumber(device.getSerialNumber()); + deviceLog.setIsMonitor(0); + if(device.getStatus()==3){ + deviceLog.setLogValue("1"); + deviceLog.setRemark("设备上线"); + deviceLog.setIdentity("online"); + deviceLog.setLogType(5); + }else if(device.getStatus()==4){ + deviceLog.setLogValue("0"); + deviceLog.setRemark("设备离线"); + deviceLog.setIdentity("offline"); + deviceLog.setLogType(6); + } + deviceLogService.insertDeviceLog(deviceLog); + return result; + } + + /** + * 根据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")); + } + } + + /** + * 上报设备信息 + * @param device 设备 + * @return 结果 + */ + @Override + public int reportDevice(Device device) { + Device deviceEntity=deviceMapper.selectDeviceBySerialNumber(device.getSerialNumber()); + int result=0; + if(deviceEntity!=null){ + // 更新设备信息 + device.setUpdateTime(DateUtils.getNowDate()); + if(deviceEntity.getActiveTime()==null || deviceEntity.getActiveTime().equals("")) { + device.setActiveTime(DateUtils.getNowDate()); + } + device.setThingsModelValue(null); + result= deviceMapper.updateDeviceBySerialNumber(device); + } + return result; + } + + + /** + * 批量删除设备 + * @param deviceIds 需要删除的设备主键 + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteDeviceByDeviceIds(Long[] deviceIds) throws SchedulerException { + // 删除设备分组 + deviceMapper.deleteDeviceGroupByDeviceIds(deviceIds); + // 删除设备日志 + deviceLogMapper.deleteDeviceLogByDeviceIds(deviceIds); + // 删除定时任务 + deviceJobService.deleteJobByDeviceIds(deviceIds); + + // TODO 删除设备用户 + return deviceMapper.deleteDeviceByDeviceIds(deviceIds); + } + + /** + * 删除设备信息 + * @param deviceId 设备主键 + * @return 结果 + */ + @Override + public int deleteDeviceByDeviceId(Long deviceId) { + return deviceMapper.deleteDeviceByDeviceId(deviceId); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceUserServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceUserServiceImpl.java new file mode 100644 index 00000000..523bd0de --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/DeviceUserServiceImpl.java @@ -0,0 +1,104 @@ +package com.ruoyi.iot.service.impl; + +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.iot.domain.DeviceUser; +import com.ruoyi.iot.mapper.DeviceUserMapper; +import com.ruoyi.iot.service.IDeviceUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static com.ruoyi.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 DeviceUser 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 int insertDeviceUser(DeviceUser deviceUser) + { + 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(deviceId); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/FirmwareServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/FirmwareServiceImpl.java new file mode 100644 index 00000000..9962fb34 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/FirmwareServiceImpl.java @@ -0,0 +1,110 @@ +package com.ruoyi.iot.service.impl; + +import java.util.List; + +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.iot.mapper.FirmwareMapper; +import com.ruoyi.iot.domain.Firmware; +import com.ruoyi.iot.service.IFirmwareService; + +import static com.ruoyi.common.utils.SecurityUtils.getLoginUser; + +/** + * 产品固件Service业务层处理 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Service +public class FirmwareServiceImpl implements IFirmwareService +{ + @Autowired + private FirmwareMapper firmwareMapper; + + /** + * 查询产品固件 + * + * @param firmwareId 产品固件主键 + * @return 产品固件 + */ + @Override + public Firmware selectFirmwareByFirmwareId(Long firmwareId) + { + return firmwareMapper.selectFirmwareByFirmwareId(firmwareId); + } + + /** + * 查询产品固件列表 + * + * @param firmware 产品固件 + * @return 产品固件 + */ + @Override + public List selectFirmwareList(Firmware firmware) + { + return firmwareMapper.selectFirmwareList(firmware); + } + + /** + * 新增产品固件 + * + * @param firmware 产品固件 + * @return 结果 + */ + @Override + public int insertFirmware(Firmware firmware) + { + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + if(roles==null || roles.size()==0) {return 0;} + // 系统管理员 + if(roles.stream().anyMatch(a->a.getRoleKey().equals("admin"))){ + firmware.setIsSys(1); + } + firmware.setTenantId(user.getUserId()); + firmware.setTenantName(user.getUserName()); + firmware.setCreateTime(DateUtils.getNowDate()); + return firmwareMapper.insertFirmware(firmware); + } + + /** + * 修改产品固件 + * + * @param firmware 产品固件 + * @return 结果 + */ + @Override + public int updateFirmware(Firmware firmware) + { + firmware.setUpdateTime(DateUtils.getNowDate()); + return firmwareMapper.updateFirmware(firmware); + } + + /** + * 批量删除产品固件 + * + * @param firmwareIds 需要删除的产品固件主键 + * @return 结果 + */ + @Override + public int deleteFirmwareByFirmwareIds(Long[] firmwareIds) + { + return firmwareMapper.deleteFirmwareByFirmwareIds(firmwareIds); + } + + /** + * 删除产品固件信息 + * + * @param firmwareId 产品固件主键 + * @return 结果 + */ + @Override + public int deleteFirmwareByFirmwareId(Long firmwareId) + { + return firmwareMapper.deleteFirmwareByFirmwareId(firmwareId); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/GroupServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/GroupServiceImpl.java new file mode 100644 index 00000000..ec23e3c2 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/GroupServiceImpl.java @@ -0,0 +1,142 @@ +package com.ruoyi.iot.service.impl; + +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.iot.domain.Group; +import com.ruoyi.iot.mapper.GroupMapper; +import com.ruoyi.iot.model.DeviceGroupInput; +import com.ruoyi.iot.model.IdOutput; +import com.ruoyi.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.ruoyi.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) + { + 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/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/OauthClientDetailsServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/OauthClientDetailsServiceImpl.java new file mode 100644 index 00000000..71302c8a --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/OauthClientDetailsServiceImpl.java @@ -0,0 +1,93 @@ +package com.ruoyi.iot.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.iot.mapper.OauthClientDetailsMapper; +import com.ruoyi.iot.domain.OauthClientDetails; +import com.ruoyi.iot.service.IOauthClientDetailsService; + +/** + * 云云对接Service业务层处理 + * + * @author kerwincui + * @date 2022-02-07 + */ +@Service +public class OauthClientDetailsServiceImpl implements IOauthClientDetailsService +{ + @Autowired + private OauthClientDetailsMapper oauthClientDetailsMapper; + + /** + * 查询云云对接 + * + * @param clientId 云云对接主键 + * @return 云云对接 + */ + @Override + public OauthClientDetails selectOauthClientDetailsByClientId(String clientId) + { + return oauthClientDetailsMapper.selectOauthClientDetailsByClientId(clientId); + } + + /** + * 查询云云对接列表 + * + * @param oauthClientDetails 云云对接 + * @return 云云对接 + */ + @Override + public List 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/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/ProductServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/ProductServiceImpl.java new file mode 100644 index 00000000..0c9788c7 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/ProductServiceImpl.java @@ -0,0 +1,196 @@ +package com.ruoyi.iot.service.impl; + +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysRole; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.iot.domain.Product; +import com.ruoyi.iot.mapper.ProductMapper; +import com.ruoyi.iot.model.ChangeProductStatusModel; +import com.ruoyi.iot.model.IdAndName; +import com.ruoyi.iot.service.IProductService; +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.ruoyi.common.utils.SecurityUtils.getLoginUser; + +/** + * 产品Service业务层处理 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Service +public class ProductServiceImpl implements IProductService +{ + private String tslPreKey ="tsl"; + + @Autowired + private ProductMapper productMapper; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ToolServiceImpl toolService; + + /** + * 查询产品 + * + * @param productId 产品主键 + * @return 产品 + */ + @Override + public Product selectProductByProductId(Long productId) + { + return productMapper.selectProductByProductId(productId); + } + + /** + * 查询产品列表 + * + * @param product 产品 + * @return 产品 + */ + @Override + public List selectProductList(Product product) + { + return productMapper.selectProductList(product); + } + + /** + * 查询产品简短列表 + * + * @return 产品 + */ + @Override + public List selectProductShortList() + { + SysUser user = getLoginUser().getUser(); + return productMapper.selectProductShortList(user.getUserId()); + } + + /** + * 新增产品 + * + * @param product 产品 + * @return 结果 + */ + @Override + public Product insertProduct(Product product) + { + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + if(roles==null || roles.size()==0) {return null;} + // 系统管理员 + if(roles.stream().anyMatch(a->a.getRoleKey().equals("admin"))){ + product.setIsSys(1); + } + // mqtt账号密码 + product.setMqttAccount("wumei-smart"); + product.setMqttPassword("P"+toolService.getStringRandom(15)); + product.setMqttSecret("K"+toolService.getStringRandom(15)); + product.setStatus(product.getStatus()==null?1:product.getStatus()); + product.setTenantId(user.getUserId()); + product.setTenantName(user.getUserName()); + product.setCreateTime(DateUtils.getNowDate()); + productMapper.insertProduct(product); + return product; + } + + /** + * 修改产品 + * + * @param product 产品 + * @return 结果 + */ + @Override + public int updateProduct(Product product) + { + product.setUpdateTime(DateUtils.getNowDate()); + return productMapper.updateProduct(product); + } + + /** + * 更新产品状态,1-未发布,2-已发布 + * + * @param model + * @return 结果 + */ + @Override + public AjaxResult changeProductStatus(ChangeProductStatusModel model) + { + if(model.getStatus()==1){ + // 产品下不能有设备 + Long[] productIds = new Long[1]; + productIds[0]=model.getProductId(); + int deviceCount=productMapper.deviceCountInProducts(productIds); + if(deviceCount>0){ + return AjaxResult.error("取消发布失败,请先删除产品下的设备"); + } + }else if(model.getStatus()==2){ + // 产品下必须包含物模型 + int thingsCount=productMapper.thingsCountInProduct(model.getProductId()); + if(thingsCount==0){ + return AjaxResult.error("发布失败,请先添加产品的物模型"); + } + }else{ + return AjaxResult.error("状态更新失败,状态值有误"); + } + if(productMapper.changeProductStatus(model)>0){ + return AjaxResult.success("操作成功"); + } + return AjaxResult.error("状态更新失败"); + } + + /** + * 批量删除产品 + * + * @param productIds 需要删除的产品主键 + * @return 结果 + */ + @Override + @Transactional + public AjaxResult deleteProductByProductIds(Long[] productIds) + { + // 删除物模型JSON缓存 + for(int i=0;i0){ + return AjaxResult.error("删除失败,请先删除对应产品下的固件"); + } + // 产品下不能有设备 + int deviceCount=productMapper.deviceCountInProducts(productIds); + if(deviceCount>0){ + return AjaxResult.error("删除失败,请先删除对应产品下的设备"); + } + // 删除产品物模型 + productMapper.deleteProductThingsModelByProductIds(productIds); + // 删除产品 + if(productMapper.deleteProductByProductIds(productIds)>0){ + return AjaxResult.success("删除成功"); + } + return AjaxResult.error("删除失败"); + } + + /** + * 删除产品信息 + * + * @param productId 产品主键 + * @return 结果 + */ + @Override + public int deleteProductByProductId(Long productId) + { + // 删除物模型JSON缓存 + redisCache.deleteObject(tslPreKey+productId); + return productMapper.deleteProductByProductId(productId); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/SceneServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/SceneServiceImpl.java new file mode 100644 index 00000000..ae203f6f --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/SceneServiceImpl.java @@ -0,0 +1,96 @@ +package com.ruoyi.iot.service.impl; + +import java.util.List; +import com.ruoyi.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.iot.mapper.SceneMapper; +import com.ruoyi.iot.domain.Scene; +import com.ruoyi.iot.service.ISceneService; + +/** + * 场景联动Service业务层处理 + * + * @author kerwincui + * @date 2022-01-13 + */ +@Service +public class SceneServiceImpl implements ISceneService +{ + @Autowired + private SceneMapper sceneMapper; + + /** + * 查询场景联动 + * + * @param sceneId 场景联动主键 + * @return 场景联动 + */ + @Override + public Scene selectSceneBySceneId(Long sceneId) + { + return sceneMapper.selectSceneBySceneId(sceneId); + } + + /** + * 查询场景联动列表 + * + * @param scene 场景联动 + * @return 场景联动 + */ + @Override + public List selectSceneList(Scene scene) + { + return sceneMapper.selectSceneList(scene); + } + + /** + * 新增场景联动 + * + * @param scene 场景联动 + * @return 结果 + */ + @Override + public int insertScene(Scene scene) + { + scene.setCreateTime(DateUtils.getNowDate()); + return sceneMapper.insertScene(scene); + } + + /** + * 修改场景联动 + * + * @param scene 场景联动 + * @return 结果 + */ + @Override + public int updateScene(Scene scene) + { + scene.setUpdateTime(DateUtils.getNowDate()); + return sceneMapper.updateScene(scene); + } + + /** + * 批量删除场景联动 + * + * @param sceneIds 需要删除的场景联动主键 + * @return 结果 + */ + @Override + public int deleteSceneBySceneIds(Long[] sceneIds) + { + return sceneMapper.deleteSceneBySceneIds(sceneIds); + } + + /** + * 删除场景联动信息 + * + * @param sceneId 场景联动主键 + * @return 结果 + */ + @Override + public int deleteSceneBySceneId(Long sceneId) + { + return sceneMapper.deleteSceneBySceneId(sceneId); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/ThingsModelServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/ThingsModelServiceImpl.java new file mode 100644 index 00000000..7013aae8 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/ThingsModelServiceImpl.java @@ -0,0 +1,265 @@ +package com.ruoyi.iot.service.impl; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.iot.domain.Product; +import com.ruoyi.iot.domain.ThingsModelTemplate; +import com.ruoyi.iot.mapper.ProductMapper; +import com.ruoyi.iot.mapper.ThingsModelTemplateMapper; +import com.ruoyi.iot.model.ImportThingsModelInput; +import com.ruoyi.iot.model.ThingsModels.EventDto; +import com.ruoyi.iot.model.ThingsModels.FunctionDto; +import com.ruoyi.iot.model.ThingsModels.PropertyDto; +import com.ruoyi.iot.model.ThingsModels.ThingsModelsDto; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.iot.mapper.ThingsModelMapper; +import com.ruoyi.iot.domain.ThingsModel; +import com.ruoyi.iot.service.IThingsModelService; + +import static com.ruoyi.common.utils.SecurityUtils.getLoginUser; + +/** + * 物模型Service业务层处理 + * + * @author kerwincui + * @date 2021-12-16 + */ +@Service +public class ThingsModelServiceImpl implements IThingsModelService +{ + private String tslPreKey ="things_model:tsl"; + + @Autowired + private ThingsModelMapper thingsModelMapper; + + @Autowired + private ThingsModelTemplateMapper thingsModelTemplateMapper; + + @Autowired + private ProductMapper productMapper; + + @Autowired + private RedisCache redisCache; + + /** + * 查询物模型 + * + * @param modelId 物模型主键 + * @return 物模型 + */ + @Override + public ThingsModel selectThingsModelByModelId(Long modelId) + { + return thingsModelMapper.selectThingsModelByModelId(modelId); + } + + /** + * 查询物模型列表 + * + * @param thingsModel 物模型 + * @return 物模型 + */ + @Override + public List selectThingsModelList(ThingsModel thingsModel) + { + return thingsModelMapper.selectThingsModelList(thingsModel); + } + + /** + * 新增物模型 + * + * @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()); + 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){ + // redis获取物模型 + String thingsModelJson = redisCache.getCacheObject(tslPreKey +productId); + if (thingsModelJson != null){ + return thingsModelJson; + } + return setCacheThingsModelByProductId(productId); + } + + /** + * 根据产品ID更新JSON物模型 + * @param productId + * @return + */ + private String setCacheThingsModelByProductId(Long productId){ + // 数据库查询物模型集合 + ThingsModel model=new ThingsModel(); + model.setProductId(productId); + List thingsModels=thingsModelMapper.selectThingsModelList(model); + // 转换为物模型 + ThingsModelsDto thingsModelsDto=new ThingsModelsDto(); + for(int i=0;i selectThingsModelTemplateList(ThingsModelTemplate thingsModelTemplate) + { + return thingsModelTemplateMapper.selectThingsModelTemplateList(thingsModelTemplate); + } + + /** + * 新增通用物模型 + * + * @param thingsModelTemplate 通用物模型 + * @return 结果 + */ + @Override + public int insertThingsModelTemplate(ThingsModelTemplate thingsModelTemplate) + { + SysUser user = getLoginUser().getUser(); + List roles=user.getRoles(); + if(roles==null || roles.size()==0) {return 0;} + // 系统管理员 + if(roles.stream().anyMatch(a->a.getRoleKey().equals("admin"))){ + thingsModelTemplate.setIsSys(1); + } + thingsModelTemplate.setTenantId(user.getUserId()); + thingsModelTemplate.setTenantName(user.getUserName()); + thingsModelTemplate.setCreateTime(DateUtils.getNowDate()); + return thingsModelTemplateMapper.insertThingsModelTemplate(thingsModelTemplate); + } + + /** + * 修改通用物模型 + * + * @param thingsModelTemplate 通用物模型 + * @return 结果 + */ + @Override + public int updateThingsModelTemplate(ThingsModelTemplate thingsModelTemplate) + { + thingsModelTemplate.setUpdateTime(DateUtils.getNowDate()); + return thingsModelTemplateMapper.updateThingsModelTemplate(thingsModelTemplate); + } + + /** + * 批量删除通用物模型 + * + * @param templateIds 需要删除的通用物模型主键 + * @return 结果 + */ + @Override + public int deleteThingsModelTemplateByTemplateIds(Long[] templateIds) + { + return thingsModelTemplateMapper.deleteThingsModelTemplateByTemplateIds(templateIds); + } + + /** + * 删除通用物模型信息 + * + * @param templateId 通用物模型主键 + * @return 结果 + */ + @Override + public int deleteThingsModelTemplateByTemplateId(Long templateId) + { + return thingsModelTemplateMapper.deleteThingsModelTemplateByTemplateId(templateId); + } +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/ToolServiceImpl.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/ToolServiceImpl.java new file mode 100644 index 00000000..f9a1a436 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/service/impl/ToolServiceImpl.java @@ -0,0 +1,173 @@ +package com.ruoyi.iot.service.impl; + +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.redis.RedisCache; +import com.ruoyi.common.exception.user.CaptchaException; +import com.ruoyi.common.exception.user.CaptchaExpireException; +import com.ruoyi.common.utils.MessageUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.manager.AsyncManager; +import com.ruoyi.framework.manager.factory.AsyncFactory; +import com.ruoyi.iot.model.RegisterUserInput; +import com.ruoyi.iot.service.IToolService; +import com.ruoyi.system.mapper.SysUserMapper; +import com.ruoyi.system.service.ISysConfigService; +import com.ruoyi.system.service.ISysUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Random; + +/** + * + * @author kerwincui + * @date 2021-12-16 + */ +@Service +public class ToolServiceImpl implements IToolService +{ + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysConfigService configService; + + @Autowired + private ISysUserService userService; + + @Autowired + private SysUserMapper userMapper; + + /** + * 生成随机数字和字母 + */ + @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; + } + + /** + * 注册 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public String register(RegisterUserInput registerBody) + { + String msg = ""; + String username = registerBody.getUsername(); + String password = registerBody.getPassword(); + String phonenumber=registerBody.getPhonenumber(); + + boolean captchaOnOff = configService.selectCaptchaOnOff(); + // 验证码开关 + if (captchaOnOff) + { + 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(username))) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + }else if (UserConstants.NOT_UNIQUE.equals(checkPhoneUnique(phonenumber))) + { + msg = "保存用户'" + username + "'失败,注册手机号码已存在"; + } + else + { + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + sysUser.setNickName(username); + sysUser.setPhonenumber(username); + 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; + } + + /** + * 校验手机号码是否唯一 + * + * @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 = Constants.CAPTCHA_CODE_KEY + 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/wumei-iot/src/main/java/com/ruoyi/iot/util/AESUtils.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/AESUtils.java new file mode 100644 index 00000000..de627797 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/AESUtils.java @@ -0,0 +1,77 @@ +package com.ruoyi.iot.util; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import sun.misc.BASE64Decoder; +import sun.misc.BASE64Encoder; + +/** + * + */ +public class AESUtils { + + /** + * 加密用的Key 可以用26个字母和数字组成 使用AES-128-CBC加密模式,key需要为16位。iv 偏移量,长度16 + */ + private static final String ivString ="wumei-smart-open"; + + // 加密 + public static String encrypt(String sSrc, String sKey) throws Exception { + if (sKey == null) { + System.out.print("Key为空null"); + return null; + } + // 判断Key是否为16位 + if (sKey.length() != 16) { + System.out.print("Key长度不是16位"); + return null; + } + byte[] raw = sKey.getBytes(); + SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); + //"算法/模式/补码方式" + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + //使用CBC模式,需要一个向量iv,可增加加密算法的强度 + IvParameterSpec iv = new IvParameterSpec(ivString.getBytes()); + cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); + byte[] encrypted = cipher.doFinal(sSrc.getBytes()); + //此处使用BASE64做转码功能,同时能起到2次加密的作用。 + return new BASE64Encoder().encode(encrypted); + } + + // 解密 + public static String decrypt(String sSrc, String sKey) throws Exception { + try { + // 判断Key是否正确 + if (sKey == null) { + System.out.print("Key为空null"); + return null; + } + // 判断Key是否为16位 + if (sKey.length() != 16) { + System.out.print("Key长度不是16位"); + return null; + } + byte[] raw = sKey.getBytes("ASCII"); + SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + IvParameterSpec iv = new IvParameterSpec(ivString.getBytes()); + cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); + //先用base64解密 + byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc); + try { + byte[] original = cipher.doFinal(encrypted1); + String originalString = new String(original); + return originalString; + } catch (Exception e) { + System.out.println(e.toString()); + return null; + } + } catch (Exception ex) { + System.out.println(ex.toString()); + return null; + } + } + +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/VelocityInitializer.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/VelocityInitializer.java new file mode 100644 index 00000000..29a7bb73 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/VelocityInitializer.java @@ -0,0 +1,35 @@ +package com.ruoyi.iot.util; + +import com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/util/VelocityUtils.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/VelocityUtils.java new file mode 100644 index 00000000..6ac0e1b8 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/VelocityUtils.java @@ -0,0 +1,89 @@ +package com.ruoyi.iot.util; + +import com.alibaba.fastjson.JSONObject; +import com.ruoyi.common.constant.GenConstants; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/AbstractQuartzJob.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/AbstractQuartzJob.java new file mode 100644 index 00000000..bb67f8b9 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/AbstractQuartzJob.java @@ -0,0 +1,108 @@ +package com.ruoyi.iot.util.quartz; + +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.constant.ScheduleConstants; +import com.ruoyi.common.utils.ExceptionUtil; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.bean.BeanUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.iot.domain.DeviceJob; +import com.ruoyi.quartz.domain.SysJobLog; +import com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/CronUtils.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/CronUtils.java new file mode 100644 index 00000000..37c506a8 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/CronUtils.java @@ -0,0 +1,64 @@ +package com.ruoyi.iot.util.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/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/JobInvokeUtil.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/JobInvokeUtil.java new file mode 100644 index 00000000..ad09d1ae --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/JobInvokeUtil.java @@ -0,0 +1,64 @@ +package com.ruoyi.iot.util.quartz; + +import com.alibaba.fastjson.JSON; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.iot.domain.DeviceJob; +import com.ruoyi.iot.model.Action; +import com.ruoyi.iot.model.ThingsModels.IdentityAndName; +import com.ruoyi.iot.mqtt.EmqxService; + +import java.util.ArrayList; +import java.util.List; + +/** + * 任务执行工具 + * + * @author kerwincui + */ +public class JobInvokeUtil +{ + /** + * 执行方法 + * + * @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;i0){ + emqxService.publishProperty(deviceJob.getProductId(),deviceJob.getSerialNumber(),propertys); + } + // 发布功能 + if(functions.size()>0){ + emqxService.publishFunction(deviceJob.getProductId(),deviceJob.getSerialNumber(),functions); + } + + }else if(deviceJob.getJobType()==2){ + // 告警 + + System.out.println("------------------------执行告警-----------------------------"); + }else if(deviceJob.getJobType()==3){ + // 场景联动 + + System.out.println("------------------------执行场景联动-----------------------------"); + } + } + + +} diff --git a/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/QuartzDisallowConcurrentExecution.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/QuartzDisallowConcurrentExecution.java new file mode 100644 index 00000000..9903064d --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/QuartzDisallowConcurrentExecution.java @@ -0,0 +1,21 @@ +package com.ruoyi.iot.util.quartz; + +import com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/QuartzJobExecution.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/QuartzJobExecution.java new file mode 100644 index 00000000..8a0e8957 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/QuartzJobExecution.java @@ -0,0 +1,19 @@ +package com.ruoyi.iot.util.quartz; + +import com.ruoyi.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/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/ScheduleUtils.java b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/ScheduleUtils.java new file mode 100644 index 00000000..0db772b9 --- /dev/null +++ b/springboot/wumei-iot/src/main/java/com/ruoyi/iot/util/quartz/ScheduleUtils.java @@ -0,0 +1,105 @@ +package com.ruoyi.iot.util.quartz; + +import com.ruoyi.common.constant.ScheduleConstants; +import com.ruoyi.common.exception.job.TaskException; +import com.ruoyi.common.exception.job.TaskException.Code; +import com.ruoyi.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/wumei-iot/src/main/resources/mapper/iot/AlertLogMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/AlertLogMapper.xml new file mode 100644 index 00000000..0d1719af --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/AlertLogMapper.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + select alert_log__id, alert_name, alert_level, status, product_id, product_name, device_id, device_name, create_by, create_time, update_by, update_time, remark from iot_alert_log + + + + + + + + insert into iot_alert_log + + alert_name, + alert_level, + status, + product_id, + product_name, + device_id, + device_name, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{alertName}, + #{alertLevel}, + #{status}, + #{productId}, + #{productName}, + #{deviceId}, + #{deviceName}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update iot_alert_log + + alert_name = #{alertName}, + alert_level = #{alertLevel}, + status = #{status}, + product_id = #{productId}, + product_name = #{productName}, + device_id = #{deviceId}, + device_name = #{deviceName}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where alert_log__id = #{alertLogId} + + + + delete from iot_alert_log where alert_log__id = #{alertLogId} + + + + delete from iot_alert_log where alert_log__id in + + #{alertLogId} + + + \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/resources/mapper/iot/AlertMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/AlertMapper.xml new file mode 100644 index 00000000..b1a74cd5 --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/AlertMapper.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + select alert_id, alert_name, alert_level,status, product_id, product_name, triggers, actions, create_by, create_time, update_by, update_time, remark from iot_alert + + + + + + + + insert into iot_alert + + alert_name, + alert_level, + status, + product_id, + product_name, + triggers, + actions, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{alertName}, + #{alertLevel}, + #{status}, + #{productId}, + #{productName}, + #{triggers}, + #{actions}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update iot_alert + + alert_name = #{alertName}, + alert_level = #{alertLevel}, + status = #{status}, + product_id = #{productId}, + product_name = #{productName}, + triggers = #{triggers}, + actions = #{actions}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where alert_id = #{alertId} + + + + delete from iot_alert where alert_id = #{alertId} + + + + delete from iot_alert where alert_id in + + #{alertId} + + + \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/resources/mapper/iot/CategoryMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/CategoryMapper.xml new file mode 100644 index 00000000..2d1d9e0b --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/CategoryMapper.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + 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/wumei-iot/src/main/resources/mapper/iot/DeviceJobMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/DeviceJobMapper.xml new file mode 100644 index 00000000..b7cbe597 --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/DeviceJobMapper.xml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select job_id, job_name, job_group,serial_number, device_id,device_name,actions,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} + + + + + update iot_device_job + + job_name = #{jobName}, + job_group = #{jobGroup}, + device_id = #{deviceId}, + device_name = #{deviceName}, + serial_number = #{serialNumber}, + actions = #{actions}, + 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, + 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}, + #{cronExpression}, + #{misfirePolicy}, + #{concurrent}, + #{status}, + #{remark}, + #{createBy}, + #{jobType}, + #{productId}, + #{productName}, + #{sceneId}, + #{alertId}, + #{isAdvance}, + sysdate() + ) + + + \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/resources/mapper/iot/DeviceLogMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/DeviceLogMapper.xml new file mode 100644 index 00000000..6f3ac8ca --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/DeviceLogMapper.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + select log_id, log_type, log_value, device_id, device_name,serial_number, identity, create_by, is_monitor, 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, + create_time, + remark, + + + #{logType}, + #{logValue}, + #{deviceId}, + #{deviceName}, + #{serialNumber}, + #{identity}, + #{createBy}, + #{isMonitor}, + #{createTime}, + #{remark}, + + + + + 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}, + create_time = #{createTime}, + remark = #{remark}, + + 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 device_id in + + #{deviceId} + + + \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/resources/mapper/iot/DeviceMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/DeviceMapper.xml new file mode 100644 index 00000000..033a64eb --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/DeviceMapper.xml @@ -0,0 +1,346 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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_custom_location,things_model_value,network_address, network_ip, longitude, latitude, active_time, create_time, update_time, img_url,remark 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_custom_location,things_model_value, active_time,img_url from iot_device + + + + select device_id, device_name,product_id, serial_number, status,is_shadow ,is_custom_location,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, + is_custom_location, + things_model_value, + network_address, + network_ip, + longitude, + latitude, + active_time, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + img_url, + + + #{deviceName}, + #{productId}, + #{productName}, + #{userId}, + #{userName}, + #{tenantId}, + #{tenantName}, + #{serialNumber}, + #{firmwareVersion}, + #{status}, + #{rssi}, + #{isShadow}, + #{isCustomLocation}, + #{thingsModelValue}, + #{networkAddress}, + #{networkIp}, + #{longitude}, + #{latitude}, + #{activeTime}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{imgUrl}, + + + + + 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_custom_location = #{isCustomLocation}, + 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}, + + where device_id = #{deviceId} + + + + update iot_device + + status = #{status}, + network_address = #{networkAddress}, + network_ip = #{networkIp}, + longitude = #{longitude}, + latitude = #{latitude}, + active_time = #{activeTime}, + + where serial_number = #{serialNumber} + + + + 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_custom_location = #{isCustomLocation}, + 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}, + + 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 where device_id in + + #{deviceId} + + + \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/resources/mapper/iot/DeviceUserMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/DeviceUserMapper.xml new file mode 100644 index 00000000..8bad70c2 --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/DeviceUserMapper.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + select device_id, user_id, device_name, user_name, is_owner,tenant_id,tenant_name,phonenumber, create_time, update_time, remark from iot_device_user + + + + + + + + insert into iot_device_user + + user_id, + device_name, + user_name, + is_owner, + tenant_id, + tenant_name, + phonenumber, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{userId}, + #{deviceName}, + #{userName}, + #{isOwner}, + #{tenantId}, + #{tenantName}, + #{phonenumber}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update iot_device_user + + user_id = #{userId}, + device_name = #{deviceName}, + user_name = #{userName}, + is_owner = #{isOwner}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + phonenumber = #{phonenumber}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where device_id = #{deviceId} + + + + delete from iot_device_user where device_id = #{deviceId} and is_owner !=1 + + + + delete from iot_device_user where is_owner !=1 and device_id in + + #{deviceId} + + + \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/resources/mapper/iot/FirmwareMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/FirmwareMapper.xml new file mode 100644 index 00000000..f0ec9c74 --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/FirmwareMapper.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + select firmware_id, firmware_name, product_id, product_name, tenant_id, tenant_name, is_sys, version, file_path, create_time, update_time, remark from iot_firmware + + + + + + + + insert into iot_firmware + + firmware_name, + product_id, + product_name, + tenant_id, + tenant_name, + is_sys, + version, + file_path, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{firmwareName}, + #{productId}, + #{productName}, + #{tenantId}, + #{tenantName}, + #{isSys}, + #{version}, + #{filePath}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update iot_firmware + + firmware_name = #{firmwareName}, + product_id = #{productId}, + product_name = #{productName}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + is_sys = #{isSys}, + version = #{version}, + file_path = #{filePath}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where firmware_id = #{firmwareId} + + + + delete from iot_firmware where firmware_id = #{firmwareId} + + + + delete from iot_firmware where firmware_id in + + #{firmwareId} + + + \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/resources/mapper/iot/GroupMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/GroupMapper.xml new file mode 100644 index 00000000..e73bcac9 --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/GroupMapper.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + 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/wumei-iot/src/main/resources/mapper/iot/OauthClientDetailsMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/OauthClientDetailsMapper.xml new file mode 100644 index 00000000..b9269d6f --- /dev/null +++ b/springboot/wumei-iot/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/wumei-iot/src/main/resources/mapper/iot/ProductMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/ProductMapper.xml new file mode 100644 index 00000000..e4ee0764 --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/ProductMapper.xml @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select product_id, product_name, category_id, category_name, tenant_id, tenant_name, is_sys,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, + 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, + + + #{productName}, + #{categoryId}, + #{categoryName}, + #{tenantId}, + #{tenantName}, + #{isSys}, + #{mqttAccount}, + #{mqttPassword}, + #{mqttSecret}, + #{status}, + #{deviceType}, + #{networkMethod}, + #{vertificateMethod}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{imgUrl}, + + + + + update iot_product + + product_name = #{productName}, + category_id = #{categoryId}, + category_name = #{categoryName}, + tenant_id = #{tenantId}, + tenant_name = #{tenantName}, + is_sys = #{isSys}, + 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}, + + 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} + + + + + + + + + \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/resources/mapper/iot/SceneMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/SceneMapper.xml new file mode 100644 index 00000000..91eea12e --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/SceneMapper.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + select scene_id, scene_name, user_id, user_name, triggers, actions, create_by, create_time, update_by, update_time, remark from iot_scene + + + + + + + + insert into iot_scene + + scene_name, + user_id, + user_name, + triggers, + actions, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{sceneName}, + #{userId}, + #{userName}, + #{triggers}, + #{actions}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update iot_scene + + scene_name = #{sceneName}, + user_id = #{userId}, + user_name = #{userName}, + triggers = #{triggers}, + actions = #{actions}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where scene_id = #{sceneId} + + + + delete from iot_scene where scene_id = #{sceneId} + + + + delete from iot_scene where scene_id in + + #{sceneId} + + + \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/resources/mapper/iot/ThingsModelMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/ThingsModelMapper.xml new file mode 100644 index 00000000..29522c51 --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/ThingsModelMapper.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + select model_id, model_name, product_id, product_name, tenant_id, tenant_name, identifier, type, datatype, specs, is_Top, is_Monitor from iot_things_model + + + + + + + + + + insert into iot_things_model + + model_name, + product_id, + product_name, + tenant_id, + tenant_name, + identifier, + type, + datatype, + specs, + is_Top, + is_Monitor, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{modelName}, + #{productId}, + #{productName}, + #{tenantId}, + #{tenantName}, + #{identifier}, + #{type}, + #{datatype}, + #{specs}, + #{isTop}, + #{isMonitor}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + insert into iot_things_model (model_name,product_id,product_name,tenant_id,tenant_name,identifier,type,datatype,specs,is_Top,is_Monitor,create_by,create_time) + values + + + #{model.modelName},#{model.productId},#{model.productName},#{model.tenantId},#{model.tenantName},#{model.identifier},#{model.type},#{model.datatype},#{model.specs},#{model.isTop},#{model.isMonitor},#{model.createBy},#{model.createTime}, + + + + + + 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}, + specs = #{specs}, + is_Top = #{isTop}, + is_Monitor = #{isMonitor}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where model_id = #{modelId} + + + + delete from iot_things_model where model_id = #{modelId} + + + + delete from iot_things_model where model_id in + + #{modelId} + + + \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/resources/mapper/iot/ThingsModelTemplateMapper.xml b/springboot/wumei-iot/src/main/resources/mapper/iot/ThingsModelTemplateMapper.xml new file mode 100644 index 00000000..45e5aa97 --- /dev/null +++ b/springboot/wumei-iot/src/main/resources/mapper/iot/ThingsModelTemplateMapper.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + select template_id, template_name, tenant_id, tenant_name, identifier, type, datatype, specs, is_sys, is_Top, is_Monitor, create_time, update_time, remark from iot_things_model_template + + + + + + + + + + insert into iot_things_model_template + + template_name, + tenant_id, + tenant_name, + identifier, + type, + datatype, + specs, + is_sys, + is_Top, + is_Monitor, + del_flag, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{templateName}, + #{tenantId}, + #{tenantName}, + #{identifier}, + #{type}, + #{datatype}, + #{specs}, + #{isSys}, + #{isTop}, + #{isMonitor}, + #{delFlag}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + 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_Top = #{isTop}, + is_Monitor = #{isMonitor}, + del_flag = #{delFlag}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where template_id = #{templateId} + + + + delete from iot_things_model_template where template_id = #{templateId} + + + + delete from iot_things_model_template where template_id in + + #{templateId} + + + \ No newline at end of file diff --git a/springboot/wumei-iot/src/main/resources/static/oauth/css/bootstrap-theme.css b/springboot/wumei-iot/src/main/resources/static/oauth/css/bootstrap-theme.css new file mode 100644 index 00000000..ea33f76a --- /dev/null +++ b/springboot/wumei-iot/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/wumei-iot/src/main/resources/static/oauth/css/bootstrap-theme.css.map b/springboot/wumei-iot/src/main/resources/static/oauth/css/bootstrap-theme.css.map new file mode 100644 index 00000000..949d0973 --- /dev/null +++ b/springboot/wumei-iot/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/wumei-iot/src/main/resources/static/oauth/css/bootstrap-theme.min.css b/springboot/wumei-iot/src/main/resources/static/oauth/css/bootstrap-theme.min.css new file mode 100644 index 00000000..2a69f48c --- /dev/null +++ b/springboot/wumei-iot/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/wumei-iot/src/main/resources/static/oauth/css/bootstrap-theme.min.css.map b/springboot/wumei-iot/src/main/resources/static/oauth/css/bootstrap-theme.min.css.map new file mode 100644 index 00000000..5d75106e --- /dev/null +++ b/springboot/wumei-iot/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/wumei-iot/src/main/resources/static/oauth/css/bootstrap.css b/springboot/wumei-iot/src/main/resources/static/oauth/css/bootstrap.css new file mode 100644 index 00000000..fcab4155 --- /dev/null +++ b/springboot/wumei-iot/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/wumei-iot/src/main/resources/static/oauth/css/bootstrap.css.map b/springboot/wumei-iot/src/main/resources/static/oauth/css/bootstrap.css.map new file mode 100644 index 00000000..caac3e61 --- /dev/null +++ b/springboot/wumei-iot/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